Notifications
Article
如何创建自定义Timeline Marker标记
Published 21 days ago
208
2
从Unity 2019.1开始,Timeline支持标记功能。本文将介绍如何创建自定义Timeline Marker标记。
在《Unity 2019.1新功能:Timeline Signals》中,我们介绍了使用Timeline Signals信号功能来触发事件。在刚开始设计该功能时,为了让该功能正常使用,我们无法使用Timeline的剪辑。
由于信号的一个主要特点是:没有“持续时间”,因此我们给Timeline加入了标记功能。在内部,信号是使用标记来实现的。下面我们介绍如何给Timeline加入自定义标记。
本文相关代码和资源,请访问:
https://github.com/Unity-Technologies/TimelineMarkerCustomization

简单标记

Marker标记是可以向Timeline资源添加的新内容,用于表示一个时间点。如同剪辑分为激活剪辑、音频剪辑、动画剪辑一般,标记也有具体类型。我们可以创建自定义类型的标记,以实现应用于特定工作流程的内容。
为了添加新的标记类型,我们只需要创建继承自Marker类的类。
public class SimpleMarker : UnityEngine.Timeline.Marker {}
此自定义标记现在可以添加到Timeline Marker区域的任何轨道。
此时,这个简单标记仅仅是可以看到的内容,这意味着该标记在触发时无法运行代码。但这不表示它毫无用处,标记可以用作对齐点或标注。标记也可以在编辑器内和运行时通过Timeline API访问。
我们需要把标记和另一个系统结合起来,使它可以执行代码。

Playable Notifications通知系统

Playable API可以在处理PlayableGraph时把通知发送给对象。Playable Notifications通知系统可用于告知目标对象:事件已经发生。
下面我们将构建简单的视图,然后手动发送通知。
首先,需要创建一个通知,它是实现INotification接口的类。
public class MyNotification : INotification { public PropertyName id { get; } }
我们可以使用id属性来识别唯一标识通知。对于本文的示例,我们并不需要该功能,因此将使用默认的实现。
然后我们需要一个接收器,它是实现INotificationReceiver接口的类。示例中,接收器会记录接收到通知的时间。
class ReceiverExample : INotificationReceiver { public void OnNotify(Playable origin, INotification notification, object context) { if (notification != null) { double time = origin.IsValid() ? origin.GetTime() : 0.0; Debug.LogFormat("Received notification of type {0} at time {1}", notification.GetType(), time); } } }
在下面的示例中,我们创建了一个新的Playable Graph可运行视图和Playable Output可运行输出。
我们通过使用AddNotificationReceiver方法,把ReceiverExample添加给可运行输出。m_Receiver实例现在可以接收发送到该输出的通知。
现在所有部分都已经为发送通知而准备好,我们可以使用来自可运行输出的PushNotification方法来推送一个新通知。
public class ManualNotification : MonoBehaviour { PlayableGraph m_Graph; ReceiverExample m_Receiver; void Start() { m_Graph = PlayableGraph.Create("NotificationGraph"); var output = ScriptPlayableOutput.Create(m_Graph, "NotificationOutput"); //创建和注册接收器 m_Receiver = new ReceiverExample(); output.AddNotificationReceiver(m_Receiver); //在输出推送一个通知 output.PushNotification(Playable.Null, new MyNotification()); m_Graph.Play(); } }
调用PushNotification后不会立即发送通知,它们仅会加入队列排队等候,这表示它们会进行积累,直到视图已经完全处理。
在LateUpdate阶段前,所有队列中的通知都会发送到视图的输出部分。在所有通知都发送后,队列会在新一帧开始前清空。
在视图播放的时候,将在m_Receiverinstance实例上调用OnNotify方法,并将通知作为参数发送。在运行模式中转换时,以下信息会出现在控制台:
Received notification of type MyNotification at time 0
现在接收器正确接收了通知,我们要如何控制通知发送的时间呢?我们需要更多帮助来实现该效果。

TimeNotificationBehaviour

我们已经知道如何通过可运行视图发送通知,现在我们调度通知,使它在选择的时间内发送。
我们可以使用内置类TimeNotificationBehaviour,该类是标准的PlayableBehaviour,所以它可以添加到任意视图,只要使用一些逻辑就可以在准确时间发送通知。
让我们调整之前的示例。
public class ScheduledNotification : MonoBehaviour { PlayableGraph m_Graph; ReceiverExample m_Receiver; void Start() { m_Graph = PlayableGraph.Create("NotificationGraph"); var output = ScriptPlayableOutput.Create(m_Graph, "NotificationOutput"); //创建和注册接收器 m_Receiver = new ReceiverExample(); output.AddNotificationReceiver(m_Receiver); //创建TimeNotificationBehaviour var timeNotificationPlayable = ScriptPlayable<TimeNotificationBehaviour>.Create(m_Graph); output.SetSourcePlayable(timeNotificationPlayable); /在时间通知行为添加通知 var notificationBehaviour = timeNotificationPlayable.GetBehaviour(); notificationBehaviour.AddNotification(2.0, new MyNotification()); m_Graph.Play(); } }
下面是生成的可运行视图。
我们没有在可运行输出上直接调用PushNotification,而是给输出附加TimeNotificationBehaviour,然后给它添加一个通知。该行为会在正确时间自动把通知推送给输出,控制台会显示以下信息:
eceived notification of type MyNotification at time 2.00363647006452
现在我们可以控制通知的发送时间了。
为什么通知不是在准确的第二秒发送呢?在我们给TimeNotificationBehaviour添加通知时,难道没有指定为正好在二秒吗?
notificationBehaviour.AddNotification(2.0, new MyNotification());
这是因为AddNotification方法不会确保准确的时间。在Unity开始渲染新一帧时,Playable Graph可运行视图会进行更新。根据游戏的帧率,在通知添加到TimeNotificationBehaviour时,PlayableGraph的估算时间可能不会正好符合指定的时间。
AddNotification方法会确保的是:通知将会在PlayableGraph的时间比通知触发时间大的时候发送。

MarkerNotification

如果我们想在可运行视图中手动发送通知的话,一些新API会很实用,但可能要进行很多处理。幸运的是,Timeline可以自动生成合适的PlayableGraph可运行视图来处理通知。
现在我们要创建一个新的标记,用来实现INotification接口。
public class NotificationMarker : Marker, INotification { public PropertyName id { get; } }
继承自Marker并实现INotification的类会告诉Timeline,它需要生成一个可运行视图来支持该通知。
如果我们添加该标记给空白的Timeline,将创建出以下可运行视图。
这和上文的可运行视图几乎一样,只不过Timeline会添加自己的PlayableBehaviour,这比创建自定义Playable Graph简单多了。
剩下的一件事是弄清楚接收通知的对象,相应规则和信号功能相同:
  • 如果标记在Timeline的Header区域:拥有PlayableDirector的对象会接收通知,PlayableDirector用于播放当前的Timeline。
  • 如果标记在轨道上:关联到轨道的对象会接收通知。
实现INotificationReceiver接口并位于目标对象的任意组件会接收通知。
在示例中,我们添加了二个NotificationMarker给Timeline Header区域,还给运行Timeline的对象添加了NotificationReceiver。
下面是控制台的输出内容:
Received notification of type NotificationMarker at time 1.00330553948879
Received notification of type NotificationMarker at time 2.016666666666
只有实现INotification接口的标记才会生成合适的Playable Graph可运行视图来支持通知。
下面表格展示了创建自定义标记的不同方法的特点。
自定义样式
我们的自定义标记会以常见的“图钉”表示,也可以把该图标改为自己选择的图像。下面演示这个过程,创建一个Annotation标记。
第一步是创建样式表。样式表可以用来扩展编辑器的可视化外观,我们可以在编辑器文件夹的StyleSheets/Extensions目录下添加common.uss文件。
在示例中,我们添加了一个新文件5-Annotation/Editor/Stylesheets/Extensions/common.uss
USS即Unity样式表,使用类似CSS的语法来描述新样式,下面是样式的示例。
Annotation { width:18px; height:18px; background-image: resource("Assets/5-Annotation/Editor/pencil.png"); }
在该样式中,我们指定了使用铅笔图标和大小属性。然后告诉Timeline, 在屏幕上绘制标记时应该使用该样式。
[CustomStyle("Annotation")] public class Annotation : Marker { [TextArea] public string annotation; }
CustomStyle属性可用于指定使用什么样式。在示例中,我们希望使用在common.uss文件中添加的Annotation样式。
在给Timeline添加Annotation标记时,它将使用我们创建的自定义样式。

组合效果

为了展示使用标记和通知可以实现的效果,我们给Github代码库添加了Jump标记示例。该标记和JumpReceiver结合起来,它会从Timeline上的一个点“跳到”另一个点。目标点使用Destination标记来指定。
该示例结合了本文中介绍的所有内容,包括自定义样式。图中橙色箭头是跳跃点,而紫色箭头是目标点。
通知和标记是给Playable Graph可运行视图和Timeline API添加的强大功能,本文示例可以帮助开发者学习如何创建出有趣的内容。

来自剪辑的通知

PlayableGraph可以发送通知,Timeline可以使用通知来强化标记的功能。但是剪辑是否可以发送通知?
答案是肯定的,通知可以从Playable Behaviour发送。
public class ClipNotificationBehaviour : PlayableBehaviour { double m_PreviousTime; public override void OnGraphStart(Playable playable) { m_PreviousTime = 0; } public override void ProcessFrame(Playable playable, FrameData info, object playerData) { if ((int)m_PreviousTime < (int)playable.GetTime()) { info.output.PushNotification(playable, new MyNotification()); } m_PreviousTime = playable.GetTime(); } }
在该示例中,我们在剪辑处理期间的每一秒推送了一个通知。如果我们把该剪辑添加给Timeline,把NotificationReceiver添加给驱动Timeline的对象,下面是生成的输出内容:
Received notification of type MyNotification at time 1.01593019999564
Received notification of type MyNotification at time 2.00227000191808
Received notification of type MyNotification at time 3.01137680560353
如果你已经有自己的Playable Behaviour类,并且希望发送通知,不一定需要标记,剪辑已经支持大部分功能。

小结

创建自定义Timeline Marker标记为大家介绍到这里,更多Unity 2019最新信息,请关注Unity官方微信:Unity官方平台。
Tags:
Unity China
612
Comments
G
Guohua
Staff
17 days ago
jasonee
good
0
G
Guohua
Staff
18 days ago
jasonee
good
0