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

具體參見:利用緩存池解決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版

 

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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM