【Unity游戏开发】Timeline&Playable结合使用,Track自定义Timeline轨道
宇宙第一小正太\ (o)/~萌量爆表求带飞=≡Σ((( つo)つ~ dalao们点个关注呗~三大继承类:(1)PlayableTrack(2)PlayableBehavior(3)PlayableAsset注:当你覆写其中的函数,如果有base开头的(在使用自动补全代码的时候出现),请删掉。另外在PlayableAsset的头上加上序列化Attribute: [System.Serializabl
宇宙第一小正太\ (o)/~萌量爆表求带飞=≡Σ((( つo)つ~ dalao们点个关注呗~
三大继承类:
(1)PlayableTrack
(2)PlayableBehavior
(3)PlayableAsset
注:当你覆写其中的函数,如果有base开头的(在使用自动补全代码的时候出现),请删掉。
另外在PlayableAsset的头上加上序列化Attribute: [System.Serializable]
完全理解Timeline:
我们在Timeline里面添加的clip序列,应该由PlayableDirector来管理,通过TrackAsset里面的GetClips函数,可以将里面指定的Track所有的clip获取。
我们可以在外部通过GetComponent来获得PlayableDirector组件,进而来操作整个Timeline的当前播放时间,开始播放,和暂停播放等一系列行为。
消失的TimelineApi:
在新版本的中文API中,并未提供有关Timeline的Api。
这里笔者找到了一个老的API参考,供读者食用:
https://docs.unity3d.com/cn/2017.2/ScriptReference/Timeline.TimelineClip.html
(下图是2017版本API参考手册:)
(下图是2021版本API参考手册:)
在Timeline的API里面提供了如何获取Timeline轨道里面的片段信息,如片段的数量,某个片段的开始时间,片段的结束时间等。
掌握Playable基础:
Playable是Unity实现动画系统的底层逻辑,实际上无论是Animator还是Timeline的实现实际上都使用了它。如果想要使用Timeline结合PlayableScript玩得很溜的话,那么熟练掌握Playable是很有必要的,好在,Playable并不复杂,很好理解和掌握。
这里推荐了一位知乎大佬的博客,通过这篇博客您可以很好的理解Playable的基本原理。
https://zhuanlan.zhihu.com/p/380124248
补充内容:
使用插件来可视化调试Playable,插件名称:PlayableGraph Visualizer,完全免费。可以看到自定义的Timeline的结构。
PlayableTrack:
继承自这个类的C#文件,定义了一个新的Timeline的轨道,当您写下如下代码:
using UnityEngine;
using UnityEngine.Timeline;
[TrackColor(2, 5, 1)]
[TrackBindingType(typeof(MonoBehaviour))]
[TrackClipType(typeof(Asset))]
public class CustomTrack : PlayableTrack {
}
您无需将代码挂在一个GameObject上面。直接保存代码,然后就可以在TimeLine添加这条轨道了。[TrackBindingType(typeof(MonoBehaviour))]
这个Attribute指定了Track绑定对象的类型。新添加的轨道名称就是CustomTrack。(如果安装上面的写法,您可以自定义你喜欢的名称)在Timeline添加轨道的加号那里,你就能看见这个选项了。
PlayableBehavior:
继承自这个类的C#文件则是定义了Playable的行为。
在这里,我们编写逻辑,比如覆写OnBehaviourPlay
函数来记录Timeline第一次进入这个Behavior的时间,以此来计算(预测)结束的时间,通过Duration
来获得clip持续的时间。
比如,我们可以进行一个阻塞的TimelineClip,当运行到结尾的时候又返回到刚刚进入Clip的时刻,直到条件允许,在继续播放Clip,这个技巧在制作GalGame的时候特别有用,特别是当您的Dialog弹出来,但是却没有找到一个高效的结构来管理你的这个Dialog文字顺序的时候,我建议您尝试一下Timeline结合您的程序,或许会给您带来新的思路。
其中Behavior的重点内容是:ProcessFrame(Playable playable, FrameData info, object playerData)
函数。可以看见的是,这个函数有三个参数,中间的参数info有一个很重要的float变量是info.weight
,这个变量的意思是,混合节点输入的权重(小熊瞎说的),其实就是我们刚刚的在“掌握Playable基础”中的那副图片的Edge Weight,在右下角。混合的权重不同罢了,这也是您在实现混合自定义的时候,可以使用到的变量。
将Asset文件as到轨道绑定的对象:下面这句话是万中的核心:
Light light = playerData as Light;
if (light != null) {
light.color = color;
light.intensity = intensity;
}
通过了as的转换操作,因为playData是object类型,所以可以使用as来转换一下。
对于playable的官方定义是:The user data of the ScriptPlayableOutput that initiated the process pass
.小熊看不懂,但是小熊会使用它,嘿嘿嘿,其实小熊也不知道为什么这样,就可以使得绑定在Track的物体进行动态修改,但是事实上还真的就是这样,因为官方给的案例程序我琢磨了一下,人家也是这么写的,如果您知道为什么答案是这样写的,请在评论区或者私信告诉小熊,小熊感激不尽。
PlayableAsset:
继承自这个类的C#文件,是Unity允许您拖拽到Track上面的脚本文件,这个文件的public变量出现在了Inspect中。
当您创建出了这个文件的时候,您一定会发现,Unity会向您报一个错误,那就是未能覆写CreatePlayable
函数。
也许到这里您已经完全猜出来了Asset文件的作用,没错。小熊当时也很搞不明白为什么有Behavior和Asset这种繁琐的东西,但是小熊在看到Unity报错的时候,就突然醒悟过来,原来不过就是这么一回事。
当Asset进入轨道的时候,Unity帮我们创建了一个Playable,这个Playable是没有灵魂的,它必须要套上一个Behavior的壳子,才会有了灵魂。(*这里肯定有读者不是我这样想的,诚然,您有别的方法创作灵魂不用Behavior,那先不在我们的讨论范围之内)
然后CreatePlayable
函数返回了这个有壳子和有灵魂的Playable,这里建议您仔细阅读代码。
至于Asset里面的GetBehavior
函数,这个函数让您可以访问刚刚创建的scriptPlayable
里面的东西。从而设置一些变量之类的东西。
小熊范例代码灯光控制:
以下所展示的代码,将会在clip末尾的时候,跳回clip开始的时候,因为没有在OnPause的时候跳回,所以这段的OnBehaviourPlay只会执行一次。
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
public class LightControlBehaviour : PlayableBehaviour {
//public Light light = null; 不再需要它了
public Color color = Color.white;
public float intensity = 1f;
public PlayableDirector playableDirector;
public TimelineClip clip;
double startTime = 0f;
double totalTime = 0f;
public override void ProcessFrame(Playable playable, FrameData info, object playerData) {
Light light = playerData as Light; // 这个地方有变化
if (light != null) {
light.color = color;
light.intensity = intensity;
}
//使用下面到判断来在pasue自带的暂停之前来停下TimeLine
if (playable.GetDuration() - playable.GetTime() < 0.083333f) {
//playableDirector.time -= 0.08f;
playableDirector.time = startTime;
#if InDebugMode
Debug.Log("手动暂停");
#endif
}
}
public override void OnBehaviourPlay(Playable playable, FrameData info) {
base.OnBehaviourPlay(playable, info);
startTime = playableDirector.time;
Debug.Log("开始");
}
}
小熊的验证程序:
测试方向:我们想通过Timeline来动态修改Cube上面的这个MonoBehavior的变量的数值。
文件存放位置: 在T1文件夹中存放了CustomTrack.cs和Behaviour.cs和Asset.cs
场景中GameObject:一个Cube挂在了SayHello.cs,一个Timeline的空物体挂在了一个PlayerDirector。Timeline序列创建完成,并且被PlayerDirector控制。
PlayableTrack:
CustomTrack.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
[TrackColor(2, 5, 1)]
[TrackBindingType(typeof(MonoBehaviour))]
[TrackClipType(typeof(Asset))]
public class CustomTrack : PlayableTrack {
}
PlayableBehaviour:
Behavior.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
// A behaviour that is attached to a playable
public class Behaviour : PlayableBehaviour {
public string Text;
public override void ProcessFrame(Playable playable, FrameData info, object playerData) {
//通过下面的这句代码,即可在绑定过对象的轨道上进行传递参数的操作。playData就是我们放在轨道上面自定义的Asset。
SayHello sayHello = playerData as SayHello;
if (sayHello != null) {
//对轨道绑定的对象进行值的传递。
sayHello.Text = Text;
}
}
}
PlayableAsset:
Asset.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
[System.Serializable]
public class Asset : PlayableAsset {
public string Text;
// Factory method that generates a playable based on this asset
public override Playable CreatePlayable(PlayableGraph graph, GameObject go) {
//通过下面的两行代码进行创建一个新的Playable(Script类型),然后通过GetBehavior来访问刚刚创建的
var scriptPlayable = ScriptPlayable<Behaviour>.Create(graph);
//上方create实际接受两个参数,第一个参数是graph,第二个参数是我们创建的这个Playable接受几个输入,默认不填写那么就是0个输入。
var scriptBehavior = scriptPlayable.GetBehaviour();
scriptBehavior.Text = Text;
//返回刚刚创建出来的Playable,Unity会帮助我们自动的连线。
return scriptPlayable;
}
}
Cube绑定的验证程序:
说明:将此脚本绑定在了一个测试用的Cube上面,实际上,您绑在空物体上面也没有什么问题。
测试方向:我们想通过Timeline来动态修改Cube上面的这个MonoBehavior的变量的数值。
SayHello.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//下面的Attribute是为了方便测试,可以让脚本在EditMode下面运行,而无需点击运行程序。
[ExecuteInEditMode]
public class SayHello : MonoBehaviour {
public string Text;
public int index;
// Start is called before the first frame update
void Start() {
}
// Update is called once per frame
void Update() {
// Debug.Log(Text);
}
}
Timeline轨道布局参考:
我们在自定义的轨道上面放置了两个Asset。
第一个Asset指定了Text的内容为12313,而第二个Asset指定了Text的内容为520。
我们在自定义轨道上面绑定了我们的Cube(SayHello)脚本。
(最上面的轨道请无视,是笔者进行研究的时候留下的轨道)
结果验证:
(1)当Timeline的时间处于第一个Asset:
注意右侧检查器的SayHello脚本的值已经变为了我们期待的结果。
(2)当Timeline的时间处于第二个Asset:
注意右侧检查器的SayHello脚本的值已经变为了我们期待的结果。这里笔者以自己的节操担保,笔者并没有偷偷去修改答案使得结果符合预期,这里笔者建议读者进行自行的验证。我们同样可以可以注意到SayHello的index(显示为“索引”)并没有进行更改,这是因为我们在Asset里面仅仅只是传入了Text数据,并没有传入index数据和进行对轨道的传递。
铛铛铛,小熊🐻的要饭时间🥺
三连三连三连啊~QAQ
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)