主题
模型动画系统
技能动画如何跟随服务端施法阶段、重新接近时怎样停止并重建,以及预警 Actor 的清理规则,见指令、施法与技能动画的完整生命周期。
技能动画也支持自定义扩展:继承 GameDataAnimation,在客户端重写 CreateSwingController 并返回自己的 IAnimationSwingController,再将动画数据 Link 配置到技能的 Animation 列表。接口的阶段通知、重建和清理约定见该指南第 6 节“自定义技能动画数据与控制器”。
> 两层 API 关系:WasiCore 提供两条 3D 骨骼动画路径,两者操作同一个底层动画对象: > > | 层级 | 入口 | 适用 | > |------|------|------| > | 本文档:Actor 层 | IActorAnimationPlayable.PlayAnimation / AnimationSequence / AnimationBracket | 数编驱动、Unit 绑定、优先级压制、序列编排、生死元组 | > | GameGraphSkeletalAnimation.md:GameGraph 层 | AnimatedModelComponent / AnimationController / AnimationState | 纯代码控制,直接操作 Node,SceneGraph 独立游戏 / 过场 prop / 装饰物 | > > 两层 API 不是互斥的 —— Actor 节点上同时可以通过扩展方法 actor.GetAnimationController() 等下钻到 GameGraph wrapper 做精细控制。最佳实践:ActorScope 内的对象用 Actor API;其他场景或精细控制用 GameGraph API。详见末尾 "GameGraph 层桥接" 章节。
概述
WasiCore中的模型动画系统提供了一个全面的框架,用于管理和播放游戏Actor上的动画。它由三个主要层次组成:
- 引擎层:底层视图动画接口(
IViewAnimation、IViewAnimationSequence、IViewAsyncActor) - 游戏层:高级动画管理(
Animation、AnimationSequence、AnimationBracket) - Actor集成:提供动画功能的Actor接口(
IActorAnimationPlayable)
架构
核心组件
1. 动画类型
系统支持三种主要的动画类型:
- 普通动画:基础的单一动画播放,带有时间控制
- 动画序列:复杂的多动画编排,包含事件和Actor生成
- 动画元组:基于生命周期的动画(出生 → 待机 → 死亡)
2. 关键类
GameCore.ModelAnimation/
├── Animation.cs # 核心动画包装器
├── AnimationSequence.cs # 复杂序列控制器
├── AnimationBracket.cs # 元组动画控制器
├── Data/
│ ├── GameDataAnimationSimple.cs # 普通动画配置
│ ├── GameDataAnimationSequence.cs # 序列配置
│ └── GameDataAnimationBracket.cs # 元组配置
├── Enum/
│ ├── BracketStage.cs # 出生/待机/死亡阶段
│ ├── AnimationEventKey.cs # 动画事件类型
│ └── SequenceEventKey.cs # 序列事件类型
└── Struct/
└── EventArgs.cs # 事件参数结构3. Actor集成
IActorAnimationPlayable接口为Actor提供动画功能:
csharp
public interface IActorAnimationPlayable : IActor
{
// 播放普通动画
Animation? PlayAnimation(ResourceType.Animation animationFile, AnimationPlayParam? param = null);
Animation? PlayAnimation(IGameLink<GameDataAnimationSimple> link);
// 播放复杂序列
AnimationSequence? PlayAnimationSequence(IGameLink<GameDataAnimationSequence> link);
AnimationSequence? PlayAnimationSequence(IGameLink<GameDataAnimationSequence> link, bool triggerEvents);
// 动画控制
bool StopAnimationOfLayer(AnimationLogicLayer logicLayer);
bool SetBirthStandDeathAnimation(UTF8String birth, UTF8String stand, UTF8String death);
bool SetAnimationMapping(UTF8String alias, UTF8String animationRaw);
bool ClearAnimationMapping(UTF8String alias);
IEnumerable<Animation> GetAnimationInstances();
}使用模式
1. 普通动画播放
csharp
// 播放普通动画
var animation = actor.PlayAnimation(animationResource, new AnimationPlayParam {
Priority = 30,
Speed = 1.2f,
IsLooping = true,
BlendIn = TimeSpan.FromSeconds(0.3f)
});
// 控制播放
animation.Speed = 0.8f;
animation.Stop(TimeSpan.FromSeconds(0.5f));2. 动画序列
csharp
// 创建序列配置
var sequenceData = new GameDataAnimationSequence {
Playbacks = [
new AnimationPlayback {
AnimationRaw = "spell_cast_start",
PlaybackDuration = TimeSpan.FromSeconds(1.0)
},
new AnimationPlayback {
AnimationRaw = "spell_cast_channel",
PlaybackDuration = TimeSpan.FromSeconds(3.0),
IsLooping = true
}
],
SequenceActors = [
new SequenceActorSpawn {
Actor = magicCircleActor,
SpawnOffset = TimeSpan.FromSeconds(0.5),
Duration = TimeSpan.FromSeconds(2.0)
}
],
SequenceEvents = [
new SequenceEvent {
EventKey = SequenceEventKey.ApplyPlayerState,
Offset = TimeSpan.FromSeconds(1.5)
}
]
};
// 播放序列
var sequence = actor.PlayAnimationSequence(sequenceDataLink, triggerEvents: true);
// 处理事件
sequence.EventTriggered += (args) => {
switch (args.EventKey) {
case SequenceEventKey.ApplyPlayerState:
ApplySpellEffect(args.Elapsed);
break;
}
};
// 控制序列
sequence.Pause();
sequence.SwitchToPlayback(1);
sequence.Resume();3. 动画元组
csharp
// 配置元组动画
var bracketData = new GameDataAnimationBracket {
BirthStandDeathAnimation = new BirthStandDeathAnimation {
BirthAnimation = "enemy_spawn",
StandAnimation = "enemy_idle",
DeathAnimation = "enemy_death"
},
ForceOneShot = false, // 出生 → 待机(循环)→ 死亡
KillOnFinish = true // 死亡后销毁actor
};
// 播放元组
var bracket = actor.PlayAnimationBracket(bracketData.CachePlayParam);动画优先级系统
系统使用 AnimationLogicLayer 和 Priority 共同管理多个动画:
- 逻辑层:动画系统会先把逻辑层和优先级合成为底层动画 layer,再按 layer 顺序和压制规则决定最终可见动画
- 优先级值:有效范围为 0-31;同一逻辑层内数值越高,优先级越高;低于 0 按 0 处理,高于 31 按 31 处理
- 压制:高底层 layer 的全身/根骨骼动画通常会压制低 layer 动画;不同骨骼范围或半身动画可以混合
- 混合:基于
BlendIn时间的动画间平滑过渡
框架自动播放的默认待机和移动动画位于 AnimationLogicLayer.Rollback:待机优先级为 1,移动优先级为 2。多数手动播放的触发器/技能动画默认位于 AnimationLogicLayer.Normal,底层 layer 高于 Rollback,因此通常可以覆盖默认待机和移动。
与默认 idle / move 状态机配合
如果需要一个“站立时可见,但单位一移动就自动让位给移动动画”的手动动画,不要放在默认的 AnimationLogicLayer.Normal。Normal 层会高于默认移动层,容易把移动动画也盖住。
这类动画应放到默认回滚层,并使用和默认移动动画相同的优先级:
csharp
_ = new GameDataAnimationSimple(standingPoseLink)
{
File = "standing_pose"u8,
LogicLayer = AnimationLogicLayer.Rollback,
Priority = 2,
IsLooping = true,
BlendIn = TimeSpan.FromSeconds(0.1),
};默认层级关系是:
| 动画 | LogicLayer | Priority | 结果 |
|---|---|---|---|
| 默认 idle | Rollback | 1 | 最底层兜底待机 |
| 手动站立表现 | Rollback | 2 | 站立时覆盖 idle |
| 默认 move | Rollback | 2 | 移动开始时与手动站立表现同层,播放 move 会淡出同层动画 |
这适合“站立展示、移动打断”的效果。如果在单位已经移动时再播放这个动画,它也会暂时抢默认 move 的同层权重;这种情况下应先判断单位是否静止,或由自己的状态逻辑决定何时重播/停止。
启用自定义 UnitLocomotionAnimation 控制器后,默认 idle / move 的固定优先级不再是唯一来源。可以通过 GameDataUnitLocomotionAnimationProfile 为 Idle、Move、JumpStart、AirRise、AirFall、Land 等姿态分别设置 LogicLayer 和 Priority,也可以通过 unit.UseUnitLocomotionAnimation(profile) 应用指定 profile。此时应按项目自己的 locomotion profile 来安排手动动画的层级,而不是假设底层默认值一定是 idle=1、move=2。
优先级示例
csharp
// 基础待机动画(低优先级)
var idleAnimation = actor.PlayAnimation(idleAnimationLink, new AnimationPlayParam {
Priority = 0,
IsLooping = true
});
// 攻击动画(高优先级)- 将压制待机动画
var attackAnimation = actor.PlayAnimation(attackAnimationLink, new AnimationPlayParam {
Priority = 30,
BlendIn = TimeSpan.FromSeconds(0.1f)
});
// 攻击完成时,待机动画自动恢复事件系统
动画事件
普通动画可以通过引擎的状态变化系统触发事件:
csharp
animation.StateChanged += (stateEvent) => {
switch (stateEvent) {
case AnimationStateEvent.Started:
OnAnimationStarted();
break;
case AnimationStateEvent.Finished:
OnAnimationFinished();
break;
case AnimationStateEvent.Removed:
OnAnimationRemoved();
break;
}
};序列事件
动画序列提供丰富的事件功能:
csharp
sequence.EventTriggered += (args) => {
// 访问全面的事件上下文
var sequenceInstance = args.Sequence;
var eventType = args.EventKey;
var timingSinceStart = args.Elapsed;
var activePlaybackIndex = args.PlaybackIndex;
var activeAnimation = args.Animation;
};
sequence.Removed += () => {
// 序列完全结束
};
sequence.PlaybackIndexChanged += (newIndex) => {
// 切换到不同的播放
};高级功能
1. 动画映射与动画来源
动画来源
模型可以使用两种来源的动画:
模型自带动画:内嵌在模型资产中的动画片段。可以直接用原名播放(如
"attack_01"),无需映射。共享 Humanoid 动画:独立的动画集(路径格式
anim/{category}/{weapon}/{set}/{name}.ani),可被多个模型复用。通过完整路径(带.ani)播放,或通过映射后用别名播放。资源统计会尝试从GameDataUnit.Model/Abilities、GameDataAbility.Animation和对应GameDataAnimation素材中自动推导模型需要烘焙的技能动画,也会收集GameDataModel.HumanoidSourceAnimations中的显式动画。编辑器随后针对当前模型骨骼进行重定向烘焙。烘焙前后动画名不变,引擎自动优先使用烘焙版本。对 Maker 导入的外部 MDL,不要把上述清单入口理解为首次骨骼适配入口。
HumanoidSourceAnimations只声明待烘焙的动画,不能替代源动作参考模型、骨骼语义映射或模型空间对齐。Maker 模型应先按 官方动作重定向 完成显式适配;只有经过验证的同骨架、同空间模型,或已经生成项目拥有的重定向动画,才适合使用该字段做资源声明。自动推导依赖代码结构可被编译期分析器静态追踪,并不保证覆盖 helper、工厂、动态集合修改或运行时条件等所有写法。对于必须稳定进入烘焙列表的动画,建议仍显式配置
HumanoidSourceAnimations;也可以手动维护ref/model_animation.json,然后在编辑器中只执行 “工具 → 资源统计和动画重定向 → 生成”。若再次执行“统计资源”或“统计资源并生成”,手工 JSON 会被分析结果覆盖。编辑器调试时如果原始动画存在但项目
res/anim/<ModelName>/...下缺少烘焙结果,运行时会给出“原始 Humanoid 动画存在但 baked 动画缺失”的日志提示,此时应先执行全量资源统计和动画烘焙。
AnimationMappings
GameDataModel.AnimationMappings 为动画创建别名,有两个作用:
- 方便引用(用短别名代替原始动画名或完整路径)
- 指定框架硬编码别名:框架通过
idle、move、death等固定别名自动播放对应动画,必须通过映射指定实际动画名
csharp
// 在 GameDataModel 中配置映射
AnimationMappings = [
new() { AnimationAlias = "idle", AnimationRaw = "idle" },
new() { AnimationAlias = "move", AnimationRaw = "move_02" },
new() { AnimationAlias = "attack", AnimationRaw = "attack_01" },
new() { AnimationAlias = "death", AnimationRaw = "death" },
],运行时也可以通过 SetAnimationMapping 动态修改映射:
csharp
// 设置映射
actor.SetAnimationMapping("walk", "character_walk_normal");
actor.SetAnimationMapping("run", "character_run_normal");
// 稍后,改变角色的移动风格
actor.SetAnimationMapping("walk", "character_walk_sneaky");
actor.SetAnimationMapping("run", "character_run_sneaky");2. 动态Actor生成
序列可以精确定时生成临时Actor:
csharp
var sequenceActors = new List<SequenceActorSpawn> {
new SequenceActorSpawn {
Actor = sparkEffectActor,
SpawnOffset = TimeSpan.FromSeconds(0.8), // 在0.8秒时生成
Duration = TimeSpan.FromSeconds(1.5) // 存活1.5秒
},
new SequenceActorSpawn {
Actor = smokeEffectActor,
SpawnOffset = TimeSpan.FromSeconds(1.0), // 在1.0秒时生成
Duration = TimeSpan.FromSeconds(3.0) // 存活3.0秒
}
};3. 播放控制
精细的动画播放控制:
csharp
// 速度操作
animation.Speed = 0.5f; // 半速
animation.Speed = 2.0f; // 双倍速
// 偏移控制
animation.PlayingOffset = TimeSpan.FromSeconds(1.5f); // 跳转到动画的1.5秒处
// 循环控制
animation.IsLooping = true;
// 混合权重淡化
animation.FadeBlendWeight(0.5f, TimeSpan.FromSeconds(1.0f));性能考虑
1. 动画缓存
系统维护高效的缓存:
- 动画缓存:
ConditionalWeakTable<IViewAnimation, Animation>用于自动清理 - 参数缓存:
CachePlayParam属性避免重复的参数构造 - 引擎集成:直接的视图层集成最小化开销
2. 内存管理
- 可释放模式:所有动画对象实现适当的释放
- 自动清理:当Actor被销毁时动画会清理
- 弱引用:引擎动画使用弱引用防止内存泄漏
3. 最佳实践
- 重用GameData:缓存动画配置而不是创建新的
- 适当释放:不再需要时始终释放动画
- 事件取消订阅:移除事件处理程序以防止内存泄漏
- 优先级管理:使用适当的优先级值避免冲突
调试和故障排除
常见问题
- 动画不播放:检查优先级冲突和压制
- 内存泄漏:确保适当的事件处理程序清理
- 时间问题:验证混入时间和序列偏移
- 性能问题:监控动画实例数量
Humanoid 动画烘焙缺失
技能或脚本播放 dash.ani 等 Humanoid 动画时,编辑器调试环境下要区分两类资源:
- 原始 Humanoid 动画:例如
anim/.../dash.ani,来自官方动画库。 - 当前模型的烘焙动画:生成在项目
res/anim/<ModelName>/...下,用于把共享 Humanoid 动画适配到具体模型骨骼。
如果客户端日志或信息面板提示原始动画存在但 baked 动画缺失,执行编辑器菜单:
text
调试 / 全量资源统计并调试AI 助手可通过 SCE Editor MCP 调用:
json
{ "tool": "debug_start_with_resource_statistics", "arguments": {} }只生成资源统计和烘焙动画、不启动调试时,调用:
json
{ "tool": "resource_statistics_generate_animations", "arguments": {} }执行后检查 ref/model_animation.json 是否包含目标模型和动画,以及 res/anim/<ModelName>/... 是否出现生成结果。若 model_animation.json 中没有目标动画,优先检查 GameDataModel.Asset、单位 Model / Abilities、技能 Animation 和对应 GameDataAnimation 素材是否以资源统计能识别的方式配置。代码结构比较动态时,将动画显式加入 HumanoidSourceAnimations;必要时也可手动编辑 ref/model_animation.json,再只执行编辑器菜单中的“生成”。
调试功能
编程方式调试
csharp
// 检查活跃动画
var activeAnimations = actor.GetAnimationInstances();
foreach (var anim in activeAnimations) {
Game.Logger.LogInformation("Animation: {Resource}, Priority: {Priority}, Playing: {Playing}",
anim.AnimationResource, anim.Priority, anim.IsActivePlaying);
}
// 监控序列状态
sequence.PlaybackIndexChanged += (index) => {
Game.Logger.LogInformation("Sequence switched to playback {Index}", index);
};动画信息 Overlay 调试工具
框架提供了专门的动画信息可视化调试工具,可以实时显示动画播放状态:
csharp
// 启用动画信息Overlay
AnimationInfoOverlay.EnableOverlay();功能特色:
- 实时显示: 鼠标悬停在Actor/Unit上时显示动画详细信息
- 信息丰富: 显示动画资源、优先级、播放进度、循环状态等
- 智能跟随: 面板自动跟随目标移动,保持信息可见
- 死亡动画支持: 支持观察单位死亡动画的完整过程
- 性能友好: 20FPS更新频率,对游戏性能影响最小
使用方法:
- 按
Ctrl + F2打开调试界面 - 启用"Animation Info Overlay"开关
- 将鼠标悬停在具有动画的单位上查看信息
更多详细信息请参考 <see cref="调试作弊系统"/>。
与其他系统的集成
1. Actor系统集成
支持动画的对象通过 IActorAnimationPlayable 提供统一接口:
ActorModel:具有完整动画支持的3D模型ActorEntity、Unit:自身实现该接口,可直接调用动画扩展方法
2. 实体与动画集成
Entity 和 Unit 自身实现 IActorAnimationPlayable,可直接调用动画扩展方法。Unit 不是 ActorModel,也没有 Unit.GetActor() 方法:
csharp
#if CLIENT
using GameCore.ActorSystem;
unit.PlayAnimation(animationLink);
#endif3. 游戏数据集成
动画配置存储为游戏数据:
- GameDataAnimationSimple:基础动画参数
- GameDataAnimationSequence:复杂序列配置
- GameDataAnimationBracket:生命周期动画定义
这允许数据驱动的动画系统,其中动画行为可以在不更改代码的情况下修改。
4. 技能系统集成
技能系统通过 GameDataAbilityActive 的 Animation 字段与模型动画系统深度集成,提供技能释放时的动画播放功能。
Animation 字段配置
GameDataAbilityActive 的 Animation 属性:若设置,技能施放时将播放对应动画;若配置多个动画,每次施放会随机选择一个。
基本用法
> 注意:以下示例中的 abilityLink、animationLink 等变量为项目内定义的 GameLink 引用(如通过 ScopeData 或数据编辑器配置),非框架 API 常量。
csharp
// 1. 首先创建动画数据
var attackAnimationLink = new GameLink<GameDataAnimation, GameDataAnimationSimple>("SwordAttack");
var attackAnimation = new GameDataAnimationSimple(attackAnimationLink)
{
File = "sword_attack"u8,
Priority = 30,
LogicLayer = AnimationLogicLayer.Normal,
Speed = 1.2f,
BodyPart = AnimationBodyPart.FullBody,
BlendIn = TimeSpan.Zero, // 短普攻/高攻速技能避免默认混入时间盖过出手动作
};
// 2. 在技能中配置动画
var swordAttackAbility = new GameDataAbilityExecute(abilityLink)
{
Time = new()
{
Preswing = static (_) => TimeSpan.FromSeconds(0.5),
Backswing = static (_) => TimeSpan.FromSeconds(0.3),
},
Effect = damageEffect,
Animation = [attackAnimationLink], // 关键:配置技能动画
// ... 其他配置
};多动画随机选择
技能系统支持为单个技能配置多个动画,每次释放时会随机选择:
csharp
// 创建多个攻击动画变体
var attack1 = new GameLink<GameDataAnimation, GameDataAnimationSimple>("SwordAttack1");
var attack2 = new GameLink<GameDataAnimation, GameDataAnimationSimple>("SwordAttack2");
var attack3 = new GameLink<GameDataAnimation, GameDataAnimationSimple>("SwordAttack3");
// 在技能中配置多个动画
var combatAbility = new GameDataAbilityExecute(abilityLink)
{
Animation = [attack1, attack2, attack3], // 随机选择播放
// ... 其他配置
};动画与技能阶段同步
技能动画会自动与技能的执行阶段同步:
- Preswing阶段:开始播放配置的动画
- Cast阶段:动画进入主要播放阶段
- Backswing阶段:动画收尾阶段
csharp
// 框架自动处理的动画生命周期
protected override void OnAnimationState(OrderStage stage)
{
switch (stage.InnerValue)
{
case EOrderStage.Preswing:
// 创建并开始播放动画
break;
case EOrderStage.Cast:
// 动画进入主要阶段
break;
case EOrderStage.Backswing:
// 动画收尾
break;
}
}实际应用示例
在项目中为技能配置动画时,使用项目内的 GameLink 引用动画数据:
csharp
// 怪物攻击动画配置(animationLink 为项目内 GameDataAnimationSimple 的 GameLink)
var monsterAttackAnimation = new GameDataAnimationSimple(animationLink)
{
File = "attack"u8,
Priority = 30,
LogicLayer = AnimationLogicLayer.Normal
};
// 怪物攻击技能配置(abilityLink 为项目内 GameDataAbilityExecute 的 GameLink)
var monsterAttackAbility = new GameDataAbilityExecute(abilityLink)
{
Time = new()
{
Preswing = static (_) => TimeSpan.FromSeconds(0.5),
Backswing = static (_) => TimeSpan.FromSeconds(0.3),
},
Effect = damageEffect,
Animation = [animationLink], // 技能释放时播放攻击动画
AbilityExecuteFlags = new() { IsAttack = true },
// ... 其他配置
};最佳实践
动画时长与技能时间匹配
- 确保动画总时长与技能的 Preswing + Backswing 时间大致匹配
- 避免动画过长或过短导致的不自然表现
- 技能动画会按总施法时长调速,但不会自动缩短
GameDataAnimationSimple.BlendIn
使用合适的动画优先级
- 技能动画通常应使用同一逻辑层内较高优先级(例如 20-31)
- 确保技能动画能正确覆盖移动等低优先级动画
混入时间匹配最短技能周期
GameDataAnimationSimple.BlendIn默认约 0.15 秒- 短普攻或高攻速普攻的周期如果接近/短于默认混入时间,出手动作会在尚未完全混入时被下一次施放覆盖
- 对这类动作显式设置
BlendIn = TimeSpan.Zero,或按手感设置为 20-50ms
考虑动画变体
- 为重复使用的技能提供多个动画变体
- 增加游戏的视觉丰富性和随机性
动画与效果同步
- 确保视觉效果的时机与动画关键帧匹配
- 使用动画事件系统进行精确的时机控制
GameGraph 层桥接
Actor 层的 IActorAnimationPlayable.PlayAnimation 等接口与 GameGraph 的 AnimationController 操作同一个底层动画控制器。可以双向控制等价 —— Actor 层做高层逻辑(优先级压制、序列编排),GameGraph 层做精细控制(单条动画的 Time/Weight/Speed、Bone/Socket 访问)。
扩展方法清单
定义在 GameGraph/NodeSystem/ExtensionMethod.cs,需要 using GameGraph.NodeSystem;:
| 扩展方法 | 返回 | 说明 |
|---|---|---|
IActorAnimationPlayable.GetAnimatedModel() | AnimatedModelComponent? | 取底层 AnimatedModel 组件 |
IActorAnimationPlayable.GetAnimationController() | AnimationController? | 取底层 AnimationController 组件 |
GameCore.ModelAnimation.Animation.GetAnimationState() | AnimationState? | 取该动画对应的底层运行时状态 |
典型下钻示例
csharp
#if CLIENT
using GameCore.ActorSystem;
using GameGraph.NodeSystem;
using GameGraph.NodeSystem.Component.Graphics;
// Unit 自身支持动画,可直接获取底层 AnimationController
var ctrl = unit.GetAnimationController();
ctrl?.SetSpeed("Animations/Run.ani", 1.5f);
// Actor 层 PlayAnimation 拿到 Animation,再下钻取 AnimationState
var anim = unit.PlayAnimation(myAnimationLink);
var state = anim?.GetAnimationState();
if (state != null)
{
state.Weight = 0.5f;
state.IsInterpolated = false;
}
// 拿到模型组件做骨骼访问
var model = unit.GetAnimatedModel();
var swordSocket = model?.GetSocket("socket_weapon_r");
if (swordSocket.HasValue)
{
var sword = swordSocket.Value.BoneNode.CreateChild("Sword");
sword.LocalPosition = swordSocket.Value.LocalPosition;
// ...
}
#endif注意事项
- Actor 的 ModelAnimation 体系还会做优先级压制等额外逻辑,绕过它直接操作底层 controller 可能与 Actor 层的状态机产生冲突
- 建议只在以下场景使用下钻:
- 单条动画的精细 Time / Weight / Speed 控制
- 骨骼访问(挂武器 / 特效到 socket)
- 已经脱离 Actor 体系的纯 GameGraph 模式(这种情况建议直接用 GameGraph API,不经过 Actor)
- 不建议通过下钻路径调
Stop/StopAll/Play等大动作 —— 这些会与 Actor 上层的优先级/序列状态机失去同步
Humanoid 烘焙动画路径
Actor 层 PlayAnimation 会自动解析烘焙动画路径(详见上方“动画映射与动画来源”章节)。GameGraph 层不做此自动解析 —— 如果纯 GameGraph 模式需要 humanoid 烘焙动画,用 animatedModel.ComputeBakedAnimationPath(animName) 显式取得路径(anim/{modelName}/{animName}),再交给 AnimationController.Play。详见 GameGraphSkeletalAnimation.md — 烘焙动画路径解析。
完整 GameGraph 层 API 文档:GameGraphSkeletalAnimation.md