游戏开发技术--Timeline封装(Unity)
一、设计意图使用Unity中的Timeline功能进行技能效果或者过场剧情开发的都知道,对于Timeline功能的扩展,凡是要扩展以下几个类:
[*]TrackAsset:轨道资源,用来创建片段和Playable混合器,提供序列化数据与Binding。
[*]PlayableAsset:片段资源,用来创建Playable以及提供序列化数据。
[*]PlayableBehaviour:逻辑行为,用来实现Playable具体的业务逻辑。
然而,在扩展过程中,会发现一些不便当的情况,最明显的就是PlayableBehaviour的回调函数,对于不熟悉Playable的开发者来说,需要必然的理解成本。为此,我对Timeline的布局进行了进一步的封装。
二、设计思路
对于TrackAsset和PlayableAsset来说,设计上但愿它们只是提供具体序列化数据,而屏蔽掉对于Playable的创建过程。而对于PlayableBehaviour来说,但愿它仅用于实现业务逻辑,同时提炼回调函数使其友好于普通的Unity游戏开发者,而不需要过多了解Playable的机制。
三、实现方式
1、PlayableBehaviour封装
先来对PlayableBehaviour进行封装,定义BaseBehaviour类,对原有的回调函数使用修饰符sealed,定义通用的回调函数(OnCreate,OnDestroy,OnStart,OnUpdate,OnStop),同时提供一些获取数据的接口(GetData,Time,Duration,Percent)。
全部代码如下:
namespace TimelineKit
{
using UnityEngine.Playables;
public abstract class BaseBehaviour : PlayableBehaviour
{
private bool _started;
private bool _played;
private object _playerData;
private Playable _playable = Playable.Null;
private PlayableAsset _asset;
public static Playable CreatePlayable<T>(PlayableGraph graph, PlayableAsset data) where T : BaseBehaviour, new()
{
var playable = ScriptPlayable<T>.Create(graph);
T behaviour = playable.GetBehaviour();
behaviour._asset = data;
behaviour._playable = playable;
return playable;
}
#region protected
protected T GetData<T>() where T : PlayableAsset => _asset as T;
protected float Time => (float)_playable.GetTime();
protected float Duration => (float) _playable.GetDuration();
protected float Percent => (float) (_playable.GetDuration().Equals(0) ? 0 : _playable.GetTime() / _playable.GetDuration());
#endregion protected
#region base
protected virtual void OnCreate() { }
protected virtual void OnDestroy() { }
protected virtual void OnStart(object binding) { }
protected virtual void OnUpdate(object binding, float deltaTime) { }
protected virtual void OnStop(object binding) { }
#endregion base
#region sealed
public sealed override void OnBehaviourPlay(Playable playable, FrameData info)
{
if (_played)
return;
_started = false;
_played = true;
}
public sealed override void OnBehaviourPause(Playable playable, FrameData info)
{
if (!_played)
return;
OnStop(_playerData);
_played = false;
}
public sealed override void OnPlayableCreate(Playable playable)
{
OnCreate();
}
public sealed override void OnPlayableDestroy(Playable playable)
{
OnDestroy();
}
public sealed override void OnGraphStart(Playable playable) { }
public sealed override void OnGraphStop(Playable playable) { }
public sealed override void PrepareData(Playable playable, FrameData info) { }
public sealed override void PrepareFrame(Playable playable, FrameData info) { }
public sealed override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
if (!_played)
return;
if (!_started)
{
_started = true;
_playerData = playerData;
OnStart(_playerData);
}
OnUpdate(_playerData, info.deltaTime);
}
#endregion sealed
}
}
2、PlayableAsset封装
为了和对应的PlayableBehaviour进行绑定,这里使用泛型对PlayableAsset进行封装,定义类型BaseClipAsset<T>,并实现了CreatePlayable的方式。因此担任此类型的子类只需要提供序列化数据即可。
全部代码如下:
namespace TimelineKit
{
using UnityEngine;
using UnityEngine.Playables;
public class BaseClipAsset<T> : PlayableAsset where T : BaseBehaviour, new()
{
public sealed override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
{
return BaseBehaviour.CreatePlayable<T>(graph, this);
}
}
}3、TrackAsset封装
TrackAsset的封装和PlayableAsset的封装思路类似,也是对Create方式进行重载权限设置,定义了BaceTrackAsset和BaceTrackAsset<T>两种类型,此中使用泛型的类型可以用来创建Mixer混合器。
全部代码如下:
namespace TimelineKit
{
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
public class BaseTrackAsset : TrackAsset
{
protected sealed override Playable CreatePlayable(PlayableGraph graph, GameObject gameObject, TimelineClip clip)
{
return base.CreatePlayable(graph, gameObject, clip);
}
public sealed override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
{
return base.CreateTrackMixer(graph, go, inputCount);
}
}
public class BaseTrackAsset<T> : TrackAsset where T : BaseBehaviour, new()
{
protected sealed override Playable CreatePlayable(PlayableGraph graph, GameObject gameObject, TimelineClip clip)
{
return base.CreatePlayable(graph, gameObject, clip);
}
public sealed override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
{
var mixer = BaseBehaviour.CreatePlayable<T>(graph, this);
mixer.SetInputCount(inputCount);
return mixer;
}
}
}四、使用案例
使用上面包装好的Timeline组件,可以很容易的对Timeline功能进行扩展,例如我们要实现一个Timeline控制光强的功能。
TrackAsset:
namespace TimelineKit
{
using UnityEngine;
using UnityEngine.Timeline;
public class LightTrackAsset : BaseTrackAsset { }
}BaseClipAsset实现:
namespace TimelineKit
{
public class LightClipAsset : BaseClipAsset<LightBehaviour>
{
public float StartIntensity;
public float EndIntensity;
}
}BaseBehaviour实现:
namespace TimelineKit
{
using UnityEngine;
public class LightBehaviour : BaseBehaviour
{
protected override void OnUpdate(object binding, float deltaTime)
{
if (binding is Light light)
{
var data = GetData<LightClipAsset>();
light.intensity = Mathf.Lerp(data.StartIntensity,data.EndIntensity, Percent);
}
}
}
}做了个简易的场景,最终效果如下:
https://www.zhihu.com/video/1630530910461411328
页:
[1]