跳到主要内容

动画系统 Animation

让界面“活”起来,是区分玩具与产品的一道分水岭。HiEasyX 的动画系统采用帧驱动设计:你在每一帧里问它“现在值是多少”,它便根据时间给你插值结果。没有后台线程,没有回调地狱,一切干净得像 IMGUI 本身。

核心设计思想

HiEasyX 的动画系统是无状态机的——它自己不维护一个“正在跑”的列表。你声明一个 HXAnimationFloat(通常用 static),每帧调用 Animate(...),它根据经过的时间 deltaMs 返回当前值。跑完了就停,想重来就 StartAnimation

为什么用 static?

IMGUI 的哲学是“控件没有对象”。static 把动画状态藏在函数局部,既避免了全局污染,又保证了跨帧持久。这是 HiEasyX 的惯用写法。


缓动函数 Ease

一切动画的本质都是插值,而插值的灵魂是缓动(Easing)。

函数原型

float Ease(float t, HXEaseType Type);

参数

参数类型说明
tfloat归一化时间,范围 [0.0, 1.0]。0 表示起点,1 表示终点。
TypeHXEaseType缓动类型,见下表。

返回值

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 + PingPong 的组合
  • Loop = false:只跑一次。
  • Loop = true, PingPong = false:跑完回到起点立刻重跑(跳变)。
  • Loop = true, PingPong = true:跑到终点后倒播回起点,再正播,丝滑往返。

同类结构

结构名插值类型说明
HXAnimationFloatfloat标量,透明度、宽度、角度等。
HXAnimationPointHXPoint二维坐标,位移、粒子位置等。
HXAnimationColorHXColor颜色,支持 RGBA 分量分别插值。
HXAnimationRectHXRect矩形,四边分别插值。

动画控制 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动画对象引用动画配置与内部状态。
deltaMsfloat本帧经过的时间(毫秒)。通常传入固定值如 16.6f,或用计时器实测。

返回值

当前帧应当使用的插值结果。

deltaMs 怎么来?

在 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;
为什么不需要 tween 库?

因为 Animate() 本身就是极简的 tween。你控制对象,你控制时机,没有隐式状态机。这套系统对游戏 UI、编辑器动效、数据可视化都足够用。


总结

需求做法
最简单的淡入淡出HXAnimationFloat + EaseOutQuad
弹性弹出EaseOutBack
循环旋转/呼吸灯Loop = true, PingPong = true
颜色过渡HXAnimationColor
位移动画HXAnimationPoint
确认动画是否跑完IsAnimationFinished()
强行重置ResetAnimation()

动画是 UI 的盐,放对了,整道菜就鲜了。祝你的面板滑得丝滑,按钮弹得带感 🎉