因緣際會之下,在和半路、Faust Li、KWL 聊天的時候談到了遊戲中動態程式化的方法,這對 Flash 或擅長使用 Javascript 做動態的開發者不算新聞,但是好像很少看到人在 native language 裡使用又有拿出來討論的,所以就趁還沒忘記這件事的時候來筆記一番吧 XD
最近幾年在開發 Flash 的開發者一定用過 Tweener 與 TweenMax 之類的 library,而後來 Javascript 夠快了以後,做網頁前端動態的開發者當然也有此需求,所以就有了各種 js porting 出爐。Flash 後來應該在 CS3 or CS4 以後(不太確定,一陣子沒用到 Flash 了)也把他自家的漸變 API 開放出來,但記得剛開始的時候評價是很爛,大家都還是繼續用 Tweener / TweenMax etc。現在的各種新出爐的遊戲開發工具的 API,也幾乎都有類似的功能(通常叫 transition API 之類的,譬如說 CoronaSDK 就有)
不過當時我們在 C++ 端開發自己的作品有這需求,所以曾經花了點時間把它 port 到 C++ 來。
當然,理想上來說,所有動態都能交給美術人員去調,或是有專門的動態編輯器,可以把動畫輸出成檔案是最好,可是總是不可能全部動態都剛好有 editor 給你用,或是動態需求剛好是很模稜兩可的地方,譬如說 UI 物件的滑進滑出,這該誰做?美術?UI 程式?如果要改,又要美術改完動態又重新交給程式?有時也碰到需要大量產生的隨機(但模式雷同)的動態效果,要如何有效率的自動化產生又方便管理、修改?
所以可以有一個程式模組來專門做 over time 的數值漸變(你想得到的數值都可以,三維XYZ、旋轉、縮放、顏色、甚至動畫影格 etc...)就很重要。
目前已知有名的漸變公式系列,應該都衍伸自 Robert Penner 這裡,最關鍵的參數就是數值起點、終點,與 delta-time(時間變量)。有了這些,就能去跑如網頁中 Visualizer 所展示的公式。
當然那頁 Visualizer 有點不夠一目了然,可以看看後來發揚光大的版本(此處為 Tweener)的公式預覽長怎樣:
我的資訊與記憶可能有點舊了,不知道後來有沒有做的更好的公式預覽表 ...
Anyway,說好的 C++ 版在此:
EasingEquations.hpp
重點在於 C++ 是個 static + strong typed language,這個 tweening library 要包裝到和 actionscript / javascript / lua 等一樣好用,當然是──幾乎不可能!最關鍵的問題就像,一維數值好處理、但是二、三維向量都要用同一套公式、程式也不要複製貼上的情況下,在 C++ 中該怎麼辦?用我們當年對 C++ template 一知半解的狀態,還是硬幹出了一套很醜(和底層 3D 引擎 couple 在一起了)但是還算堪用的模組。
Accessors.hpp
這邊我想辦法描述了通用化的 getter / setter,並設定這些 getter / setter 實際上是處理 int? float? 還是引擎所提供的 vector2d / vector3d? 當然這樣寫轉接那麼多次,效能沒辦法太好,雖然我們也沒遇過效能問題就是了。
比較大的問題還是這系統目前不只相依到引擎的型別,他也必需相依底層引擎的 Animator 實作邏輯(繼承自 Irrlicht),然後得依靠引擎的 tick 去餵 delta-time 給它:
CustomAnimator.hpp
以上的東西,透過了一些我無法三言兩語在這邊解釋的噁心包裝之後,終端程式終於可以這樣寫:
view::pSprite sp = view::Sprite::create(省略); sp->tween<IOExpo, Pos2D>(start, end, duration, loop, callback, delay);
來描述一個動作。到很後來很後來,把這個 API binding 到 Lua 後因為實在沒辦法直接對應 C++ template 的關係,改用字串來描述使用的公式與 getter/setter:
local sp = view.new_sprite(省略) sp:tween("IOExpo", "Pos2D", start, end, duration, loop, callback, delay)
還好這個呼叫只是「指定動作」對電腦而言,久久才做一次,真正會有效能問題的地方也當然不在這裡。在下面我們的作品 CuBeat demo 影片中,所有有在動的東西全部都是透過這樣方法讓它動的,除了玩家操作的 1p 紅色游標,與正常緩速落下的方塊(這屬於遊戲邏輯,而非視覺動態)
字彈出來、旋轉、淡入淡出、人物動畫(漸變標的為「動畫影格數」)、方塊飛來飛去 etc etc...
看下來大家也看得出全部的關鍵就是那個 easing equation 漸變公式,後面的變化會因各自使用語言、程式架構等等的不同,而有完全不一樣的因應方法,而後面提到太多東西都是相依我們自己的架構,如果還有人有興趣瞭解可以直接在 github 上翻一翻 code,但對大家的幫助應該非常有限,就不再加篇幅說明。或是可以回文詢問 ~
把後來在 G+ 上做的一些後續延伸討論整理起來:
https://plus.google.com/u/0/108250149397624187704/posts/iy1zpe7Q28V
當然類似的 library porting 不會僅限於 Actionscript / Javascript
Java
這套後來想起來應該看過,但是因為個人過去沒有什麼機會寫 Java,所以大概忘了。實際上非常完整強大:
http://www.aurelienribon.com/blog/projects/universal-tween-engine/
http://code.google.com/p/java-universal-tween-engine/
不過或許有些人會覺得太龐雜了,那可以看看:
https://github.com/mattdesl/slim/blob/master/slim/src/slim/easing/Easing.java
C++
雖然正文中提到的我們自己寫的 tween API 也是公開的,但畢竟與底層引擎有藕合,除非進行改寫,不然他人也很難直接利用。這套 C++ 版 tweener 風格有點囉嗦,不過簡單場合應該都還堪用:
http://libclaw.sourceforge.net/tweeners.html
而且也有進入 boost 體系的打算:
https://groups.google.com/forum/#!topic/boost-developers-archive/eS5PhphX3D0
Lua
我們的 tween API 也有包給 Lua,但實際上只是做指定的動作,本身並不在 Lua 層運行,若要增加功能或改變既有行為,就還是得改 C++,不過感謝半路找到了 Lua 版:
1) LuaTweener
http://sourceforge.net/projects/luatweener/
2) tweener
https://github.com/EmmanuelOga/tweener
基本上既然 Actionscript 與 Javascript 中都夠快,Lua 其實應該也夠,而且有 LuaJIT 的幫忙,Pure Lua 版的 tweener 應該效能會有辦法接近 native language 量級的速度。
另一方面來說,就我們自己的經驗,最極端狀況下,每一個 frame 通常大概會有數百個物件在跑漸變動態,然而這些物件本身的 rendering 成本,遠遠超過漸變動態「一個物件 1 frame 算一次」的成本。所以實際上雖然看起來是數值運算吃重,實際上 tweener 類的 API 應該還是要以使用自由度與彈性為優先考量 ... 效能通常不是重點。
我有用到的技巧是在向量繪圖軟體上編輯路徑,用外掛把它的方程式輸出成程式碼,遊戲裡的物體就可以照這個路徑移動。目前這樣就夠用所以沒考慮用比較龐大而靈活的系統,不過以後或許需求增加就要研究一下。
貝茲曲線是很好用的工具,可以近似大部分的曲線,而且很多向量繪圖軟體都有支援,可利用現有軟體編輯。
x=x0*(1-t)^3 + 3x1(1-t)^2*t + 3x2(1-t)*t^2 + x3*t^3
t=0~1
(好像不能用上下標所以用比較難看的寫法,^是指乘方)
效能的話除非是彈幕遊戲上千顆子彈+特效的情況,應該不是問題
Ok,過了這麼久我終於發現之前向 littleshan@ptt 請教是否可轉貼他的 link 過來這件事,居然是隔了一整個月都忘記做。
http://electronic-blue.herokuapp.com/blog/2012/09/quick-animation-by-easing-functions/
說起來非常巧妙,很剛好都在這個時間點在討論一模一樣的問題,但是 littleshan@ptt 的文章中有詳細地解釋了漸變公式的基本構成,以及其舉例是為 Unity 主,若剛好是以 Unity 在開發遊戲的朋友可能很適合參考。
但最重要的是,littleshan 的文章中提到了一個公開的漸變公式產生器!
http://timotheegroleau.com/Flash/experiments/easing_function_generator.htm
包含 source,所以看是你想要手動把產生出的公式程式碼改成你使用的語言,或是直接把 generator port 到可以產生目標語言應該都做得到。唯一的問題大概就是產生出的公式其實運算量是比一些「基本款」高出不少的,若有大量使用的需求時最好先 benchmark 一下。