技能编辑器前期准备

TimeLine的作用非常广泛,本人之前做剧情编辑器用timeline实现了剧情动画,现在想将timeline的概念融入技能系统当中,所以有了这篇文章。

说清楚什么是Timeline

以下是本人理解

首先我们把概念区间暂时限定在游戏世界中

timeline就是游戏运行过程总时间区间内指定实体们的某一段时间的游戏逻辑。

而好的timeline实现代码可以让我们方便的编辑一段timeline的逻辑和指定这段timeline的开始时间

大家也可以看看罡少的笔记,从更多的角度来思考这个问题

为什么选用Slate而不是Unity自带的Timeline

先说结论: 为了能更好的编辑timeline逻辑。

本人想将timeline概念引入技能模块已经很久了,期间参考了各种资料以及各路大佬的意见最终得出了这个结论。

原因有2:

1.unity自带timeline是天然黑盒状态,当然你也可以让它变成白盒,但终究不是那么方便。

2.unity自带timeline实现框架的逻辑相对来说很乱且框架并不是相对独立的(听来的0.0本人没看过Unity Timeline底层源码),不易于扩展和维护。

解析Slate基本框架

IDirector--“导演接口”

用来主管Timeline的整体运行流程,可以说是整段timeline的最上层播放逻辑

一整段timeline预览窗口

IDirectable--“被导演指挥的演员们的接口”

IDirectable的继承者有三大类:Group,track,clip

这三大类在timeline驱动下生命周期相对独立,不存在生命周期函数的嵌套调用,但是有逻辑上的从属关系。

Group

track的集合

cubegroup

track

clip的集合

Cube下的animatorTrack

Clip

track中的一小段逻辑

animatortrack 中的anim1 clip

Slate驱动方式设计

1.Slate自带的提供两种更新方式

分别是Unity自带的生命周期LateUpdate 和 FixedUpdate

会分别传入 deltaTime unscaledDeltaTime 或者 fixedDeltaTime

传入的时间将是我们完成驱动的重要数据

同样也是我们让slate适配技能模块的需要重点关注的地方

 protected void LateUpdate() {
            if ( isActive && ( updateMode == UpdateMode.Normal || updateMode == UpdateMode.UnscaledTime ) ) {
                if ( isPaused ) {
                    Sample();
                    return;
                }
                var dt = updateMode == UpdateMode.Normal ? Time.deltaTime : Time.unscaledDeltaTime;
                UpdateCutscene(dt);
            }
        }

        //UNITY CALLBACK
        protected void FixedUpdate() {
            if ( isActive && updateMode == UpdateMode.AnimatePhysics ) {
                if ( isPaused ) {
                    Sample();
                    return;
                }
                UpdateCutscene(Time.fixedDeltaTime);
            }
        }

2.采样函数

采样函数采的是我们编辑好的时间点

其中 Internal_SamplePointers 是主要驱动函数

InitializeTimePointers 用来初始化时间点

public void Sample(float time) {

            currentTime = time;

            //ignore same minmax times
            if ( ( currentTime == 0 || currentTime == length ) && previousTime == currentTime ) {
                return;
            }

            //Initialize time pointers if required.
            if ( !preInitialized && currentTime > 0 && previousTime == 0 ) {
                InitializeTimePointers();
            }

            //Sample started
            if ( currentTime > 0 && currentTime < length && ( previousTime == 0 || previousTime == length ) ) {
                if ( !Application.isPlaying || isActive ) {
                    OnSampleStarted();
                }
            }

            //Sample pointers
            if ( timePointers != null ) {
                Internal_SamplePointers(currentTime, previousTime);
            }

            //Sample ended
            if ( ( currentTime == 0 || currentTime == length ) && previousTime > 0 && previousTime < length ) {
                if ( !Application.isPlaying || isActive ) {
                    OnSampleEnded();
                }
            }

            previousTime = currentTime;
        }

3.被采样的可视化

箭头所指就是slate采样函数中会采取的时间节点

通过时间点(TimePointers)slate可以判定出group,track,clip,是否处于生命周期中,并调用它们各自的生命周期函数,实现timeline效果。

被采样的时间点

4.Slate默认更新顺序

(Group Enter -> Track Enter -> Clip Enter | Clip Exit -> Track Exit -> Group Exit)

小结

以上就是slate的基本实现思路

slate已经在此思路框架之上为我们做好了:

CutScene--继承 IDirector 的导演类用来播放timeline,我们可以以此为父类或者 IDirector 直接继承继续扩展自己的播放类

CutsceneGroup--继承 IDirectable 的group基类,我们可以继承 CutsceneGroup 实现自己想要的 Group

CutsceneTrack--- 继承 IDirectable 的 Track- 基类 我们可以继承 CutsceneTrack 实现自己想要的 Track

ActionClip---- 继承 IDirectable 的 Clip 基类 我们可以继承 CutsceneGroup 实现自己想要的 Clip

同时Slate也帮我们完成了比较困难的动画轨道制作

等等...

Slate已经写好的Group track clip类型

Slate还实现了很多播放过程中的小功能如Sections,或者各种常用播放暂停模式的选择这里就不一一展开了。

总结

Slate是一个开源的timeline框架且基建完善。

我们完全可以在此基础之上进行改造和拓展,以此来满足自己特定的需求。

Slate的timeline数据驱动方式简洁但通用性极强,几乎可以将这套实现思路搬去任何需要timeline的平台。