一個遊戲程序員的堅持 —— 論向量化編程
2020年12月7日,由騰訊遊戲學院舉辦的第四屆騰訊遊戲開發者大會(Tencent Game Developers Conference,簡稱TGDC)於線上舉行。來自重慶帕斯亞科技的CTO謝怡欣先生,分享了他對於向量化編程的一些看法。以下是分享視頻和文字實錄:
大家好,我是來自重慶帕斯亞科技的謝怡欣。
首先,我想向大家簡單介紹一下我的經歷。之前我在加拿大温哥華工作了幾年,是Offworld Industries的一個高級程序員,參與制作了一款射擊遊戲《Squad》。我還做過一個手遊,叫《Lionheart Tactics》。現在我在重慶帕斯亞科技擔任技術負責,帕斯亞科技是2011年成立的,我們專注於高創意度的沙盒獨立遊戲。我先後參與開發了《星球探險家》、《波西亞時光》和《超級巴基球》這些作品。
我接下來要跟大家分享的視頻,就是他做技術分享之後的問答環節。我們可以看到一個比較年長的業界人士,對他分享觀點的一些質問。結果Mike Acton就直接把話給他懟回去了。請先看一下這個視頻:
確實態度不算特別的友好。一個很偶然的機會,我和一個做HR的小夥伴,就聊到了這個事情。我和她一起看了這個視頻,她就提出一個觀點,就是這個穿橘黃色衣服的Mike Acton,他的姿態是比較有攻擊性的。就是他很喜歡把手舉到肩膀以上,這樣的話實際上是給觀眾一個信息,就是我説的東西,或者是我的地位比你們要高。可能這也是我當時聽到他説的話,覺得難以接受的原因之一。當然,如果説有機會Mike Acton能看到我今天的演講的話,我想説我肯定不會有任何的不敬,我非常欣賞和佩服您的才華和知識。
這裏有另外一個鏈接,這個鏈接就是很早以前微軟一個寫Windows的程序員他的一個抱怨。他就是講Windows為什麼比其他的操作系統慢。他説因為Windows程序實際上是由於商業化開發,然後迭代了很多很多次,真正寫了很多代碼的那些程序員,可能早就被類似亞馬遜和谷歌這些公司挖走了。剩下的一些程序員,都是相對來説沒那麼多經驗,改代碼也不知道從何下手的。
然後説一下面向對象編程。在我看來,根據程序員自身的水平,他對於這種編程的理解是相差比較大的。就算是很高級很有經驗的程序員,他對面向對象編程的一些設計模式,也會有一些細微的差別。在經過多次修改之後,肯定也都會產生剛才那個程序員抱怨的那種大片大片的死代碼。但是你又不是很敢刪,我相信很多在座的程序員都有過這樣的經歷。
此外,面向對象編程實際上對緩存是很不友好的,但是這一部分資料網上有很多,我就不再贅述了。還有一個點是,我不知道大家有沒有發現,現在主流的遊戲軟件、遊戲程序或者説應用程序,都只是用了一到兩個線程,很少有多線程能得到充分利用的。現在大多數的中高端硬件,都是支持十個以上的線程,而向量化編程的話,實際上是可以充分利用這些計算資源的。
接下來,我想説一下為什麼需要了解向量化編程。在我看來,向量化編程實際上是提高程序員的內力。內力是什麼東西?就比如説張無忌他的內功深厚,他學了九陽神功之後,感覺他學其他的武功都很快,基本上就是信手拈來。如果你作為一個程序員,有很強的內功的話,那你要學那些比如説遊戲客户端、服務器、全棧工程師,包括多線程編程什麼的,都會變得很容易。我覺得至少從我一段時間的學習經歷來看的話,我覺得真的是有這種效果的。
向量化編程提高了代碼的可讀性。大家可以想一下,比如説你有一個函數,函數里面有很多很多行,實際上每一個行都是一個節點,然後每一個節點如果説是調用另外一個函數,它實際上在那個地方,就是一個分支。它就是可能分到另外一個深度的指數下面去了。那個東西又可能調用其他的函數,就分得更細一點。面向對象編程實際上是非常複雜的,基本上是比較鼓勵這種分支。
向量化編程實際上它也是一個樹形結構,但是相對來説要平坦很多,就是樹的複雜程度要簡化很多,也比較線性化。還有一點是,作為程序員,你學面向對象編程也就學幾年,我覺得應該差不多掌握以後,就可以考慮去學習一些新的技術和新的研究方向了。我覺得向量化編程就是一個不錯的選擇。
説到向量化編程,就不得不借助ECS框架。ECS在網上實際上是有非常多的很成熟的教程的。就是説為什麼它的速度快,這些緩存、數據對齊這一系列東西,在這裏我就不再贅述了。我就做個很簡單的介紹,然後再加上我自己的一些理解。
在這裏我想説一下,它跟傳統的Object Oriented Programming差別沒有想象中的那麼大,從概念上幾乎是一樣的。Entity對應那邊就是Object,Component對應那邊可能就是Object上面的一個屬性。你像比如説一個英雄,在ECS的話,英雄可能就是Entity,他的那些屬性,就是那些Component。在面向對象編程的話,英雄就是Object,他的那些屬性,比如説他的Class裏面,可能有其他的一些字段。那麼從概念上面來講,這個基本上是一對一的。區別就是在於面向對象變成裏面的那些方法,實際上是和它的類是寫到一塊的。在那個方法裏面,基本上是想怎麼來就怎麼來。就是你想訪問什麼樣的數據,你就訪問什麼樣的數據,沒有什麼規定。ECS裏面的System的話,對數據的訪問是非常嚴格的。這也就是可能會勸退很多程序員的一個點。
我想再説明一點,就是從頻率這個維度來講,向量化編程可能是比較初級的。這是我在摸索過程當中,尋找出來的一條路徑。我不排除有其他更好更高效的維度,我就想引入相對來説比較簡單的編程實例。這個例子就是在遊戲當中,比如説你有NPC,他可能每一幀都要去檢測他的視野範圍裏面有沒有其他陣營裏的人。如果有,可能這個NPC就需要做一些反應。這段代碼是偽代碼,簡化了很多的一個版本。我只是想讓大家能夠看一看就好了。在Update裏面,就是做一個物理上面的查詢,Get OverlapSphere,把自己坦克的位置放進去,然後把自己的視野半徑放進去,最後看Collider,就是有沒有碰撞體。要是有碰撞體的話,在它上面去再去拿一個看它有沒有Tank的這個Component。如果説有,再生成特效,生成飛行道具,播一些音效這些之類的東西,這一段代碼大家可以看一下。
然後就是向量化編程的一個實例,這個我引用了Unity dots最新出的Date-Oriented...TechStack的一些API。肯定也不是很完整,如果大家真的要去用的話,可能也要去參考一下他們官方網站上面的一些文檔,這裏我大概有那個意思就行了。
劣勢,確實這個技術的起點會比較高,在寫System代碼的時候,需要把所有的數據的讀寫關係,是隻讀還是隻寫,還是又讀又寫,這些東西要把它摸索得很清楚,你才可以寫出比較好的System的代碼。在這方面確實門檻是比面向對象編程是要高一些的。然後算法從單線程改成多線程,難度確實是比較高。在這個點上我想給大家一個建議,一開始不要想把所有的算法,所有的在順序化,或者説面向對象編程的那種思維,想出來的那種算法,都把它改成多線程。我覺得這是一個難度比較大的問題。可能從項目管理上面來説,可以先就用單線程寫一下就好了。如果説這個東西真的在最後產品測試的時候發現花的時間太多,我們需要優化,然後在那個時候,再考慮怎麼把它的高頻的那些操作向量化。
還有最後一點,如果説用ECS這套框架,寫順序化執行的代碼,它的boilerplate會比較多。如果説是面向對象編程,你有一個實例,你點一下,自動就把它的屬性這些成員變量就給你點出來了。在ECS就用Get Component data,如果説對數據有改動,還會再用Set Component data,把它賦值賦回去,大概就是這個樣子。
然後下面是我在自己學習向量化編程的時候,自己摸索做的一個展示。這是一個比較典型的塔防的一個DEMO。你們可能會發現有的時候這些小蟲子會消失,實際上它們是被炮塔攻擊了,只是沒有添加特效。大家可以看到比較多的蟲子,它們是沿着這個地形去走的。我特別花時間做了一個爬牆的邏輯,就是每個蟲子實際上都做了兩個射線查詢,來判斷自己是不是在牆上。當然有些岩石是沒有做碰撞體的,所以説它可能就是從岩石上面就穿過去了。當時也是時間比較趕,所以説也沒有做太多的那種細節的打磨。
在這個場景裏面差不多有七八千到一萬個蟲子,每一幀都做了射線查詢,以及它的周圍有哪些蟲子,避免蟲子與蟲子之間有穿插的現象,當時也是在開發環境裏面維持了有三十幀的樣子。
今天我的演講就到此結束,謝謝大家!