Unity MyFramework 用法说明(二十):使用 KeyFrameManager 统一管理缓动曲线

前一篇通过 TimeManager 实现了暂停和慢动作,其中时间缩放的渐变过程就是由 KeyFrameManager 驱动的。

在 MyFramework 中,移动、旋转、缩放、透明度、摄像机参数和普通数值渐变,都可以使用同一套关键帧曲线。

项目地址:

github.com/ZHOURUIH/My...

一、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.91.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 统一处理。

这样不同系统可以共用同一套缓动规则,也不需要在每个类中重复实现插值和生命周期管理。

相关推荐
SmalBox6 小时前
【节点】[BranchOnInputConnection节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox1 天前
【节点】[Branch节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox2 天前
【节点】[Any节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox3 天前
【节点】[And节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox4 天前
【节点】[All节点]原理解析与实际应用
unity3d·游戏开发·图形学
qiqizizzz5 天前
Unity模型导入系列 #04 | MMD资产导入Unity的完整流程
unity3d
_zhourui_h_5 天前
Unity MyFramework 用法说明(十六):使用 AsyncTaskGroup 等待多个异步任务完成
unity3d
_zhourui_h_5 天前
Unity MyFramework 用法说明(十七):使用 WaitingManager 统一等待异步任务与业务条件
unity3d
SmalBox5 天前
【节点】[UVCombine节点]原理解析与实际应用
unity3d·游戏开发·图形学