Snake成長與死亡
July 05, 2017
這次會針對成長和死亡部份做處理。首先是先確立成長的規則。傳統的Snake規則中每一次吃到蘋果時,Snake都會多增加一格。這個規則沒有太大的問題,但那一個格怎麼秀出來卻是有很多種版本。有些版本是在下一次移動週期時才看到尾部多出一格。而在此系列前面的篇幅有提到的Unity Snake教學,它的移動當下已預留了頸部的空格,因此在吃到蘋果時,是頸部那直接產生一格,而不再由尾部遞補。
而這次加入的成長規則簡化了這些複雜的操作,單純的加在尾部後一格當做Snake成長的規則。雖然和傳統的規則有些出入,但重製本來就有可能會有些許許的改變,結果本身沒有超出預出就可以了。
為了要加入成長的關鍵物件,額外定義了二個型別,主要還是FruitManager。這個型別中結合了之前的Option.ofObj,為了要讓整個Stream的生成是安全的,𣊬間就產生了很多防錯的程式碼,雖然是和規則沒有相關聯性的,但正式開發遊戲(而非目前的教學Demo)時,防錯機制是不可避免的。
整個Stream要能順利的註冊,要從一開始的Prefab就不是空值(Null Reference)。其實在F#的環境中是很難產生null值的,但因為和C#的型別做銜接,在不是F#的.Net語言中,很習慣用null代表空值,所以會有一堆null的檢查。在F#裡不會有空值,所以通常不做空值檢查,但F#裡其實是有選擇值,簡單說是有或者沒有,但選擇值並不是用null表示,而是用Option型別表示。Option有Some值表示有值,None值表示是沒有值的狀況。
在F#的環境中,沒有簡單的方式可以產生null值,所以Option型別在使用時不會考量到Some出現null值的情況。但和C#做銜接時,這些F#原本規避的問題又再度的浮現出來,C#中的null原罪也會溢出至F#中。所以和C#銜接處都必需謹傎處理null值的可能性。
在MonoBehaviour的殼層,將所有可能發生的null值問題處理後再進入到F#單純的領域是比較建議的。因此,從Prefab有可能是空值到拿取Component時都要仔細的處理若是null值時該怎麼解決(多數情況是無法解決,故不再往下做下去)。而非null值時,則一層層走到可以產出事件流的階段。
回應到前幾篇看到的二個事件流,此處增加第三個事件流,並增加相對應的事件和處理步驟。碰到水果(HitFruit)事件產生後,在Snake的主邏輯中會做相對應的處理,也就是依照規則在尾部後一格直接生出一格。
死亡流程的製作可以想像成是吃水果的反向操作,當Snake碰到水果會增長一格,而Snake碰到陷阱(Trap)則會直接掛掉,就傳統的Snake而言並不會只澸少一格,而是直接掛掉,所以沿用此規則不做多餘的設計。
製作上大致和FruitManager相同,只是改名成TrapManager並於生成時產生多個Trap而非像Fruit只有一個。不同於Fruit被觸碰後置入一個倒數計時,時間到於別處出現(Fruit),Trap則是一碰到就會讓整個遊戲結束,所以沒有置入倒數計時。
因為是和Fruit非常接近的實作,故沒有很花時間,直到一個怎麼修都修不掉的問題出現,才正視F#和C#的實作差異性。這個問題是事件流生成的反向操作-移除,利用殼層的方式拿取事件流並放入某function中使用時,在移除時完全不起作用。
以往的經驗中,每次Subscribe某一stream後會回傳一IDisposable物件,利用AddTo method加入元件本身或是引用此元件的物件則可以自行的於元件或物件被移除時也跟著被移除。
但多次的嘗試後不論在何處加入移除的用法,物件被移除時絲毫沒有任何影響,也就是事件流照樣的接受到事件,完全沒有被移除的痕跡。這個永續的事件流若是沒有真正的移除前,死亡機制是沒有正式完成的。雖然暫時用了很折衷的方式讓Snake消失在場景中,呈現出像是死亡的狀態,但它只是被暫時關掉,Stream並沒有被移除掉,會引發記憶體和CPU的損耗。
仔細思考後,移除事件流在製作中或許有必要又或許是不必要的。首先,關閉物件會讓事件無法傳遞,等同於沒有事件產生。再來,該物件若是管理者階級般的重要,也不會移除事件流,一但事件流生成後,直到換場景才會整個移除。若是該物件是單位物件,在正常的情況下會回收至物件池裡,以關閉的形態等到下一次被拿出來使用,而這段時間是關閉的所以不會產生事件。總括來看有可能不會有移除的需求,但或許仍有移除的情況存在,還是要找時間看怎麼解決。
雖然有無法移除事件流的困擾,但用Stream的想法並沒有因此動搖,相信近日會找到方式移除,讓Rx的使用完美的接合於遊戲製作中。接下來,不論是否能移除Stream,都要開始展開UI的製作了。在UniRx的配合下,用MVVM的概念是可以很方便加入UI的。