游戲腳本
[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)建自定義腳本的工具類:
- AssetDatabase、Selection婴削、Path廊镜。用于獲取當(dāng)前選中的路徑、添加生成的新文件唉俗。
- StreamReader嗤朴、StreamWriter。用于讀取腳本模板的內(nèi)容虫溜,生成新的腳本文件雹姊。
- 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
對象作為配置文件的過程:
- 通過
Scriptable.CreateInstance
來創(chuàng)建對象座慰。 - 使用
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文件有不同的編譯順序谜慌,后編譯的代碼能訪問到先編譯的代碼然想。
編譯順序:
- Assembly-CSharp-firstpass.dll(Assets/Plugins下的C#文件)
- Assembly-CSharp-Editor-firstpass.dll(Assets/Plugins/Editor下的C#文件)
- Assembly-CSharp.dll(Assets下的C#文件)
- 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ù)的方式:
-
Debug.Log();
打印自定義數(shù)據(jù)表窘。 -
Debug.DrawLine();
在Scene窗口繪制線段。 -
Gizmos.Draw();
在Scene窗口中繪制各種內(nèi)容甜滨。
但是有些問題只會出現(xiàn)在真機(jī)上乐严,此時(shí)我們可以通過監(jiān)聽事件Application.logMessageReseived
來獲取程序打印出的數(shù)據(jù),并將這些數(shù)據(jù)記錄在本地衣摩,以供調(diào)試bug昂验。