记一次尴尬事件,顺便记一下control asset的属性。起因是这样的,美术在timeline的control track上放置了一个例子特效,结果在timeline播完,特效又重新播放了一次。排查完别的地方,并没有控制该特效,那么问题就能只是timeline中的问题。
看到asset的属性如下:
感觉其中可能有问题的也就post-playback这个属性了。去查了下手册:
首先注意一下,post playback并不仅仅是只对嵌套的timeline起作用,大家可以测试下。由于我把revert(还原)和reverse(取反)看混了,无巧不成书,我在看完手册以后又看到了这位老哥的文章(如下图所示,试图说明不止我一个人会犯这种错误,没有说这位老哥跟我一样菜的意思,而且老哥已经在别的文章里纠正了)就十分不解,为什么播完以后这个特效又激活了?继续看了一些资料才意识到看错了(难受)。然后告诉美术把post playback改成了inactive,就没再播放了。我推断可能是这样的:Timeline开始播放之前特效是开着的,Timeline一开始播放就控制特效关闭了,等到特效的clip播放时,特效正常播放。Timeline播放完重置特效为激活状态,同时又释放了对特效的控制权,所以特效就又播了一遍。
还有,关于postplayback的执行时间,有看到不同的说法,这里直接上代码吧。看最后的OnPlayableDestroy()就明白了,当timeline播完,Playable开始销毁的时候才会修改Postplayback
using UnityEngine.Playables;
namespace UnityEngine.Timeline
{
/// <summary>
/// Playable that controls the active state of a GameObject.
/// </summary>
public class ActivationControlPlayable : PlayableBehaviour
{
/// <summary>
/// The state of a GameObject's activeness when a PlayableGraph stops.
/// </summary>
public enum PostPlaybackState
{
/// <summary>
/// Set the GameObject to active when the PlayableGraph stops.
/// </summary>
Active,
/// <summary>
/// Set the GameObject to inactive when the [[PlayableGraph]] stops.
/// </summary>
Inactive,
/// <summary>
/// Revert the GameObject to the active state it was before the [[PlayableGraph]] started.
/// </summary>
Revert
}
enum InitialState
{
Unset,
Active,
Inactive
}
public GameObject gameObject = null;
public PostPlaybackState postPlayback = PostPlaybackState.Revert;
InitialState m_InitialState;
/// <summary>
/// Creates a ScriptPlayable with an ActivationControlPlayable behaviour attached
/// </summary>
/// <param name="graph">PlayableGraph that will own the playable</param>
/// <param name="gameObject">The GameObject that triggered the graph build</param>
/// <param name="postPlaybackState">The state to leave the gameObject after the graph is stopped</param>
/// <returns>Returns a playable that controls activation of a game object</returns>
public static ScriptPlayable<ActivationControlPlayable> Create(PlayableGraph graph, GameObject gameObject, ActivationControlPlayable.PostPlaybackState postPlaybackState)
{
if (gameObject == null)
return ScriptPlayable<ActivationControlPlayable>.Null;
var handle = ScriptPlayable<ActivationControlPlayable>.Create(graph);
var playable = handle.GetBehaviour();
playable.gameObject = gameObject;
playable.postPlayback = postPlaybackState;
return handle;
}
/// <summary>
/// This function is called when the Playable play state is changed to Playables.PlayState.Playing.
/// </summary>
/// <param name="playable">The playable this behaviour is attached to.</param>
/// <param name="info">The information about this frame</param>
public override void OnBehaviourPlay(Playable playable, FrameData info)
{
if (gameObject == null)
return;
gameObject.SetActive(true);
}
/// <summary>
/// This function is called when the Playable play state is changed to PlayState.Paused.
/// </summary>
/// <param name="playable">The playable this behaviour is attached to.</param>
/// <param name="info">The information about this frame</param>
public override void OnBehaviourPause(Playable playable, FrameData info)
{
// OnBehaviourPause can be called if the graph is stopped for a variety of reasons
// the effectivePlayState will test if the pause is due to the clip being out of bounds
if (gameObject != null && info.effectivePlayState == PlayState.Paused)
{
gameObject.SetActive(false);
}
}
/// <summary>
/// This function is called during the ProcessFrame phase of the PlayableGraph.
/// </summary>
/// <param name="playable">The playable this behaviour is attached to.</param>
/// <param name="info">A FrameData structure that contains information about the current frame context.</param>
/// <param name="userData">unused</param>
public override void ProcessFrame(Playable playable, FrameData info, object userData)
{
if (gameObject != null)// && !gameObject.activeSelf)
gameObject.SetActive(true);
}
/// <summary>
/// This function is called when the PlayableGraph that owns this PlayableBehaviour starts.
/// </summary>
/// <param name="playable">The playable this behaviour is attached to.</param>
public override void OnGraphStart(Playable playable)
{
if (gameObject != null)
{
if (m_InitialState == InitialState.Unset)
m_InitialState = gameObject.activeSelf ? InitialState.Active : InitialState.Inactive;
}
}
/// <summary>
/// This function is called when the Playable that owns the PlayableBehaviour is destroyed.
/// </summary>
/// <param name="playable">The playable this behaviour is attached to.</param>
public override void OnPlayableDestroy(Playable playable)
{
if (gameObject == null || m_InitialState == InitialState.Unset)
return;
switch (postPlayback)
{
case PostPlaybackState.Active:
gameObject.SetActive(true);
break;
case PostPlaybackState.Inactive:
gameObject.SetActive(false);
break;
case PostPlaybackState.Revert:
gameObject.SetActive(m_InitialState == InitialState.Active);
break;
}
}
}
}
最后稍微提一下ITimeCOntrol。这个是用来自定义在clip开始和结束时要执行的操作。
using UnityEngine.Playables;
namespace UnityEngine.Timeline
{
/// <summary>
/// A PlayableBehaviour that manages a component that implements the ITimeControl interface
/// </summary>
public class TimeControlPlayable : PlayableBehaviour
{
ITimeControl m_timeControl;
bool m_started;
/// <summary>
/// Creates a Playable with a TimeControlPlayable behaviour attached
/// </summary>
/// <param name="graph">The PlayableGraph to inject the Playable into.</param>
/// <param name="timeControl"></param>
/// <returns></returns>
public static ScriptPlayable<TimeControlPlayable> Create(PlayableGraph graph, ITimeControl timeControl)
{
if (timeControl == null)
return ScriptPlayable<TimeControlPlayable>.Null;
var handle = ScriptPlayable<TimeControlPlayable>.Create(graph);
handle.GetBehaviour().Initialize(timeControl);
return handle;
}
/// <summary>
/// Initializes the behaviour
/// </summary>
/// <param name="timeControl">Component that implements the ITimeControl interface</param>
public void Initialize(ITimeControl timeControl)
{
m_timeControl = timeControl;
}
/// <summary>
/// This function is called during the PrepareFrame phase of the PlayableGraph.
/// </summary>
/// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
/// <param name="info">A FrameData structure that contains information about the current frame context.</param>
public override void PrepareFrame(Playable playable, FrameData info)
{
Debug.Assert(m_started, "PrepareFrame has been called without OnControlTimeStart being called first.");
if (m_timeControl != null)
m_timeControl.SetTime(playable.GetTime());
}
/// <summary>
/// This function is called when the Playable play state is changed to Playables.PlayState.Playing.
/// </summary>
/// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
/// <param name="info">A FrameData structure that contains information about the current frame context.</param>
public override void OnBehaviourPlay(Playable playable, FrameData info)
{
if (m_timeControl == null)
return;
if (!m_started)
{
m_timeControl.OnControlTimeStart();
m_started = true;
}
}
/// <summary>
/// This function is called when the Playable play state is changed to PlayState.Paused.
/// </summary>
/// <param name="playable">The playable this behaviour is attached to.</param>
/// <param name="info">A FrameData structure that contains information about the current frame context.</param>
public override void OnBehaviourPause(Playable playable, FrameData info)
{
if (m_timeControl == null)
return;
if (m_started)
{
m_timeControl.OnControlTimeStop();
m_started = false;
}
}
}
}