Unity3D占用内存太大的解决方法[转]

Unity3D 里有两种动态加载机制:一个是Resources.Load,另外一个通过AssetBundle,其实两者区别不大。 Resources.Load就是从一个缺省打进程序包里的AssetBundle里加载资源,而一般AssetBundle文件需要你自己创建,运行时 动态加载,可以指定路径和来源的。

其实场景里所有静态的对象也有这么一个加载过程,只是Unity3D后台替你自动完成了。

详细说一下细节概念:
AssetBundle运行时加载:
来自文件就用CreateFromFile(注意这种方法只能用于standalone程序)这是最快的加载方法
也可以来自Memory,用CreateFromMemory(byte[]),这个byte[]可以来自文件读取的缓冲,www的下载或者其他可能的方式。
其实WWW的assetBundle就是内部数据读取完后自动创建了一个assetBundle而已
Create完以后,等于把硬盘或者网络的一个文件读到内存一个区域,这时候只是个AssetBundle内存镜像数据块,还没有Assets的概念。
Assets加载:
用AssetBundle.Load(同Resources.Load) 这才会从AssetBundle的内存镜像里读取并创建一个Asset对象,创建Asset对象同时也会分配相应内存用于存放(反序列化)
异步读取用AssetBundle.LoadAsync
也可以一次读取多个用AssetBundle.LoadAll
AssetBundle的释放:
AssetBundle.Unload(flase)是释放AssetBundle文件的内存镜像,不包含Load创建的Asset内存对象。
AssetBundle.Unload(true)是释放那个AssetBundle文件内存镜像和并销毁所有用Load创建的Asset内存对象。

一个Prefab从assetBundle里Load出来 里面可能包括:Gameobject transform mesh texture material shader script和各种其他Assets。
你 Instaniate一个Prefab,是一个对Assets进行Clone(复制)+引用结合的过程,GameObject transform 是Clone是新生成的。其他mesh / texture / material / shader 等,这其中些是纯引用的关系的,包括:Texture和TerrainData,还有引用和复制同时存在的,包括:Mesh/material /PhysicMaterial。引用的Asset对象不会被复制,只是一个简单的指针指向已经Load的Asset对象。这种含糊的引用加克隆的混合, 大概是搞糊涂大多数人的主要原因。
专门要提一下的是一个特殊的东西:Script Asset,看起来很奇怪,Unity里每个Script都是一个封闭的Class定义而已,并没有写调用代码,光Class的定义脚本是不会工作的。其 实Unity引擎就是那个调用代码,Clone一个script asset等于new一个class实例,实例才会完成工作。把他挂到Unity主线程的调用链里去,Class实例里的OnUpdate OnStart等才会被执行。多个物体挂同一个脚本,其实就是在多个物体上挂了那个脚本类的多个实例而已,这样就好理解了。在new class这个过程中,数据区是复制的,代码区是共享的,算是一种特殊的复制+引用关系。
你可以再Instaniate一个同样的Prefab,还是这套mesh/texture/material/shader…,这时候会有新的GameObject等,但是不会创建新的引用对象比如Texture.
所以你Load出来的Assets其实就是个数据源,用于生成新对象或者被引用,生成的过程可能是复制(clone)也可能是引用(指针)
当你Destroy一个实例时,只是释放那些Clone对象,并不会释放引用对象和Clone的数据源对象,Destroy并不知道是否还有别的object在引用那些对象。
等到没有任何 游戏场景物体在用这些Assets以后,这些assets就成了没有引用的游离数据块了,是UnusedAssets了,这时候就可以通过 Resources.UnloadUnusedAssets来释放,Destroy不能完成这个任 务,AssetBundle.Unload(false)也不行,AssetBundle.Unload(true)可以但不安全,除非你很清楚没有任何 对象在用这些Assets了。
配个图加深理解:

Unity3D占用内存太大怎么解决呢?

虽然都叫Asset,但复制的和引用的是不一样的,这点被Unity的暗黑技术细节掩盖了,需要自己去理解。

关于内存管理
按照传统的编程思维,最好的方法是:自己维护所有对象,用一个Queue来保存所有object,不用时该Destory的,该Unload的自己处理。
但这样在C# .net框架底下有点没必要,而且很麻烦。
稳妥起见你可以这样管理

创建时:
先建立一个AssetBundle,无论是从www还是文件还是memory
用AssetBundle.load加载需要的asset
加载完后立即AssetBundle.Unload(false),释放AssetBundle文件本身的内存镜像,但不销毁加载的Asset对象。(这样你不用保存AssetBundle的引用并且可以立即释放一部分内存)
释放时:
如果有Instantiate的对象,用Destroy进行销毁
在合适的地方调用Resources.UnloadUnusedAssets,释放已经没有引用的Asset.
如果需要立即释放内存加上GC.Collect(),否则内存未必会立即被释放,有时候可能导致内存占用过多而引发异常。
这样可以保证内存始终被及时释放,占用量最少。也不需要对每个加载的对象进行引用。

当然这并不是唯一的方法,只要遵循加载和释放的原理,任何做法都是可以的。

系统在加载新场景时,所有的内存对象都会被自动销毁,包括你用AssetBundle.Load加载的对象和Instaniate克隆的。但是不包括AssetBundle文件自身的内存镜像,那个必须要用Unload来释放,用.net的术语,这种数据缓存是非托管的。

总结一下各种加载和初始化的用法:
AssetBundle.CreateFrom…..:创建一个AssetBundle内存镜像,注意同一个assetBundle文件在没有Unload之前不能再次被使用
WWW.AssetBundle:同上,当然要先new一个再 yield return 然后才能使用
AssetBundle.Load(name): 从AssetBundle读取一个指定名称的Asset并生成Asset内存对象,如果多次Load同名对象,除第一次外都只会返回已经生成的Asset 对象,也就是说多次Load一个Asset并不会生成多个副本(singleton)。
Resources.Load(path&name):同上,只是从默认的位置加载。
Instantiate(object):Clone 一个object的完整结构,包括其所有Component和子物体(详见官方文档),浅Copy,并不复制所有引用类型。有个特别用法,虽然很少这样 用,其实可以用Instantiate来完整的拷贝一个引用类型的Asset,比如Texture等,要拷贝的Texture必须类型设置为 Read/Write able。

总结一下各种释放
Destroy: 主要用于销毁克隆对象,也可以用于场景内的静态物体,不会自动释放该对象的所有引用。虽然也可以用于Asset,但是概念不一样要小心,如果用于销毁从文 件加载的Asset对象会销毁相应的资源文件!但是如果销毁的Asset是Copy的或者用脚本动态生成的,只会销毁内存对象。
AssetBundle.Unload(false):释放AssetBundle文件内存镜像
AssetBundle.Unload(true):释放AssetBundle文件内存镜像同时销毁所有已经Load的Assets内存对象
Reources.UnloadAsset(Object):显式的释放已加载的Asset对象,只能卸载磁盘文件加载的Asset对象
Resources.UnloadUnusedAssets:用于释放所有没有引用的Asset对象
GC.Collect()强制垃圾收集器立即释放内存 Unity的GC功能不算好,没把握的时候就强制调用一下

在3.5.2之前好像Unity不能显式的释放Asset

举两个例子帮助理解
例子1:
一个常见的错误:你从某个AssetBundle里Load了一个prefab并克隆之:obj = Instaniate(AssetBundle1.Load(‘MyPrefab”);
这个prefab比如是个npc
然后你不需要他的时候你用了:Destroy(obj);你以为就释放干净了
其实这时候只是释放了Clone对象,通过Load加载的所有引用、非引用Assets对象全都静静静的躺在内存里。
这种情况应该在Destroy以后用:AssetBundle1.Unload(true),彻底释放干净。
如果这个AssetBundle1是要反复读取的 不方便Unload,那可以在Destroy以后用:Resources.UnloadUnusedAssets()把所有和这个npc有关的Asset都销毁。
当然如果这个NPC也是要频繁创建 销毁的 那就应该让那些Assets呆在内存里以加速游戏体验。
由此可以解释另一个之前有人提过的话题:为什么第一次Instaniate 一个Prefab的时候都会卡一下,因为在你第一次Instaniate之前,相应的Asset对象还没有被创建,要加载系统内置的 AssetBundle并创建Assets,第一次以后你虽然Destroy了,但Prefab的Assets对象都还在内存里,所以就很快了。

顺便提一下几种加载方式的区别:
其实存在3种加载方式:
一是静态引用,建一个public的变量,在Inspector里把prefab拉上去,用的时候instantiate
二是Resource.Load,Load以后instantiate
三是AssetBundle.Load,Load以后instantiate
三种方式有细 节差异,前两种方式,引用对象texture是在instantiate时加载,而assetBundle.Load会把perfab的全部assets 都加载,instantiate时只是生成Clone。所以前两种方式,除非你提前加载相关引用对象,否则第一次instantiate时会包含加载引用 assets的操作,导致第一次加载的lag。

例子2:
从磁盘读取一个1.unity3d文件到内存并建立一个AssetBundle1对象
AssetBundle AssetBundle1 = AssetBundle.CreateFromFile(“1.unity3d”);
从AssetBundle1里读取并创建一个Texture Asset,把obj1的主贴图指向它
obj1.renderer.material.mainTexture = AssetBundle1.Load(“wall”) as Texture;
把obj2的主贴图也指向同一个Texture Asset
obj2.renderer.material.mainTexture =obj1.renderer.material.mainTexture;
Texture是引用对象,永远不会有自动复制的情况出现(除非你真需要,用代码自己实现copy),只会是创建和添加引用
如果继续:
AssetBundle1.Unload(true) 那obj1和obj2都变成黑的了,因为指向的Texture Asset没了
如果:
AssetBundle1.Unload(false) 那obj1和obj2不变,只是AssetBundle1的内存镜像释放了
继续:
Destroy(obj1),//obj1被释放,但并不会释放刚才Load的Texture
如果这时候:
Resources.UnloadUnusedAssets();
不会有任何内存释放 因为Texture asset还被obj2用着
如果
Destroy(obj2)
obj2被释放,但也不会释放刚才Load的Texture
继续
Resources.UnloadUnusedAssets();
这时候刚才load的Texture Asset释放了,因为没有任何引用了
最后CG.Collect();
强制立即释放内存
由此可以引申出论坛里另一个被提了几次的问题,如何加载一堆大图片轮流显示又不爆掉
不考虑AssetBundle,直接用www读图片文件的话等于是直接创建了一个Texture Asset
假设文件保存在一个List里
TLlist<string> fileList;
int n=0;
IEnumerator OnClick()
{
WWW image = new www(fileList[n++]);
yield return image;
obj.mainTexture = image.texture;

n = (n>=fileList.Length-1)?0:n;
Resources.UnloadUnusedAssets();
}
这样可以保证内存里始终只有一个巨型Texture Asset资源,也不用代码追踪上一个加载的Texture Asset,但是速度比较慢
或者:
IEnumerator OnClick()
{
WWW image = new www(fileList[n++]);
yield return image;
Texture tex = obj.mainTexture;
obj.mainTexture = image.texture;

n = (n>=fileList.Length-1)?0:n;
Resources.UnloadAsset(tex);
}
这样卸载比较快

 

 

Hog的评论引用:

感觉这是Unity内存管理暗黑和混乱的地方,特别是牵扯到Texture
我最近也一直在测试这些用AssetBundle加载的asset一样可以用Resources.UnloadUnusedAssets卸载,但必须先AssetBundle.Unload,才会被识别为无用的asset。比较保险的做法是
创建时:
先建立一个AssetBundle,无论是从www还是文件还是memory
用AssetBundle.load加载需要的asset
用完后立即AssetBundle.Unload(false),关闭AssetBundle但不摧毁创建的对象和引用
销毁时:
对Instantiate的对象进行Destroy
在合适的地方调用Resources.UnloadUnusedAssets,释放已经没有引用的Asset.
如果需要立即释放加上GC.Collect()
这样可以保证内存始终被及时释放
只要你Unload过的AssetBundle,那些创建的对象和引用都会在LoadLevel时被自动释放。

 

全面理解Unity加载和内存管理机制之二:进一步深入和细节
Unity几种动态加载Prefab方式的差异:
其实存在3种加载prefab的方式:
一是静态引用,建一个public的变量,在Inspector里把prefab拉上去,用的时候instantiate
二是Resource.Load,Load以后instantiate
三是AssetBundle.Load,Load以后instantiate
三种方式有细节差异,前两种方式,引用对象texture是在instantiate时加载,而assetBundle.Load会把perfab的全部 assets都加载,instantiate时只是生成Clone。所以前两种方式,除非你提前加载相关引用对象,否则第一次instantiate时会 包含加载引用类assets的操作,导致第一次加载的lag。官方论坛有人说Resources.Load和静态引用是会把所有资源都预先加载的,反复测试的结果,静态引用和Resources.Load也是OnDemand的,用到时才会加载。

几种AssetBundle创建方式的差异:
CreateFromFile:这种方式不会把整个硬盘AssetBundle文件都加载到 内存来,而是类似建立一个文件操作句柄和缓冲区,需要时才实时Load,所以这种加载方式是最节省资源的,基本上AssetBundle本身不占什么内 存,只需要Asset对象的内存。可惜只能在PC/Mac Standalone程序中使用。
CreateFromMemory和www.assetBundle:这两种方式AssetBundle文件会整个镜像于内存中,理论上文件多大就需要多大的内存,之后Load时还要占用额外内存去生成Asset对象。

什么时候才是UnusedAssets?
看一个例子:
Object obj = Resources.Load(“MyPrefab”);
GameObject instance = Instantiate(obj) as GameObject;
………
Destroy(instance);
创建随后销毁了一个Prefab实例,这时候 MyPrefab已经没有被实际的物体引用了,但如果这时:
Resources.UnloadUnusedAssets();
内存并没有被释放,原因:MyPrefab还被这个变量obj所引用
这时候:
obj  = null;
Resources.UnloadUnusedAssets();
这样才能真正释放Assets对象
所以:UnusedAssets不但要没有被实际物体引用,也要没有被生命周期内的变量所引用,才可以理解为 Unused(引用计数为0)
所以所以:如果你用个全局变量保存你Load的Assets,又没有显式的设为null,那 在这个变量失效前你无论如何UnloadUnusedAssets也释放不了那些Assets的。如果你这些Assets又不是从磁盘加载的,那除了 UnloadUnusedAssets或者加载新场景以外没有其他方式可以卸载之。

一个复杂的例子,代码很丑陋实际也不可能这样做,只是为了加深理解

IEnumerator OnClick()

{

Resources.UnloadUnusedAssets();//清干净以免影响测试效果

yield return new WaitForSeconds(3);

float wait = 0.5f;

//用www读取一个assetBundle,里面是一个Unity基本球体和带一张大贴图的材质,是一个Prefab

WWW aa = new WWW(@”file://SpherePrefab.unity3d”);

yield return aa;

AssetBundle asset = aa.assetBundle;

yield return new WaitForSeconds(wait);//每步都等待0.5s以便于分析结果

Texture tt = asset.Load(“BallTexture”) as  Texture;//加载贴图

yield return new WaitForSeconds(wait);

GameObject ba = asset.Load(“SpherePrefab”) as  GameObject;//加载Prefab

yield return new WaitForSeconds(wait);

GameObject obj1 = Instantiate(ba) as GameObject;//生成实例

yield return new WaitForSeconds(wait);

Destroy(obj1);//销毁实例

yield return new WaitForSeconds(wait);

asset.Unload(false);//卸载Assetbundle

yield return new WaitForSeconds(wait);

Resources.UnloadUnusedAssets();//卸载无用资源

yield return new WaitForSeconds(wait);

ba = null;//将prefab引用置为空以后卸无用载资源

Resources.UnloadUnusedAssets();

yield return new WaitForSeconds(wait);

tt = null;//将texture引用置为空以后卸载无用资源

Resources.UnloadUnusedAssets();

}

这是测试结果的内存Profile曲线图

Unity3D占用内存太大怎么解决呢?

图片:p12.jpg

很经典的对称造型,用多少释放多少。

这是各阶段的内存和其他数据变化

说明:
1        初始状态
2        载入AssetBundle文件后,内存多了文件镜像,用量上升,Total Object和Assets增加1(AssetBundle也是object)
3        载入Texture后,内存继续上升,因为多了Texture Asset,Total Objects和Assets增加1
4        载入Prefab后,内存无明显变化,因为最占内存的Texture已经加载,Materials上升是因为多了Prefab的材质,Total Objects和Assets增加6,因为 Perfab 包含很多 Components
5        实例化Prefab以后,显存的Texture Memory、GameObjectTotal、Objects in Scene上升,都是因为实例化了一个可视的对象
6        销毁实例后,上一步的变化还原,很好理解
7        卸载AssetBundle文件后,AssetBundle文件镜像占用的内存被释放,相应的Assets和Total Objects Count也减1
8        直接Resources.UnloadUnusedAssets,没有任何变化,因为所有Assets引用并没有清空
9        把Prefab引用变量设为null以后,整个Prefab除了Texture外都没有任何引用了,所以被UnloadUnusedAssets销毁,Assets和Total Objects Count减6
10        再把Texture的引用变量设为null,之后也被UnloadUnusedAssets销毁,内存被释放,assets和Total Objects Count减1,基本还原到初始状态

从中也可以看出:
Texture加载以后是到内存,显示的时候才进入显存的Texture Memory。
所有的东西基础都是Object
Load的是Asset,Instantiate的是GameObject和Object in Scene
Load的Asset要Unload,new的或者Instantiate的object可以Destroy

原文地址

 

Unity 3D中的内存管理[转]

Unity3D在内存占用上一直被人诟病,特别是对于面向移动设备的游戏开发,动辄内存占用飙上一两百兆,导致内存资源耗尽,从而被系统强退造成极 差的体验。类似这种情况并不少见,但是绝大部分都是可以避免的。虽然理论上Unity的内存管理系统应当为开发者分忧解难,让大家投身到更有意义的事情中 去,但是对于Unity对内存的管理方式,官方文档中并没有太多的说明,基本需要依靠自己摸索。最近在接手的项目中存在严重的内存问题,在参照文档和 Unity Answer众多猜测和证实之后,稍微总结了下Unity中的内存的分配和管理的基本方式,在此共享。

虽然Unity标榜自己的内存使用全都是“Managed Memory”,但是事实上你必须正确地使用内存,以保证回收机制正确运行。如果没有做应当做的事情,那么场景和代码很有可能造成很多非必要内存的占用, 这也是很多Unity开发者抱怨内存占用太大的原因。接下来我会介绍Unity使用内存的种类,以及相应每个种类的优化和使用的技巧。遵循使用原则,可以 让非必要资源尽快得到释放,从而降低内存占用。

 

Unity中的内存种类
实际上Unity游戏使用的内存一共有三种:程序代码、托管堆(Managed Heap)以及本机堆(Native Heap)。

程序代码包括了所有的Unity引擎,使用的库,以及你所写的所有的游戏代码。在编译后,得到的运行文件将会被加载到设备中执行,并占用一定内存。

这部分内存实际上是没有办法去“管理”的,它们将在内存中从一开始到最后一直存在。一个空的Unity默认场景,什么代码都不放,在iOS设备上占 用内存应该在17MB左右,而加上一些自己的代码很容易就飙到20MB左右。想要减少这部分内存的使用,能做的就是减少使用的库,稍后再说。

托管堆是被Mono使用的一部分内存。Mono项目一个开源的.net框架的一种实现,对于Unity开发,其实充当了基本类库的角色。

托管堆用来存放类的实例(比如用new生成的列表,实例中的各种声明的变量等)。“托管”的意思是Mono“应该”自动地改变堆的大小来适应你所需要的内存,

并且定时地使用垃圾回收(Garbage Collect)来释放已经不需要的内存。关键在于,有时候你会忘记清除对已经不需要再使用的内存的引用,

从而导致Mono认为这块内存一直有用,而无法回收。

最后,本机堆是Unity引擎进行申请和操作的地方,比如贴图,音效,关卡数据等。Unity使用了自己的一套内存管理机制来使这块内存具有和托管堆类似的功能。

基本理念是,如果在这个关卡里需要某个资源,那么在需要时就加载,之后在没有任何引用时进行卸载。听起来很美好也和托管堆一样,

但是由于Unity有一套自动加载和卸载资源的机制,让两者变得差别很大。自动加载资源可以为开发者省不少事儿,

但是同时也意味着开发者失去了手动管理所有加载资源的权力,这非常容易导致大量的内存占用(贴图什么的你懂的),

也是Unity给人留下“吃内存”印象的罪魁祸首。

 

优化程序代码的内存占用
这部分的优化相对简单,因为能做的事情并不多:主要就是减少打包时的引用库,改一改build设置即可。

对于一个新项目来说不会有太大问题,但是如果是已经存在的项目,可能改变会导致原来所需要的库的缺失(虽说一般来说这种可能性不大),

因此有可能无法做到最优。

 

当使用Unity开发时,默认的Mono包含库可以说大部分用不上,在Player Setting(Edit->Project Setting->Player或者Shift+Ctrl(Command)+B里的Player Setting按钮)

面板里,将最下方的Optimization栏目中“Api Compatibility Level”选为.NET 2.0 Subset,表示你只会使用到部分的.NET 2.0 Subset,不需要Unity将全部.NET的Api包含进去。接下来的“Stripping Level”表示从build的库中剥离的力度,每一个剥离选项都将从打包好的库中去掉一部分内容。你需要保证你的代码没有用到这部分被剥离的功能,

选为“Use micro mscorlib”的话将使用最小的库(一般来说也没啥问题,不行的话可以试试之前的两个)。库剥离可以极大地降低打包后的程序的尺寸以及程序代码的内存占用,唯一的缺点是这个功能只支持Pro版的Unity。

这部分优化的力度需要根据代码所用到的.NET的功能来进行调整,有可能不能使用Subset或者最大的剥离力度。

如果超出了限度,很可能会在需要该功能时因为找不到相应的库而crash掉(iOS的话很可能在Xcode编译时就报错了)。

比较好地解决方案是仍然用最强的剥离,并辅以较小的第三方的类库来完成所需功能。

一个最常见问题是最大剥离时Sysytem.Xml是不被Subset和micro支持的,如果只是为了xml,完全可以导入一个轻量级的xml库来解决依赖(Unity官方推荐这个)。

关于每个设定对应支持的库的详细列表,可以在这里找到。关于每个剥离级别到底做了什么,Unity的文档也有说明。

实际上,在游戏开发中绝大多数被剥离的功能使用不上的,因此不管如何,库剥离的优化方法都值得一试。

 

托管堆优化
Unity有一篇不错的关于托管堆代码如何写比较好的说明,在此基础上我个人有一些补充。

首先需要明确,托管堆中存储的是你在你的代码中申请的内存(不论是用js,C#还是Boo写的)。

一般来说,无非是new或者Instantiate两种生成object的方法(事实上Instantiate中也是调用了new)。

在接收到alloc请求后,托管堆在其上为要新生成的对象实例以及其实例变量分配内存,如果可用空间不足,则向系统申请更多空间。

当你使用完一个实例对象之后,通常来说在脚本中就不会再有对该对象的引用了(这包括将变量设置为null或其他引用,超出了变量的作用域,

或者对Unity对象发送Destory())。在每隔一段时间,Mono的垃圾回收机制将检测内存,将没有再被引用的内存释放回收。总的来说,

你要做的就是在尽可能早的时间将不需要的引用去除掉,这样回收机制才能正确地把不需要的内存清理出来。但是需要注意在内存清理时有可能造成游戏的短时间卡顿,

这将会很影响游戏体验,因此如果有大量的内存回收工作要进行的话,需要尽量选择合适的时间。

如果在你的游戏里,有特别多的类似实例,并需要对它们经常发送Destroy()的话,游戏性能上会相当难看。比如小熊推金币中的金币实例,按理说每枚金币落下台子后

都需要对其Destory(),然后新的金币进入台子时又需要Instantiate,这对性能是极大的浪费。一种通常的做法是在不需要时,不摧毁这个GameObject,而只是隐藏它,

并将其放入一个重用数组中。之后需要时,再从重用数组中找到可用的实例并显示。这将极大地改善游戏的性能,相应的代价是消耗部分内存,一般来说这是可以接受的。

关于对象重用,可以参考Unity关于内存方面的文档中Reusable Object Pools部分,或者Prime31有一个是用Linq来建立重用池的视频教程(Youtube,需要FQ,上,下)。

如果不是必要,应该在游戏进行的过程中尽量减少对GameObject的Instantiate()和Destroy()调用,因为对计算资源会有很大消耗。在便携设备上短时间大量生成和摧毁物体的

话,很容易造成瞬时卡顿。如果内存没有问题的话,尽量选择先将他们收集起来,然后在合适的时候(比如按暂停键或者是关卡切换),将它们批量地销毁并 且回收内存。Mono的内存回收会在后台自动进行,系统会选择合适的时间进行垃圾回收。在合适的时候,也可以手动地调用 System.GC.Collect()来建议系统进行一次垃圾回收。

要注意的是这里的调用真的仅仅只是建议,可能系统会在一段时间后在进行回收,也可能完全不理会这条请求,不过在大部分时间里,这个调用还是靠谱的。

 

本机堆的优化
当你加载完成一个Unity的scene的时候,scene中的所有用到的asset(包括Hierarchy中所有GameObject上以及脚本中赋值了的的材质,贴图,动画,声音等素材),

都会被自动加载(这正是Unity的智能之处)。也就是说,当关卡呈现在用户面前的时候,所有Unity编辑器能认识的本关卡的资源都已经被预先加 入内存了,这样在本关卡中,用户将有良好的体验,不论是更换贴图,声音,还是播放动画时,都不会有额外的加载,这样的代价是内存占用将变多。Unity最 初的设计目的还是面向台式机,

几乎无限的内存和虚拟内存使得这样的占用似乎不是问题,但是这样的内存策略在之后移动平台的兴起和大量移动设备游戏的制作中出现了弊端,因为移动设 备能使用的资源始终非常有限。因此在面向移动设备游戏的制作时,尽量减少在Hierarchy对资源的直接引用,而是使用Resource.Load的方 法,在需要的时候从硬盘中读取资源,

在使用后用Resource.UnloadAsset()和Resources.UnloadUnusedAssets()尽快将其卸载掉。总之,这里是一个处理时间和占用内存空间的trade off,

如何达到最好的效果没有标准答案,需要自己权衡。

在关卡结束的时候,这个关卡中所使用的所有资源将会被卸载掉(除非被标记了DontDestroyOnLoad)的资源。注意不仅是DontDestroyOnLoad的资源本身,

其相关的所有资源在关卡切换时都不会被卸载。DontDestroyOnLoad一般被用来在关卡之间保存一些玩家的状态,比如分数,级别等偏向文 本的信息。如果DontDestroyOnLoad了一个包含很多资源(比如大量贴图或者声音等大内存占用的东西)的话,这部分资源在场景切换时无法卸 载,将一直占用内存,

这种情况应该尽量避免。

另外一种需要注意的情况是脚本中对资源的引用。大部分脚本将在场景转换时随之失效并被回收,但是,在场景之间被保持的脚本不在此列(通常情况是被附 着在DontDestroyOnLoad的GameObject上了)。而这些脚本很可能含有对其他物体的Component或者资源的引用,这样相关的 资源就都得不到释放,

这绝对是不想要的情况。另外,static的单例(singleton)在场景切换时也不会被摧毁,同样地,如果这种单例含有大量的对资源的引用,也会成为大问题。

因此,尽量减少代码的耦合和对其他脚本的依赖是十分有必要的。如果确实无法避免这种情况,那应当手动地对这些不再使用的引用对象调用Destroy()

或者将其设置为null。这样在垃圾回收的时候,这些内存将被认为已经无用而被回收。

需要注意的是,Unity在一个场景开始时,根据场景构成和引用关系所自动读取的资源,只有在读取一个新的场景或者reset当前场景时,才会得到清理。

因此这部分内存占用是不可避免的。在小内存环境中,这部分初始内存的占用十分重要,因为它决定了你的关卡是否能够被正常加载。因此在计算资源充足

或是关卡开始之后还有机会进行加载时,尽量减少Hierarchy中的引用,变为手动用Resource.Load,将大大减少内存占用。在 Resource.UnloadAsset()和Resources.UnloadUnusedAssets()时,只有那些真正没有任何引用指向的资源 会被回收,因此请确保在资源不再使用时,将所有对该资源的引用设置为null或者Destroy。

同样需要注意,这两个Unload方法仅仅对Resource.Load拿到的资源有效,而不能回收任何场景开始时自动加载的资源。与此类似的还有 AssetBundle的Load和Unload方法,灵活使用这些手动自愿加载和卸载的方法,是优化Unity内存占用的不二法则~

总之这些就是关于Unity3d优化细节,具体还是查看Unity3D的技术手册,以便实现最大的优化.

原帖:http://www.onevcat.com/2012/11/memory-in-unity3d/

Git常用操作[转]

Git是目前最流行的版本管理系统,学会Git几乎成了开发者的必备技能。

Git有很多优势,其中之一就是远程操作非常简便。本文详细介绍5个Git命令,它们的概念和用法,理解了这些内容,你就会完全掌握Git远程操作。

git clone
git remote
git fetch
git pull
git push
本文针对初级用户,从最简单的讲起,但是需要读者对Git的基本用法有所了解。同时,本文覆盖了上面5个命令的几乎所有的常用用法,所以对于熟练用户也有参考价值。

一、git clone
远程操作的第一步,通常是从远程主机克隆一个版本库,这时就要用到git clone命令。

$ git clone <版本库的网址>

比如,克隆jQuery的版本库。

$ git clone https://github.com/jquery/jquery.git

该命令会在本地主机生成一个目录,与远程主机的版本库同名。如果要指定不同的目录名,可以将目录名作为git clone命令的第二个参数。

$ git clone <版本库的网址> <本地目录名>

git clone支持多种协议,除了HTTP(s)以外,还支持SSH、Git、本地文件协议等,下面是一些例子。

$ git clone http[s]://example.com/path/to/repo.git/
$ git clone ssh://example.com/path/to/repo.git/
$ git clone git://example.com/path/to/repo.git/
$ git clone /opt/git/project.git
$ git clone file:///opt/git/project.git
$ git clone ftp[s]://example.com/path/to/repo.git/
$ git clone rsync://example.com/path/to/repo.git/

SSH协议还有另一种写法。

$ git clone [user@]example.com:path/to/repo.git/

通常来说,Git协议下载速度最快,SSH协议用于需要用户认证的场合。各种协议优劣的详细讨论请参考官方文档。

二、git remote
为了便于管理,Git要求每个远程主机都必须指定一个主机名。git remote命令就用于管理主机名。

不带选项的时候,git remote命令列出所有远程主机。

$ git remote
origin

使用-v选项,可以参看远程主机的网址。

$ git remote -v
origin git@github.com:jquery/jquery.git (fetch)
origin git@github.com:jquery/jquery.git (push)

上面命令表示,当前只有一台远程主机,叫做origin,以及它的网址。

克隆版本库的时候,所使用的远程主机自动被Git命名为origin。如果想用其他的主机名,需要用git clone命令的-o选项指定。

$ git clone -o jQuery https://github.com/jquery/jquery.git
$ git remote
jQuery

上面命令表示,克隆的时候,指定远程主机叫做jQuery。

git remote show命令加上主机名,可以查看该主机的详细信息。

$ git remote show <主机名>

git remote add命令用于添加远程主机。

$ git remote add <主机名> <网址>

git remote rm命令用于删除远程主机。

$ git remote rm <主机名>

git remote rename命令用于远程主机的改名。

$ git remote rename <原主机名> <新主机名>

三、git fetch
一旦远程主机的版本库有了更新(Git术语叫做commit),需要将这些更新取回本地,这时就要用到git fetch命令。

$ git fetch <远程主机名>

上面命令将某个远程主机的更新,全部取回本地。

git fetch命令通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。

默认情况下,git fetch取回所有分支(branch)的更新。如果只想取回特定分支的更新,可以指定分支名。

$ git fetch <远程主机名> <分支名>

比如,取回origin主机的master分支。

$ git fetch origin master

所取回的更新,在本地主机上要用”远程主机名/分支名”的形式读取。比如origin主机的master,就要用origin/master读取。

git branch命令的-r选项,可以用来查看远程分支,-a选项查看所有分支。

$ git branch -r
origin/master

$ git branch -a
* master
remotes/origin/master

上面命令表示,本地主机的当前分支是master,远程分支是origin/master。

取回远程主机的更新以后,可以在它的基础上,使用git checkout命令创建一个新的分支。

$ git checkout -b newBrach origin/master

上面命令表示,在origin/master的基础上,创建一个新分支。

此外,也可以使用git merge命令或者git rebase命令,在本地分支上合并远程分支。

$ git merge origin/master
# 或者
$ git rebase origin/master

上面命令表示在当前分支上,合并origin/master。

四、git pull
git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。

$ git pull <远程主机名> <远程分支名>:<本地分支名>

比如,取回origin主机的next分支,与本地的master分支合并,需要写成下面这样。

$ git pull origin next:master

如果远程分支是与当前分支合并,则冒号后面的部分可以省略。

$ git pull origin next

上面命令表示,取回origin/next分支,再与当前分支合并。实质上,这等同于先做git fetch,再做git merge。

$ git fetch origin
$ git merge origin/next

在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动”追踪”origin/master分支。

Git也允许手动建立追踪关系。

git branch –set-upstream master origin/next

上面命令指定master分支追踪origin/next分支。

如果当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名。

$ git pull origin

上面命令表示,本地的当前分支自动与对应的origin主机”追踪分支”(remote-tracking branch)进行合并。

如果当前分支只有一个追踪分支,连远程主机名都可以省略。

$ git pull

上面命令表示,当前分支自动与唯一一个追踪分支进行合并。

如果合并需要采用rebase模式,可以使用–rebase选项。

$ git pull –rebase <远程主机名> <远程分支名>:<本地分支名>

如果远程主机删除了某个分支,默认情况下,git pull 不会在拉取远程分支的时候,删除对应的本地分支。这是为了防止,由于其他人操作了远程主机,导致git pull不知不觉删除了本地分支。

但是,你可以改变这个行为,加上参数 -p 就会在本地删除远程已经删除的分支。

$ git pull -p
# 等同于下面的命令
$ git fetch –prune origin
$ git fetch -p

五、git push
git push命令用于将本地分支的更新,推送到远程主机。它的格式与git pull命令相仿。

$ git push <远程主机名> <本地分支名>:<远程分支名>

注意,分支推送顺序的写法是<来源地>:<目的地>,所以git pull是<远程分支>:<本地分支>,而git push是<本地分支>:<远程分支>。

如果省略远程分支名,则表示将本地分支推送与之存在”追踪关系”的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。

$ git push origin master

上面命令表示,将本地的master分支推送到origin主机的master分支。如果后者不存在,则会被新建。

如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。

$ git push origin :master
# 等同于
$ git push origin –delete master

上面命令表示删除origin主机的master分支。

如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。

$ git push origin

上面命令表示,将当前分支推送到origin主机的对应分支。

如果当前分支只有一个追踪分支,那么主机名都可以省略。

$ git push

如果当前分支与多个主机存在追踪关系,则可以使用-u选项指定一个默认主机,这样后面就可以不加任何参数使用git push。

$ git push -u origin master

上面命令将本地的master分支推送到origin主机,同时指定origin为默认主机,后面就可以不加任何参数使用git push了。

不带任何参数的git push,默认只推送当前分支,这叫做simple方式。此外,还有一种matching方式,会推送所有有对应的远程分支的本地分支。Git 2.0版本之前,默认采用matching方法,现在改为默认采用simple方式。如果要修改这个设置,可以采用git config命令。

$ git config –global push.default matching
# 或者
$ git config –global push.default simple

还有一种情况,就是不管是否存在对应的远程分支,将本地的所有分支都推送到远程主机,这时需要使用–all选项。

$ git push –all origin

上面命令表示,将所有本地分支都推送到origin主机。

如果远程主机的版本比本地版本更新,推送时Git会报错,要求先在本地做git pull合并差异,然后再推送到远程主机。这时,如果你一定要推送,可以使用–force选项。

$ git push –force origin

上面命令使用–force选项,结果导致远程主机上更新的版本被覆盖。除非你很确定要这样做,否则应该尽量避免使用–force选项。

最后,git push不会推送标签(tag),除非使用–tags选项。

$ git push origin –tags

(完)

原文

UniRx – Unity响应式编程插件[转]

UniRx – Unity响应式编程插件

插件作者Yoshifumi Kawai(neuecc) 
本文译者:郑洪智 – 你的技术探路者

Gitter

UniRx是什么?

UniRx (Unity响应式编程插件) 重写了.Net的响应式扩展。.Net官方的Rx很棒,但是在Unity中无法使用,并且与IOS的IL2CPP有兼容性问题。这个库这些问题并且添加了一些Unity专属的工具类。 支持的平台有:PC/Mac/Android/iOS/WP8/WindowsStore/等等,并且支持Unity4.6之后的所有版本。

UniRx 在 Unity Asset Store 的地址(免费) – http://u3d.as/content/neuecc/uni-rx-reactive-extensions-for-unity/7tT

演讲PPT – http://www.slideshare.net/neuecc/unirx-reactive-extensions-for-unityen

更新博客 – https://medium.com/@neuecc

在Unity论坛中提供支持 – http://forum.unity3d.com/threads/248535-UniRx-Reactive-Extensions-for-Unity

更新日志 UniRx/releases

UniRx 包含 Core Library (Port of Rx) + Platform Adaptor (MainThreadScheduler/FromCoroutine/etc) + Framework (ObservableTriggers/ReactiveProeperty/etc)

为什么用Rx?

一般来说,网络操作需要用到 WWW  Coroutine。但是使用 Coroutine 对于异步操作来说不是一个好的选择,原因如下:

  1. 协程不能有返回值,因为它返回类型必须是IEnumerator
  2. 协程不能处理异常,因为 yield return 语句没办法被 try-catch

会造成代码大面积的强耦合。

Rx就是为了解决异步问题而来的。Rx可以让异步操作更优雅,使用事件驱动编程,使用LINQ操作。

游戏循环 (every Update, OnCollisionEnter, etc), 传感器数据 (Kinect, Leap Motion, VR Input, etc.) 都是事件。Rx将事件转化为响应式的序列,通过LINQ操作可以很简单地组合起来,还支持时间操作。

Unity通常是单线程,但是UniRx可以让多线程更容易。

UniRx 可以简化 uGUI 的编程,所有的UI事件 (clicked, valuechanged, etc) 可以转化为 UniRx 的事件流。

简介

介绍 Rx 的非常棒的文章: The introduction to Reactive Programming you’ve been missing.

下面的代码实现了双击的检测:

var clickStream = Observable.EveryUpdate()
    .Where(_ => Input.GetMouseButtonDown(0));

clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))
    .Where(xs => xs.Count >= 2)
    .Subscribe(xs => Debug.Log("DoubleClick Detected! Count:" + xs.Count));

 

这个例子仅用5行代码,展示出了下面的特性:

  • 将游戏循环为 (Update) 变成事件流
  • 组合事件流
  • 合并自身事件流
  • 基于时间的操作非常简单

网络操作

使用 ObservableWWW 进行异步网络操作。它的 Get/Post 方法返回可订阅(Subscribe)的 IObservables:

ObservableWWW.Get("http://google.co.jp/")
    .Subscribe(
        x => Debug.Log(x.Substring(0, 100)), // onSuccess
        ex => Debug.LogException(ex)); // onError

 

Rx 可以组合和取消。你也可以通过LINQ表达式进行查询:

// composing asynchronous sequence with LINQ query expressions
var query = from google in ObservableWWW.Get("http://google.com/")
            from bing in ObservableWWW.Get("http://bing.com/")
            from unknown in ObservableWWW.Get(google + bing)
            select new { google, bing, unknown };

var cancel = query.Subscribe(x => Debug.Log(x));

// Call Dispose is cancel.
cancel.Dispose();

 

并行请求使用 Observable.WhenAll :

// Observable.WhenAll is for parallel asynchronous operation
// (It's like Observable.Zip but specialized for single async operations like Task.WhenAll)
var parallel = Observable.WhenAll(
    ObservableWWW.Get("http://google.com/"),
    ObservableWWW.Get("http://bing.com/"),
    ObservableWWW.Get("http://unity3d.com/"));

parallel.Subscribe(xs =>
{
    Debug.Log(xs[0].Substring(0, 100)); // google
    Debug.Log(xs[1].Substring(0, 100)); // bing
    Debug.Log(xs[2].Substring(0, 100)); // unity
});

 

也可以获取进度信息:

// notifier for progress use ScheudledNotifier or new Progress<float>(/* action */)
var progressNotifier = new ScheduledNotifier<float>();
progressNotifier.Subscribe(x => Debug.Log(x)); // write www.progress

// pass notifier to WWW.Get/Post
ObservableWWW.Get("http://google.com/", progress: progressNotifier).Subscribe();

 

错误处理:

// If WWW has .error, ObservableWWW throws WWWErrorException to onError pipeline.
// WWWErrorException has RawErrorMessage, HasResponse, StatusCode, ResponseHeaders
ObservableWWW.Get("http://www.google.com/404")
    .CatchIgnore((WWWErrorException ex) =>
    {
        Debug.Log(ex.RawErrorMessage);
        if (ex.HasResponse)
        {
            Debug.Log(ex.StatusCode);
        }
        foreach (var item in ex.ResponseHeaders)
        {
            Debug.Log(item.Key + ":" + item.Value);
        }
    })
    .Subscribe();

 

和 IEnumerators 一起使用(协程)

IEnumerator (协程) 是Unity主要的异步工具。UniRx 集成了协程和 IObservables。 你可以用协程写异步代码,然后用 UniRx 来组织他们。这是控制异步流的最好的方法。

// two coroutines

IEnumerator AsyncA()
{
    Debug.Log("a start");
    yield return new WaitForSeconds(1);
    Debug.Log("a end");
}

IEnumerator AsyncB()
{
    Debug.Log("b start");
    yield return new WaitForEndOfFrame();
    Debug.Log("b end");
}

// main code
// Observable.FromCoroutine converts IEnumerator to Observable<Unit>.
// You can also use the shorthand, AsyncA().ToObservable()

// after AsyncA completes, run AsyncB as a continuous routine.
// UniRx expands SelectMany(IEnumerator) as SelectMany(IEnumerator.ToObservable())
var cancel = Observable.FromCoroutine(AsyncA)
    .SelectMany(AsyncB)
    .Subscribe();

// you can stop a coroutine by calling your subscription's Dispose.
cancel.Dispose();

 

在 Unity 5.3 或更新版本里, 可以使用 ToYieldInstruction 将 Observable 转为 Coroutine。

IEnumerator TestNewCustomYieldInstruction()
{
    // wait Rx Observable.
    yield return Observable.Timer(TimeSpan.FromSeconds(1)).ToYieldInstruction();

    // you can change the scheduler(this is ignore Time.scale)
    yield return Observable.Timer(TimeSpan.FromSeconds(1), Scheduler.MainThreadIgnoreTimeScale).ToYieldInstruction();

    // get return value from ObservableYieldInstruction
    var o = ObservableWWW.Get("http://unity3d.com/").ToYieldInstruction(throwOnError: false);
    yield return o;

    if (o.HasError) { Debug.Log(o.Error.ToString()); }
    if (o.HasResult) { Debug.Log(o.Result); }

    // other sample(wait until transform.position.y >= 100) 
    yield return this.transform.ObserveEveryValueChanged(x => x.position).FirstOrDefault(p => p.y >= 100).ToYieldInstruction();
}

 

通常,协程想要返回一个值需要使用回调callback。Observable.FromCoroutine 可以将协程转为可以取消的 IObservable[T]。

// public method
public static IObservable<string> GetWWW(string url)
{
    // convert coroutine to IObservable
    return Observable.FromCoroutine<string>((observer, cancellationToken) => GetWWWCore(url, observer, cancellationToken));
}

// IObserver is a callback publisher
// Note: IObserver's basic scheme is "OnNext* (OnError | Oncompleted)?" 
static IEnumerator GetWWWCore(string url, IObserver<string> observer, CancellationToken cancellationToken)
{
    var www = new UnityEngine.WWW(url);
    while (!www.isDone && !cancellationToken.IsCancellationRequested)
    {
        yield return null;
    }

    if (cancellationToken.IsCancellationRequested) yield break;

    if (www.error != null)
    {
        observer.OnError(new Exception(www.error));
    }
    else
    {
        observer.OnNext(www.text);
        observer.OnCompleted(); // IObserver needs OnCompleted after OnNext!
    }
}

 

这有一些例子。接下来是一个多OnNext模式。

public static IObservable<float> ToObservable(this UnityEngine.AsyncOperation asyncOperation)
{
    if (asyncOperation == null) throw new ArgumentNullException("asyncOperation");

    return Observable.FromCoroutine<float>((observer, cancellationToken) => RunAsyncOperation(asyncOperation, observer, cancellationToken));
}

static IEnumerator RunAsyncOperation(UnityEngine.AsyncOperation asyncOperation, IObserver<float> observer, CancellationToken cancellationToken)
{
    while (!asyncOperation.isDone && !cancellationToken.IsCancellationRequested)
    {
        observer.OnNext(asyncOperation.progress);
        yield return null;
    }
    if (!cancellationToken.IsCancellationRequested)
    {
        observer.OnNext(asyncOperation.progress); // push 100%
        observer.OnCompleted();
    }
}

// usecase
Application.LoadLevelAsync("testscene")
    .ToObservable()
    .Do(x => Debug.Log(x)) // output progress
    .Last() // last sequence is load completed
    .Subscribe();

 

用于多线程

// Observable.Start is start factory methods on specified scheduler
// default is on ThreadPool
var heavyMethod = Observable.Start(() =>
{
    // heavy method...
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));
    return 10;
});

var heavyMethod2 = Observable.Start(() =>
{
    // heavy method...
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(3));
    return 10;
});

// Join and await two other thread values
Observable.WhenAll(heavyMethod, heavyMethod2)
    .ObserveOnMainThread() // return to main thread
    .Subscribe(xs =>
    {
        // Unity can't touch GameObject from other thread
        // but use ObserveOnMainThread, you can touch GameObject naturally.
        (GameObject.Find("myGuiText")).guiText.text = xs[0] + ":" + xs[1];
    }); 

 

默认调度器

UniRx 中基于时间的操作(Interval, Timer, Buffer(timeSpan), etc) 使用 Scheduler.MainThread 作为他们的默认调度器。这意味大多数操作符(除了Observable.Start)在单线程上工作,所以 ObserverOn 不是必须的而且不需要处理线程安全。这和标准的 RxNet 不太一样,但是更符合Unity环境。

Scheduler.MainThread 会受 Time.timeScale 的影响。如果你想要忽略time scale,使用 Scheduler.MainThreadIgnoreTimeScale 

MonoBehaviour的触发器

使用 UniRx.Triggers 可以处理Monobehaviour的事件:

using UniRx;
using UniRx.Triggers; // need UniRx.Triggers namespace

public class MyComponent : MonoBehaviour
{
    void Start()
    {
        // Get the plain object
        var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);

        // Add ObservableXxxTrigger for handle MonoBehaviour's event as Observable
        cube.AddComponent<ObservableUpdateTrigger>()
            .UpdateAsObservable()
            .SampleFrame(30)
            .Subscribe(x => Debug.Log("cube"), () => Debug.Log("destroy"));

        // destroy after 3 second:)
        GameObject.Destroy(cube, 3f);
    }
}

 

支持的触发器列表 UniRx.wiki#UniRx.Triggers.

通过订阅Component/GameObject的扩展方法,控制这些触发器可以更简单。这些方法会自动添加ObservableTrigger到物体上(除了 ObservableEventTrigger and ObservableStateMachineTrigger):

using UniRx;
using UniRx.Triggers; // need UniRx.Triggers namespace for extend gameObejct

public class DragAndDropOnce : MonoBehaviour
{
    void Start()
    {
        // All events can subscribe by ***AsObservable
        this.OnMouseDownAsObservable()
            .SelectMany(_ => this.UpdateAsObservable())
            .TakeUntil(this.OnMouseUpAsObservable())
            .Select(_ => Input.mousePosition)
            .Subscribe(x => Debug.Log(x));
    }
}

 

之前的UniRx提供了 ObservableMonoBehaviour。这是一个不再支持的过时的接口。请使用 UniRx.Triggers 代替。

创建自定义的触发器

处理Unity的事件的最好办法是转成Observable。如果UniRx自带的触发器还不够的话,你也可以自己创建触发器。下面是一个UGUI的长按触发器:

public class ObservableLongPointerDownTrigger : ObservableTriggerBase, IPointerDownHandler, IPointerUpHandler
{
    public float IntervalSecond = 1f;

    Subject<Unit> onLongPointerDown;

    float? raiseTime;

    void Update()
    {
        if (raiseTime != null && raiseTime <= Time.realtimeSinceStartup)
        {
            if (onLongPointerDown != null) onLongPointerDown.OnNext(Unit.Default);
            raiseTime = null;
        }
    }

    void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
    {
        raiseTime = Time.realtimeSinceStartup + IntervalSecond;
    }

    void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
    {
        raiseTime = null;
    }

    public IObservable<Unit> OnLongPointerDownAsObservable()
    {
        return onLongPointerDown ?? (onLongPointerDown = new Subject<Unit>());
    }

    protected override void RaiseOnCompletedOnDestroy()
    {
        if (onLongPointerDown != null)
        {
            onLongPointerDown.OnCompleted();
        }
    }
}

 

使用起来和自带触发器一样简单:

var trigger = button.AddComponent<ObservableLongPointerDownTrigger>();

trigger.OnLongPointerDownAsObservable().Subscribe();

 

Observable 生命周期管理

OnCompleted 什么时候会被调用?使用UniRx的时候Subscription的生命周期管理非常重要。ObservableTriggers 在attached的物体被销毁的时候调用。其他静态的方法 (Observable.Timer, Observable.EveryUpdate, etc…) 不会自动停止,需要手动去管理。

Rx 提供了一些方法, 例如 IDisposable.AddTo 可以让你一次性释放多个subscriptions:

// CompositeDisposable is similar with List<IDisposable>, manage multiple IDisposable
CompositeDisposable disposables = new CompositeDisposable(); // field

void Start()
{
    Observable.EveryUpdate().Subscribe(x => Debug.Log(x)).AddTo(disposables);
}

void OnTriggerEnter(Collider other)
{
    // .Clear() => Dispose is called for all inner disposables, and the list is cleared.
    // .Dispose() => Dispose is called for all inner disposables, and Dispose is called immediately after additional Adds.
    disposables.Clear();
}

 

如果你想在物体销毁时自动释放,可以用AddTo(GameObject/Component):

void Start()
{
    Observable.IntervalFrame(30).Subscribe(x => Debug.Log(x)).AddTo(this);
}

 

AddTo 方法可以带来自动释放。如果你需要一些特殊的 OnCompleted 处理,使用 TakeWhile, TakeUntil, TakeUntilDestroy  TakeUntilDisable 

Observable.IntervalFrame(30).TakeUntilDisable(this)
    .Subscribe(x => Debug.Log(x), () => Debug.Log("completed!"));

 

处理事件时,Repeat 时一个重要但是很危险的方法。可能会造成死循环,一定要小心处理:

using UniRx;
using UniRx.Triggers;

public class DangerousDragAndDrop : MonoBehaviour
{
    void Start()
    {
        this.gameObject.OnMouseDownAsObservable()
            .SelectMany(_ => this.gameObject.UpdateAsObservable())
            .TakeUntil(this.gameObject.OnMouseUpAsObservable())
            .Select(_ => Input.mousePosition)
            .Repeat() // dangerous!!! Repeat cause infinite repeat subscribe at GameObject was destroyed.(If in UnityEditor, Editor is freezed)
            .Subscribe(x => Debug.Log(x));
    }
}

 

UniRx额外提供了一个安全的Repeat方法。RepeatSafe: 如果”OnComplete”连续调用,Repeat会自动停止。 RepeatUntilDestroy(gameObject/component), RepeatUntilDisable(gameObject/component) 可以在gameobject被销毁时停止Repeat:

this.gameObject.OnMouseDownAsObservable()
    .SelectMany(_ => this.gameObject.UpdateAsObservable())
    .TakeUntil(this.gameObject.OnMouseUpAsObservable())
    .Select(_ => Input.mousePosition)
    .RepeatUntilDestroy(this) // safety way
    .Subscribe(x => Debug.Log(x));            

 

UniRx保证动态observable(FromEvent/Subject/ReactiveProperty/UnityUI.AsObservable…, there are like event) 内有未处理的异常时也能持续工作。什么意思呢?如果在Subscribe内subscribe,里面subscribe的异样不会造成外部的subscribe失效。

button.OnClickAsObservable().Subscribe(_ =>
{
    // If throws error in inner subscribe, but doesn't detached OnClick event.
    ObservableWWW.Get("htttp://error/").Subscribe(x =>
    {
        Debug.Log(x);
    });
});

 

这样在处理用户事件时很有用。

所有类的实例提供了一个 ObserveEveryValueChanged 方法,可以在每帧监听值的变化:

// watch position change
this.transform.ObserveEveryValueChanged(x => x.position).Subscribe(x => Debug.Log(x));

 

这很有用。如果监听的目标是一个GameObject, 会在目标被销毁时停止并调用OnCompleted。如果监听目标是C#的对象,OnCompleted会在GC时调用。

将Unity回调转为IObservable

使用Subject (或AsyncSubject用于异步操作):

public class LogCallback
{
    public string Condition;
    public string StackTrace;
    public UnityEngine.LogType LogType;
}

public static class LogHelper
{
    static Subject<LogCallback> subject;

    public static IObservable<LogCallback> LogCallbackAsObservable()
    {
        if (subject == null)
        {
            subject = new Subject<LogCallback>();

            // Publish to Subject in callback
            UnityEngine.Application.RegisterLogCallback((condition, stackTrace, type) =>
            {
                subject.OnNext(new LogCallback { Condition = condition, StackTrace = stackTrace, LogType = type });
            });
        }

        return subject.AsObservable();
    }
}

// method is separatable and composable
LogHelper.LogCallbackAsObservable()
    .Where(x => x.LogType == LogType.Warning)
    .Subscribe();

LogHelper.LogCallbackAsObservable()
    .Where(x => x.LogType == LogType.Error)
    .Subscribe();

 

Unity5中移除了 Application.RegisterLogCallback,并用 Application.logMessageReceived替代。所以可以更简单地用 Observable.FromEvent

public static IObservable<LogCallback> LogCallbackAsObservable()
{
    return Observable.FromEvent<Application.LogCallback, LogCallback>(
        h => (condition, stackTrace, type) => h(new LogCallback { Condition = condition, StackTrace = stackTrace, LogType = type }),
        h => Application.logMessageReceived += h, h => Application.logMessageReceived -= h);
}

 

流式日志记录器Stream Logger

// using UniRx.Diagnostics;

// logger is threadsafe, define per class with name.
static readonly Logger logger = new Logger("Sample11");

// call once at applicationinit
public static void ApplicationInitialize()
{
    // Log as Stream, UniRx.Diagnostics.ObservableLogger.Listener is IObservable<LogEntry>
    // You can subscribe and output to any place.
    ObservableLogger.Listener.LogToUnityDebug();

    // for example, filter only Exception and upload to web.
    // (make custom sink(IObserver<EventEntry>) is better to use)
    ObservableLogger.Listener
        .Where(x => x.LogType == LogType.Exception)
        .Subscribe(x =>
        {
            // ObservableWWW.Post("", null).Subscribe();
        });
}

// Debug is write only DebugBuild.
logger.Debug("Debug Message");

// or other logging methods
logger.Log("Message");
logger.Exception(new Exception("test exception"));

 

调试Debugging

UniRx.Diagnostics 中的 Debug 操作符可以帮助调试。

// needs Diagnostics using
using UniRx.Diagnostics;

---

// [DebugDump, Normal]OnSubscribe
// [DebugDump, Normal]OnNext(1)
// [DebugDump, Normal]OnNext(10)
// [DebugDump, Normal]OnCompleted()
{
    var subject = new Subject<int>();

    subject.Debug("DebugDump, Normal").Subscribe();

    subject.OnNext(1);
    subject.OnNext(10);
    subject.OnCompleted();
}

// [DebugDump, Cancel]OnSubscribe
// [DebugDump, Cancel]OnNext(1)
// [DebugDump, Cancel]OnCancel
{
    var subject = new Subject<int>();

    var d = subject.Debug("DebugDump, Cancel").Subscribe();

    subject.OnNext(1);
    d.Dispose();
}

// [DebugDump, Error]OnSubscribe
// [DebugDump, Error]OnNext(1)
// [DebugDump, Error]OnError(System.Exception)
{
    var subject = new Subject<int>();

    subject.Debug("DebugDump, Error").Subscribe();

    subject.OnNext(1);
    subject.OnError(new Exception());
}

 

将按事件顺序显示 OnNext, OnError, OnCompleted, OnCancel, OnSubscribe 的调用并通过Debug.Log打印出来。只在 #if DEBUG时生效。

Unity-specific Extra Gems(针对Unity的超酷额外功能)

译者注:SubscribeOnMainThread()经测试现在没有效果,有BUG(2018年1月30日,UniRx版本5.5)

// Unity's singleton UiThread Queue Scheduler
Scheduler.MainThreadScheduler 
ObserveOnMainThread()/SubscribeOnMainThread()

// Global StartCoroutine runner
MainThreadDispatcher.StartCoroutine(enumerator)

// convert Coroutine to IObservable
Observable.FromCoroutine((observer, token) => enumerator(observer, token)); 

// convert IObservable to Coroutine
yield return Observable.Range(1, 10).ToYieldInstruction(); // after Unity 5.3, before can use StartAsCoroutine()

// Lifetime hooks
Observable.EveryApplicationPause();
Observable.EveryApplicationFocus();
Observable.OnceApplicationQuit();

 

Framecount-based time operators(基于帧数的时间操作)

UniRx 提供了一些基于帧数的时间操作:

Method
EveryUpdate
EveryFixedUpdate
EveryEndOfFrame
EveryGameObjectUpdate
EveryLateUpdate
ObserveOnMainThread
NextFrame
IntervalFrame
TimerFrame
DelayFrame
SampleFrame
ThrottleFrame
ThrottleFirstFrame
TimeoutFrame
DelayFrameSubscription
FrameInterval
FrameTimeInterval
BatchFrame

例如,一次延迟调用:

Observable.TimerFrame(100).Subscribe(_ => Debug.Log("after 100 frame"));

 

Every* 方法们的执行顺序是

EveryGameObjectUpdate(in MainThreadDispatcher's Execution Order) ->
EveryUpdate -> 
EveryLateUpdate -> 
EveryEndOfFrame

 

EveryGameObjectUpdate 在同一帧被调用,从 MainThreadDispatcher.Update 中调用(作者建议 MainThreadDispatcher 脚本比其他脚本先执行(ScriptExecutionOrder 设置为 -32000) 
EveryLateUpdate, EveryEndOfFrame 在同一帧调用。 
然后在下一帧调用EveryGameObjectUpdate,以此类推

MicroCoroutine(微协程)

MicroCoroutine 在内存上更有效率,而且更快。这是基于Unity的这篇博客《10000 UPDATE() CALLS》(http://blogs.unity3d.com/2015/12/23/1k-update-calls/)实现的,可以快上几十倍。MicroCoroutine会自动用于基于帧数的时间操作和ObserveEveryValueChanged。

如果你想要使用MicroCoroutine替代unity内置的协程,使用 MainThreadDispatcher.StartUpdateMicroCoroutine 或者 Observable.FromMicroCoroutine

int counter;

IEnumerator Worker()
{
    while(true)
    {
        counter++;
        yield return null;
    }
}

void Start()
{
    for(var i = 0; i < 10000; i++)
    {
        // fast, memory efficient
        MainThreadDispatcher.StartUpdateMicroCoroutine(Worker());

        // slow...
        // StartCoroutine(Worker());
    }
}

 

image

MicroCoroutine的局限是:仅支持 yield return null 并且调用的时间是根据调用的方法来确定(StartUpdateMicroCoroutine, StartFixedUpdateMicroCoroutine, StartEndOfFrameMicroCoroutine).。

如果和其他IObservable一起用,你可以检查完成属性比如isDone。

IEnumerator MicroCoroutineWithToYieldInstruction()
{
    var www = ObservableWWW.Get("http://aaa").ToYieldInstruction();
    while (!www.IsDone)
    {
        yield return null;
    }

    if (www.HasResult)
    {
        UnityEngine.Debug.Log(www.Result);
    }
}

 

uGUI集成

UniRx处理UnityEvent很简单。使用UnityEvent.AsObservable订阅事件。

public Button MyButton;
// ---
MyButton.onClick.AsObservable().Subscribe(_ => Debug.Log("clicked"));

 

将事件转为Observables,这样就可以用声名式UI编程。

public Toggle MyToggle;
public InputField MyInput;
public Text MyText;
public Slider MySlider;

// On Start, you can write reactive rules for declaretive/reactive ui programming
void Start()
{
    // Toggle, Input etc as Observable (OnValueChangedAsObservable is a helper providing isOn value on subscribe)
    // SubscribeToInteractable is an Extension Method, same as .interactable = x)
    MyToggle.OnValueChangedAsObservable().SubscribeToInteractable(MyButton);

    // Input is displayed after a 1 second delay
    MyInput.OnValueChangedAsObservable()
        .Where(x => x != null)
        .Delay(TimeSpan.FromSeconds(1))
        .SubscribeToText(MyText); // SubscribeToText is helper for subscribe to text

    // Converting for human readability
    MySlider.OnValueChangedAsObservable()
        .SubscribeToText(MyText, x => Math.Round(x, 2).ToString());
}

 

更多的响应式UI编程参见工程中例子Sample12, Sample13以及下面ReactiveProperty部分。

ReactiveProperty, ReactiveCollection

游戏数据变化时通常需要通知别的类。我们应该用属性和事件(回调)么?这通常太繁琐了。UniRx提供了ReactiveProperty类,一个轻量的属性代理。

// Reactive Notification Model
public class Enemy
{
    public ReactiveProperty<long> CurrentHp { get; private set; }

    public ReactiveProperty<bool> IsDead { get; private set; }

    public Enemy(int initialHp)
    {
        // Declarative Property
        CurrentHp = new ReactiveProperty<long>(initialHp);
        IsDead = CurrentHp.Select(x => x <= 0).ToReactiveProperty();
    }
}

// ---
// onclick, HP decrement
MyButton.OnClickAsObservable().Subscribe(_ => enemy.CurrentHp.Value -= 99);
// subscribe from notification model.
enemy.CurrentHp.SubscribeToText(MyText);
enemy.IsDead.Where(isDead => isDead == true)
    .Subscribe(_ =>
    {
        MyButton.interactable = false;
    });

 

你可以用UnityEvent.AsObservable将ReactiveProperties, ReactiveCollections 和 observables 组合起来。 所有UI组件都是observable的。

一般来说ReactiveProperties不是可序列化的或者说在Unity编辑器的Inspector面板中看不到,但是UniRx提供了特殊的子类来实现这个功能。包括Int/LongReactiveProperty, Float/DoubleReactiveProperty, StringReactiveProperty, BoolReactiveProperty,还有更多参见:InspectableReactiveProperty.cs(https://github.com/neuecc/UniRx/blob/master/Assets/Plugins/UniRx/Scripts/UnityEngineBridge/InspectableReactiveProperty.cs))。都可以在Inspector中编辑。对于自定义的枚举ReactiveProperty,写一个可检视的ReactiveProperty[T]也很容易。

如果你需要[Multiline] 或者 [Range] 添加到ReactiveProperty上,你可以使用 MultilineReactivePropertyAttribute RangeReactivePropertyAttribute 替换 Multiline  Range

这些InpsectableReactiveProperties可以在inspector面板显示,并且当他们的值发生变化时发出通知,甚至在编辑器里变化时也可以。

这个功能是实现在 InspectorDisplayDrawer (https://github.com/neuecc/UniRx/blob/master/Assets/Plugins/UniRx/Scripts/UnityEngineBridge/InspectorDisplayDrawer.cs)。你可以通过继承这个类实现你自定义的ReactiveProperties在inspector面板的绘制:

public enum Fruit
{
    Apple, Grape
}

[Serializable]
public class FruitReactiveProperty : ReactiveProperty<Fruit>
{
    public FruitReactiveProperty()
    {
    }

    public FruitReactiveProperty(Fruit initialValue)
        :base(initialValue)
    {
    }
}

[UnityEditor.CustomPropertyDrawer(typeof(FruitReactiveProperty))]
[UnityEditor.CustomPropertyDrawer(typeof(YourSpecializedReactiveProperty2))] // and others...
public class ExtendInspectorDisplayDrawer : InspectorDisplayDrawer
{
}

 

如果ReactiveProperty的值只在stream中更新,你可以用 ReadOnlyReactiveProperty 让这个属性只读。

public class Person
{
    public ReactiveProperty<string> GivenName { get; private set; }
    public ReactiveProperty<string> FamilyName { get; private set; }
    public ReadOnlyReactiveProperty<string> FullName { get; private set; }

    public Person(string givenName, string familyName)
    {
        GivenName = new ReactiveProperty<string>(givenName);
        FamilyName = new ReactiveProperty<string>(familyName);
        // If change the givenName or familyName, notify with fullName!
        FullName = GivenName.CombineLatest(FamilyName, (x, y) => x + " " + y).ToReadOnlyReactiveProperty();
    }
}

 

MVP设计模式 Model-View-(Reactive)Presenter Pattern

用UniRx可以实现MVP(MVRP)设计模式。

为什么应该用MVP模式而不是MVVM模式?Unity没有提供UI绑定机制,创建一个绑定层过于复杂并且会对性能造成影响。 尽管如此,视图还是需要更新。Presenters层知道view的组件并且能更新它们。虽然没有真的绑定,但Observables可以通知订阅者,功能上也差不多。这种模式叫做Reactive Presenter:

// Presenter for scene(canvas) root.
public class ReactivePresenter : MonoBehaviour
{
    // Presenter is aware of its View (binded in the inspector)
    public Button MyButton;
    public Toggle MyToggle;

    // State-Change-Events from Model by ReactiveProperty
    Enemy enemy = new Enemy(1000);

    void Start()
    {
        // Rx supplies user events from Views and Models in a reactive manner 
        MyButton.OnClickAsObservable().Subscribe(_ => enemy.CurrentHp.Value -= 99);
        MyToggle.OnValueChangedAsObservable().SubscribeToInteractable(MyButton);

        // Models notify Presenters via Rx, and Presenters update their views
        enemy.CurrentHp.SubscribeToText(MyText);
        enemy.IsDead.Where(isDead => isDead == true)
            .Subscribe(_ =>
            {
                MyToggle.interactable = MyButton.interactable = false;
            });
    }
}

// The Model. All property notify when their values change
public class Enemy
{
    public ReactiveProperty<long> CurrentHp { get; private set; }

    public ReactiveProperty<bool> IsDead { get; private set; }

    public Enemy(int initialHp)
    {
        // Declarative Property
        CurrentHp = new ReactiveProperty<long>(initialHp);
        IsDead = CurrentHp.Select(x => x <= 0).ToReactiveProperty();
    }
}

 

视图层是一个场景scene,是Unity的hierachy定义的。展示层在Unity初始化时将视图层绑定。XxxAsObservable方法可以很容易的创建事件信号signals,没有任何开销。SubscribeToText and SubscribeToInteractable 都是简洁的类似绑定的辅助函数。虽然这些工具很简单,但是非常有用。在Unity中使用很平滑,性能很好,而且让你的代码更简洁。

V -> RP -> M -> RP -> V 完全用响应式的方式连接。UniRx提供了所有的适配方法和类,不过其他的MVVM(or MV*)框架也可以使用。UniRx/ReactiveProperty只是一个简单的工具包。

GUI编程也可以从ObservableTriggers获益良多。ObservableTriggers将Unity事件转为Observables,所以MV(R)P模式可以用它们来组成。例如 ObservableEventTrigger 将 uGUI 事件转为 Observable:

var eventTrigger = this.gameObject.AddComponent<ObservableEventTrigger>();
eventTrigger.OnBeginDragAsObservable()
    .SelectMany(_ => eventTrigger.OnDragAsObservable(), (start, current) => UniRx.Tuple.Create(start, current))
    .TakeUntil(eventTrigger.OnEndDragAsObservable())
    .RepeatUntilDestroy(this)
    .Subscribe(x => Debug.Log(x));

 

(已过时)PresenterBase

备注: 
PresenterBase有用,不过太复杂了 
你可以仅用 Initialize 方法,在子类中调用父类就足够应付大多数情况了。 
所以不再建议使用 PresenterBase

ReactiveCommand, AsyncReactiveCommand

ReactiveCommand 抽象了按钮的interactable属性。

public class Player
{       
   public ReactiveProperty<int> Hp;     
   public ReactiveCommand Resurrect;        

   public Player()
   {        
        Hp = new ReactiveProperty<int>(1000);       

        // If dead, can not execute.        
        Resurrect = Hp.Select(x => x <= 0).ToReactiveCommand();     
        // Execute when clicked     
        Resurrect.Subscribe(_ =>        
        {       
             Hp.Value = 1000;       
        });         
    }       
}       

public class Presenter : MonoBehaviour      
{       
    public Button resurrectButton;      

    Player player;      

    void Start()
    {       
      player = new Player();        

      // If Hp <= 0, can't press button.        
      player.Resurrect.BindTo(resurrectButton);     
    }       
}       

 

AsyncReactiveCommand是ReactiveCommand的变种,它的 CanExecute (通常绑定到button的interactable) 在异步操作完成后变为false。

public class Presenter : MonoBehaviour      
{       
    public UnityEngine.UI.Button button;        

    void Start()
    {       
        var command = new AsyncReactiveCommand();       

        command.Subscribe(_ =>      
        {       
            // heavy, heavy, heavy method....       
            return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();        
        });     

        // after clicked, button shows disable for 3 seconds        
        command.BindTo(button);     

        // Note:shortcut extension, bind aync onclick directly      
        button.BindToOnClick(_ =>       
        {       
            return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();        
        });     
    }       
}       

 

AsyncReactiveCommand 有三个构造方法。

  • () – CanExecute is changed to false until async execution finished
  • (IObservable<bool> canExecuteSource) – 当 canExecuteSource 变为 true 并且没有在执行的时候 CanExecute 变为true 。
  • (IReactiveProperty<bool> sharedCanExecute) – 在多个AsyncReactiveCommands之间共享运行状态,如果一个 AsyncReactiveCommand 在执行,那么其他的 AsyncReactiveCommands(拥有相同 sharedCanExecute 属性) 的 CanExecute 变为 false 直到异步操作完成。
public class Presenter : MonoBehaviour
{
    public UnityEngine.UI.Button button1;
    public UnityEngine.UI.Button button2;

    void Start()
    {
        // share canExecute status.
        // when clicked button1, button1 and button2 was disabled for 3 seconds.

        var sharedCanExecute = new ReactiveProperty<bool>();

        button1.BindToOnClick(sharedCanExecute, _ =>
        {
            return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();
        });

        button2.BindToOnClick(sharedCanExecute, _ =>
        {
            return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();
        });
    }
}

 

MessageBroker, AsyncMessageBroker(消息代理,异步消息代理)

MessageBroker 是基于 Rx 的内存发布订阅(pubsub)系统,基于类型筛选。

public class TestArgs
{
    public int Value { get; set; }
}

---

// Subscribe message on global-scope.
MessageBroker.Default.Receive<TestArgs>().Subscribe(x => UnityEngine.Debug.Log(x));

// Publish message
MessageBroker.Default.Publish(new TestArgs { Value = 1000 });

 

AsyncMessageBroker 是 MessageBroker 的一个变种, 可以处理异步的 Publish 调用。

AsyncMessageBroker.Default.Subscribe<TestArgs>(x =>
{
    // show after 3 seconds.
    return Observable.Timer(TimeSpan.FromSeconds(3))
        .ForEachAsync(_ =>
        {
            UnityEngine.Debug.Log(x);
        });
});

AsyncMessageBroker.Default.PublishAsync(new TestArgs { Value = 3000 })
    .Subscribe(_ =>
    {
        UnityEngine.Debug.Log("called all subscriber completed");
    });

 

UniRx.Toolkit

UniRx.Toolkit 包含了一些Rx风格的工具。目前报错 ObjectPool  AsyncObjectPool。可以 Rent, Return and PreloadAsync (在rent操作之前预加载对象池)。

// sample class
public class Foobar : MonoBehaviour
{
    public IObservable<Unit> ActionAsync()
    {
        // heavy, heavy, action...
        return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();
    }
}

public class FoobarPool : ObjectPool<Foobar>
{
    readonly Foobar prefab;
    readonly Transform hierarchyParent;

    public FoobarPool(Foobar prefab, Transform hierarchyParent)
    {
        this.prefab = prefab;
        this.hierarchyParent = hierarchyParent;
    }

    protected override Foobar CreateInstance()
    {
        var foobar = GameObject.Instantiate<Foobar>(prefab);
        foobar.transform.SetParent(hierarchyParent);

        return foobar;
    }

    // You can overload OnBeforeRent, OnBeforeReturn, OnClear for customize action.
    // In default, OnBeforeRent = SetActive(true), OnBeforeReturn = SetActive(false)

    // protected override void OnBeforeRent(Foobar instance)
    // protected override void OnBeforeReturn(Foobar instance)
    // protected override void OnClear(Foobar instance)
}

public class Presenter : MonoBehaviour
{
    FoobarPool pool = null;

    public Foobar prefab;
    public Button rentButton;

    void Start()
    {
        pool = new FoobarPool(prefab, this.transform);

        rentButton.OnClickAsObservable().Subscribe(_ =>
        {
            var foobar = pool.Rent();
            foobar.ActionAsync().Subscribe(__ =>
            {
                // if action completed, return to pool
                pool.Return(foobar);
            });
        });
    }
}

 

Visual Studio 分析器

Visual Studio 2015的用户可以使用一个分析器UniRxAnalyzer。它可以检测没有被subscribed的streams。

ObservableWWW 直到subscribed才会执行,所以分析器会发出警告。分析器可以从NuGet下载。

请在GitHub Issues上提交对分析器的新想法!

例子

参见 UniRx/Examples(https://github.com/neuecc/UniRx/tree/master/Assets/Plugins/UniRx/Examples)

Sample09_EventHandling 展示了如何进行资源管理,包括MainThreadDispatcher和其他一些东西。

Windows Store/Phone App (NETFX_CORE)

一些接口,如 UniRx.IObservable<T> and System.IObservable<T> 在Windows Store App中使用时会造成冲突。 
因此,如果使用 NETFX_CORE,避免使用类似 UniRx.IObservable<T> 的构造器,避免不加命名空间使用UniRx组件。这样可以解决冲突问题。

async/await Support

基于 《Upgraded Mono/.Net in Editor on 5.5.0b4》(https://forum.unity3d.com/threads/upgraded-mono-net-in-editor-on-5-5-0b4.433541/),Unity支持了 .NET 4.6 和 C# 6。UniRx 提供了 UniRxSynchronizationContext 来将多线程的任务返回到主线程。

async Task UniRxSynchronizationContextSolves()
{
    Debug.Log("start delay");

    // UniRxSynchronizationContext is automatically used.
    await Task.Delay(TimeSpan.FromMilliseconds(300));

    Debug.Log("from another thread, but you can touch transform position.");
    Debug.Log(this.transform.position);
}

 

UniRx 也直接支持 await Coroutine。

async Task CoroutineBridge()
{
    Debug.Log("start www await");

    var www = await new WWW("https://unity3d.com");

    Debug.Log(www.text);
}

 

当然了,IObservable可是可以await的。

async Task AwaitObservable()
{
    Debug.Log("start await observable");

    await Observable.NextFrame();   // like yield return null
    await Observable.TimerFrame(5); // await 5 frame

    Debug.Log("end await observable");
}

 

DLL分割

如果你想预编译UniRx,你可以自行编译dll。clone这个project,打开 UniRx.sln, 你可以看到 UniRx, 是一个完全独立的工程。你应该定义一些宏如 UNITY;UNITY_5_4_OR_NEWER;UNITY_5_4_0;UNITY_5_4;UNITY_5; + UNITY_EDITOR, UNITY_IPHONE或这其他平台宏。我们没法提供预编译的dll,因为不同版本Unity的宏是不一样的。

如果你想将UniRx用于.NET 3.5 CLR应用,你可以使用UniRx.Library UniRx.Library 不依赖UnityEngine,编译 UniRx.Library 需要定义 UniRxLibrary 宏。另外预编译的 UniRx.Library 库可以在NuGet获取到。

Install-Package UniRx(https://www.nuget.org/packages/UniRx)

其他参考

UniRx API文档。

The home of ReactiveX. Introduction, All operators are illustrated with graphical marble diagrams, there makes easy to understand. And UniRx is official ReactiveX Languages.

A great online tutorial and eBook.

Many videos, slides and documents for Rx.NET.

Intro slide by @torisoup

Intro slide and sample game by @Xerios

How to integrate with PlayFab API

What game or library is using UniRx?

Games 
– [Farm Away!][(http://www.farmawaygame.com/) 
 Build Away! 
 AdVenture Capitalist 
 AdVenture Communist

Libraries

  • PhotonWire – Typed Asynchronous RPC Layer for Photon Server + Unity.
  • uFrame Game Framework – MVVM/MV* framework designed for the Unity Engine.
  • EcsRx – A simple framework for unity using the ECS paradigm but with unirx for fully reactive systems.
  • ActionStreetMap Demo – ASM is an engine for building real city environment dynamically using OSM data.
  • utymap – UtyMap is library for building real city environment dynamically using various data sources (mostly, OpenStreetMap and Natural Earth).
  • Submarine – A mobile game that is made with Unity3D and RoR, WebSocket server written in Go.

If you use UniRx, please comment to UniRx/issues/152.

Help & Contribute

Support thread on the Unity forum. Ask me any question – http://forum.unity3d.com/threads/248535-UniRx-Reactive-Extensions-for-Unity

We welcome any contributions, be they bug reports, requests or pull request. 
Please consult and submit your reports or requests on GitHub issues. 
Source code is available in Assets/Plugins/UniRx/Scripts. 
This project is using Visual Studio with UnityVS.

Author’s other Unity + LINQ Assets

LINQ to GameObject is a group of GameObject extensions for Unity that allows traversing the hierarchy and appending GameObject to it like LINQ to XML. It’s free and opensource on GitHub.

Author Info

Yoshifumi Kawai(a.k.a. neuecc) is a software developer in Japan. 
He is the Director/CTO at Grani, Inc. 
Grani is a top social game developer in Japan. 
He is awarding Microsoft MVP for Visual C# since 2011. 
He is known as the creator of linq.js(LINQ to Objects for JavaScript)

Blog: https://medium.com/@neuecc (English) 
Blog: http://neue.cc/ (Japanese) 
Twitter: https://twitter.com/neuecc (Japanese)

License

This library is under the MIT License.

Some code is borrowed from Rx.NET and mono/mcs.

Unity2017安卓编译问题二三事

这两天开始搭建Unity开发环境。

之前都在用公司电脑,家里电脑很久没用在开发上,之前搭的环境时过境迁,果然又冒出新问题来,止不住的折腾人。

拿之前的工程试编下APK,报了以下错误:

 

 

 

 

SDK Build Tools version 23.0.1<25.0.0

原因是SDK版本太老旧,由于运行SDK Manger去更新,发现这个工具一运行就闪退。在网上找到解决方法,将\sdk\tools\android.bat 里的java.exe文件路径改成本机绝对路径,就可以打开了。

但打开后发现软件并没有新的SDK可更新,这就很困惑了,Unity明明报说需要新包。无奈,自己上网搜了下,是有更新的包,于是在CSN上下了个25.0.0的包,解压放进子目录下,心想这下可以了吧,还是太天真,运行Unity后又报同样错误.心想是不是Unity不认自己解压的包.

后面就是一堆折腾,又是百度,又是谷歌,还装了Android Studio,虽然不知道它怎么用.金诚所至,金石为开.偶然翻到一篇文章才解了以上困惑.原文地址:https://www.jianshu.com/p/166a2d07f99b,原来用SDK Manager工具更新Android SDK的更新方式,只在android-sdk_r24.4.1-windows.zip之前,在这之后就没这个工具了,自然它也看不到之后的新包体信息.由于谷歌在大陆被墙,所以无论是在命令行中用dos命令更新,还是用Android Studio里的相关功能,均更新不到,即使用了VPN软件.这也解释了为什么外国人和我的台湾同事很少碰到类似问题.(搜到更多的是另一个:/sdk/tool,用了新版,而要还原成tools_r25.2.5的问题).而我在CSN上下载的包,只是部分资源,虽然装上了,Unity也认为没更新到位.

上述文章中的方法虽然麻烦点却能解决问题.费了些工夫把包下到,更新好,这个问总算得以解决.碰到类似问题需要更新SDK的朋友可以去这里下载sdk_24-28的资源


在装好NDK后,指定编译版本类型为IL2CPP,编译过程又报了以下错误:

CommandInvokationFailure: Gradle build failed.
C:/Program Files/Java/jdk1.8.0_161\bin\java.exe -classpath “D:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Tools\gradle\lib\gradle-launcher-4.0.1.jar” org.gradle.launcher.GradleMain “-Dorg.gradle.jvmargs=-Xmx2048m” “assembleRelease”

stderr[

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project ‘gradleOut’.
> Failed to find Build Tools revision 28.0.0

* Try:
Run with –stacktrace option to get the stack trace. Run with –info or –debug option to get more log output.

BUILD FAILED in 2m 5s
]
stdout[
Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use –status for details
NDK is missing a “platforms” directory.
If you are using NDK, verify the ndk.dir is set to a valid NDK directory. It is currently set to F:\adt-bundle-windows-x86_64-20140702\sdk\ndk-bundle.
If you are not using NDK, unset the NDK variable from ANDROID_NDK_HOME or local.properties to remove this warning.

Observed package id ‘build-tools;20.0.0’ in inconsistent location ‘F:\adt-bundle-windows-x86_64-20140702\sdk\build-tools\android-4.4W’ (Expected ‘F:\adt-bundle-windows-x86_64-20140702\sdk\build-tools\20.0.0’)
Observed package id ‘build-tools;24.0.3’ in inconsistent location ‘F:\adt-bundle-windows-x86_64-20140702\sdk\build-tools\android-7.0’ (Expected ‘F:\adt-bundle-windows-x86_64-20140702\sdk\build-tools\24.0.3’)
……
……
java.net.ConnectException: Connection timed out: connect
IOException: https://dl.google.com/android/repository/addons_list-3.xml
java.net.ConnectException: Connection timed out: connect
IOException: https://dl.google.com/android/repository/addons_list-2.xml
java.net.ConnectException: Connection timed out: connect
IOException: https://dl.google.com/android/repository/addons_list-1.xml
java.net.ConnectException: Connection timed out: connect
Failed

这里分成两部分内容:后面这个”Observed package id ‘build-tools;25.0.3’ in inconsistent location ‘F:\adt-bundle-windows-x86_64-20140702\sdk\build-tools\android-7.1.1’ (Expected ‘F:\adt-bundle-windows-x86_64-20140702\sdk\build-tools\25.0.3’)”能看懂是我解压SDK后,放入文件夹时文件名没改,于是根据提示一一订正.

另一个内容是:”A problem occurred configuring root project ‘gradleOut’.
> Failed to find Build Tools revision 28.0.0″,在谷歌上找到解决方法:打开Android Studio,file -> setting -> SDK Tools,卸载带version 28.0.0rcl的工具,然后重编就可以了.

至此,Unity2017的开发环境就装好了.