:物理驅(qū)動的完整狀態(tài)機(jī)方案)
Godot 骨骼動畫實戰(zhàn)物理驅(qū)動的完整狀態(tài)機(jī)方案【免費下載鏈接】godot-demo-projectsDemonstration and Template Projects項目地址: https://gitcode.com/GitHub_Trending/go/godot-demo-projects讓 2D 角色動起來多半會不對勁空中還在跑步、落地卡一頓、再加個攻擊狀態(tài)if-else 就開始失控。本文用 Godot 官方演示項目里的兩個例子沿骨骼動畫、AnimationTree、狀態(tài)機(jī)這條線把這套動畫系統(tǒng)從 Bug 修到能擴(kuò)展講清楚每一步在干什么。角色空中跑步、落地一頓先修好這兩個 Bug2d/skeleton/ 這個示例里角色 GBot 由 Skeleton2D 和一層 Bone2D 構(gòu)成紋理綁在骨骼上父骨骼一轉(zhuǎn)子骨骼跟著動——這就是骨骼動畫用少量關(guān)鍵幀驅(qū)動全身的基本原理。真正有意思的不是骨骼本身而是角色控制器整段代碼只有百來行卻踩滿了新手最常遇到的坑??罩信懿铰涞嘏袛嘁旁跔顟B(tài)賦值的外層空中還在跑步這個 Bug 的根源基本只有一個動畫狀態(tài)只按速度賦值沒有用is_on_floor()做門檻。結(jié)果就是人在天上腿部照樣循環(huán)走路動畫看起來像在空氣里狂奔。修法一句話就能說清地面狀態(tài)只在地面分支里賦值空中只給 fly/fall。skeleton 示例的控制器就是這么寫的骨架長這樣if is_on_floor(): if abs(velocity.x) 50: $AnimationTree[parameters/state/transition_request] States.RUN $AnimationTree[parameters/run_timescale/scale] abs(velocity.x) / 60 elif velocity.x: $AnimationTree[parameters/state/transition_request] States.WALK else: $AnimationTree[parameters/state/transition_request] States.IDLE else: # 下落播 fall上升播 fly $AnimationTree[parameters/state/transition_request] \ States.FALL if velocity.y 0 else States.FLYStates是文件頂部的常量字典IDLE/WALK/RUN/FLY/FALL文件里專門注釋了一句要和 AnimationTree 里的狀態(tài)名保持同步——這條提醒先記下來后面還會用到。落地卡頓一次性動畫和常態(tài)動畫搶同一幀第二種一頓發(fā)生在落地瞬間腿部先擺出走路姿勢再啪地切到落地動作兩套動畫搶同一幀觀感就是卡了一下。解法是把落地做成一次性one-shot動畫。AnimationTree 你可以理解成一個動畫調(diào)度面板把動畫片段和切換規(guī)則畫成一張圖引擎照著圖自動播。skeleton 示例里常態(tài)狀態(tài)機(jī)只有 idle/walk/run/fly/fall 五個落地動畫不占狀態(tài)位而是掛在獨立的 OneShot 節(jié)點上——觸發(fā)后播完一輪自動交回給狀態(tài)機(jī)if falling_fast: $AnimationTree[parameters/land_hard/request] \ AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE no_move_horizontal_time 0.4 elif falling_slow: $AnimationTree[parameters/land/request] \ AnimationNodeOneShot.ONE_SHOT_REQUEST_FIREfalling_fast是空中下落速度達(dá)到末速度 400px/s的標(biāo)志位。硬著陸除了播動畫還會把no_move_horizontal_time設(shè)成 0.4——硬著陸硬直接下來 0.4 秒水平速度強(qiáng)制歸零。落地那下砸出來的重量感其實來自這行物理代碼不是動畫本身。AnimationTree 參數(shù)怎么讀寫過渡時間調(diào)不卡的關(guān)鍵那串 parameters/xxx 字符串對應(yīng)編輯器里哪個框上面代碼里的$AnimationTree[parameters/state/transition_request]看著像魔法字符串其實就是編輯器面板字段的訪問路徑它對應(yīng) AnimationTree 面板里 State 節(jié)點的 Request狀態(tài)請求字段樹里改一下這里就能生效。parameters/run_timescale/scale對應(yīng)樹上掛在 run 分支前的時縮節(jié)點AnimationNodeTimeScale的 Scale 字段。樹里節(jié)點改名這些字符串就得跟著改所以示例才用常量表統(tǒng)一收口。如果你的項目結(jié)構(gòu)不同示例的 AnimationTree 直接掛在角色根節(jié)點下。如果你的角色把精靈包在別的容器里只需把$AnimationTree換成實際路徑parameters/...這串路徑只跟樹內(nèi)節(jié)點名有關(guān)不跟場景層級走。整套狀態(tài)流轉(zhuǎn)畫出來是這樣的過渡時間調(diào)不卡混合時長和時縮放各管一半過渡不自然一半是狀態(tài)切換的混合時長沒配對樹上連線旁的 transition 設(shè)置一半是動畫播放速度和角色實際速度脫節(jié)。示例的做法是后者run 的播放速度用abs(velocity.x) / 60實時縮放走得快腿就擺得快而不是硬切一段更快的動畫。walk 同理除以 12。這兩個系數(shù)就是手感旋鈕改起來比重做動畫便宜得多。加一個攻擊狀態(tài)代碼就爆炸狀態(tài)機(jī)什么時候該外掛攻擊是打斷不是新狀態(tài)給角色加攻擊、受擊、死亡之后問題就不是缺動畫了而是狀態(tài)之間的關(guān)系變復(fù)雜攻擊要能打斷移動但打完得回到被打斷前的狀態(tài)而不是無腦回 idle。skeleton 示例的 AnimationTree 只處理純動畫層的切換管不了這種業(yè)務(wù)邏輯——這正是 2d/finite_state_machine/ 登場的地方。這個示例的核心是一個狀態(tài)棧pushdown automaton。切換狀態(tài)時不是覆蓋而是往棧頂壓壓上去的狀態(tài)播完發(fā)出finished(previous)棧頂彈掉自然露出下面的原狀態(tài)。基類里就這幾行func _change_state(state_name: String) - void: current_state.exit() if state_name previous: states_stack.pop_front() # 彈?;氐奖淮驍嗲暗臓顟B(tài) else: states_stack[0] states_map[state_name] current_state states_stack[0] state_changed.emit(current_state) current_state.enter()角色的狀態(tài)機(jī)則只做了兩件事受擊、跳躍、攻擊屬于打斷型狀態(tài)進(jìn)棧頂而不是替換移動時的速度原樣傳給 jump 狀態(tài)保證起跳不丟失動量func _change_state(state_name: String) - void: # ... 省略 if state_name in [PLAYER_STATE.stagger, PLAYER_STATE.jump, PLAYER_STATE.attack]: states_stack.push_front(states_map[state_name]) # 壓棧頂不替換 if state_name PLAYER_STATE.jump and current_state move: jump.initialize(move.speed, move.velocity) super._change_state(state_name)注意攻擊狀態(tài)的收尾靠動畫信號驅(qū)動——劍的揮砍動畫播完回調(diào)finished.emit(PLAYER_STATE.previous)回哪去不用關(guān)心。狀態(tài)之間就這樣解耦了每個狀態(tài)只管我什么時候結(jié)束不管結(jié)束后去哪。AnimationTree 內(nèi)置狀態(tài)機(jī) vs 外掛 FSM怎么選判斷標(biāo)準(zhǔn)很簡單只要管動畫片段之間怎么切AnimationTree 就夠一旦狀態(tài)開始承載業(yè)務(wù)規(guī)則打斷、返回、計數(shù)、觸發(fā)事件再上 FSM。維度AnimationTree 內(nèi)置狀態(tài)機(jī)外掛 FSM獨立節(jié)點控制規(guī)?;コ獾囊苿訝顟B(tài)十幾個以內(nèi)含打斷/返回/子狀態(tài)的復(fù)雜流程擴(kuò)展成本樹上加節(jié)點連線基本不寫代碼一個狀態(tài)一個類但每個類只干一件事調(diào)試難度樹面板直接看當(dāng)前狀態(tài)需自建面板示例 debug/ 目錄給了狀態(tài)棧顯示器適用場景跑跳落地等純動畫切換攻擊、受擊、死亡等業(yè)務(wù)流從 demo 到項目你還需要做什么兩個示例合起來是一條完整鏈路Skeleton2D 管形AnimationTree 管動畫層FSM 管業(yè)務(wù)層。拿去做自己的角色剩下要補(bǔ)的是這幾件事狀態(tài)名兩側(cè)同步。常量表和樹節(jié)點名對不上不會報錯只會靜默切換失敗 ?? 排查時先對名字。落地反饋分檔做極速落地播硬著陸 0.4 秒水平硬直較快落地播輕著陸 one-shot正常落地只做狀態(tài)切換。分檔來自falling_fast/falling_slow兩個標(biāo)志位閾值 300 和末速度 400。手感優(yōu)先調(diào)物理參數(shù)起跳速度 -400、松手提前結(jié)束跳躍時垂直速度乘 0.6、末速度 400。動畫只是跟隨結(jié)果物理對了動畫才自然。留調(diào)試入口。FSM 示例帶狀態(tài)棧顯示面板開發(fā)期常駐很劃算上線前再摘。別重復(fù)造輪子。動畫混合、交叉淡入淡出這些臟活讓樹去做腳本只發(fā)狀態(tài)請求和 one-shot 請求。把角色跑順了再回頭看會發(fā)現(xiàn)所謂動畫系統(tǒng)無非是物理結(jié)果映射成狀態(tài)請求剩下的是紀(jì)律——狀態(tài)名收口、打斷和返回分開處理、硬直寫在物理側(cè)?!久赓M下載鏈接】godot-demo-projectsDemonstration and Template Projects項目地址: https://gitcode.com/GitHub_Trending/go/godot-demo-projects創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考