Unity3D 学习笔记
不是什么技术文章,纯粹是我个人学习是遇到一些觉得需要注意的要点,当成笔记。
1.关于调试,在Android下无法断点,Debug也无法查看,已修正参考第37条
查看日志方法可以启动adb的log功能,或者自己写个GUI控件直接在屏幕上显示Info 参考30
2.所有自定义的编辑器扩展插件脚本必须放在Editor文件夹里,不然会导致编译程序时出错,
放到Editor文件下,编译成游戏时才会忽略这些脚本
3.打包资源时,假设是在移动设备上使用,打包方式务必选择成:BuildTarget.Android
BuildPipeline.BuildAssetBundle(obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle, BuildTarget.Android)
否则到安卓上会读不出资源。PC则无此问题。
4.重新打包资源后,加入在LoadAssetBunldes时没给指定读取新版本,例如原来为:LoadFromCacheOrDownload(path, 1);
现在仍然为LoadFromCacheOrDownload(path, 1);那么是无法读取到新包的。因为系统会先从缓存检查有没有版本为1的同名bundle,如果有,则直接使用缓存的,
如果没有,则读取这个新的包。因此,重新打包文件后,应该给文件包升1级,读取的同时也多读一级。这样才会读出来。
5.如果在测试的时候为了避免频繁打包频繁换级造成麻烦,则可以在每次加载时或者打包时选择手动清空缓存:Caching.CleanCache();这样就会强制读取最新的包。
但千万不要在正式版使用,因为这样是清空全部数据,频繁的读取会造成性能消耗。PC就无所谓了。
6.想要控制磁盘缓存不超标. 只要设置Caching.maximumAvailableDiskSpace 的值为你预期的容量大小就可以(byte为单位)例如, 想要限定200M的缓存空间可以这样:Caching.maximumAvailableDiskSpace = 200*1024*1024;
7.画线可以用LineRenderer,或者直接GL画,或者更方便的可以Debug.DrawLine,甚至可以将物理射线画出Debug.DrawRay,但是Debug画出的线只能在调试模
式看得到,编译成游戏后将不再出现。参见:http://www.cnblogs.com/jeason1997/p/4805825.html
8.判断游戏是否联网
Application.internetReachability == NetworkReachability.NotReachable
NotReachable 网络不可达
ReachableViaCarrierDataNetwork 网络通过运营商数据网络是可达的。
ReachableViaLocalAreaNetwork 网络通过WiFi或有线网络是可达的。
9.UGUI做在人物头上血条的HUD的制作方法
public Transform follow;
Vector2 position = Camera.main.WorldToScreenPoint(follow.position);
img.rectTransform.position = position;//位置
img.rectTransform.localScale = new Vector2(2,2);//大小
10.动态改变相机的Culling Mask
http://answers.unity3d.com/questions/348974/edit-camera-culling-mask.html
11.安卓调式时,连接上手机,然后打开epclise就可以看到locat输出Debug内容了。
12.AssetBundle依赖关系
如果一个公共对象被多个对象依赖,我们打包的时候,可以有两种选取。一种是比较省事的,就是将这个公共对象打包到每个对象中。这样会有很多弊端:内存被浪费了;加入公共对象改变了,每个依赖对象都得重新打包。AssetBundle提供了依赖关系打包。
//启用交叉引用,用于所有跟随的资源包文件,直到我们调用PopAssetDependencies BuildPipeline.PushAssetDependencies(); var options = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets; //所有后续资源将共享这一资源包中的内容,由你来确保共享的资源包是否在其他资源载入之前载入 BuildPipeline.BuildAssetBundle( AssetDatabase.LoadMainAssetAtPath("assets/artwork/lerpzuv.tif"), null, "Shared.unity3d", options); //这个文件将共享这些资源,但是后续的资源包将无法继续共享它 BuildPipeline.PushAssetDependencies(); BuildPipeline.BuildAssetBundle( AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/Lerpz.fbx"), null, "Lerpz.unity3d", options); BuildPipeline.PopAssetDependencies(); 这个文件将共享这些资源,但是后续的资源包将无法继续共享它 BuildPipeline.PushAssetDependencies(); BuildPipeline.BuildAssetBundle( AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/explosive guitex.prefab"), null, "explosive.unity3d", options); BuildPipeline.PopAssetDependencies(); BuildPipeline.PopAssetDependencies();
13.非MonooBehavior脚本实现协程:
一般要实现多线程功能(即协程)时,一般都是在MonoBehavior脚本里StartCoroutine一个返回值为IEnumerator的函数,
但有些时候,需要在非继承自MonoBehaviro的脚本(例如单例等)里也实现多线程效果,就无法通过自身实现了,我采取这样的方法:
public class CoroutineProvider : MonoBehaviour { private static CoroutineProvider instance = null; public static CoroutineProvider GetInstance() { if (instance == null) { GameObject go = new GameObject(); go.name = "CoroutineProvider"; instance = go.AddComponent<CoroutineProvider>(); } return instance; } }
创建一个继承自MonoBehavior的协程提供者,该提供者初始时并不存在,但有地方需要使用到协程时,就通过:
CoroutineProvider.GetInstance().StartCoroutine(Fuction());
这个时候就会在Scene里实例化一个GameObject,以它作为协程提供者的身份出现。
14.创建HTTP服务器并添加自定义格式文件下载支持:
为了让游戏能够自动更新,需每次登陆游戏时都向服务器下载版本文件验证游戏版本,若发现版本不同,
则下载文件更新列表,根据列表下载最新的资源,但默认情况下HTTP只能下载一些默认资源,例如:rar,txt,xml等,
像“.assetbundle”这类的的自定义格式默认情况下HTTP服务器是无法下载的,如果提交请求,例如:http://127.0.0.1/Aseet/Resource.assetbundle,
则结果是404。为了能够下载自定义格式文件,需要配置服务器,比如IIS的做法就是:
打开管理器->点击根节点->在右边找到IIS列表->找到MIME类型->打开并添加自定义格式扩展名
15.寻找游戏中的某个对象
为了降低耦合,尽量避免对象脚本间通过“public value”拖动对象进行互相引用。
但有时候有必须某个对象引用另一个对象,可以通过在脚本中查找对象。
常用的查找方法有:GameObject.Find / FindWithTag 等一系列,这种方法寻找对象虽然最简单,
但是效率却比较低,尽量避免在Update中使用,最好就是在Start和Awake中使用。
还有一种高效的做法,就是将特定对象归类,并集中放到一个List中,使用时查找就快速多了。
16.获取拥有某种类型组件的子gameobject,后面的参数的意思是运行获取不活跃的对象,即active = false
gameObject.GetComponentsInChildren<Component> (true);
17.只删除gameobject 脚本组件会残留
彻底删除方法如下:
GameObject go = new GameObject();
Component c = go.GetComponent<Component >();
MonoBehaviour.DestroyImmediate(c);
MonoBehaviour.DestroyImmediate(go);
18.单例模式慎用,如果一个GameObject对象为单例对象,请小心在其他的GameObject的OnDestory里引用该单例
因为在程序关闭前会执行所有GameObject的OnDestory,但是执行的先后顺序不定。
如果GameObject_1在OnDestory时引用GameObject_2(单例对象),假设GO2的OnDestory先运行,这时候它的
Instance为null,被GO1引用时它会再创建一次。
而如果在OnDestory里创建任何对象,该对象都将不会被UnityEngine释放,一直保留下来。
19.Unity3D 优化技术(3D图形方面)
① http://blog.csdn.net/candycat1992/article/details/42127811
② http://blog.csdn.net/leonwei/article/details/18042603
③ 尽量不要动态的instantiate和destroy object,使用object pool代替,前者的开销很大。
20.Unity中的.meta文件
每个资源文件(外部导入和内部创建的)对应一个.meta文件。这个.meta文件中的guid就唯一标识这个资源。
内部创建的资源会使用外部导入的资源,比如 内部资源材质Material使用贴图Textures(预制体、场景中使用了更多的资源)。
材质怎么知道自己使用了那些资源呢? 就在自己的文件中记录着其它资源的GUID。
而且每个meta里的标识都是随机产生的,而且同一文件多次生成的meta文件里的标识一不同。
在多人合作项目中,如果你上传资源时没有同步上传.meta文件,那么别人的机器就会为这个文件生成一个,但标识可能和你不同,
导致的结果就是Prefab引用的各种组件丢失。
21.Editor运行时从Scene视图观察对象
在Hierarchy选择要观察的GameObject,鼠标联系点击4下,
这样的话,Scene视图就会将焦点集中在该GameObject身上,
就跟绑定一个摄影机一样,调式的时候非常方便。
22.Profiler分析器
分析器平时只检测关键代码,如果要检测所有代码,
就必须深度检测,但深度检测又非常消耗性能。所以有时候只想检测某一小段代码的时候,
可以设置检测段:
Profiler.BeginSample ("标签"); // 要检测性能的代码 Profiler.EndSample ();
然后就可以在分析器中找到相应的标签了:
23.Unity 5.3 C# 部分源码
https://bitbucket.org/xuanyusong/unity-decompiled/src/779abf10e556?at=master
24.调节脚本执行顺序
Unity的不同脚本间的执行顺序一般是没有规矩的,不同脚本的同一函数,例如Start,启动的顺序也不同。
因此很少在U3D的脚本里的同一函数做先后顺序依赖。例如在脚本A的Start里调用脚本B的某个属性,而该
属性又在脚本B的Start函数里初始化,由于不知道两者的Start的先后顺序,因此一般改为,在脚本B的Awake里
初始化该属性,而不是Start。
但要强制改变脚本的执行顺序也可以:
在这里调节脚本执行顺序,数值越小,越先执行
25.通过命令行在控制台编译U3D项目
示例:
(Unity.exe Path) -batchmode -projectPath (Project Path) -executeMethod MyEditorScript.MyBuildMethod -quit -logFile (Log Path)
UnityPath : Windows: C:\program files\Unity\Editor\Unity.exe
UnityPath : Mac OS: /Applications/Unity/Unity.app/Contents/MacOS/Unity
26.C# 以及 Uniyt3D 添加全局预编译指令
#define DEBUG #if DEBUG dosome; #endif
呐,这就是预编译指令拉,要注意,这不叫宏,跟C/C++的宏还是有一定差距的。
至于如何定义,在代码里定义的话,当疼的C#只能在每个文件的文件头定义才生效,也就是说,你在test1.cs里定义#define DEBUG的话,
那么你也必须在test2.cs里的头部定义一个才能在test2里生效。如果有一百个文件要使用这个预编译调节,那么你就要手动定义100次。
还好微软留了下后路,其实全局预编译指令可以在项目里的*.CSharp.csproj里设置,
右键项目,在属性里设置即可,或者干脆直接用文本文件打开项目路径下的该文件,直接编辑。
Unity也给我们留了解决方案,不过藏得比较深:
Editor->ProjectSetting->Player->Other->如图红圈处
需要注意的是,Unity里设置的全局预编译,是按平台分开的,
也就是说,你在安卓平台下设置的指令,在PC平台是没法使用的,除非复制过去。
这里有个插件可以方便地设置预编译指令:
源代码:
下载后把中文部分去掉,扔到“Editor”文件夹下,然后打开使用便是
27.Unity3D自定义Debug
Unity的Debug功能比较有限,有时候要自定义一些特殊的Debug,比如封装一个Logger,在Editor平台时打印日志
到控制台,而在其他平台,则打印信息到屏幕或输出到Log文件。功能倒挺容易实现,问题就是,这个封装的Logger底层
部分也是通过Debug实现,在调试的时候要实现Unity控制台那种双击日志就跳到日志输出的地方的功能,就比较麻烦了。
因为就是Logger封装了Debug,双击的时候也不会跳到Logger.Log的地方,而是跳到内部用Debug实现的地方,导致调式
不方便。
解决方法就是,将Logger编译成dll,然后再在Unity内部引用,这时候Debug输出控制台的时候,双击日志就会直接跳到Logger.Log。
雨松也是这么做的:http://www.xuanyusong.com/archives/2782
28.Unity3D里的特殊文件夹以及脚本编译顺序
第一阶段: Standard Assets, Pro Standard Assets 和Plugins文件夹之内的脚本,编译好后为:Assembly-CSharp-firstpass.dll(C#)
第二阶段: 在第一阶段那些文件夹里任何名字为'Editor'的文件夹里的脚本,例如'Plugins/Editor'
第三阶段: 所有其他不在'Editor'文件夹里的脚本,编译好后为:Assembly-CSharp.dll(C#)
第四阶段: 其他剩余脚本,例如'Editor'文件夹里的脚本(注,不是第二阶段那些在特殊文件夹里的Editor),编译好后为:Assembly-CSharp-Editor.dll(C#)
在U3D里,注意脚本的编译顺序有时候极其重要,例如:
要在C#脚本里引用UnityScprite脚本时,或者反过来,那么被引用的那些脚本,就必须在前一阶段先编译,不然会出现‘找不到脚本’的编译错误。
29.Unity AssetBundle打包资源的一个坑:
就是Shader不会被打包进去,如果在对象身上挂有Shader,那么加载AB后,会出现Shader丢失的情况。
解决方法就是对通过AB动态加载的对象在Awake时做个判定,看看自身的Shader列表是否为空,如果为空,
则手动从本地加载。
30.通过Application.RegisterLogCallback来捕获Debug输出:
该方法可以监听到unity中所有的log消息,然后你可以选择保存到文件中或者显示到gui上做调试来用。
通过LogType来判断处理哪些类型的消息需要被保存或显示。
LogType如下:
Error |
LogType used for Errors. |
Assert |
LogType used for Asserts. (These indicate an error inside Unity itself.) |
Warning |
LogType used for Warnings. |
Log |
LogType used for regular log messages. |
Exception |
LogType used for Exceptions. |
using UnityEngine; using System.Collections; public class example : MonoBehaviour { public string output = ""; public string stack = ""; void OnEnable() { Application.RegisterLogCallback(HandleLog); } void OnDisable() { Application.RegisterLogCallback(null); } void HandleLog(string logString, string stackTrace, LogType type) { output = logString; stack = stackTrace; } }
31.物理射线检测之BoxCast
在Physics下的RayCast已经很熟悉了,今天发现有一个BoxCast,起初以为它的意思就是检测给定立方体内的物体,
如果物体在该Box内,则返回true:
如图,但实际使用起来总是检测失败,仔细看下BoxCast的参数:
第一二个参数很好理解,立方体的中心,立方体的大小,第三个参数就让我有点疑惑了,竟然都已经决定好Box的大小了,为何还有方向这个概念,
直接检测是否在Box里不就好,知道去Google了下网上的用法才知道:
原来这个BoxCast的意思是,在给定的center处,建立一个给定大小的Box,然后往给定的方向一直往前拖/射
出这个Box,一直拉长,然后检测这条路上的所有障碍物,大概效果如图(中间的空隙是我加上便于理解的,实际中检测是无缝的)
2D版
那么想达到理解中的做法,即把BoxCast当成一个矩形检测区域,其实很简单,只要把dir设置为Vector.zero,该矩形盒检测就在原地,检测的范围就是Size
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { public Vector2 pos; public Vector2 Size; public float angle; public Vector2 dir; public float dis; void Update() { var hit = Physics2D.BoxCast(pos, Size, angle, dir, dis); if (hit.collider != null) { Debug.Log(hit.point); } } private void OnDrawGizmos() { Gizmos.color = new Color(1, 0, 0, 0.3f); Quaternion rot = new Quaternion(); rot.eulerAngles = new Vector3(0, 0, angle); // 变换矩阵 Matrix4x4 trs = Matrix4x4.TRS(pos, rot, Vector2.one); Gizmos.matrix = trs; // 在该Transform的Vector3.zero处绘制一个位置、角度、缩放均与该Transform一样的方块,大小为size Gizmos.DrawCube(Vector3.zero, Size); // 复位Gizmos设置 Gizmos.matrix = Matrix4x4.identity; } }
32.U3D中的Time
一般Time中有两个比较重要的参数:
Time.realTime: 表示现实中的时间
Time.time: 表示游戏中的时间(受TimeScale影响)
而我们游戏中的Time.fixedTime,Time.deltaTime都是与游戏时间对应,而不是与现实时间对应。
比如我们的fixedTime设置为0.2,那就表明游戏中每0.2“秒”会调用一次FixedUpdate,而这个0.2秒并不是现实中的0.2秒,
也就是说,FixedUpdate不能保证现实时间中每0.2秒调用一次,他的实际调用还会受到TimeScale等的影响。
如果我们在一个FixedUpdate里做太多的内容,那么,现实中每一个FixedUpdate的时长就会拉长,但是在游戏中,两个FixedUpdate
的间隔仍然是0.2秒(可以理解为TimeScale变化了,但实际上不是),造成的结果就是,我们看到的游戏比较卡,帧数下降。
结论:
FixedUpdate与Update都不能保证在现实中多久调用一次,只能保证在游戏时间中FixedUpdate固定调用,而Update会受渲染影响
同样的游戏,当FixedUpdate里处理的内容很少时
FixedUpdate处理的内容较多,可以明星地看到卡了
但是这两个例子,Time.fixedTime都是0.2s
同理, deltaTime表示的是游戏中每两个Update中的间隔时间,即完成最后一帧的时间,
所以如果你要让一个物体匀速移动,你应该在Update里与Time.deltaTime相乘。当你乘以Time.deltaTime实际表示:每秒移动物体10米,而不是每帧10米。如果是每帧10米,游戏卡顿会造成物体移动变成非匀速。
33.物理中的Collider性能对比
首先,这是一个有700多颗树的场景,每棵树都挂有一个Rigibody组件
没有任何Collider时:
Box: 可见大概涨了1ms
Shpere: 涨幅巨大,大概是Box的6-7倍
Capsule:虽然也涨幅比较大,但没Sphere严重
由此可见,性能上Box >> Capsule > Shpere,
分别耗时:1ms 7.4ms 5ms,
网上有篇试验教程,是09年的,结果跟我完全反过来,不知道是不是这几年来U3D物理进行了大优化
http://forum.unity3d.com/threads/capsule-vs-box-colliders.34254/#post-222443
另外,以上是基于动态Collider(所谓动态Collider,即该Collider挂载在一个非静止的物体的上,即有Rigibody且不为IsKinematic)
如果是在IsKinematic条件下,则性能消耗会进一步下降,比如以上情况,Box的情况为:
如果把Rigibody去掉,那么情况也差不多,主要消耗性能的部分是动态Collider
34. Unity里的Transform在SetPos或者SetRot时会造成很大的性能开销的原因:
这种情况一般是因为该Transform的GameObject上挂有太多的Collider,Collider分为静态与动态部分,
静态的碰撞体,物理系统会批优化处理,他们基本不占多少资源。但如果这个Collider是动态运行的,比如一直在
改变位置或者旋转,那么物理系统会不断重新计算他,如果你的GameObject上挂有太多的Collider,那么在
改变Transform时就会造成额外的巨大开销。
解决方法就是尽量减少运动的Collider,如果一个物体身上的Collider太多,可以尝试使用MeshCollider的方式来优化,
在运动的情况下,一个MeshCollider的开销比许多个小BoxCollider要小得多。
35.物理各种射线检测性能对比
36.精简缩小程序集
若想缩减移动端的游戏包大小,可以通过在打包选项里做手脚:
Api Compatibility Level 设置为 .NET 2.0 Subset的话,就会只将.NET的子集导出,仅有部分常用功能
Stripping Level的话也差不多,设置不同级别能精简导出不同的程序集,越精简的话,导出的函数越少,这样虽然包小了,
但若用到的部分功能被剔除的话,说不定程序会crash,因此若要用这功能,先去Unity官方的列表里查哪些函数是包含在哪个级别里的:
https://docs.unity3d.com/410/Documentation/ScriptReference/MonoCompatibility.html
比如SetAccessControl这条函数,就只有在.NET 2.0原版的程序集中能用,其他的都不行。
缩减的效果很明显(上为.NET 2.0 Subset,下位.NET 2.0):
37.通过WIFI调试安卓上的Unity项目
1.首先在手机上开启USB调试功能,并安装驱动(这一步很多手机助手都可以完成)。
2.用USB电缆连接手机和电脑。
3.确保手机和电脑在一个局域网内,简单的说就是电脑和手机共用一个路由器,网段一样。
4.打开电脑上CMD窗口,输入以下命令:
adb tcpip 5555(该命令打开手机adb网络调试功能)
正常情况下输入命令后控制台会出现回显
restarting in TCP mode port: 5555
打开手机查看手机的IP地址(不会请百度)假设手机的地址是192.168.1.x输入命令
adb connect 192.168.1.x
如果一切正常控制台会回显以下内容
connected to 192.168.1.x:5555
如果你想查看是否连接成功请输入以下内容
adb devices
控制台会回显连接的设备
5.如果一切连接成功,请拔掉USB电缆,选择File->Build&Run,在编译之前要勾选上Development Build 和Script Debugging这两项(在build setting里面勾选不要忘记否则是不能调试的)电脑会自动编译文件并将APK推送至手机,在手机上同意并安装。
6.当程序运行后再Monodevelop里面打开Run->Attach to process 会发现你手机的选项,选择手机,在脚本里面添加断点,你发现可以调试了,那叫一个爽!出现问题再也不用去瞎猜,或者添加Debuglog了。
7.同时UnityEditor的Profiler也可以用了(要记得在上面输入IP)
8.若想打印安卓的log,则可以在CMD里输入adb logcat -s Unity -d > xxx.txt,“xxx.txt”为具体路径,其中Unity是过滤用的tag,unity中的所有输出都是“Unity”
注:除了第5步要用USB才能装好,其他的都在WIFI环境下测试成功,而且貌似远程调试不能用USB,只能用WIFI?
38.安卓游戏拆包
有时候在玩一些外国厂商做的比较大型的手游的时候,经常会发现一个情况,就是安装包下来很小,一般也就20来M,然后还要下载一个很大的obb文件放到/SDcard/Android/obb/packName里,
那是因为一些平台限制上传的apk容量,所以开发者无奈只能将游戏的资源分包出来,然后再在游戏运行时下载下来。用压缩文件打开obb,会发现它其实也就是一个压缩文件,像U3D的游戏,里面存放
的一般是/assets/data/里的一些资源,其实我们手动将obb里的资源解压后放入apk里,然后重新回编译apk,会发现也可以直接玩。
U3D拆包的做法:
勾上最后一项“Split Application Binary”就会将数据包和apk分离,然后在打开apk之后,进入选择下载或直接下载界面,将你的.obb文件从服务器或者其他地方下载下来
先把apk安装到Android设备,然后将对应obb文件改名为:main.<Bundle Version Code>.<包名>.obb
并拷贝到Android设备的“/android/obb/<包名>/ ”路径下。
如在Unity3D编辑中,你可以在工程设置的如图位置处,看到“Bundle Version Code”和包名(即“Bundle Identifier”).
假设“Bundle Version Code”值为2,包名为“com.Demo.ABC”:
- 首先,在Android设备上安装ABC.apk;- 接着,将ABC.obb改名为“main.2.com.Demo.ABC.obb”;
- 然后,将文件“main.2.com.Demo.ABC.obb”拷贝到Android设备的“/android/obb/com. Demo.ABC/”路径下;
- 启动App,你会发现新安装的APP已经可以正常使用了。
![如何安装分包app安装包(apk+obb)](http://f.hiphotos.baidu.com/exp/w=500/sign=de487a20ac64034f0fcdc2069fc27980/1e30e924b899a901d9bbefcb1a950a7b0308f545.jpg)
39.Smcs.rsp文件
可以在Unity Assets目录下创建smcs.rsp文件,并向其中添加预编译命令,其会在unity启动时执行,比如新建一个smcs.rsp文件,向其中添加内容:
-define:MYDEF
然后就可以在脚本中加入宏判断:
#if MYDEF .... #endif
其原理是启动Unity时会执行unity目录下的smcs.exe文件并添加预编译命令,也可以通过cmd运行smcs.exe逐个添加预编译命令。
另外还有可以创建gmcs.rsp文件,对应Editor脚本中的预编译命令。
比如如果想要在C#语言中使用指针,必须标记为unsafe的,默认情况下unity中使用unsafe标记会报错,可以在项目中添加smcs.rsp文件并加入-unsafe预编译命令,就可以编译通过。
注:Unity5.6貌似将smcs.rsp改成smc.rsp
40.Unity代码的编译运行流程
早期:
安卓:c# -> IL -> mono jit -> 机器码
苹果:c# -> mono aot -> 机器码
现在
安卓:c# -> IL -> mono jit -> 机器码
c# -> IL2CPP -> 机器码
苹果:c# -> IL2CPP -> 机器码
未来?
安卓:c# -> IL -> LLVM jit -> 机器码
苹果:c# -> IL -> LLVM aot -> 机器码
mono jit:在运行时,CLR动态将IL代码解释成机器码,然后运行
mono aot:在编译时,将IL代码解释成机器码,运行时直接执行机器码
il2cpp:在编译时,将IL代码转成C++代码,再将C++代码编译成机器码
llvm(一种中间件,mono跟.net core都有对应的实现):http://www.mono-project.com/docs/advanced/runtime/docs/llvm-backend/
之所以早期苹果用aot而现在换成il2cpp,是因为Unity的mono2.6只支持32位aot,而苹果已经限制32位应用了
未来很有可能用微软的.net core方案,然后用llvm实现对ios平台的支持
41.Unity编辑器监听脚本重载事件
[InitializeOnLoad] public class UnityScriptCompiling:AssetPostprocessor { [UnityEditor.Callbacks.DidReloadScripts] static void AllScriptsReloaded() { } }
除此之外,编辑器还能监听其他时间,例如监听资源文件被打开:
42.Unity的RuntimeInitializeOnLoadMethod属性
Unity 5.0开始增加了RuntimeInitializeOnLoadMethodAttribute,这样就很方便在游戏初始化之前做一些额外的初始化工作,比如:Bulgy参数设置、SDK初始等工作。
用此属性修饰某个静态方法,则这个方法就会被Unity注册到系统中,游戏启动的时候,会自动检测并运行该方法,就算打包了游戏,依然会生效
注意:该方法是Unity注册的,也就是说,一个已经编译好的游戏,就算你反编译dll,并加入自己的静态方法并用上该属性,也不会生效
(注册方式,估计是通过名字,写在资源文件里了,看能不能找到是注册在哪个文件里头的)
using UnityEngine; public class Test { [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void OnBeforeSceneLoadRuntimeMethod () { Debug.Log("Before scene loaded"); } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] static void OnAfterSceneLoadRuntimeMethod() { Debug.Log("After scene loaded"); } [RuntimeInitializeOnLoadMethod] static void OnRuntimeMethodLoad() { Debug.Log("RuntimeMethodLoad: After scene loaded"); } }
43.Unity中提供的Attribute有很多,如果自己写程序扩展编辑器的功能,就需要了解这些属性。常用的有:
1、AddComponentMenu 导航栏菜单
2、ContextMenu 右键菜单
3、HeaderAttribute
4、HideInInspector 可以让public变量在Inspector上隐藏,无法在Editor中进行编辑
5、MultilineAttribute 支持输入多行文本
6、RangeAttribute 限定输入值的范围
7、RequireComponent 组件依赖,使用该组件后自动添加依赖组件
8、RuntimeInitializeOnLoadMethodAttribute
9、SerializeField 强制对变量进行序列化,即使变量是private
10、SpaceAttribute 增加空位
11、TooltipAttribute 提示信息,当鼠标移到Inspector上时显示相应的提示
12、InitializeOnLoadAttribute 在启动Unity编辑器并打开项目的时候运行编辑器脚本
13、InitializeOnLoadMethodAttribute
14、MenuItem 导航栏的菜单项
44.Unity层级结构Hierarchy优化
Hierarchy Structure Guidelines
- If something moves every frame, make sure all its children care about position. Only rendering, physics, audio, or core systems like that should be there.
- When dynamically creating game objects at runtime, if they do not need to be children of the spawner for the above reasons, spawn things at the root of the scene.
- You can easily register everything you spawn and pass along the ActiveInHeirarchy state of the spawner using OnEnable and OnDisable.
- Try to group your moving transforms such that you have around 50 or so GameObjects per root. This lets the underlying system group your TransformChangeDispatch jobs into a fairly optimal amount of work per thread. Not so few that the thread overhead dominates; not so many that you are waiting on thread execution.