Unity3D 学习笔记

时间:2024-02-16 14:06:42

不是什么技术文章,纯粹是我个人学习是遇到一些觉得需要注意的要点,当成笔记。

 

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代替,前者的开销很大。

具体参见:利用缓存池解决Instantiate慢的问题用

 

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。

但要强制改变脚本的执行顺序也可以:

在这里调节脚本执行顺序,数值越小,越先执行

 edit->projectseeting->script execution order 

 

25.通过命令行在控制台编译U3D项目

官方Manual教程

示例:

(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.物理各种射线检测性能对比

参考:Unity中各类物理投射性能横向比较

 

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)
 
 

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.