动画系统 Animation
让界面“活”起来,是区分玩具与产品的一道分水岭。HiEasyX 的动画系统采用帧驱动设计:你在每一帧里问它“现在值是多少”,它便根据时间给你插值结果。没有后台线程,没有回调地狱,一切干净得像 IMGUI 本身。
核心设计思想
HiEasyX 的动画系统是无状态机的——它自己不维护一个“正在跑”的列表。你声明一个 HXAnimationFloat(通常用 static),每帧调用 Animate(...),它根据经过的时间 deltaMs 返回当前值。跑完了就停,想重来就 StartAnimation。
IMGUI 的哲学是“控件没有对象”。static 把动画状态藏在函数局部,既避免了全局污染,又保证了跨帧持久。这是 HiEasyX 的惯用写法。
缓动函数 Ease
一切动画的本质都是插值,而插值的灵魂是缓动(Easing)。
函数原型
float Ease(float t, HXEaseType Type);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
t | float | 归一化时间,范围 [0.0, 1.0]。0 表示起点,1 表示终点。 |
Type | HXEaseType | 缓动类型,见下表。 |
返回值
float — 缓动后的归一化值,范围通常 [0.0, 1.0](部分弹性/回弹曲线会轻微超出)。
26 种 Penner 缓动
namespace HX {
enum class HXEaseType {
Linear,
EaseInQuad, EaseOutQuad, EaseInOutQuad,
EaseInCubic, EaseOutCubic, EaseInOutCubic,
EaseInQuart, EaseOutQuart, EaseInOutQuart,
EaseInSine, EaseOutSine, EaseInOutSine,
EaseInCirc, EaseOutCirc, EaseInOutCirc,
EaseInExpo, EaseOutExpo, EaseInOutExpo,
EaseInBack, EaseOutBack, EaseInOutBack,
EaseInElastic, EaseOutElastic, EaseInOutElastic,
EaseInBounce, EaseOutBounce, EaseInOutBounce
};
}
In:起步慢,收尾快(加速)。Out:起步快,收尾慢(减速)。InOut:两头慢,中间快(先加速后减速)。Back:会冲过头再回弹一点,很有弹性感。Elastic:像橡皮筋,适合吸引注意力的弹窗出现动画。Bounce:像皮球落地,适合计数器、通知徽章。
直接使用 Ease 示例
// 手动算一个 0~255 的透明度渐变
float t = 0.5f; // 假设走到一半
float eased = HX::Ease(t, HX::HXEaseType::EaseOutQuad);
int alpha = (int)(eased * 255); // 191
动画对象
HXAnimationFloat
struct HXAnimationFloat {
float Start = 0.0f; // 起始值
float End = 1.0f; // 目标值
float DurationMs = 300.0f; // 持续时间(毫秒)
float DelayMs = 0.0f; // 延迟启动时间(毫秒)
HXEaseType Ease = HXEaseType::Linear; // 缓动曲线
bool Loop = false; // 是否循环
bool PingPong = false; // 是否往返(正过去再倒回来)
};
Loop = false:只跑一次。Loop = true, PingPong = false:跑完回到起点立刻重跑(跳变)。Loop = true, PingPong = true:跑到终点后倒播回起点,再正播,丝滑往返。
同类结构
| 结构名 | 插值类型 | 说明 |
|---|---|---|
HXAnimationFloat | float | 标量,透明度、宽度、角度等。 |
HXAnimationPoint | HXPoint | 二维坐标,位移、粒子位置等。 |
HXAnimationColor | HXColor | 颜色,支持 RGBA 分量分别插值。 |
HXAnimationRect | HXRect | 矩形,四边分别插值。 |
动画控制 API
Animate
float Animate(HXAnimationFloat &profile, float deltaMs);
HXPoint Animate(HXAnimationPoint &profile, float deltaMs);
HXColor Animate(HXAnimationColor &profile, float deltaMs);
HXRect Animate(HXAnimationRect &profile, float deltaMs);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
profile | 动画对象引用 | 动画配置与内部状态。 |
deltaMs | float | 本帧经过的时间(毫秒)。通常传入固定值如 16.6f,或用计时器实测。 |
返回值
当前帧应当使用的插值结果。
在 EasyX 主循环里,你可以用 clock() 或 GetTickCount() 计算两帧间隔:
static DWORD lastTick = GetTickCount();
DWORD now = GetTickCount();
float deltaMs = (float)(now - lastTick);
lastTick = now;
HiEasyX 的示例通常直接传入 16.6f(60 FPS)作为近似值。
StartAnimation
void StartAnimation(HXAnimationFloat &profile);
void StartAnimation(HXAnimationPoint &profile);
void StartAnimation(HXAnimationColor &profile);
void StartAnimation(HXAnimationRect &profile);
重置动画内部计时器,让动画从起点(或当前点,如果是往返)重新开始跑。
StopAnimation
void StopAnimation(HXAnimationFloat &profile);
// ... 同理其他类型
停止动画,但保留当前进度。再次 Animate 不会推进,除非重新 StartAnimation。
ResetAnimation
void ResetAnimation(HXAnimationFloat &profile);
// ... 同理其他类型
把动画进度清零,回到 Start 值。不自动启动;下一帧 Animate 会按 Delay 重新倒计时。
IsAnimationRunning
bool IsAnimationRunning(const HXAnimationFloat &profile);
// ... 同理其他类型
返回值:动画是否正在运行(未结束、未停止)。
IsAnimationFinished
bool IsAnimationFinished(const HXAnimationFloat &profile);
// ... 同理其他类型
返回值:动画是否已经自然跑完一次(对 Loop = true 的动画,每次跑完都会短暂为 true 一帧,然后重置)。
完整示例:面板滑入 + 颜色渐变
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
void DemoAnimation() {
// static = 跨帧持久
static HX::HXAnimationFloat slideAnim;
static HX::HXAnimationColor colorAnim;
static bool initialized = false;
if (!initialized) {
slideAnim.Start = -300.0f; // 从屏幕左侧外开始
slideAnim.End = 50.0f; // 停到 x=50
slideAnim.DurationMs = 600.0f;
slideAnim.Ease = HX::HXEaseType::EaseOutBack;
colorAnim.Start = HX::HXColor{80, 80, 80, 255};
colorAnim.End = HX::HXColor{0, 150, 200, 255};
colorAnim.DurationMs = 800.0f;
colorAnim.Ease = HX::HXEaseType::EaseInOutSine;
HX::StartAnimation(slideAnim);
HX::StartAnimation(colorAnim);
initialized = true;
}
// 假设 deltaMs 已提前算好
float deltaMs = 16.6f;
float x = HX::Animate(slideAnim, deltaMs);
HX::HXColor c = HX::Animate(colorAnim, deltaMs);
// 用动画值驱动 UI
HX::WindowProfile wp;
wp.Title = HXStr("Animated Panel");
wp.Position = {(int)x, 100};
wp.Size = {300, 400};
HX::Window(HXStr("anim_panel"), wp);
// 把颜色应用到背景
HX::GetTheme().WindowBackground = c;
HX::Text(HXStr("HiEasyX 动画系统"));
HX::End();
}
进阶技巧
动画队列(链式触发)
static HX::HXAnimationFloat fadeIn;
static HX::HXAnimationFloat moveDown;
static int stage = 0;
if (stage == 0) {
HX::StartAnimation(fadeIn);
stage = 1;
}
if (stage == 1 && HX::IsAnimationFinished(fadeIn)) {
HX::StartAnimation(moveDown);
stage = 2;
}
响应式触发动画
static HX::HXAnimationFloat widthAnim;
static bool wasHovered = false;
bool hovered = /* 判断鼠标是否悬停按钮 */;
if (hovered && !wasHovered) {
widthAnim.Start = 100.0f;
widthAnim.End = 140.0f;
HX::StartAnimation(widthAnim);
}
if (!hovered && wasHovered) {
widthAnim.Start = 140.0f;
widthAnim.End = 100.0f;
HX::StartAnimation(widthAnim);
}
wasHovered = hovered;
因为 Animate() 本身就是极简的 tween。你控制对象,你控制时机,没有隐式状态机。这套系统对游戏 UI、编辑器动效、数据可视化都足够用。
总结
| 需求 | 做法 |
|---|---|
| 最简单的淡入淡出 | HXAnimationFloat + EaseOutQuad |
| 弹性弹出 | EaseOutBack |
| 循环旋转/呼吸灯 | Loop = true, PingPong = true |
| 颜色过渡 | HXAnimationColor |
| 位移动画 | HXAnimationPoint |
| 确认动画是否跑完 | IsAnimationFinished() |
| 强行重置 | ResetAnimation() |
动画是 UI 的盐,放对了,整道菜就鲜了。祝你的面板滑得丝滑,按钮弹得带感 🎉