架構(gòu)解析:從核心原理到聯(lián)合調(diào)試實(shí)戰(zhàn))
1. 物理與動畫系統(tǒng)在游戲引擎中的定位與整體設(shè)計(jì)1.1 為什么物理和動畫是引擎架構(gòu)的“下半身”如果把游戲引擎比作一個人渲染系統(tǒng)是臉面腳本系統(tǒng)是大腦那物理和動畫就是下半身——玩家不一定看得見但一旦出問題整個角色就會“飄”起來或者“穿?!钡?jīng)]法玩。物理系統(tǒng)負(fù)責(zé)回答“這個東西在哪里、會不會撞、撞了之后怎么動”動畫系統(tǒng)負(fù)責(zé)回答“這個角色看起來在做什么動作、動作之間怎么過渡”。兩者在運(yùn)行時是緊耦合的角色移動時物理系統(tǒng)算出膠囊體的位置動畫系統(tǒng)根據(jù)速度決定播放走還是跑布娃娃死亡時物理接管骨骼動畫系統(tǒng)退居二線。我在實(shí)際項(xiàng)目里踩過最大的坑就是早期把物理和動畫當(dāng)成兩個獨(dú)立模塊來寫結(jié)果角色跑動時腳底打滑、上下樓梯時動畫和碰撞體對不上。后來才明白這兩個系統(tǒng)必須共享同一套坐標(biāo)變換和時序更新邏輯。游戲引擎架構(gòu)深度解析到第三篇物理與動畫系統(tǒng)是繞不開的核心因?yàn)樗苯記Q定了游戲“手感”的下限。1.2 物理系統(tǒng)的核心職責(zé)拆解物理系統(tǒng)在引擎里通常承擔(dān)四件事碰撞檢測、剛體動力學(xué)、約束求解、場景查詢。碰撞檢測負(fù)責(zé)找出“誰和誰可能碰上了”剛體動力學(xué)負(fù)責(zé)算“碰上了之后速度和位置怎么變”約束求解負(fù)責(zé)處理關(guān)節(jié)、彈簧、車輛懸掛這類限制關(guān)系場景查詢則是給游戲邏輯提供射線檢測、重疊檢測、掃掠檢測這些接口。這四件事的復(fù)雜度是遞增的。碰撞檢測可以用空間劃分結(jié)構(gòu)BVH、八叉樹、網(wǎng)格來加速剛體動力學(xué)要處理積分器和穩(wěn)定性約束求解最麻煩因?yàn)橐庖粋€大型的線性互補(bǔ)問題。很多自研引擎在約束求解上翻車表現(xiàn)就是堆疊的箱子會抖動、角色踩在移動平臺上會滑走。1.3 動畫系統(tǒng)的分層架構(gòu)動畫系統(tǒng)一般分成三層動畫數(shù)據(jù)層、動畫狀態(tài)層、動畫混合層。動畫數(shù)據(jù)層管的是關(guān)鍵幀、曲線、蒙皮矩陣這些原始數(shù)據(jù)動畫狀態(tài)層管的是狀態(tài)機(jī)、過渡條件、事件觸發(fā)動畫混合層管的是多個動畫片段如何按權(quán)重融合成最終姿勢。這三層的分離很重要。我見過把狀態(tài)機(jī)和混合邏輯寫在一起的代碼改一個過渡條件要動到混合權(quán)重的計(jì)算維護(hù)成本極高。正確的做法是狀態(tài)機(jī)只負(fù)責(zé)輸出“當(dāng)前應(yīng)該播放哪些片段、權(quán)重是多少”混合層只負(fù)責(zé)“給定權(quán)重算出最終骨骼姿勢”。這樣換一套狀態(tài)機(jī)不影響混合算法換混合算法也不影響狀態(tài)機(jī)。1.4 物理與動畫的時序耦合設(shè)計(jì)引擎主循環(huán)里物理和動畫的更新順序會直接影響表現(xiàn)。常見的有兩種方案物理先更新、動畫后更新或者動畫先更新、物理后更新。前者適合物理驅(qū)動的角色比如布娃娃、載具后者適合動畫驅(qū)動的角色比如根運(yùn)動動畫。我一般會在引擎里做一個“更新階段”的抽象把物理步進(jìn)、動畫求值、變換同步分成三個獨(dú)立的階段每個階段有明確的輸入輸出。物理步進(jìn)輸出剛體變換動畫求值輸出骨骼局部變換變換同步負(fù)責(zé)把兩者按需合并到場景圖里。這樣即使以后要改成多線程并行更新階段之間的依賴關(guān)系也是清晰的。2. 物理系統(tǒng)核心細(xì)節(jié)與實(shí)操要點(diǎn)2.1 碰撞形狀的選型與代價物理系統(tǒng)里最影響性能的不是求解器而是碰撞形狀的復(fù)雜度。常見形狀按代價從低到高排列球體、膠囊體、盒子、凸包、三角網(wǎng)格。球體和膠囊體的碰撞檢測有解析解速度極快盒子和凸包要用分離軸定理或者GJK算法三角網(wǎng)格最貴通常只用于靜態(tài)場景。角色控制器一般用膠囊體因?yàn)槟z囊體在斜坡和臺階上的滑動行為比較可控。載具用盒子或者凸包因?yàn)橐_模擬底盤形狀??善茐奈镉猛拱纸獍褟?fù)雜網(wǎng)格拆成若干凸包。我實(shí)測下來一個場景里如果三角網(wǎng)格碰撞體超過總碰撞體的百分之二十物理步進(jìn)時間會明顯上升。注意凸包分解工具如V-HACD生成的凸包數(shù)量要控制一般單個物體不超過十六個凸包否則求解器負(fù)擔(dān)會很大。2.2 積分器的選擇與穩(wěn)定性剛體動力學(xué)里位置和速度的積分方式?jīng)Q定了模擬的穩(wěn)定性。最簡單的顯式歐拉積分實(shí)現(xiàn)容易但能量不守恒彈簧會越彈越高。半隱式歐拉先更新速度再更新位置穩(wěn)定性好很多是大多數(shù)引擎的默認(rèn)選擇。Verlet積分適合布料和粒子但不適合有旋轉(zhuǎn)的剛體。我在項(xiàng)目里做過對比測試同樣的彈簧系統(tǒng)顯式歐拉在六十幀下幾秒內(nèi)就發(fā)散了半隱式歐拉能穩(wěn)定運(yùn)行隱式歐拉更穩(wěn)但需要解線性系統(tǒng)代價高。所以除非是做高精度工業(yè)模擬游戲引擎用半隱式歐拉就夠了。2.3 約束求解的迭代次數(shù)與收斂約束求解器通常用迭代法比如序列脈沖或者投影高斯-賽德爾。迭代次數(shù)越多約束越精確但耗時也越長。常見配置是速度迭代八次、位置迭代三次。速度迭代負(fù)責(zé)消除相對速度位置迭代負(fù)責(zé)消除穿透。這里有個經(jīng)驗(yàn)值如果場景里堆疊物體多位置迭代要加到五次以上否則箱子會慢慢陷進(jìn)地面。如果場景里主要是角色和靜態(tài)碰撞三次就夠。我一般會在物理設(shè)置里暴露這兩個參數(shù)讓關(guān)卡設(shè)計(jì)師按場景調(diào)。2.4 物理材質(zhì)與摩擦恢復(fù)系數(shù)物理材質(zhì)定義了摩擦系數(shù)和恢復(fù)系數(shù)彈性。摩擦系數(shù)決定物體在斜面上會不會滑恢復(fù)系數(shù)決定碰撞后反彈多少。這兩個參數(shù)不是隨便填的要根據(jù)游戲手感來調(diào)。角色踩在地面上不滑地面摩擦系數(shù)要大于角色的摩擦系數(shù)。彈力球恢復(fù)系數(shù)接近一鉛球接近零。我見過把恢復(fù)系數(shù)設(shè)成一的箱子結(jié)果箱子落地后永遠(yuǎn)彈個不停因?yàn)槟芰繘]有耗散。實(shí)際項(xiàng)目里恢復(fù)系數(shù)超過零點(diǎn)八就要加阻尼否則模擬會失控。2.5 場景查詢的精度與性能平衡場景查詢包括射線檢測、球形重疊、盒形重疊、膠囊掃掠。射線檢測最常用用于射擊、拾取、視線判斷。球形重疊用于范圍傷害、觸發(fā)區(qū)域。膠囊掃掠用于角色移動預(yù)測。這些查詢的精度和性能要平衡。射線檢測如果對三角網(wǎng)格做精確求交代價很高。常見優(yōu)化是先用包圍盒做粗篩再對候選三角形做精確求交。另外查詢頻率也要控制不要每幀對全場景做重疊檢測可以用空間劃分結(jié)構(gòu)縮小查詢范圍。3. 動畫系統(tǒng)核心細(xì)節(jié)與實(shí)操要點(diǎn)3.1 關(guān)鍵幀動畫的數(shù)據(jù)組織關(guān)鍵幀動畫的數(shù)據(jù)組織方式直接影響內(nèi)存占用和求值速度。最樸素的方式是每個骨骼每個通道存一組關(guān)鍵幀但這樣內(nèi)存碎片多。更好的方式是把所有骨骼的所有通道的關(guān)鍵幀按時間排序后存成連續(xù)數(shù)組再用索引表記錄每個通道的起止位置。求值時給定時間t先在索引表里找到對應(yīng)通道的關(guān)鍵幀區(qū)間然后做插值。插值方式有線性、步進(jìn)、三次樣條。線性插值最快但不夠平滑三次樣條平滑但需要額外切線數(shù)據(jù)。我一般對位置用線性插值對旋轉(zhuǎn)用球面線性插值對縮放用線性插值。3.2 動畫狀態(tài)機(jī)的設(shè)計(jì)模式動畫狀態(tài)機(jī)有兩種常見設(shè)計(jì)集中式狀態(tài)機(jī)和分布式狀態(tài)機(jī)。集中式狀態(tài)機(jī)把所有狀態(tài)和過渡寫在一個大表里優(yōu)點(diǎn)是全局可見、容易調(diào)試缺點(diǎn)是狀態(tài)多了之后表會爆炸。分布式狀態(tài)機(jī)把每個狀態(tài)做成獨(dú)立對象優(yōu)點(diǎn)是模塊化好缺點(diǎn)是狀態(tài)之間的過渡條件分散在各處容易漏改。我傾向于混合方案狀態(tài)定義用數(shù)據(jù)驅(qū)動的方式寫在配置表里過渡條件用腳本或者表達(dá)式求值。這樣策劃可以改狀態(tài)和過渡程序只需要維護(hù)求值引擎。狀態(tài)機(jī)的輸出不是直接播放哪個動畫而是輸出一組帶權(quán)重的動畫片段請求交給混合層處理。3.3 動畫混合的數(shù)學(xué)原理動畫混合的本質(zhì)是骨骼局部變換的加權(quán)平均。位置和縮放可以直接線性加權(quán)旋轉(zhuǎn)要用球面線性插值或者歸一化線性插值。球面線性插值精度高但計(jì)算量大歸一化線性插值在權(quán)重接近時誤差小速度快?;旌蠘涫腔旌蠈拥某R娊Y(jié)構(gòu)有一維混合、二維混合、多維混合。一維混合按一個參數(shù)比如速度在多個動畫片段之間插值。二維混合按兩個參數(shù)比如速度和方向在網(wǎng)格狀排列的動畫片段之間插值。多維混合一般用梯度帶或者三角剖分。注意混合權(quán)重必須歸一化否則骨骼會縮放異常。我見過權(quán)重和不為一時角色變形的案例排查了半天才發(fā)現(xiàn)是混合層沒做歸一化。3.4 根運(yùn)動與物理的協(xié)同根運(yùn)動是指動畫本身包含位移信息角色移動由動畫驅(qū)動而不是物理驅(qū)動。根運(yùn)動的好處是腳步和位移完全匹配不會打滑。壞處是物理系統(tǒng)不能直接控制角色位置需要把根運(yùn)動的位移提取出來再同步給物理膠囊體。我的做法是動畫求值后從根骨骼提取本幀的位移和旋轉(zhuǎn)增量把這個增量應(yīng)用到物理膠囊體上物理系統(tǒng)做碰撞檢測和響應(yīng)然后把修正后的位置寫回場景圖。這樣既保留了根運(yùn)動的精確腳步又讓物理系統(tǒng)能處理碰撞。3.5 動畫壓縮與內(nèi)存優(yōu)化動畫數(shù)據(jù)是內(nèi)存大戶一個角色幾十個骨骼、幾百幀動畫不壓縮的話輕松上百兆。常見壓縮手段有關(guān)鍵幀抽稀、曲線擬合、量化。關(guān)鍵幀抽稀是去掉冗余關(guān)鍵幀只保留曲率變化大的。曲線擬合是用多項(xiàng)式或者樣條逼近原始曲線。量化是把浮點(diǎn)數(shù)降到十六位甚至八位。我一般對旋轉(zhuǎn)用十六位量化對位置用十六位量化對縮放用八位量化。誤差在視覺上幾乎看不出來內(nèi)存能省一半以上。另外動畫片段可以按需加載不要一次性全讀進(jìn)內(nèi)存。4. 物理與動畫的聯(lián)合調(diào)試與常見問題排查4.1 角色腳底打滑的根因分析腳底打滑是物理和動畫不同步的典型癥狀。根因通常有三個動畫速度與物理速度不匹配、根運(yùn)動位移沒有正確提取、物理材質(zhì)摩擦系數(shù)不對。排查時先看動畫播放速度是否隨移動速度變化再看根運(yùn)動增量是否應(yīng)用到了物理膠囊體最后檢查地面和角色的摩擦系數(shù)。我遇到過一次打滑最后發(fā)現(xiàn)是動畫狀態(tài)機(jī)在速度變化時過渡太快走和跑的混合權(quán)重跳變導(dǎo)致腳步頻率突變。解決辦法是給速度變化加一個平滑時間讓混合權(quán)重漸變。4.2 布娃娃系統(tǒng)的穩(wěn)定性調(diào)優(yōu)布娃娃系統(tǒng)是物理接管動畫的典型場景。常見問題是關(guān)節(jié)抖動、肢體穿透、整體癱軟。關(guān)節(jié)抖動通常是約束求解迭代不夠增加速度迭代次數(shù)可以緩解。肢體穿透是碰撞形狀太小或者求解器穿透修正不夠可以加大碰撞形狀或者增加位置迭代。整體癱軟是關(guān)節(jié)角度限制太松需要收緊限制。我一般會給布娃娃的每個關(guān)節(jié)設(shè)置角度限制和角速度限制防止肢體反關(guān)節(jié)彎曲。另外布娃娃的碰撞形狀要比動畫骨骼稍微大一點(diǎn)避免視覺穿透。4.3 動畫事件與物理觸發(fā)的時序問題動畫事件是在動畫特定時間點(diǎn)觸發(fā)邏輯的機(jī)制比如腳步聲、攻擊判定、特效生成。如果動畫事件觸發(fā)時物理狀態(tài)還沒更新就會出現(xiàn)判定位置錯誤。比如攻擊動畫的判定幀觸發(fā)了但角色位置還是上一幀的導(dǎo)致打空。解決辦法是把動畫事件的處理放在物理更新之后、渲染之前?;蛘甙褎赢嬍录龀申?duì)列在物理更新后統(tǒng)一處理。我傾向于后者因?yàn)殛?duì)列可以保證事件按時間順序處理不會因?yàn)閹什▌佣鴣y序。4.4 物理與動畫的常見問題速查表問題現(xiàn)象可能原因排查方向解決手段角色腳底打滑動畫速度與物理速度不匹配檢查動畫播放速率和移動速度同步動畫速率或改用根運(yùn)動角色穿墻碰撞檢測頻率不足檢查是否用了連續(xù)碰撞檢測開啟連續(xù)碰撞檢測或減小步長布娃娃抖動約束求解迭代不足檢查速度迭代次數(shù)增加速度迭代到十次以上動畫過渡跳變混合權(quán)重突變檢查過渡曲線加平滑時間或改用漸變過渡物理堆疊抖動位置迭代不足檢查位置迭代次數(shù)增加位置迭代到五次以上動畫事件錯位事件處理時序不對檢查事件處理在物理前還是后移到物理更新后處理內(nèi)存占用過高動畫數(shù)據(jù)未壓縮檢查動畫片段大小啟用量化和抽稀根運(yùn)動不生效位移未提取檢查根骨骼位移提取邏輯提取增量并應(yīng)用到物理體4.5 多線程下的物理與動畫更新現(xiàn)代引擎越來越多地把物理和動畫放到多線程里跑。物理步進(jìn)可以并行化但約束求解的并行化比較麻煩因?yàn)榧s束之間有依賴。動畫求值可以按骨骼樹并行父骨骼求值完子骨骼才能求值所以并行度受骨骼樹深度限制。我的經(jīng)驗(yàn)是物理步進(jìn)放在獨(dú)立線程動畫求值放在任務(wù)圖里按骨骼樹分層并行變換同步放在主線程。這樣既能利用多核又不會引入復(fù)雜的同步問題。需要注意的是物理和動畫共享的變換數(shù)據(jù)要用雙緩沖或者原子操作保護(hù)避免讀寫沖突。5. 從零搭建一個最小物理動畫聯(lián)合系統(tǒng)的實(shí)操記錄5.1 環(huán)境準(zhǔn)備與依賴選型假設(shè)我們要從零搭一個最小系統(tǒng)語言用C物理用Bullet或者PhysX動畫自己寫。Bullet輕量、開源、文檔全適合學(xué)習(xí)。PhysX性能好、功能全適合生產(chǎn)。我選Bullet因?yàn)樗募s束求解器代碼可讀性強(qiáng)方便理解原理。動畫部分不依賴第三方庫自己實(shí)現(xiàn)關(guān)鍵幀插值、狀態(tài)機(jī)、混合樹。數(shù)學(xué)庫用GLM因?yàn)樗蚈penGL生態(tài)兼容好向量矩陣操作簡潔。5.2 物理世界的初始化與步進(jìn)初始化Bullet世界需要創(chuàng)建碰撞配置、調(diào)度器、寬相、窄相、求解器、離散動力學(xué)世界。配置里設(shè)置重力、求解器迭代次數(shù)。然后創(chuàng)建地面和角色的剛體。步進(jìn)時調(diào)用動力學(xué)世界的stepSimulation傳入時間步長和最大子步數(shù)。時間步長一般取六十分之一秒最大子步數(shù)取四防止幀率波動導(dǎo)致模擬爆炸。步進(jìn)后遍歷剛體把變換同步到場景圖。5.3 動畫數(shù)據(jù)的加載與求值動畫數(shù)據(jù)用自定義格式存儲每個動畫片段有名稱、時長、骨骼通道數(shù)組。每個通道有關(guān)鍵幀時間數(shù)組和值數(shù)組。加載時解析成內(nèi)存結(jié)構(gòu)求值時根據(jù)當(dāng)前時間在通道里二分查找關(guān)鍵幀區(qū)間做插值。求值輸出的是骨骼局部變換數(shù)組然后按骨骼樹層級累乘得到全局變換最后算蒙皮矩陣。蒙皮矩陣傳給渲染器做頂點(diǎn)變換。5.4 狀態(tài)機(jī)與混合樹的實(shí)現(xiàn)狀態(tài)機(jī)用配置表驅(qū)動狀態(tài)表定義每個狀態(tài)的動畫片段和循環(huán)方式過渡表定義從哪個狀態(tài)到哪個狀態(tài)、條件是什么、過渡時長多少。條件用表達(dá)式求值支持比較參數(shù)和邏輯運(yùn)算?;旌蠘溆靡痪S混合輸入?yún)?shù)是速度輸出是走和跑的混合權(quán)重。權(quán)重計(jì)算用線性映射速度為零時走權(quán)重為一速度達(dá)到跑速時跑權(quán)重為一中間線性插值。5.5 根運(yùn)動與物理的同步根運(yùn)動提取動畫求值后從根骨骼的局部變換里提取位移和旋轉(zhuǎn)增量。把這個增量應(yīng)用到角色的物理膠囊體上調(diào)用物理世界的步進(jìn)得到修正后的位置。然后把修正后的位置寫回場景圖的角色節(jié)點(diǎn)同時更新動畫的根骨骼偏移讓動畫和物理對齊。這一步的關(guān)鍵是增量要按時間步長縮放否則幀率變化時移動速度會變。我一般把增量除以動畫幀時長再乘以物理步長。5.6 調(diào)試工具與可視化調(diào)試物理和動畫離不開可視化。物理方面用Bullet的調(diào)試?yán)L制器畫出碰撞形狀、接觸點(diǎn)、約束。動畫方面畫出骨骼、混合權(quán)重、狀態(tài)機(jī)當(dāng)前狀態(tài)。這些可視化工具在開發(fā)期能省大量時間。我還會做一個參數(shù)面板實(shí)時調(diào)整物理迭代次數(shù)、摩擦系數(shù)、動畫混合速度觀察效果變化。這個面板用簡單的GUI庫就能做但對調(diào)參幫助極大。6. 物理動畫系統(tǒng)的性能優(yōu)化與擴(kuò)展思路6.1 物理步進(jìn)的性能瓶頸定位物理步進(jìn)的性能瓶頸通常在窄相碰撞檢測和約束求解。定位方法是加計(jì)時器分別統(tǒng)計(jì)寬相、窄相、求解器的時間。如果窄相占大頭說明碰撞形狀太復(fù)雜需要簡化。如果求解器占大頭說明約束太多或者迭代次數(shù)太高。我一般會做一個物理統(tǒng)計(jì)面板顯示剛體數(shù)、碰撞體數(shù)、接觸點(diǎn)數(shù)、約束數(shù)、各階段耗時。這樣一眼就能看出瓶頸在哪。6.2 動畫求值的優(yōu)化手段動畫求值的優(yōu)化手段有減少求值頻率、緩存求值結(jié)果、按需求值。減少求值頻率是指遠(yuǎn)處角色的動畫降頻求值比如三十幀降到十五幀。緩存求值結(jié)果是指如果動畫時間沒變直接復(fù)用上一幀的骨骼變換。按需求值是指只求值可見角色的動畫不可見的不求值。另外骨骼樹的遍歷可以用扁平化數(shù)組代替指針跳轉(zhuǎn)提高緩存命中率。我實(shí)測下來扁平化數(shù)組能提升百分之二十到三十的求值速度。6.3 大規(guī)模角色的物理動畫管理大規(guī)模角色場景比如百人同屏需要特殊管理。物理方面遠(yuǎn)處角色可以用簡化碰撞體甚至不用物理只用動畫驅(qū)動。動畫方面遠(yuǎn)處角色降頻求值或者用烘焙好的頂點(diǎn)動畫代替骨骼動畫。我做過一個百人同屏的測試近處十個角色全物理全動畫中間三十個角色簡化物理降頻動畫遠(yuǎn)處六十個角色無物理烘焙動畫。整體幀率能穩(wěn)定在六十幀視覺上幾乎看不出差別。6.4 物理動畫系統(tǒng)的未來擴(kuò)展方向物理動畫系統(tǒng)還有很多可擴(kuò)展的方向。比如程序化動畫用物理模擬生成動畫而不是播放關(guān)鍵幀適合布娃娃、繩索、布料。比如機(jī)器學(xué)習(xí)動畫用神經(jīng)網(wǎng)絡(luò)生成動畫適合大量相似角色的群體動畫。比如軟體物理模擬肌肉和脂肪適合高真實(shí)度角色。這些方向目前還在演進(jìn)中但核心思路是一致的物理和動畫的邊界會越來越模糊最終會融合成一個統(tǒng)一的“運(yùn)動系統(tǒng)”。我在實(shí)際項(xiàng)目里已經(jīng)開始嘗試用物理約束來驅(qū)動部分動畫效果比純關(guān)鍵幀更自然但調(diào)參成本也更高。6.5 一個容易被忽略的細(xì)節(jié)單位與尺度物理和動畫對單位尺度很敏感。物理引擎通常假設(shè)一米等于一個單位如果動畫數(shù)據(jù)用的是厘米物理模擬就會出問題。我見過因?yàn)閱挝徊唤y(tǒng)一導(dǎo)致角色像螞蟻一樣輕飄飄的案例。解決辦法是在導(dǎo)入動畫數(shù)據(jù)時統(tǒng)一轉(zhuǎn)成米物理參數(shù)也按米來設(shè)。另外重力加速度用九點(diǎn)八米每二次方秒不要用九百八十。這個細(xì)節(jié)雖小但一旦搞錯整個物理表現(xiàn)都會不對勁。6.6 物理動畫系統(tǒng)的測試與驗(yàn)證物理動畫系統(tǒng)的測試比較麻煩因?yàn)楹芏鄦栴}是視覺相關(guān)的自動化測試難覆蓋。我的做法是單元測試覆蓋數(shù)學(xué)計(jì)算插值、混合、積分集成測試覆蓋典型場景角色移動、碰撞、布娃娃視覺測試用截圖對比。視覺測試可以用腳本控制角色做固定動作然后截圖和基準(zhǔn)圖對比。雖然不能覆蓋所有情況但能抓住明顯的回歸問題。另外我會保留一些“手感測試”場景讓策劃和程序定期手動跑一遍確保手感沒有退化。