讀書小結(jié)(《Unity3D游戲開發(fā)(第2版)》第四章 游戲腳本)


游戲腳本

[TOC]

注意套硼,在使用UnityEditor命名空間下的類脊凰,或者寫UnityEditor相關(guān)代碼時(shí),代碼塊應(yīng)該使用宏#ifdef UNITY_EDITOR#endif包裹起來。避免非Editor環(huán)境下導(dǎo)致腳本編譯失敗睡榆。

本章主要講述C#腳本的創(chuàng)建萍肆、生命周期、執(zhí)行順序胀屿、序列化塘揣、編譯、調(diào)試相關(guān)內(nèi)容宿崭。
(本文忽略了自定義類的Inspector擴(kuò)展亲铡、工作線程以及一些細(xì)節(jié)內(nèi)容。)


創(chuàng)建腳本

創(chuàng)建腳本主要通過Asset/Create/C# Script菜單項(xiàng)來進(jìn)行葡兑。

模板

通常創(chuàng)建腳本使用的是UnityEditor默認(rèn)的模板奖蔓,默認(rèn)模板在目錄/Applications/Unity/Unity.app/Contents/Resources/ScriptTemplates下。如有需要讹堤,可以在該目錄下添加自己常用的C#模板吆鹤。

通過以上方式添加的模板,因?yàn)椴辉诠こ棠夸浵轮奘兀鶡o法使用Git進(jìn)行統(tǒng)一管理疑务。所以,更常用的方式應(yīng)該是在工程目錄下新建一個(gè)Templates目錄岖沛,然后為Asset/Create菜單添加自己的腳本創(chuàng)建按鈕暑始。

創(chuàng)建自定義腳本的工具類:

  1. AssetDatabase、Selection婴削、Path廊镜。用于獲取當(dāng)前選中的路徑、添加生成的新文件唉俗。
  2. StreamReader嗤朴、StreamWriter。用于讀取腳本模板的內(nèi)容虫溜,生成新的腳本文件雹姊。
  3. Regex。用于替換新生成的腳本文件中的自定義內(nèi)容衡楞。

創(chuàng)建自定義腳本功能的基本步驟(偽代碼):

// 獲取選中的路徑
var path = AssetDatabase.GetAssetPath(Selection.activeObject);
path = Path.GetDirectoryName(path);
// 讀取模板內(nèi)容
var reader = new StreamReader(_templatePath_);
var text = reader.ReadToEnd();
reader.Close();
// 替換模板內(nèi)容
text = Regex.Replace(text, "#SCRIPTNAME#", "NewScript");
// 生成新腳本
var encoding = new UTF8Encoding(true, false);
var writer = new StreamWriter(path, false, encoding);
writer.Write(text);
writer.Close();
// 導(dǎo)入新腳本
AssetDatabase.ImportAsset(path);

腳本的生命周期

生命周期事件

腳本的生命周期可以按功能劃分為多塊:Editor吱雏、Physics、SceneRender瘾境、Gizmos歧杏、GUI。

常用的生命周期函數(shù):

// Editor
Reset   // 編輯器模式下迷守,當(dāng)腳本被添加或重置時(shí)被調(diào)用

// Initialization 
Awake   // 腳本自己的初始化犬绒,只會被調(diào)用一次
OnEnable    // 腳本每次進(jìn)入Enable狀態(tài)都會被調(diào)用
Start   // 腳本被啟動時(shí)調(diào)用,在此初始化與其他腳本相關(guān)內(nèi)容兑凿,只會被調(diào)用一次

// Physics
FixedUpdate     // (相對)固定間隔的Update
yield WaitForFixedUpdate
//[Internal physics update]
OnTriggerXXX    // 物理系統(tǒng)處理Trigger相關(guān)事件
OnCollisionXXX  // 屋里系統(tǒng)處理Collision相關(guān)事件

// Input
OnMouseXXX      // 輸入事件

// Game Logic
Update      // 游戲邏輯刷新事件
yield WaitForSeconds
yield WWW
yield StartCoroutine    // 協(xié)程的啟動時(shí)機(jī)
//[Internal animation update]
LateUpdate  // 延遲的Update凯力,適用于處理攝像機(jī)和UI的刷新

// Scene
OnWillRenderObject
OnPreCull
OnBecameVisible
OnBecameInvisible
OnPreRender
OnRenderObject
OnPostRender
OnRenderImage

// Gizmo
OnDrawGizmos    // 繪制Scene窗口可視化輔助內(nèi)容

// GUI
OnGUI   // 繪制GUI茵瘾,一幀之內(nèi)會被調(diào)用多次

// End of frame
yield WaitForEndOfFrame

// Pause
OnApplicationPause  // 程序暫停,切到后臺咐鹤、收到電話都會被調(diào)用

// Enable/Disable
OnDisable   // 腳本每次進(jìn)入Disable的狀態(tài)都會被調(diào)用

// Decommissioning
OnDestroy   // 腳本被銷毀時(shí)被調(diào)用
OnApplicationQuit   // 程序直接退出時(shí)被調(diào)用

協(xié)程

Unity只支持單線程拗秘,但是可以使用C#的協(xié)程來完成需要延時(shí)進(jìn)行的操作。

示例:

public class TestCoroutine : MonoBehaviour
{
    Coroutine myCoroutine;

    Start()
    {
        // 啟動協(xié)程
        myCoroutine = StartCouroutine(MyCoroutine());
        // 停止協(xié)程
        StopCoroutine(myCoroutine);
    }
    
    // 協(xié)程函數(shù)返回值必須是IEnumerator
    private IEnumerator MyCoroutine()
    {
        for(int i = 0; i < 1000; i++)
        {
            // do something
        }
        yield return new WaitForSeconds(1f);
    }
}

使用協(xié)程和單例實(shí)現(xiàn)一個(gè)簡單的計(jì)時(shí)器:

public class MyTimer
{
    class MyBehaviour:MonoBehaviour
    {}
    
    private static MyBehaviour mBehaviour;
    
    // 在靜態(tài)構(gòu)造函數(shù)中創(chuàng)建不會被銷毀的對象慷暂,并初始化一個(gè)繼承MonoBehaviour的腳本用于開啟/關(guān)閉協(xié)程
    static MyTimer()
    {
        var obj = new GameObject("MyTimer");
        GameObject.DontDestroyOnLoad(obj);
        mBehaviour = obj.AddComponent<MyBehaviour>();
    }
    
    public static Coroutine Wait(float time, Action action)
    {
        return mBehaviour.StartCoroutine(WaitCallback(time, action));
    }
    
    public static void CancelWait(ref Coroutine coroutine)
    {
        if(coroutine != null)
        {
            mBehaviour.StopCoroutine(coroutine);
            coroutine = null;
        }
    }
    
    private static IEnumerator WaitCallback(float time, Action action)
    {
        yield return new WaitForSeconds(time);
        action?.Invoke();
    }
}

如果需要更復(fù)雜的定時(shí)器:每隔一定時(shí)間回調(diào)一次聘殖。可以通過繼承CustomYieldInstruction來實(shí)現(xiàn)行瑞。


腳本序列化

序列化標(biāo)簽

腳本的public參數(shù)默認(rèn)會被序列化奸腺,并顯示在Inspector面板上。Unity還提供了標(biāo)簽用于靈活控制屬性的序列化血久。

public class TestSerialize : MonoBehaviour
{
    // public參數(shù)默認(rèn)初始化
    public string name;
    
    // 標(biāo)記參數(shù)不序列化
    [NonSerialized]
    public int age;
    
    // 標(biāo)記參數(shù)要序列化
    [SerializeField]
    private float weight;
}

序列化的配置文件

通常我們的腳本會掛在GameObject上突照,腳本會被序列化并顯示在Inspector面板上。往往我們有一些用于游戲配置的腳本氧吐,它們不需要依賴GameObject讹蘑,但是又要能被序列化并顯示在Inspector面板上便于修改。這里我們可以使用ScriptableObject對象來保存這些數(shù)據(jù)筑舅。

創(chuàng)建ScriptableObject對象作為配置文件的過程:

  1. 通過Scriptable.CreateInstance來創(chuàng)建對象座慰。
  2. 使用AssetDatabase將對象保存為.asset文件。

Wiki上有一個(gè)用于創(chuàng)建ScriptableObject的工具類:

// 用于創(chuàng)建ScriptableObject的工具類
public class ScriptableUtilty
{
    public static void CreateAsset<T> where T:ScriptableObject
    {
        // 生成ScriptableObject
        var obj = ScriptableObject.CreateInstance<T>();
        // 獲取當(dāng)前path
        var path = AssetDatabase.GetAssetPath( Selection.activeObject );
        if( path == "" )
        {
            path = "Assets";
        }
        else if(Path.GetExtension( path ) != "")
        {
            path = path.Replace( Path.GetFileName( AssetDatabase.GetAssetPath( Selection.activeObject ) ), "" );
        }
        // 生成asset文件路徑
        path = AssetDatabase.GenerateUniqueAssetPath( $"{path}/New{typeof( T ).ToString()}.asset" );
        // 保存為asset文件
        AssetDatabase.CreateAsset( obj, path );
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        // 窗口聚焦到創(chuàng)建的asset文件
        EditorUtility.FocusProjectWindow();
        Selection.activeObject = obj;
    }
}

// 測試類
public class MyConfig:ScriptableObject
{
    // 配置參數(shù)
    public int id;
    public string name;
    public float speed;
    
    // 創(chuàng)建菜單
    [MenuItem("Assets/Scriptable/MyConfig")]
    public static void Create()
    {
        ScriptableUtilty.CreateAsset<MyConfig>();
    }
}


編譯

Unity的跨平臺是通過Mono實(shí)現(xiàn)的翠拣。Unity提供的接口都封裝到了UnityEditor.dll和UnityEngine.dll中版仔,更底層由DLL調(diào)用C++接口實(shí)現(xiàn)。開發(fā)過程中編寫的C#文件也會被編譯成多個(gè)dll文件误墓。

程序集定義

在默認(rèn)情況下蛮粮,Unity游戲工程中的代碼會被編譯成4個(gè)dll文件,這4個(gè)dll文件有不同的編譯順序谜慌,后編譯的代碼能訪問到先編譯的代碼然想。

編譯順序:

  1. Assembly-CSharp-firstpass.dll(Assets/Plugins下的C#文件)
  2. Assembly-CSharp-Editor-firstpass.dll(Assets/Plugins/Editor下的C#文件)
  3. Assembly-CSharp.dll(Assets下的C#文件)
  4. Assembly-CSharp-Editor.dll(Assets/Editor下的C#文件)

除了默認(rèn)的程序集,我們還可以通過創(chuàng)建AssemblyDefinition來配置我們自己的程序集定義欣范,程序集定義之間可以有依賴關(guān)系变泄。

通常,項(xiàng)目中的底層框架代碼是不需要經(jīng)常修改的恼琼,此時(shí)可以將底層代碼配置在一個(gè)Assembly中杖刷。如果其中代碼不被修改,這個(gè)程序集是不需要被重新編譯成dll的驳癌。通過將不常修改的代碼分離出去避免重復(fù)編譯,能加快項(xiàng)目的編譯速度役听。

調(diào)試

在開發(fā)階段颓鲜,有如下常用的顯示調(diào)試數(shù)據(jù)的方式:

  1. Debug.Log(); 打印自定義數(shù)據(jù)表窘。
  2. Debug.DrawLine(); 在Scene窗口繪制線段。
  3. Gizmos.Draw(); 在Scene窗口中繪制各種內(nèi)容甜滨。

但是有些問題只會出現(xiàn)在真機(jī)上乐严,此時(shí)我們可以通過監(jiān)聽事件Application.logMessageReseived來獲取程序打印出的數(shù)據(jù),并將這些數(shù)據(jù)記錄在本地衣摩,以供調(diào)試bug昂验。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市艾扮,隨后出現(xiàn)的幾起案子既琴,更是在濱河造成了極大的恐慌,老刑警劉巖泡嘴,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甫恩,死亡現(xiàn)場離奇詭異,居然都是意外死亡酌予,警方通過查閱死者的電腦和手機(jī)磺箕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抛虫,“玉大人松靡,你說我怎么就攤上這事〗ㄒ” “怎么了雕欺?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長广凸。 經(jīng)常有香客問我阅茶,道長,這世上最難降的妖魔是什么谅海? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任脸哀,我火速辦了婚禮,結(jié)果婚禮上扭吁,老公的妹妹穿的比我還像新娘撞蜂。我一直安慰自己,他們只是感情好侥袜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布蝌诡。 她就那樣靜靜地躺著,像睡著了一般枫吧。 火紅的嫁衣襯著肌膚如雪浦旱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天九杂,我揣著相機(jī)與錄音颁湖,去河邊找鬼宣蠕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甥捺,可吹牛的內(nèi)容都是我干的抢蚀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼镰禾,長吁一口氣:“原來是場噩夢啊……” “哼皿曲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吴侦,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤屋休,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后妈倔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體博投,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年盯蝴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了毅哗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捧挺,死狀恐怖虑绵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闽烙,我是刑警寧澤翅睛,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站黑竞,受9級特大地震影響捕发,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜很魂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一扎酷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遏匆,春花似錦法挨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至帝蒿,卻和暖如春荐糜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工狞尔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丛版,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓偏序,卻偏偏與公主長得像,于是被迫代替她去往敵國和親胖替。 傳聞我的和親對象是個(gè)殘疾皇子研儒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容