Files
HelloVulkan/Engine/include/Engine/Animation/AnimationController.h
T
atdunbg cc837bc605 Add animation controller system, material assets and skeleton visualization
- .animctrl state machine asset (params/transitions/blend trees, 1D+2D
  directional blending) with Unity Animator-style editor panel: state
  list, transitions, params, directional pad preview driving live
  CrossFade on the selected entity
- Split animation evaluation out of SceneRenderer into
  AnimationSystem/AnimationEvaluator; skin palettes with rest-pose
  fallback and blend-weight epsilon filtering
- MaterialAsset (.material) with reload-safe preview tile pool
  (DropMaterial clears dirty refs before instance destruction)
- EditorGizmoPass skeleton wires + viewport joint overlay; camera
  projection rework (baked Vulkan Y-flip, ortho support)
- Editor/game build split via HELLOVULKAN_EDITOR option
2026-10-07 01:50:04 +08:00

147 lines
6.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// AnimationController:动画状态机资产(.animctrl,YAML)——Unity
// AnimatorController / UE AnimBlueprint 数据侧的对应物。
//
// 职责:纯数据资产(参数声明 / 状态表 / 混合树 / 过渡规则),零求值
// 逻辑零运行时状态。clip 按"名字"引用(不持骨骼数据)——同一
// Controller 可复用到任何 clip 名匹配的模型(缺 clip 降级跳过该层,
// 不崩)。加载走 AssetManager 缓存(模式同 MaterialAsset)。
//
// 与求值的接缝:AnimationSystem 每帧把"当前状态 + 组件参数值"翻译成
// PoseLayer[](本资产只提供规则查询),N 层混合在 AnimationEvaluator。
//
#ifndef HELLOVULKAN_ANIMATIONCONTROLLER_H
#define HELLOVULKAN_ANIMATIONCONTROLLER_H
#include <Engine/Core/Types.h>
#include <glm/glm.hpp>
#include <string>
#include <unordered_map>
#include <vector>
namespace Engine::Animation {
// 混合树子节点:clip 名 + 2D 平面定位点
struct BlendTreeChild {
std::string clip; // clip 名(运行时按名查 Model::GetClips)
glm::vec2 pos{0.0f}; // 2D 平面定位(1D 时 x=阈值,y 忽略)
};
// 状态的运动源:单 clip 直采 or 混合树
struct BlendTree {
enum class Type : u8 {
Single = 0, // 直接播一个 clip
Directional1D = 1,// 一轴阈值插值(Speed 站走跑)
Directional2D = 2,// 2D 方向混合(MoveX/MoveZ → 8 方向)
};
Type type = Type::Single;
std::string clip; // Single:clip 名
std::string xParam; // 1D/2D:横轴参数名
std::string zParam; // 2D:纵轴参数名
std::vector<BlendTreeChild> children;
};
// 过渡条件(Load 期解析 cond 字符串,运行时只做数值比较)
struct TransitionCond {
enum class Op : u8 { Gt, Lt, Ge, Le, Eq, Ne };
std::string param;
Op op = Op::Gt;
f32 numOperand = 0.0f; // float 比较
bool boolOperand = false; // bool 比较(true/false)
bool isBool = false; // bool 语义(含 trigger 名命中即真)
bool always = false; // cond 空 = 恒真(无条件出边)
bool valid = false; // 解析失败 = false(永不触发)
};
struct Transition {
std::string from; // "*" = AnyState
std::string to;
TransitionCond cond;
f32 fade = 0.25f; // 过渡时长(秒)
bool hasExitTime = false; // true = 当前状态非循环播完一轮才评估
};
struct ControllerState {
std::string name;
BlendTree tree;
f32 speed = 1.0f; // 状态内播放速度倍率(乘组件 speed)
bool loop = true; // false = 播完一轮即停(transitionsOnEnd 生效)
std::vector<std::string> transitionsOnEnd; // 播完出口(按序首个可用)
};
// 参数声明(类型 + 默认值;实例值在 AnimationComponent)
struct ControllerParam {
enum class Type : u8 { Float = 0, Bool = 1, Trigger = 2 };
std::string name;
Type type = Type::Float;
f32 defFloat = 0.0f;
bool defBool = false;
};
// 2D 方向混合树权重求解(单一事实源:System 运行时采样与编辑器
// 方向盘可视化共用同一套数学)——原点子(|pos|<ε)权重 = 1-|v|,
// 方向子找覆盖 v 角度的相邻扇区做角度插值 ×min(|v|,1)。只回有权重
// >0 的子节点(下标入 children + 权重)。依赖 children 已按 Load
// 期角度排序
void EvaluateBlendTree2D(const BlendTree& tree, f32 x, f32 z,
std::vector<std::pair<u32, f32>>& outWeights);
class AnimationController {
public:
// .animctrl YAML 解析;失败返回 nullptr + ERROR 日志。
// Version 缺省键向后兼容(同 MaterialAsset 惯例)
static Scope<AnimationController> Load(const std::string& path);
// 写回 .animctrl(编辑器 Save 用:与 Load 对齐的 YAML 反序列化;
// 失败返回 false + ERROR 日志,缓存实例不动)
bool Save(const std::string& path) const;
// 条件字符串 <-> 编译结构(面板编辑 cond 输入框 Enter 时就地
// 重编译,Save 时反推字符串)
static TransitionCond ParseCond(const std::string& s);
static std::string CondToString(const TransitionCond& c);
const std::vector<ControllerParam>& GetParams() const
{
return m_Params;
}
const std::unordered_map<std::string, ControllerState>&
GetStates() const { return m_States; }
// 状态声明序(编辑器 UI 稳定枚举 + entry 兜底;unordered_map
// 迭代无序,UI/初始化一律走这里)
const std::vector<std::string>& GetStateOrder() const
{
return m_StateOrder;
}
// 入口状态:显式 Entry 键优先,缺省 = states 声明序首个
const std::string& GetEntryState() const { return m_Entry; }
const std::vector<Transition>& GetTransitions() const
{
return m_Transitions;
}
const std::string& GetPath() const { return m_Path; }
// ---- 编辑器可变访问(ControllerEditorPanel 专用;改完
// Save 写回 + AssetManager::ReloadController 刷新缓存)----
std::vector<ControllerParam>& ParamsMut() { return m_Params; }
std::unordered_map<std::string, ControllerState>& StatesMut()
{
return m_States;
}
std::vector<std::string>& StateOrderMut() { return m_StateOrder; }
std::vector<Transition>& TransitionsMut() { return m_Transitions; }
void SetEntry(const std::string& e) { m_Entry = e; }
private:
std::vector<ControllerParam> m_Params;
std::unordered_map<std::string, ControllerState> m_States;
std::vector<std::string> m_StateOrder;
std::vector<Transition> m_Transitions;
std::string m_Entry;
std::string m_Path;
};
} // namespace Engine::Animation
#endif