- .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
147 lines
6.1 KiB
C++
147 lines
6.1 KiB
C++
//
|
||
// 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
|