一点心得


  • 首页

  • 归档

Unity 苹果IAP和微信支付接入

发表于 2016-09-05

苹果IAP接入

相比微信支付和支付宝支付要麻烦一些,麻烦的地方主要体现在对测试支付环境的要求以及苹果审核方面的要求上面。本文是自己在接入iOS的IAP模块的经验及总结,发出来分享下。建议需要接入的话还是浏览一遍苹果官方文档

注意事项

  • 测试支付的ipa必须使用[App-Store]证书
  • 越狱机器无法测试IAP
  • 用SandBox账号测试支付的时候,必须把在系统[设置]里面把[Itunes Store 与 App Store]登录的非SandBox账号注销掉,否则向苹果服务器请求不到订单信息
  • Sandbox账号不要在正式支付环境登陆支付,登陆过的正式支付环境的SandBox账号会失效
  • 所有在itunes上配置的商品都必须可购买,不能有某些商品根据商户自己的服务器的数据在某个时期出现免费的情况
  • 商品列表不能按照某些特定条件进行排序(比如说下载量)
  • 非消耗型商品必须的有恢复商品功能
  • 非消耗类型的商品不要和商户自己的服务器关联
阅读全文 »

我所理解的Unity AssetBundle

发表于 2016-07-16

本文是学习Unity官方教程、Unity官方的API和阅读Unity代码的总结

AssetBundle基本知识

1. AssetBundle由两部分组成:头部(header)数据部分(data segment)

  • 头部保存了版本号、数据类型、<font color=#FF0000>文件信息</font>、是否压缩等这些描述信息。
    • 文件信息记录了数据部分里面的所有单个资源的文件名以及在整个AssetBundle中文件offset和size,通过这个信息可以直接获取到AssetBundle中的某一个文件的数据。从Unity5.3版本开始这部分数据会单独生成一个AssetBundle同名的.manifest文件。
  • 数据块保存了在build AssetBundle时候标记的textures、prefabs、materials等这些资源文件
阅读全文 »

Unity中内存资源释放总结

发表于 2016-06-18

Unity内存释放 问题主要考虑一下几个方面: (1) Resources 加载的资源需要释放的时候调用 Resources.UnloadUnusedAssets();

(2) WWW 加载的任何资源<font color=#FF0000>必须</font>调用WWW的Dispose接口

(3) 创建的RenderTexture<font color=#FF0000>必须</font>调用Release接口

(4) 必须用Xcode联机调试确定C++层有没有内存泄漏

(5) AssetBundle资源在Unity Profile中的Scene Memory不会显示,可以通过Xcode联机调试确定有没有释放完全

(6) 加载资源和卸载资源的异步处理,避免同一帧内加载过多资源导致内存瞬间增大从而导致Crash(低配机器上表现更明显)

一般通过WWW加载一张Texture来通过Image显示的话,一种的方法是使用Texture通过Sprite.Create来创建一个Sprite对象赋值给Image.overrideSprite完成显示。但是需要注意一个问题,Sprite.Create创建对象对拷贝Texture中数据,并生成一个新的Sprite对象,这样会造成耗费将近2倍Texture大小的内存,而且这个内存占用不会随着Image的销毁而马上销毁,知道你调用Resources.UnloadUnusedResources()接口才会销毁。如果在一个场景中有大量的图片需要动态加载的话,那么必须注意内存问题了。所以建议不要使用显示Image的方式,有一种方式是显示RawImage,按照官方文档的描述:

The Raw Image control displays a non-interactive image to the user. This can be used for decoration, icons, etc, and the image can also be changed from a script to reflect changes in other controls.

RawImage是一个没有和用户交互的Image,所以如果我们不需要比如事件这种交互的操作的话,还是建议用RawImage来显示Texture。RawImage的方式会高效很多,如果从AssetBundle里面加载了一张Texture,不管是赋值给多少个RawImage,Texture是公用的,只会有一分内存。

阅读全文 »

C#多线程下数据同步处理

发表于 2016-06-05

本文是阅读CLR via C#和 Theading in C#做的个人总结

为什么要进行多线程的同步

多个线程同时访问共享数据时有可能对数据造成损坏,线程同步的目的就是保证多线程访问共享数据时数据的安全性。在C#中能够对一个变量中的所有字节都一次性读取或写入,我们就认为这个变量类型具有原子性,CLR保证对以下类型变量的读写是原子性的: Boolean, Char, (S)Byte, (U)Int16, (U)Int32, (U)IntPtr,Single。

不具备原子性的类型变量的读取或者写入操作会被分割,也就是说对x变量的读取或者写入操作需要多条指令完。对于变量x来说,赋值操作需要两个MOV指令来完成即:

1
Int64 x = 0x0123456789abcdef;  // Step1: 0x0123456700000000  Step2: 0x000000089abcdef

那么在多线程的情况下会出现另外一个读取该值的操作取出来的值是0x0123456700000000,所以<font color=#FF4040>我们要对非原子性的类型变量在多线程访问的情况下要进行同步操作(类似于自己构造变量的原子性),以保证我们获取的数据的完整性</font>。

需要进行多线程同步的数据具体有哪些

  • static 数据
  • new 操作符分配的对象,并且这个对象传递给了其他线程

这两类数据都是作为共享数据,可以让多个线程同时访问的。所以我们要对其进行同步操作。但是当多个线程同时对共享数据进行只读操作的时候是不需要进行任何数据同步的。

多线程同步的几种常用接口

  • Monitor (lock)

Monitor为static类,它的工作原理是操作对象的同步快索引。CLR在初始化的时候会在堆中分配一个同步块数组,这个数组的元素用来关联new构造器构造出来的对象的同步索引。一个对象在构造时它的同步块索引初始化为-1,表示不引用任何同步索引块。然后调用Monitor.Enter的时候,CLR在数组中找到一个空白的同步块来关联当前Monitor传入的对象,对象的同步块索引将会自增,对象处于锁定状态。当调用Monitor.Exit的时候如果没有其他线程等待这个对象,如果没有那么Exit将对象的同步块索引设置回-1,对象处于自由状态。

  • Mutex
1
2
3
4
    public sealed class Mutex : WaitHandler {
      public Mutex();
      public void ReleaseMutex();
    }

Mutex类强制线程标识,只能由获得它的线程可以释放互斥体。Mutex内部维护着一个递归计数,拥有互斥锁的线程可以递归调用相同互斥体(互斥对象)WaitOne而不会阻止线程执行。但是必须调用相同次数的ReleaseMutex来释放互斥体。

  • Semaphore
1
2
3
4
5
    public sealed class Semaphore : WaitHandler {
        public Semaphore(Int32 initialCount, Int32 maximumCount);
        public Int32 Release();
        public Int32 Release(Int32 releaseCount);
    }

内部维护者一个Int32变量。信号量为0时在信号量上等待的线程会阻塞,信号量大于0时解除阻塞。在信号量上等待的线程解除阻塞时,内核自动从信号量的计数中减1。

  • ReaderWriterLockSlim

    在Monitor、Mutex和Semaphore方式来同步线程时就算某一时刻多个线程来同时读取数据也会造成只有一个线程工作其他线程等待的情况,这样就在成了资源的浪费。ReaderWriterLockSlim基于多个线程对共享数据同时读取不需要同步这个点来做的优化。它的工作方式如下:

    (1) 一个线程向数据写入时,请求访问的其他所有线程都被阻塞

    (2) 一个线程从数据读取时,请求读取的其他线程能够继续执行,但请求写入的线程仍然被阻塞

    (3) 写入的线程结束后,要么解除一个写入线程的阻塞,要么解除所有的读取线程的阻塞。如果没有线程被阻塞则锁就进入自由状态

    (4) 读取线程结束后,一个写入的线程被解除阻塞(如果有)。如果没有线程被阻塞则锁进入自由状态

测试例子

阅读全文 »

Unity GameObject深度剖析

发表于 2016-05-09

创建一个GameObject实例的过程是怎样的?

UnityEngine的Mono对象 全部是用C++实现的,我们在Mono层的使用的如GameObject、MonoBehaviour这些对象都是Mono层绑定的C++对象。Mono层对这些C++对象做了简单的封装。下图是Mono层GameObject类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//mono
public GameObject(string name)
{
    GameObject.Internal_CreateGameObject(this, name);
}

public GameObject()
{
    GameObject.Internal_CreateGameObject(this, null);
}

public GameObject(string name, params Type[] components)
{
    GameObject.Internal_CreateGameObject(this, name);
    for (int i = 0; i < components.Length; i++)
    {
        Type componentType = components[i];
        this.AddComponent(componentType);
    }
}

可以看到GameObject的所有构造函数都调用了Internal_CreateGameObject静态方法, 接着我们再看这个方法的走向

1
2
3
4
5
6
7
	//cpp
	CUSTOM private static void Internal_CreateGameObject ([Writable]GameObject mono, string name)
	{
		DISALLOW_IN_CONSTRUCTOR
		GameObject* go = MonoCreateGameObject (name.IsNull() ? NULL : name.AsUTF8().c_str() ); 
		ConnectScriptingWrapperToObject (mono.GetScriptingObject(), go); 
	}

通过这个方法Internal_CreateGameObject方法是CShape创建并绑定C++对象的方法,其中很重的是MonoCreateGameObject方法,此方法创建了一个C++的GameObject对象,然后Scripting::ConnectScriptingWrapperToObject把新创建的GameObject的C++对象绑定到了Mono层的GameObject对象,这样我们就可以通过Mono层的GameObject对象访问到C++层的GameObject对象了(Scripting作用空间下定义了很多全局的静态方法,用于Mono和C++交互)。接着我们再看看MonoCreateGameObject方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//cpp
GameObject* MonoCreateGameObject (const char* name)
{
	string cname;
	if (!name)
	{
		cname = "New Game Object";
	}
	else
	{
		cname = name;
	}
	return &CreateGameObject (cname, "Transform", NULL);
}

//cpp
GameObject& CreateGameObject (const string& name, const char* componentName, ...)
{
	// Create game object with name!
	GameObject &go = *NEW_OBJECT (GameObject);

	ActivateGameObject (go, name);

	// Add components with class names!
	va_list ap;
	va_start (ap, componentName);
	AddComponentsFromVAList (go, componentName, ap);
	va_end (ap);

	return go;
}

最终我们看到通过在CreateGameObject方法中分配的内存创建的对象, 这是我们从Mono层创建一个GameObject对象的过程, 通过这个过程我们解释了问题一。

通过GameObject对象获取到自身任何Component的内部实现是怎样的?

Transform只是一个普通的Component和其他继承自Component的组建不同的是,在Mono层的GameObject对象包含了一个Transform的 property, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//mono
namespace UnityEngine
{
	public sealed class GameObject : Object
	{
		public extern Transform transform
		{
			[WrapperlessIcall]
			[MethodImpl(MethodImplOptions.InternalCall)]
			get;
		}
		...
	}
}

1
2
3
4
5
6

//cpp
CUSTOM_PROP Transform transform { FASTGO_QUERY_COMPONENT(Transform) }

 #define FASTGO_QUERY_COMPONENT(x) GameObject& go = *self; return Scripting::GetComponentObjectToScriptingObject (go.QueryComponent (x), go, ClassID (x));

可以看到Mono的GameObject transform属性的get函数内部是通过GameObject的QueryComponent函数来得到C++层的Transform组件,再通过Scripting::GetComponentObjectToScriptingObject返回C++组建对应的Mono层Transform组件。现在我们知道其实Mono层的GameObject并没有持有任何Transform应用了。但是在C++呢?我们在看下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
//cpp  GameObject.h
class EXPORT_COREMODULE GameObject : public EditorExtension
{
	public:

	typedef std::pair<SInt32, ImmediatePtr<Component> > ComponentPair;
	typedef UNITY_VECTOR(kMemBaseObject, ComponentPair)	Container;
	...
	private:
	Container	m_Component;
	...
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//GameObject.cpp
...
void GameObject::AddComponentInternal (Component* com)
{
	AssertIf (com == NULL);
	{
		m_Component.push_back (std::make_pair (com->GetClassID (), ImmediatePtr<Component> (com)));
	}
	...
}
...
void GameObject::AddComponentInternal (GameObject& gameObject, Component& clone)
{
	SET_ALLOC_OWNER(&gameObject);
	Assert(clone.m_GameObject == NULL);
	gameObject.m_Component.push_back(make_pair(clone.GetClassID(), &clone));
	clone.m_GameObject = &gameObject;
}
..

C++层的GameObject有一个成员变量m_Component。m_Component的作用可以通过代码中的两个方法AddComponentInternal方法可知,m_Component是用来存储添加到GameObject中的Component的集合。另外我还发现Container包装的Component用ImmediatePtr修饰了,那么ImmediatePtr有什么作用呢?通过查看代码我总结如(感兴趣的同学可以查看BaseObject.h/cpp文件)): Unity Engine在c++层封装了两个对象引用的包装类ImmediatePtr和PPtr。用ImmediatePtr包装的对象引用属于弱引用,用PPtr包装的对象引用属于强引用。变量m_Component为什么要定义成弱类型的呢?稍后再讲。现在GameObject的m_Component保存所有的添加的Component,那就解释了问题二。

通过Component(Component的派生类,如transform)获取到对应的Gameobject对象的内部实现是怎样的?

我们看下面代码

1
2
3
4
5
6
7
8
9
10
11
12
//Mono
public class Component : Object
{
		public extern GameObject gameObject
		{
			[WrapperlessIcall]
			[MethodImpl(MethodImplOptions.InternalCall)]
			get;
		}
		...
}

1
2
3
4
5
6
7
8
9
//cpp

class EXPORT_COREMODULE Component : public EditorExtension
{
	private:

	ImmediatePtr<GameObject>	m_GameObject;
	...
}

通过上面代码可以看出,Component持有了GameObject的引用,这样就解释了问题三了。此外我们还发现了m_GameObject用ImmediatePtr修饰了,现在我们应该明白了为什么要用ImmediatePtr来修饰GameObject的成员m_Component了,因为GameObject和Component两个类互相持有引用,造成循环引用了。所以Unity 自己封装了保持弱引用功能的ImmediatePtr类解决了这个问题。

阅读全文 »
1 … 7 8 9
© 2025 yiliangduan@qq.com