生物体视觉与动画
本文介绍一组 mod 可以接入的运行时 Godot 工厂接口(替换原版 CreateVisuals / GenerateAnimator),以及后端无关的动画状态机 ModAnimStateMachine。后者让非 Spine 的战斗视觉通过和 Spine 生物相同的触发协议驱动动画。
概览
原版将 MonsterModel / CharacterModel 绑定到战斗视觉的三个入口:
Model.CreateVisuals()— 返回一个NCreatureVisualsModel.GenerateAnimator(MegaSprite controller)— 返回一个CreatureAnimatorNCreature.SetAnimationTrigger(trigger)— 在运行时把触发器派发给 animator
RitsuLib 为这三种需求暴露了三个彼此正交的工厂接口,以及一个针对非 Spine 场景的状态机抽象。
| 接口 | 用途 | 对应原版入口 |
|---|---|---|
IModCreatureVisualsFactory | 从代码构造 NCreatureVisuals | CharacterModel.CreateVisuals、MonsterModel.CreateVisuals |
IModCreatureAnimatorFactory | 从代码构造 Spine CreatureAnimator | CharacterModel.GenerateAnimator、MonsterModel.GenerateAnimator |
IModNonSpineAnimationStateMachineFactory | 为非 Spine 视觉构造 ModAnimStateMachine | NCreature.SetAnimationTrigger(路由补丁) |
IModCharacterMerchantAnimationStateMachineFactory | 为商人 / 休息站中的角色视觉构造 ModAnimStateMachine | 商人场景初始化流程 |
生物视觉工厂
IModCreatureVisualsFactory 在返回非 null 的 NCreatureVisuals 时,会替换 (Character|Monster)Model.CreateVisuals 的原有行为;返回 null 则退回到 CustomVisualsPath 等原版解析链路。
public class MyCharacter : ModCharacterTemplate<...>
{
protected override NCreatureVisuals? TryCreateCreatureVisuals()
{
var scene = GD.Load<PackedScene>(
"res://MyMod/scenes/my_character/my_character_visuals.tscn");
return scene.Instantiate<NCreatureVisuals>();
}
}Spine Animator 工厂
IModCreatureAnimatorFactory 用来替换 GenerateAnimator,适用于 Spine 视觉。推荐使用 ModAnimStateMachines.Standard 以复用原版状态图的形状:
public class MySpineCharacter : ModCharacterTemplate<...>
{
protected override CreatureAnimator? SetupCustomCreatureAnimator(MegaSprite controller) =>
ModAnimStateMachines.Standard(
controller,
idleName: "idle_loop",
deadName: "die",
hitName: "hit",
attackName: "attack",
castName: "cast",
relaxedName: "relaxed");
}非 Spine 状态机
如果生物的战斗视觉不是 Spine(没有 MegaSprite 控制器),实现 IModNonSpineAnimationStateMachineFactory 并返回绑定到视觉根节点的 ModAnimStateMachine。
public class MyWolf : ModMonsterTemplate
{
protected override ModAnimStateMachine? SetupCustomNonSpineAnimationStateMachine(
Node visualsRoot, MonsterModel monster)
{
if (visualsRoot is not MyWolfVisuals wolfVisuals)
return null;
var backend = new AnimatedSprite2DBackend(wolfVisuals.GetAnimatedSprite());
return ModAnimStateMachineBuilder.Create()
.AddState("idle", loop: true).AsInitial().Done()
.AddState("attack").WithNext("idle").Done()
.AddState("hurt").WithNext("idle").Done()
.AddState("die").Done()
.AddAnyState("Idle", "idle")
.AddAnyState("Attack", "attack")
.AddAnyState("Hit", "hurt")
.AddAnyState("Dead", "die")
.Build(backend);
}
}动画后端
IAnimationBackend 是 ModAnimStateMachine 消费的统一驱动层。
| 后端 | 驱动对象 | 适用场景 |
|---|---|---|
AnimatedSprite2DBackend | AnimatedSprite2D | 基于帧的 sprite 动画 |
GodotAnimationPlayerBackend | AnimationPlayer | Godot .tres 动画库 |
CueAnimationBackend | VisualCueSet(cue 帧序列 / cue 贴图) | 单帧贴图或帧序列播放 |
SpineAnimationBackend | MegaSprite | Spine 骨骼动画 |
CompositeAnimationBackend | 任意组合 | 多后端派发 |
生命周期触发补丁
原版 NCreature.StartDeathAnim 和 NCreature.StartReviveAnim 只在 _spineAnimator != null 时派发 Dead / Revive 触发器。RitsuLib 通过两个 Postfix 补丁修正这一缺陷:
NCreatureNonSpineDeathAnimationTriggerPatch— 在StartDeathAnim之后派发DeadNCreatureNonSpineReviveAnimationTriggerPatch— 在StartReviveAnim之后派发Revive
速查表
目标 实现的接口
---------------------------------------------------------------------------
替换 CreateVisuals(玩家或怪物) IModCreatureVisualsFactory
替换 Spine GenerateAnimator IModCreatureAnimatorFactory
驱动非 Spine 状态机 IModNonSpineAnimationStateMachineFactory
驱动商人 / 休息站状态机 IModCharacterMerchantAnimationStateMachineFactory