主題

【開發 week 10 與學習筆記】 淺談IEnumerable、IEnumerator與Unity Coroutine 和LINQ Select、Aggregate

趴趴鼠Loading | 2020-12-23 02:38:19 | 巴幣 24 | 人氣 205

效果:
跳躍動畫共分成5個階段: 開始、上升中、下降中、落地、二段跳。 跳到頂點的瞬間也可以做動畫,只是懶了XD

以前判斷這些動畫的切換要寫一堆判斷式和校正數值,現在統一給排程工具安排做就簡化許多,唯一煩的是要去調方法的參數。

關於這個排程工具我試著做了些小優化(?
原因是這樣的,原本這個工具是給怪物AI用的,怪物AI每個動作之間都有一段間隔秒數,不至於太頻繁的去做排序所以我在每次新增動作時都排序過一次list。
第194行 : actionQueue.Sort((a, b) => b.priority.CompareTo(a.priority));

但後來發現給玩家操作用也行,就能簡單做到上面那樣,動作與動作之間的順序關係。只是這樣會變成很密集的新增動作,假設玩家按下移動鍵,則每fixedUpdate就會插入個"移動"的動作進入排序。
於是我改成新增的動作直接加在末端,不排序,要執行下個動作時再去抓優先度最高的。
第58行:
int next_index = !actionQueue.Any() ? -1 :
                            actionQueue
                            .Select((value, index) => new { Value = value.priority, Index = index })
                            .Aggregate((a, b) => (a.Value > b.Value) ? a : b)
                            .Index;

關於上面的程式碼可以看這篇
Select(a,b)語法可以看微軟文件:將a、b投影成匿名類型。根據文件,b固定代表零為起始的索引。有點像foreach只是多了個index的功能。

至於Aggregate(a,b),可以參考這篇:跟foreach也是有雷同的想法,只是Aggregate(a,b)的a代表結果,b是列舉的資料,最後會套用在a上。

所以整體的意思是,把actionQueue裡面的元素投影成 (int Value, int Index )的匿名類型,再將這表格資料列舉一遍,其中Aggregate(a,b)的a剛開始代表第一筆資料,b是下一筆,每次move next的時候都會比較 (a.Value > b.Value) ?,true就將a設成a,false就將a設成b,然後b繼續讀下一筆。

最後取的值是".Index" 是因為在Select那個環節已經轉換成匿名類型的關係,不再是用原本actionQueue的類型資料。

玩家控制的腳本像這樣設好幾個動作:

然後判斷跳都只要簡單幾行:
被註解掉是因為沒做到頂點的轉換動畫XD


這周除了做了個圍毆遊戲,就是在學這個和把舊的控制腳本改成用這個工具去執行,瞬間少了一堆繁雜判斷,只要專心在什麼狀況下會觸發甚麼動作就好。





同篇文,Skeet的作法是自己擴增個LINQ方法,但我對介面操作不熟,經過一番嘗試就放棄了 ,照該篇的留言說,這個方法比上面select的還要快3倍,有空還是要研究看看。



小談IEnumerable、IEnumerator:
參考這個網站,我們常用的list其實是繼承自IEnumerable。

IEnumerable代表這個資料類型的資料可以被列舉,實作方法只有:
IEnumerator GetEnumerator();
意思是回傳指定類型的列舉器。

IEnumerator是列舉器,負責loop過指定的資料。實作方法有:
bool MoveNext();
<T> Current { get; }
void Reset();
其中需自己建個flag判斷是否可以MoveNext,若到不能再繼續時reture false,與一個用來承接待被巡訪的資料元素的變數。

然後再來看看Unity中Coroutine也是迭代器的一種,根據微軟文件:《迭代器方法或 get 存取子會對集合執行自訂反覆運算。 迭代器方法使用 yield return 陳述式,一次傳回一個項目。 當到達 yield return 陳述式時,系統會記住程式碼中的目前位置。 下次呼叫迭代器函式時,便會從這個位置重新開始執行。》

我覺得這篇滿有趣的:
根據它的說法,Coroutine的WaitForSeconds(1)是怎麼做到的? 原來是系統自動產生個類似於:
float timer = Time.time + 1.0f;

   while (Time.time < timer) {

       yield return null;

   }
用null去累計時間。






雜談:
原本想專注在美術上,結果一不小心越陷越深,變成程式篇了XD

最後還是補個不知所云的動畫好了



創作回應

御安鴨鴨
最近怎麼每個人都在做動畫
2020-12-23 03:36:41
趴趴鼠Loading
剛好而已吧XD
2020-12-26 02:13:05
甜在心饅頭
補充一下,不只 list 喔,Collection 大多都有繼承 IEnumerable。像 Dictionary 我就滿常用的,上面的一些參考資料我也覺得很有趣。
var e = testDict.GetEnumerator();
while (e.MoveNext())
{
//遍歷內容物
}
2020-12-23 11:00:36
趴趴鼠Loading
感謝補充![e5]
2020-12-26 02:13:32
教授加博士先生
LINQ 值得一學(順便也會資料庫操作ㄌ Code也比較易讀 但是在意performance和GC萬萬不能用LINQ 如這篇Unity優化提到, 1. Avoid use of LINQ, 還是要手動寫for loop最快而且沒GC https://docs.microsoft.com/en-us/windows/mixed-reality/develop/unity/performance-recommendations-for-unity
2020-12-23 15:11:38
趴趴鼠Loading
感謝提供,剛剛查了一下LINQ造成效能問題的原因是它多做間接轉型的動作? 像上面的select把每組資料都投影一次,等於多了一組記憶體空間去記錄暫時的資料。 不知道有沒有錯[e12]
2020-12-26 03:05:10
教授加博士先生
原因很多 轉型也是可能其中之一 boxing unboxing也都會有GC (關於boxing可以查一下c# boxing) 再來就是一些CG拷貝等 究竟為何還是要看內部實現(你也看不到XD 基本上共識還是遊戲上盡量少用(特別是update那種每frame的地方) 其他WEB開發就隨便沒差了
2020-12-26 13:42:44
追蹤 創作集

作者相關創作

更多創作