前一篇通过 TimeManager 实现了暂停和慢动作,其中时间缩放的渐变过程就是由 KeyFrameManager 驱动的。
在 MyFramework 中,移动、旋转、缩放、透明度、摄像机参数和普通数值渐变,都可以使用同一套关键帧曲线。
项目地址:
一、KeyFrameManager 解决什么问题
游戏中经常需要编写缓动动画:
bash
界面弹出
按钮缩放
角色移动
图片渐隐
摄像机视野变化
数值平滑增长
如果每个功能都自己计算进度:
bash
mTime += elapsedTime;
float percent = mTime / mDuration;
float value = Mathf.Lerp(mStart, mTarget, percent);
不仅会产生大量重复代码,各个系统使用的缓动方式也难以统一。
MyFramework 将变化过程拆成两部分:
bash
KeyFrameManager
负责提供曲线值
ComponentKeyFrame
负责播放曲线并驱动具体属性
业务代码只需要指定:
bash
使用什么曲线
从什么值变化到什么值
变化需要多长时间
二、使用内置曲线
框架已经提供了多种常用曲线:
bash
ZERO_ONE
从 0 线性变化到 1
ZERO_ONE_ZERO
从 0 变化到 1,再回到 0
QUAD_IN
开始较慢,随后加速
QUAD_OUT
开始较快,随后减速
QUAD_IN_OUT
开始和结束较慢
BACK_OUT
超过目标后回弹
BOUNCE_OUT
到达目标时产生弹跳
ELASTIC_OUT
带有弹性振荡效果
这些曲线通过 KEY_CURVE 中的 ID 使用:
bash
KEY_CURVE.ZERO_ONE
KEY_CURVE.QUAD_OUT
KEY_CURVE.BACK_OUT
KEY_CURVE.BOUNCE_OUT
同一条曲线可以用于位置、缩放、透明度和其他属性。
三、移动一个对象
让 UI 节点从左侧移动到目标位置:
bash
Vector3 start = new(-500.0f, 0.0f, 0.0f);
Vector3 target = Vector3.zero;
mPanel.MOVE(
KEY_CURVE.QUAD_OUT,
start,
target,
0.3f);
参数含义:
bash
KEY_CURVE.QUAD_OUT 使用的曲线
start 起始位置
target 目标位置
0.3f 动画时长
不指定曲线时,默认使用线性变化:
bash
mPanel.MOVE(
start,
target,
0.3f);
调用后,框架会为对象添加或复用 COMTransformableMove,由组件自动完成后续更新。
业务类不需要自己编写 update()。
四、缩放和透明度
界面弹出时,可以同时播放缩放和透明度:
bash
mPanel.SCALE(
KEY_CURVE.BACK_OUT,
0.7f,
1.0f,
0.25f);
mPanel.ALPHA(
KEY_CURVE.QUAD_OUT,
0.0f,
1.0f,
0.2f);
最终效果是:
bash
缩放从 0.7 回弹到 1.0
透明度从 0 渐变到 1
直接设置目标值:
bash
mPanel.SCALE(1.0f);
mPanel.ALPHA(1.0f);
这种写法会停止当前同类型缓动,并立即应用新的值。
如果只是想立即改变属性,不要传入持续时间为 0,直接调用对应的单值接口即可。
五、监听播放过程和完成结果
需要在变化中或完成后执行逻辑,可以使用带 _EX 的接口:
bash
mPanel.MOVE_EX(
KEY_CURVE.QUAD_OUT,
start,
target,
0.3f,
onMoving,
onMoveDone);
回调函数:
bash
protected void onMoving(
ComponentKeyFrame component,
bool isBreak)
{
// 每次应用新的位置后调用
}
protected void onMoveDone(
ComponentKeyFrame component,
bool isBreak)
{
if (isBreak)
{
return;
}
log("移动完成");
}
isBreak 表示本次缓动是否被其他操作打断。
例如移动尚未结束,又执行:
bash
mPanel.MOVE(Vector3.zero);
旧缓动的回调会收到:
bash
isBreak = true
这样可以区分:
bash
动画正常完成
动画被新操作覆盖
对象销毁导致动画中断
六、同一种缓动会自动复用组件
第一次执行移动:
bash
mPanel.MOVE(start, target, 0.3f);
框架会创建:
bash
COMTransformableMove
再次对同一个对象执行移动时,不会继续创建新组件,而是复用原来的组件并替换参数:
bash
mPanel.MOVE(
mPanel.getPosition(),
new Vector3(300.0f, 0.0f, 0.0f),
0.5f);
旧移动会被中断,新移动立即开始。
不同类型的缓动则可以同时存在:
bash
mPanel.MOVE(start, target, 0.3f);
mPanel.SCALE(0.7f, 1.0f, 0.3f);
mPanel.ALPHA(0.0f, 1.0f, 0.3f);
因为它们分别由不同组件驱动。
七、循环播放
让图标持续呼吸缩放:
bash
mIcon.SCALE(
KEY_CURVE.ZERO_ONE_ZERO,
0.9f,
1.1f,
0.8f,
true);
ZERO_ONE_ZERO 会让曲线值从:
bash
0 → 1 → 0
所以缩放会在 0.9 和 1.1 之间往返。
最后一个参数表示循环播放:
bash
true
停止循环并恢复正常大小:
bash
mIcon.SCALE(1.0f);
八、设置播放偏移
多个对象使用同一条循环曲线时,可以通过偏移制造错开的效果:
bash
mIcon1.SCALE(
KEY_CURVE.ZERO_ONE_ZERO,
0.9f,
1.1f,
0.8f,
true,
0.0f);
mIcon2.SCALE(
KEY_CURVE.ZERO_ONE_ZERO,
0.9f,
1.1f,
0.8f,
true,
0.4f);
两个图标使用相同曲线,但第二个从曲线中间开始播放。
需要注意:
bash
offset 不能大于 onceLength
它表示当前曲线的起始时间,而不是开始播放前的等待时间。
九、创建自定义曲线
除了内置曲线,还可以在下面的资源中编辑自定义曲线:
bash
Assets/GameResources/KeyFrame/Keyframe.prefab
该 Prefab 上挂有:
bash
GameKeyframe
自定义曲线 ID 从 101 开始,因为 100 以内由框架内置曲线使用。
例如创建一条 UI 弹出曲线:
bash
ID 101
Name UIPop
在应用层定义常量:
bash
public class GAME_KEY_CURVE
{
public const int UI_POP = 101;
}
使用方式和内置曲线一致:
bash
mPanel.SCALE(
GAME_KEY_CURVE.UI_POP,
0.7f,
1.0f,
0.25f);
KeyFrameManager 初始化时会加载 Keyframe.prefab,并把其中的 AnimationCurve 加入曲线列表。
这样美术或策划可以直接在 Unity 曲线面板中调整效果,而不需要修改插值代码。
十、渐变一个普通数值
有些数据不属于任何可变换对象,例如经验条数字、计数器或自定义 Shader 参数。
可以使用:
bash
MyTweenerFloat
例如让数字从 0 增长到 100:
bash
protected MyTweenerFloat mNumberTweener;
protected void playNumber()
{
OT.TWEEN_FLOAT(ref mNumberTweener);
mNumberTweener = OT.TWEEN_FLOAT(
0.0f,
100.0f,
1.0f,
(value) =>
{
mNumberText.setText(
((int)value).IToS());
});
}
开始新的渐变前,先销毁旧对象:
bash
OT.TWEEN_FLOAT(ref mNumberTweener);
避免多个数值渐变同时修改同一个目标。不过这种设计其实不好,因为只在这里才会去销毁Tween对象,后续会改成缓动结束后会自动销毁Tween对象.手动销毁只用于中途需要中断的情况.
TWEEN_FLOAT 同样使用 KeyFrameManager 的曲线,因此可以指定缓动方式:
bash
mNumberTweener = OT.TWEEN_FLOAT(
KEY_CURVE.QUAD_OUT,
1.0f,
onNumberChanging);
十一、UI 缓动需要允许界面更新
UI 缓动依赖 LayoutScript 的更新流程。
如果当前界面没有开启更新,却对 UI 节点播放缓动,编辑器中会输出提示:
bash
想要使窗口播放缓动动画,但是窗口当前未开启更新
因此,需要播放持续动画的界面,应确保对应 UI 对象能够被布局更新。
框架在开始移动、旋转或缩放时,也会自动把对象标记为需要更新:
bash
setNeedUpdate(true)
但所属布局本身仍然需要处于可更新状态。
十二、总结
MyFramework 的关键帧体系可以统一驱动:
bash
位置
旋转
缩放
透明度
摄像机参数
时间缩放
普通浮点数
常用调用方式包括:
bash
obj.MOVE(...);
obj.ROTATE(...);
obj.SCALE(...);
obj.ALPHA(...);
OT.TIME(...);
OT.TWEEN_FLOAT(...);
业务代码只描述:
bash
从哪里开始
变化到哪里
使用哪条曲线
需要多长时间
是否循环
具体的时间累计、曲线求值、组件更新和中断回调,都由 ComponentKeyFrame 统一处理。
这样不同系统可以共用同一套缓动规则,也不需要在每个类中重复实现插值和生命周期管理。