數據結構設計
任務基類包括的字段:
string 任務內容;
Transform 任務目的地;
MyCharacter 任務開啟后要更新對話的NPC;
MyTalkData 任務開啟后相關NPC要說的對話數據;
共同方法:開啟任務、完成任務。
每類任務有一些特有的字段和方法。
任務的兩個關鍵行為是觸發和完成,這兩個行為的條件都很多樣:對話、戰斗、取得物品、到達地點、刺殺等。要觸發任務,需要那些可能觸發任務的行為能指向任務,這些行為都要加上可能觸發的任務字段。觸發任務可能是特定行為也可能是一個任務完成。也就是說任務系統不是只加一個系統,還需要對其他系統的行為加回調。
對話完成任務
任務開啟后相關NPC需要記錄所屬的任務,玩家找ta觸發對話時完成任務,且把這個任務變量置空,防止再次對話再次完成任務。為此NPC對話腳本要加一個任務變量。
對話觸發任務
則需要在對話結束后開啟任務,然后NPC不能再觸發這段對話,可能是觸發任務進行中的對話,或者沒有對話。
和上面觸發對話完成任務加起來,對話的開始和結束都要加一個UnityEvent。
戰斗完成任務
字段:要消滅的敵人列表;
敵人列表的每個敵人也記錄自己所屬的任務,每個敵人死時,把敵人移出敵人列表,然后判斷列表是否為空,若是,則任務完成。
由于任務是依次觸發的,一個關卡的任務的數據結構可以是1.鏈表,每個任務記錄自己完成后下一個任務。但是這樣一個關卡的任務先后順序不直觀;但是這樣支持多任務分支;2.列表,一個中心任務管理器有一個任務列表,這樣不支持多任務分支。然而我做了任務列表界面,也就是我一直想做多分支,這就和使用關卡流程任務列表沖突了。現在問題就是,沒有一個圖形化的表示多分支關卡的工具,用一連多的“鏈表”做多分支很混亂。所以在有合適工具前,只能先放棄做多分支。
有一個列表記錄此關依次要執行的任務對象,關卡流程會清楚很多:
但是多任務分支用列表就不行了。
兩種NPC
NPC分為兩類:永遠只能觸發相同對話,對關卡沒有推進的npc(簡稱氛圍NPC);對關卡有推進,不同階段觸發不同對話的npc(簡稱推進NPC)。
對于推進NPC,在關卡不同階段有不同對話,在一個任務進行中觸發同一個對話(如“拜托你了”),實際上出現了完成任務和進行中的多分支。這么多對話的存儲位置有幾種方案。
1.用List全部記錄在NPC對象上。由任務對象指定完成后NPC該說哪一段對話。這樣NPC對象腳本的檢查器上會存一大堆對話數據。也難以看出一段對話對應哪個任務,是觸發任務的對話還是任務進行中的對話。
2.NPC上只記錄一段對話數據,就是當前去找ta會觸發的對話。任務對象上記錄兩段對話,任務觸發、進行中要說的對話。當關卡進度管理器顯示該觸發一個任務時,任務對象把觸發任務對話寫入npc腳本的對話變量,任務觸發后把任務進行中對話寫入npc對話變量。
這樣氛圍npc因為沒有任務系統修改ta們的對話數據,自然就一直觸發同一段對話,不用修改。
3.對話數據記錄在任務對象上,npc只記錄關聯的任務對象。這樣不推進進度的npc沒有關聯的任務,ta們的對話就要另外處理了。
很明顯2是最優方案,既防止推進進度的npc的腳本里的對話數據過多,又兼容兩種npc,而且不推進進度的npc只有一段對話,給ta們聲明一個一段對話的List完全是浪費。總之對于氛圍NPC,只有一個一段對話變量,對于推進NPC,不同任務階段對話不同,對話應該記錄在任務對象。
問題:回復時開啟任務 ,直接把下一個任務NPC要說的話寫入了NPC的對話數據,導致當前對話變成下一個任務階段的。需要在回復時記下要觸發任務,這段對話結束后再開啟任務。所以在一段對話數據結構里放一個List<UnityEvent>,回復的結構體里有一個UnityEvent用于在檢查器配置回調函數,選擇該回復時把這個UnityEvent加入列表,對話結束后執行。
數據結構定義
一個任務對象應有的字段有:
1.任務內容文本(可能分成任務標題和任務描述);
2.任務目的地位置;
如果想直觀地標記任務目的地位置,應該用一個場景內游戲對象,那么任務類就不能繼承ScriptableObject做成Asset,而應該繼承Monobehavior做成Component。
一個任務對象應有的方法有:
1.開啟任務(可能包括NPC、敵人、物品的生成和擺放、設置NPC可觸發的對話,總之幾種腳本里不確定,需要在場景里確定的函數);
2.完成任務(也可能有上述函數);
坑
協程沖突
在修改NPC位置等突變操作時我寫了一個畫面漸變為黑色,執行操作,再變透明的函數:
public UnityAction blackoutCallback;
[ContextMenu("畫面變黑")]public void Blackout(){StartCoroutine(BlackoutCoroutine(blackoutCallback));}float fadeSpeed=.04f;IEnumerator BlackoutCoroutine(UnityAction callback=null){while(blackBack.color.a<1){blackBack.color+=new Color(0,0,0,fadeSpeed);yield return 0;}if(callback!=null){callback.Invoke();}while(blackBack.color.a>0){blackBack.color-=new Color(0,0,0,fadeSpeed);yield return 0;}}
然后這個函數在ContextMenu調用時正常,但是完成任務調用時畫面就不變透明了。NPC被正確移動了。然后在第二個while循環里加了個Debug.Log(),發現第二個while循環一直在執行,但是alpha值沒有變。
?然后又在第一個while循環加了個打印,發現兩個while循環都在一直執行。
在協程開頭加打印,發現協程被執行了兩次。因為一個很笨的錯誤。
?這說明寫淡入淡出時如果以imag.color.a作為循環條件,如果在一個淡入淡出完成前開始另一個,兩個協程就會打架,淡入淡出永遠完不成。實際開發中如果無法避免一個淡入淡出進行中開始另一個,就根據計算好的循環次數,或者直接規定循環次數,并且循環結束后直接把alpha值寫成目標值,因為如果不這么做就算循環能結束,最終的alpha會是一個半透明值。
改進后的淡入淡出:
int fadeStep=10;IEnumerator BlackoutCoroutine(UnityAction callback=null){for(int i=0;i<fadeStep;i++){blackBack.color+=new Color(0,0,0,1/(float)fadeStep);yield return 0;}blackBack.color=Color.black;if(callback!=null){callback.Invoke();}for(int i=0;i<fadeStep;i++){blackBack.color-=new Color(0,0,0,1/(float)fadeStep);yield return 0;}blackBack.color=new Color(0,0,0,0);}