游戲的數據有哪些類型
無非是只讀數據(各種道具配表里的數據)和可讀可寫數據(玩家屬性、擁有的物品)。
游戲框架需要哪些管理器
用戶數據管理器
負責找到數據持久化文件,從中讀取指定用戶的數據,包括玩家的設置數據(音量等)、擁有的物品(金幣、人物、道具)。
不需要依附游戲對象,一般是不繼承MonoBehavior的單例。
資源配置管理器
負責找到配表文件,根據外界要求的資源種類、ID返回資源的詳細信息(游戲內名字、圖標、預制體,其他信息如價格)。
資源加載管理器
負責封裝加載資源的方法(Resources和AB包),以便快速切換。主要用于資源配置信息管理器加載配表文件,和加載UI面板預制體。
和資源配置信息管理器可以合成一個嗎?不合適,因為還要加載UI預制體。
這個管理器就是提供加載資源的方法,并能方便在Resources和AB包加載之間切換。
場景管理器
提供異步加載場景的方法。顯示加載界面、更新進度條的代碼可以放在里面。
因為要用協程,且全局存在,應該是DontDestroyOnLoad的繼承MonoBehavior的類。
UI管理器
提供加載面板的方法,包括從Resources和AB包加載,包括全屏面板和部分面板。為此需要面板預制體的路徑和資源名,需要一個方法知道當前哪些面板在顯示,可以是查看一個面板類的單例是否存在,也可以用一個字典存儲顯示的面板。
聲音管理器
使玩家可在任意場景的設置面板改變音樂和音效音量。游戲所有播放聲音都要使用它封裝的函數。它無需儲存用戶設置的音量,因為那屬于用戶數據,由用戶數據管理器存儲。
看想不想用拖賦值,想就用DontDestroyOnLoad的繼承MonoBehavior的類。
關卡場景管理模塊
以上是游戲全局需要的管理器,在關卡場景有一些所有關卡都相同的管理模塊:輸入模塊、相機模塊、UI模塊(HUD、對話面板、交互選項面板、任務面板)、關卡管理器、緩存池。
這些模塊一般打成一個預制體,新建關卡時直接拖入。(虛幻項目直接就有一套這些)
緩存池
在射擊游戲里一般用來存彈殼、彈頭、擊中效果。只在關卡場景需要,所以用繼承MonoBehavior的單例,可以和游戲管理器、輸入模塊等打進同一個預制體。
public enum BufferType{impact,bullet, rifleShell,handgunShell,impactBlood}
public class BufferPoolBase : MonoSingleton<BufferPoolBase>
{public const float impactLifeTime=1;Dictionary<BufferType,Transform> bufferDict=new Dictionary<BufferType,Transform>();//可能的情況包括沒緩沖池、有緩沖池沒物體(一般不會有,但理論上可能)、有緩沖池有物體public GameObject Depool(GameObject prefab,BufferType bufferType,Vector3 position){GameObject instance;if(bufferDict.ContainsKey(bufferType)){//緩沖池已建立if(bufferDict[bufferType].childCount>0){//緩沖池里有物體instance=bufferDict[bufferType].GetChild(0).gameObject;//取出instance.transform.SetParent(null);//解綁instance.transform.position = position;instance.SetActive(true);//激活}else{//有緩沖池沒物體(曾經放入過物體,又拿出了)instance=Instantiate(prefab,position,Quaternion.identity);}}else{//沒有緩沖池Transform bufferTransform=new GameObject(bufferType.ToString()).transform;bufferTransform.parent=transform;bufferDict.Add(bufferType, bufferTransform);instance=Instantiate(prefab,position,Quaternion.identity);}return instance;}public void Enpool(GameObject instance,BufferType bufferType){if(!bufferDict.ContainsKey(bufferType)){Transform bufferTransform=new GameObject(bufferType.ToString()).transform;bufferDict.Add(bufferType, bufferTransform);}instance.transform.SetParent(bufferDict[bufferType]);instance.SetActive(false);}public IEnumerator EnpoolLater(GameObject instance,BufferType bufferType,float delay){yield return new WaitForSeconds(delay);Enpool(instance,bufferType);}
事件中心
我們知道事件中心是用于一個事件引發很多操作的,它比直接調用避免了蜘蛛網一樣的耦合,但是又比直接調用麻煩一點。那么問題是,任何一個類操作引起另一個類的操作都要用事件中心嗎?那樣很明顯事件中心會有大量事件。應該設置一個操作引起其他類操作個數的閾值,達到閾值的使用事件中心。
輸入模塊、玩家人物和人物
玩家人物和NPC人物有一些共性,玩家人物還有一些個性:更新UI、接受輸入控制。通常會把玩家人物作為人物的子類,重寫一些方法。問題是任何一個類操作引起另一個類操作都要用事件中嗎?
是否只有一個操作引起的操作達到一個數量時才值得用事件中心?
輸入模塊和玩家人物要寫在一個腳本嗎?
如果用了新的Input System,我們知道給幾十個按鍵配回調很麻煩,會想把PlayerInput和處理回調的腳本放在一個預制體里,如果把PlayerInput和玩家人物打進預制體,那么不同關卡用不同人物時又要換人物的骨架和模型,人物身上綁的物品掛載點、約束也要重新綁。會想到寫一個輸入回調處理腳本MyInputHandler和PlayerInput放在一起,MyInputHandler指向玩家人物。也就是輸入模塊和玩家人物分離。
輸入腳本和人物腳本的代碼分布
人物的各種行為會封裝成方法Move()、Turn()、Run()、Jump()。這些方法是在
- 人物腳本Update()
- 輸入腳本Update()
- 輸入腳本處理輸入的方法
執行?
放在輸入腳本Update()就等于輸入回調先寫入輸入腳本字段,再傳給人物腳本行為方法的輸入參數。
放在人物腳本Update就等于先寫入人物腳本字段再讓行為方法讀取。
放在輸入腳本的輸入回調方法就是把輸入變量傳給人物方法的輸入參數,只在輸入變量改變時執行。
為了減少字段,可以盡量用3,但有一些方法必須每幀執行,比如跑步可能被各種情況打斷(射擊、換彈、墜落)。跑步要執行,除了按下跑步,還要滿足幾個條件,這些條件有的靠狀態機的互斥就可以(跳躍、墜落),有的要另外判斷(主要是上半身層的)。執行跑步前把這些條件全部&&。
管理器分類
根據管理器的生命周期,可以分為整個游戲內存在的和場景內存在的。根據是否必須繼承MonoBehavior,可以分為繼承和不繼承。這兩個問題組合,其中不繼承MonoBehavior的一定整個游戲存在。這樣就把管理器分為3類:
- 不繼承MonoBehavior;
- 繼承MonoBehavior;
- 繼承MonoBehavior且DontDestroyOnLoad;
不繼承MonoBehavior
用戶數據管理器、資源配置管理器;
繼承MonoBehavior
各場景內的管理器。對于關卡場景,有輸入、相機、秩序管理、緩存池。
繼承MonoBehavior且DontDestroyOnLoad
通常是游戲全局存在且需要協程的管理器:場景管理器、AB包管理器。
還有必須和組件關聯的管理器,比如聲音管理器,必須記錄音樂聲源和UI音效聲源。
這些管理器可以打成一個預制體。
登錄注冊系統
多用戶數據系統
如果一個游戲要做登錄注冊界面,那么它就是一個多用戶游戲,意味著它有一個記錄用戶名和密碼的文件,而其他大部分數據(玩家擁有的人物、武器、物品、音量)都因用戶而異,需要在每個用戶注冊時建一個文件夾,把這些用戶數據文件都放在里面。
這樣UserDataManager在構建的時候就不能加載所有的數據,因為還沒登錄,不知道用戶名,也就不知道這些數據文件的路徑。所以需要一個UserManager,開始運行時構建,負責獲取上次登錄用戶、注冊新用戶、判斷登錄是否成功。加載登入用戶數據的DataManager在登錄成功后構建,它讀取數據的路徑依賴登入的用戶名。
場景管理
需要有一個《場景轉換圖》列出所有的場景和哪些場景。一般需要登錄場景、主界面場景、若干游戲場景。登錄場景和主界面場景應該分開,因為主界面場景可能從登錄場景或游戲場景進入。