Animation
概述
在格鬥遊戲中的角色動畫對於遊戲遊玩而言是非常重要的,因為它回應了玩家輸入(攻擊、防禦、連擊),並且規定在任何給定時間的各個角色的目前的命中盒及傷害盒的位置,來驅動遊戲遊玩。
格鬥範例使用自訂動畫工具,以啟用確定性動畫(請參見Addons > Custom Animator
頁面以取得更多資訊),並且擴展以包含在格鬥遊戲中所需的功能。
動畫工具參數
自訂動畫圖表被來自Unity動畫工具的動畫參數嵌入,其嵌入順序與它們在嵌入程序時呈現的順序相同。為了方便這些參數的運行階段操控,格鬥範例已經建立了CA參數列舉。它列出在格鬥遊戲中預設包含的所有參數,並且將它們的確切索引明確地指派給它們。
有了如此設定的CA參數,可以使用相同的介面以:
- 在運行階段設定參數,方法是投射列舉到一個整數;及,
- 在
InputCommandDataBase
中呈現用於建立新的輸入序列的參數。
自訂動畫工具擴展
格鬥範例以FighterAnimatorState
、CustomAnimatorBehaviour
及CustomAnimatorStateSystem
來擴展自訂動畫工具。
自訂動畫工具狀態系統
CustomAnimatorStateSystem
是一個簡單的SystemSignalsOnly
系統。它的目的是回應由動畫工具狀態發射的與OnEnter、OnUpdate及OnExit狀態相關的信號。
C#
ISignalOnAnimatorStateEnter
ISignalOnAnimatorStateUpdate
ISignalOnAnimatorStateExit
然後它在實體的自訂動畫工具上設定被選擇的參數,迭代在FightAnimationState
中包含的行為,並且在它們(OnEnter、OnUpdate及OnExit)上運行補充方法。
格鬥動畫工具狀態
預設下,CustomAnimator
使用AnimatorState
資產以更新、混合及取得特定動畫狀態的運動。格鬥範例透過包含一個參照到FigherAnimatorState
資產,來擴展這個資產。
C#
// See CustomAnimatorState.cs
public AssetRefFighterAnimatorState StateAsset;
FighterAnimatorState
是一個資產,其有兩種目的:
- 它含有與給定的狀態關聯的各種
FighterAnimatorBehaviours
。在嵌入Quantum動畫工具資產之前,這些FighterAnimatorBehaviours
在編輯階段被設定為Unity動畫工具中的QTASB Container
的一部分。 - 針對給定動畫狀態,它以
HurtBoxSet
的形式,來含有角色的傷害盒的位置。針對動畫的各個幀,HurtBoxSet
指定了角色的傷害盒的位置,並且相應地在各個刷新來更新格鬥遊戲元件的傷害盒清單變數。
Quantum動畫工具狀態行為
格鬥範例透過抽象資產類別CustomAnimatorBehaviour
,針對自訂動畫工具,啟用了動畫工具狀態行為。
C#
public abstract unsafe partial class CustomAnimatorBehaviour
{
public abstract void OnEnter(Frame f, EntityRef entity, CustomAnimator* animator);
/// <summary>
/// Performed during a state's update.
/// <returns>If true, the state will stop updating behaviours. Usually done if a transition occurs mid-state</returns>
public abstract bool OnUpdate(Frame f, EntityRef entity, CustomAnimator* animator);
public abstract void OnExit(Frame f, EntityRef entity, CustomAnimator* animator);
}
格鬥動畫工具行為
FighterAnimatorBehaviour
源自於CustomAnimatorBehaviour
資產。它也是一種抽象資產,並且客製化了OnEnter()
、OnUpdate()
及OnExit()
方法,以用於「主動」狀態的具體執行方式。
C#
public abstract unsafe class FighterAnimatorBehaviour : CustomAnimatorBehaviour
{
public override unsafe void OnEnter(Frame f, EntityRef entity, CustomAnimator* animator) {
GetFighterAndTransform(f, entity, out Fighter* fighter, out Transform3D* transform);
OnEnter(f, entity, fighter, animator, transform);
}
private static void GetFighterAndTransform(Frame f, EntityRef entity, out Fighter* fighter, out Transform3D* transform) {
fighter = f.Unsafe.GetPointer<Fighter>(entity);
transform = f.Unsafe.GetPointer<Transform3D>(entity);
}
public override unsafe bool OnUpdate(Frame f, EntityRef entity, CustomAnimator* animator) {
GetFighterAndTransform(f, entity, out Fighter* fighter, out Transform3D* transform);
return OnUpdate(f, entity, fighter, animator, transform);
}
public override unsafe void OnExit(Frame f, EntityRef entity, CustomAnimator* animator) {
GetFighterAndTransform(f, entity, out Fighter* fighter, out Transform3D* transform);
OnExit(f, entity, fighter, animator, transform);
}
public abstract void OnEnter(Frame frame, EntityRef fighterRef, Fighter* fighter, CustomAnimator* animator, Transform3D* transform);
public abstract bool OnUpdate(Frame frame, EntityRef fighterRef, Fighter* fighter, CustomAnimator* animator, Transform3D* transform);
public abstract void OnExit(Frame frame, EntityRef fighterRef, Fighter* fighter, CustomAnimator* animator, Transform3D* transform);
}
一個主動狀態的特點是試著立即影響遊戲遊玩的功能性(OnEnter()
或OnUpdate()
)以及缺少計時器。舉例而言,在進入時,QTASBResetCombo
行為重新設定一個實體的連擊數。
C#
public class QTASBResetCombo : FighterAnimatorBehaviour
{
public override unsafe void OnEnter(Frame frame, EntityRef fighterRef, Fighter* fighter, CustomAnimator* animator, Transform3D* transform)
{
fighter->comboCount = 0;
frame.Events.OnComboCountChanged(fighter->index, fighter->comboCount);
}
}
另一方面,QTASBSetBlockState
在各個幀OnUpdate()
更新一個角色的阻擋狀態。
C#
public override bool OnUpdate(Frame frame, EntityRef fighterRef, Fighter* fighter, CustomAnimator* anim, Transform3D* transform)
{
if (CustomAnimator.GetInteger(frame, anim, CAParameters.Horizontal_I_Index) < 0)
{
fighter->blocking = CustomAnimator.GetInteger(frame, anim,
CAParameters.Vertical_I_Index) < 0 ? BlockState.Low : BlockState.High;
}
else
{
fighter->blocking = BlockState.None;
}
return false;
}
QT動畫工具觸發行為
「主動」行為的另一種選擇是「被動」行為。這些只被觸發一次,並且只在動畫連續處於其目前狀態達到triggerTime
參數定義的時間量之後被觸發。這個行為由抽象資產類別QTAnimatorTriggerBehaviour
所管理。
C#
public abstract unsafe class QTAnimatorTriggerBehaviour : FighterAnimatorBehaviour
{
public FP triggerTime;
public override unsafe bool OnUpdate(Frame frame, EntityRef entity, Fighter* fighter, CustomAnimator* animator, Transform3D* transform)
{
var triggeredList = frame.ResolveList(animator->TriggeredStateBehaviours);
if (triggeredList.IndexOf(Guid) >= 0)
return false;
if (CustomAnimator.GetActiveWorldTime(frame, animator) >= triggerTime)
{
triggeredList.Add(Guid);
return OnTriggerBehaviour(frame, entity, fighter, animator, transform);
}
return false;
}
protected abstract unsafe bool OnTriggerBehaviour(Frame frame, EntityRef entity, Fighter* fighter, CustomAnimator* animator, Transform3D* transform);
public override unsafe void OnExit(Frame frame, EntityRef fighterRef, Fighter* fighter, CustomAnimator* animator, Transform3D* transform)
{
var triggeredList = frame.ResolveList(animator->TriggeredStateBehaviours);
triggeredList.Remove(Guid);
}
}
舉例而言,在玩家執行一個攻擊時,QTAB建立拋射物用於觸發一個拋射物的建立。
C#
public class QTABCreateProjectile : QTAnimatorTriggerBehaviour
{
public FPVector3 startPoint;
public AssetRefEntityPrototype projectileEntityPrototype;
public override unsafe void OnEnter(Frame frame, EntityRef fighterRef, Fighter* fighter, CustomAnimator* animator, Transform3D* transform)
{
}
protected override unsafe bool OnTriggerBehaviour(Frame frame, EntityRef fighterRef, Fighter* fighter, CustomAnimator* animator, Transform3D* transform)
{
var prototypeRef = frame.Assets.Prototype(projectileEntityPrototype);
var prototype = frame.Create(prototypeRef);
var proj = frame.Get<Projectile>(prototype);
var projT = frame.Get<Transform3D>(prototype);
var projHB = frame.Get<HitBox>(prototype);
proj.side = fighter->side;
FPVector3 pos = startPoint;
pos.X *= fighter->side;
projT.Position = transform->Position + pos;
projHB.active = true;
projHB.attacker = fighterRef;
projHB.target = frame.Global->fighters[1 - fighter->index];
projHB.bounds.Center = projT.Position.XY;
frame.Set(prototype, proj);
frame.Set(prototype, projT);
frame.Set(prototype, projHB);
frame.Events.PlayAudioEffect(projT.Position.XY, frame.Assets.AudioPlaybackData(proj.sfxOnCreate));
return false;
}
}
Back to top