Unity-jump-proj
This commit is contained in:
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0cd29fb1ad218b48b814bc3e6d8ac0e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,71 @@
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
class ActivationMixerPlayable : PlayableBehaviour
|
||||
{
|
||||
ActivationTrack.PostPlaybackState m_PostPlaybackState;
|
||||
bool m_BoundGameObjectInitialStateIsActive;
|
||||
|
||||
private GameObject m_BoundGameObject;
|
||||
|
||||
|
||||
public static ScriptPlayable<ActivationMixerPlayable> Create(PlayableGraph graph, int inputCount)
|
||||
{
|
||||
return ScriptPlayable<ActivationMixerPlayable>.Create(graph, inputCount);
|
||||
}
|
||||
|
||||
public ActivationTrack.PostPlaybackState postPlaybackState
|
||||
{
|
||||
get { return m_PostPlaybackState; }
|
||||
set { m_PostPlaybackState = value; }
|
||||
}
|
||||
|
||||
public override void OnPlayableDestroy(Playable playable)
|
||||
{
|
||||
if (m_BoundGameObject == null)
|
||||
return;
|
||||
|
||||
switch (m_PostPlaybackState)
|
||||
{
|
||||
case ActivationTrack.PostPlaybackState.Active:
|
||||
m_BoundGameObject.SetActive(true);
|
||||
break;
|
||||
case ActivationTrack.PostPlaybackState.Inactive:
|
||||
m_BoundGameObject.SetActive(false);
|
||||
break;
|
||||
case ActivationTrack.PostPlaybackState.Revert:
|
||||
m_BoundGameObject.SetActive(m_BoundGameObjectInitialStateIsActive);
|
||||
break;
|
||||
case ActivationTrack.PostPlaybackState.LeaveAsIs:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
|
||||
{
|
||||
if (m_BoundGameObject == null)
|
||||
{
|
||||
m_BoundGameObject = playerData as GameObject;
|
||||
m_BoundGameObjectInitialStateIsActive = m_BoundGameObject != null && m_BoundGameObject.activeSelf;
|
||||
}
|
||||
|
||||
if (m_BoundGameObject == null)
|
||||
return;
|
||||
|
||||
int inputCount = playable.GetInputCount();
|
||||
bool hasInput = false;
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
if (playable.GetInputWeight(i) > 0)
|
||||
{
|
||||
hasInput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_BoundGameObject.SetActive(hasInput);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e165a99d845c10e4ea0f546e542e8684
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,29 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.ComponentModel;
|
||||
#endif
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Playable Asset class for Activation Tracks
|
||||
/// </summary>
|
||||
#if UNITY_EDITOR
|
||||
[DisplayName("Activation Clip")]
|
||||
#endif
|
||||
class ActivationPlayableAsset : PlayableAsset, ITimelineClipAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a description of the features supported by activation clips
|
||||
/// </summary>
|
||||
public ClipCaps clipCaps { get { return ClipCaps.None; } }
|
||||
|
||||
/// <summary>
|
||||
/// Overrides PlayableAsset.CreatePlayable() to inject needed Playables for an activation asset
|
||||
/// </summary>
|
||||
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
|
||||
{
|
||||
return Playable.Create(graph);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fde0d25a170598d46a0b9dc16b4527a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Track that can be used to control the active state of a GameObject.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[TrackClipType(typeof(ActivationPlayableAsset))]
|
||||
[TrackBindingType(typeof(GameObject))]
|
||||
[ExcludeFromPreset]
|
||||
[TimelineHelpURL(typeof(ActivationTrack))]
|
||||
public class ActivationTrack : TrackAsset
|
||||
{
|
||||
[SerializeField]
|
||||
PostPlaybackState m_PostPlaybackState = PostPlaybackState.LeaveAsIs;
|
||||
ActivationMixerPlayable m_ActivationMixer;
|
||||
|
||||
/// <summary>
|
||||
/// Specify what state to leave the GameObject in after the Timeline has finished playing.
|
||||
/// </summary>
|
||||
public enum PostPlaybackState
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the GameObject to active.
|
||||
/// </summary>
|
||||
Active,
|
||||
|
||||
/// <summary>
|
||||
/// Set the GameObject to Inactive.
|
||||
/// </summary>
|
||||
Inactive,
|
||||
|
||||
/// <summary>
|
||||
/// Revert the GameObject to the state in was in before the Timeline was playing.
|
||||
/// </summary>
|
||||
Revert,
|
||||
|
||||
/// <summary>
|
||||
/// Leave the GameObject in the state it was when the Timeline was stopped.
|
||||
/// </summary>
|
||||
LeaveAsIs
|
||||
}
|
||||
|
||||
internal override bool CanCompileClips()
|
||||
{
|
||||
return !hasClips || base.CanCompileClips();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies what state to leave the GameObject in after the Timeline has finished playing.
|
||||
/// </summary>
|
||||
public PostPlaybackState postPlaybackState
|
||||
{
|
||||
get { return m_PostPlaybackState; }
|
||||
set { m_PostPlaybackState = value; UpdateTrackMode(); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
|
||||
{
|
||||
var mixer = ActivationMixerPlayable.Create(graph, inputCount);
|
||||
m_ActivationMixer = mixer.GetBehaviour();
|
||||
|
||||
UpdateTrackMode();
|
||||
|
||||
return mixer;
|
||||
}
|
||||
|
||||
internal void UpdateTrackMode()
|
||||
{
|
||||
if (m_ActivationMixer != null)
|
||||
m_ActivationMixer.postPlaybackState = m_PostPlaybackState;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GatherProperties(PlayableDirector director, IPropertyCollector driver)
|
||||
{
|
||||
var gameObject = GetGameObjectBinding(director);
|
||||
if (gameObject != null)
|
||||
{
|
||||
driver.AddFromName(gameObject, "m_IsActive");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnCreateClip(TimelineClip clip)
|
||||
{
|
||||
clip.displayName = "Active";
|
||||
base.OnCreateClip(clip);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21bf7f712d84d26478ebe6a299f21738
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de9eb5e2046ffc9448f07e495c436506
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,93 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
// Does a post processing of the weights on an animation track to properly normalize
|
||||
// the mixer weights so that blending does not bring default poses and subtracks, layers and
|
||||
// layer graphs blend correctly
|
||||
class AnimationOutputWeightProcessor : ITimelineEvaluateCallback
|
||||
{
|
||||
struct WeightInfo
|
||||
{
|
||||
public Playable mixer;
|
||||
public Playable parentMixer;
|
||||
public int port;
|
||||
}
|
||||
|
||||
AnimationPlayableOutput m_Output;
|
||||
AnimationMotionXToDeltaPlayable m_MotionXPlayable;
|
||||
readonly List<WeightInfo> m_Mixers = new List<WeightInfo>();
|
||||
|
||||
public AnimationOutputWeightProcessor(AnimationPlayableOutput output)
|
||||
{
|
||||
m_Output = output;
|
||||
output.SetWeight(0);
|
||||
FindMixers();
|
||||
}
|
||||
|
||||
void FindMixers()
|
||||
{
|
||||
var playable = m_Output.GetSourcePlayable();
|
||||
var outputPort = m_Output.GetSourceOutputPort();
|
||||
|
||||
m_Mixers.Clear();
|
||||
// only write the final output in playmode. it should always be 1 in editor because we blend to the defaults
|
||||
FindMixers(playable, outputPort, playable.GetInput(outputPort));
|
||||
}
|
||||
|
||||
// Recursively accumulates mixers.
|
||||
void FindMixers(Playable parent, int port, Playable node)
|
||||
{
|
||||
if (!node.IsValid())
|
||||
return;
|
||||
|
||||
var type = node.GetPlayableType();
|
||||
if (type == typeof(AnimationMixerPlayable) || type == typeof(AnimationLayerMixerPlayable))
|
||||
{
|
||||
// use post fix traversal so children come before parents
|
||||
int subCount = node.GetInputCount();
|
||||
for (int j = 0; j < subCount; j++)
|
||||
{
|
||||
FindMixers(node, j, node.GetInput(j));
|
||||
}
|
||||
|
||||
// if we encounter a layer mixer, we assume there is nesting occuring
|
||||
// and we modulate the weight instead of overwriting it.
|
||||
var weightInfo = new WeightInfo
|
||||
{
|
||||
parentMixer = parent,
|
||||
mixer = node,
|
||||
port = port,
|
||||
};
|
||||
m_Mixers.Add(weightInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = node.GetInputCount();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
FindMixers(parent, port, node.GetInput(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Evaluate()
|
||||
{
|
||||
float weight = 1;
|
||||
m_Output.SetWeight(1);
|
||||
for (int i = 0; i < m_Mixers.Count; i++)
|
||||
{
|
||||
var mixInfo = m_Mixers[i];
|
||||
weight = WeightUtility.NormalizeMixer(mixInfo.mixer);
|
||||
mixInfo.parentMixer.SetInputWeight(mixInfo.port, weight);
|
||||
}
|
||||
|
||||
// only write the final weight in player/playmode. In editor, we are blending to the appropriate defaults
|
||||
// the last mixer in the list is the final blend, since the list is composed post-order.
|
||||
if (Application.isPlaying)
|
||||
m_Output.SetWeight(weight);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a429b38ee9d48c7408c8870baf406034
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,325 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// A Playable Asset that represents a single AnimationClip clip.
|
||||
/// </summary>
|
||||
[System.Serializable, NotKeyable]
|
||||
public partial class AnimationPlayableAsset : PlayableAsset, ITimelineClipAsset, IPropertyPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the source AnimationClip loops during playback.
|
||||
/// </summary>
|
||||
public enum LoopMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the loop time setting from the source AnimationClip.
|
||||
/// </summary>
|
||||
[Tooltip("Use the loop time setting from the source AnimationClip.")]
|
||||
UseSourceAsset = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The source AnimationClip loops during playback.
|
||||
/// </summary>
|
||||
[Tooltip("The source AnimationClip loops during playback.")]
|
||||
On = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The source AnimationClip does not loop during playback.
|
||||
/// </summary>
|
||||
[Tooltip("The source AnimationClip does not loop during playback.")]
|
||||
Off = 2
|
||||
}
|
||||
|
||||
|
||||
[SerializeField] private AnimationClip m_Clip;
|
||||
[SerializeField] private Vector3 m_Position = Vector3.zero;
|
||||
[SerializeField] private Vector3 m_EulerAngles = Vector3.zero;
|
||||
[SerializeField] private bool m_UseTrackMatchFields = true;
|
||||
[SerializeField] private MatchTargetFields m_MatchTargetFields = MatchTargetFieldConstants.All;
|
||||
[SerializeField] private bool m_RemoveStartOffset = true; // set by animation track prior to compilation
|
||||
[SerializeField] private bool m_ApplyFootIK = true;
|
||||
[SerializeField] private LoopMode m_Loop = LoopMode.UseSourceAsset;
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private AnimationOffsetPlayable m_AnimationOffsetPlayable;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The translational offset of the clip
|
||||
/// </summary>
|
||||
public Vector3 position
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Position;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_Position = value;
|
||||
#if UNITY_EDITOR
|
||||
if (m_AnimationOffsetPlayable.IsValid())
|
||||
m_AnimationOffsetPlayable.SetPosition(position);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rotational offset of the clip, expressed as a Quaternion
|
||||
/// </summary>
|
||||
public Quaternion rotation
|
||||
{
|
||||
get
|
||||
{
|
||||
return Quaternion.Euler(m_EulerAngles);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_EulerAngles = value.eulerAngles;
|
||||
#if UNITY_EDITOR
|
||||
if (m_AnimationOffsetPlayable.IsValid())
|
||||
m_AnimationOffsetPlayable.SetRotation(value);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rotational offset of the clip, expressed in Euler angles
|
||||
/// </summary>
|
||||
public Vector3 eulerAngles
|
||||
{
|
||||
get { return m_EulerAngles; }
|
||||
set
|
||||
{
|
||||
m_EulerAngles = value;
|
||||
#if UNITY_EDITOR
|
||||
if (m_AnimationOffsetPlayable.IsValid())
|
||||
m_AnimationOffsetPlayable.SetRotation(rotation);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether to use offset matching options as defined by the track.
|
||||
/// </summary>
|
||||
public bool useTrackMatchFields
|
||||
{
|
||||
get { return m_UseTrackMatchFields; }
|
||||
set { m_UseTrackMatchFields = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which fields should be matched when aligning offsets.
|
||||
/// </summary>
|
||||
public MatchTargetFields matchTargetFields
|
||||
{
|
||||
get { return m_MatchTargetFields; }
|
||||
set { m_MatchTargetFields = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether to make the animation clip play relative to its first keyframe.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This option only applies to animation clips that animate Transform components.
|
||||
/// </remarks>
|
||||
public bool removeStartOffset
|
||||
{
|
||||
get { return m_RemoveStartOffset; }
|
||||
set { m_RemoveStartOffset = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Enable to apply foot IK to the AnimationClip when the target is humanoid.
|
||||
/// </summary>
|
||||
public bool applyFootIK
|
||||
{
|
||||
get { return m_ApplyFootIK; }
|
||||
set { m_ApplyFootIK = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the source AnimationClip loops during playback
|
||||
/// </summary>
|
||||
public LoopMode loop
|
||||
{
|
||||
get { return m_Loop; }
|
||||
set { m_Loop = value; }
|
||||
}
|
||||
|
||||
|
||||
internal bool hasRootTransforms
|
||||
{
|
||||
get { return m_Clip != null && HasRootTransforms(m_Clip); }
|
||||
}
|
||||
|
||||
// used for legacy 'scene' mode.
|
||||
internal AppliedOffsetMode appliedOffsetMode { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The source animation clip
|
||||
/// </summary>
|
||||
public AnimationClip clip
|
||||
{
|
||||
get { return m_Clip; }
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
name = "AnimationPlayableAsset of " + value.name;
|
||||
m_Clip = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the duration required to play the animation clip exactly once
|
||||
/// </summary>
|
||||
public override double duration
|
||||
{
|
||||
get
|
||||
{
|
||||
double length = TimeUtility.GetAnimationClipLength(clip);
|
||||
if (length < float.Epsilon)
|
||||
return base.duration;
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a description of the PlayableOutputs that may be created for this asset.
|
||||
/// </summary>
|
||||
public override IEnumerable<PlayableBinding> outputs
|
||||
{
|
||||
get { yield return AnimationPlayableBinding.Create(name, this); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the root of a Playable subgraph to play the animation clip.
|
||||
/// </summary>
|
||||
/// <param name="graph">PlayableGraph that will own the playable</param>
|
||||
/// <param name="go">The gameobject that triggered the graph build</param>
|
||||
/// <returns>The root playable of the subgraph</returns>
|
||||
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
|
||||
{
|
||||
Playable root = CreatePlayable(graph, m_Clip, position, eulerAngles, removeStartOffset, appliedOffsetMode, applyFootIK, m_Loop);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
m_AnimationOffsetPlayable = AnimationOffsetPlayable.Null;
|
||||
if (root.IsValid() && root.IsPlayableOfType<AnimationOffsetPlayable>())
|
||||
{
|
||||
m_AnimationOffsetPlayable = (AnimationOffsetPlayable)root;
|
||||
}
|
||||
|
||||
LiveLink();
|
||||
#endif
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
internal static Playable CreatePlayable(PlayableGraph graph, AnimationClip clip, Vector3 positionOffset, Vector3 eulerOffset, bool removeStartOffset, AppliedOffsetMode mode, bool applyFootIK, LoopMode loop)
|
||||
{
|
||||
if (clip == null || clip.legacy)
|
||||
return Playable.Null;
|
||||
|
||||
|
||||
var clipPlayable = AnimationClipPlayable.Create(graph, clip);
|
||||
clipPlayable.SetRemoveStartOffset(removeStartOffset);
|
||||
clipPlayable.SetApplyFootIK(applyFootIK);
|
||||
clipPlayable.SetOverrideLoopTime(loop != LoopMode.UseSourceAsset);
|
||||
clipPlayable.SetLoopTime(loop == LoopMode.On);
|
||||
|
||||
Playable root = clipPlayable;
|
||||
|
||||
if (ShouldApplyScaleRemove(mode))
|
||||
{
|
||||
var removeScale = AnimationRemoveScalePlayable.Create(graph, 1);
|
||||
graph.Connect(root, 0, removeScale, 0);
|
||||
removeScale.SetInputWeight(0, 1.0f);
|
||||
root = removeScale;
|
||||
}
|
||||
|
||||
if (ShouldApplyOffset(mode, clip))
|
||||
{
|
||||
var offsetPlayable = AnimationOffsetPlayable.Create(graph, positionOffset, Quaternion.Euler(eulerOffset), 1);
|
||||
graph.Connect(root, 0, offsetPlayable, 0);
|
||||
offsetPlayable.SetInputWeight(0, 1.0F);
|
||||
root = offsetPlayable;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static bool ShouldApplyOffset(AppliedOffsetMode mode, AnimationClip clip)
|
||||
{
|
||||
if (mode == AppliedOffsetMode.NoRootTransform || mode == AppliedOffsetMode.SceneOffsetLegacy)
|
||||
return false;
|
||||
|
||||
return HasRootTransforms(clip);
|
||||
}
|
||||
|
||||
private static bool ShouldApplyScaleRemove(AppliedOffsetMode mode)
|
||||
{
|
||||
return mode == AppliedOffsetMode.SceneOffsetLegacyEditor || mode == AppliedOffsetMode.SceneOffsetLegacy || mode == AppliedOffsetMode.TransformOffsetLegacy;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void LiveLink()
|
||||
{
|
||||
if (m_AnimationOffsetPlayable.IsValid())
|
||||
{
|
||||
m_AnimationOffsetPlayable.SetPosition(position);
|
||||
m_AnimationOffsetPlayable.SetRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Returns the capabilities of TimelineClips that contain a AnimationPlayableAsset
|
||||
/// </summary>
|
||||
public ClipCaps clipCaps
|
||||
{
|
||||
get
|
||||
{
|
||||
var caps = ClipCaps.Extrapolation | ClipCaps.SpeedMultiplier | ClipCaps.Blending;
|
||||
if (m_Clip != null && (m_Loop != LoopMode.Off) && (m_Loop != LoopMode.UseSourceAsset || m_Clip.isLooping))
|
||||
caps |= ClipCaps.Looping;
|
||||
|
||||
// empty clips don't support clip in. This allows trim operations to simply become move operations
|
||||
if (m_Clip != null && !m_Clip.empty)
|
||||
caps |= ClipCaps.ClipIn;
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the offsets to default values
|
||||
/// </summary>
|
||||
public void ResetOffsets()
|
||||
{
|
||||
position = Vector3.zero;
|
||||
eulerAngles = Vector3.zero;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
|
||||
{
|
||||
driver.AddFromClip(m_Clip);
|
||||
}
|
||||
|
||||
internal static bool HasRootTransforms(AnimationClip clip)
|
||||
{
|
||||
if (clip == null || clip.empty)
|
||||
return false;
|
||||
|
||||
return clip.hasRootMotion || clip.hasGenericRootTransform || clip.hasMotionCurves || clip.hasRootCurves;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 030f85c3f73729f4f976f66ffb23b875
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Animations;
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
using UnityEngine.Experimental.Animations;
|
||||
#endif
|
||||
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
class AnimationPreviewUpdateCallback : ITimelineEvaluateCallback
|
||||
{
|
||||
AnimationPlayableOutput m_Output;
|
||||
PlayableGraph m_Graph;
|
||||
List<IAnimationWindowPreview> m_PreviewComponents;
|
||||
|
||||
public AnimationPreviewUpdateCallback(AnimationPlayableOutput output)
|
||||
{
|
||||
m_Output = output;
|
||||
|
||||
Playable playable = m_Output.GetSourcePlayable();
|
||||
if (playable.IsValid())
|
||||
{
|
||||
m_Graph = playable.GetGraph();
|
||||
}
|
||||
}
|
||||
|
||||
public void Evaluate()
|
||||
{
|
||||
if (!m_Graph.IsValid())
|
||||
return;
|
||||
|
||||
if (m_PreviewComponents == null)
|
||||
FetchPreviewComponents();
|
||||
|
||||
foreach (var component in m_PreviewComponents)
|
||||
{
|
||||
if (component != null)
|
||||
{
|
||||
component.UpdatePreviewGraph(m_Graph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FetchPreviewComponents()
|
||||
{
|
||||
m_PreviewComponents = new List<IAnimationWindowPreview>();
|
||||
|
||||
var animator = m_Output.GetTarget();
|
||||
if (animator == null)
|
||||
return;
|
||||
|
||||
var gameObject = animator.gameObject;
|
||||
m_PreviewComponents.AddRange(gameObject.GetComponents<IAnimationWindowPreview>());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 755e3e942f7784d458bddba421c0bb72
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,1075 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Animations;
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
using UnityEngine.Experimental.Animations;
|
||||
#endif
|
||||
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags specifying which offset fields to match
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MatchTargetFields
|
||||
{
|
||||
/// <summary>
|
||||
/// Translation X value
|
||||
/// </summary>
|
||||
PositionX = 1 << 0,
|
||||
/// <summary>
|
||||
/// Translation Y value
|
||||
/// </summary>
|
||||
PositionY = 1 << 1,
|
||||
/// <summary>
|
||||
/// Translation Z value
|
||||
/// </summary>
|
||||
PositionZ = 1 << 2,
|
||||
/// <summary>
|
||||
/// Rotation Euler Angle X value
|
||||
/// </summary>
|
||||
RotationX = 1 << 3,
|
||||
/// <summary>
|
||||
/// Rotation Euler Angle Y value
|
||||
/// </summary>
|
||||
RotationY = 1 << 4,
|
||||
/// <summary>
|
||||
/// Rotation Euler Angle Z value
|
||||
/// </summary>
|
||||
RotationZ = 1 << 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes what is used to set the starting position and orientation of each Animation Track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, each Animation Track uses ApplyTransformOffsets to start from a set position and orientation.
|
||||
/// To offset each Animation Track based on the current position and orientation in the scene, use ApplySceneOffsets.
|
||||
/// </remarks>
|
||||
public enum TrackOffset
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this setting to offset each Animation Track based on a set position and orientation.
|
||||
/// </summary>
|
||||
ApplyTransformOffsets,
|
||||
/// <summary>
|
||||
/// Use this setting to offset each Animation Track based on the current position and orientation in the scene.
|
||||
/// </summary>
|
||||
ApplySceneOffsets,
|
||||
/// <summary>
|
||||
/// Use this setting to offset root transforms based on the state of the animator.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only use this setting to support legacy Animation Tracks. This mode may be deprecated in a future release.
|
||||
///
|
||||
/// In Auto mode, when the animator bound to the animation track contains an AnimatorController, it offsets all animations similar to ApplySceneOffsets.
|
||||
/// If no controller is assigned, then all offsets are set to start from a fixed position and orientation, similar to ApplyTransformOffsets.
|
||||
/// In Auto mode, in most cases, root transforms are not affected by local scale or Animator.humanScale, unless the animator has an AnimatorController and Animator.applyRootMotion is set to true.
|
||||
/// </remarks>
|
||||
Auto
|
||||
}
|
||||
|
||||
// offset mode
|
||||
enum AppliedOffsetMode
|
||||
{
|
||||
NoRootTransform,
|
||||
TransformOffset,
|
||||
SceneOffset,
|
||||
TransformOffsetLegacy,
|
||||
SceneOffsetLegacy,
|
||||
SceneOffsetEditor, // scene offset mode in editor
|
||||
SceneOffsetLegacyEditor,
|
||||
}
|
||||
|
||||
// separate from the enum to hide them from UI elements
|
||||
static class MatchTargetFieldConstants
|
||||
{
|
||||
public static MatchTargetFields All = MatchTargetFields.PositionX | MatchTargetFields.PositionY |
|
||||
MatchTargetFields.PositionZ | MatchTargetFields.RotationX |
|
||||
MatchTargetFields.RotationY | MatchTargetFields.RotationZ;
|
||||
|
||||
public static MatchTargetFields None = 0;
|
||||
|
||||
public static MatchTargetFields Position = MatchTargetFields.PositionX | MatchTargetFields.PositionY |
|
||||
MatchTargetFields.PositionZ;
|
||||
|
||||
public static MatchTargetFields Rotation = MatchTargetFields.RotationX | MatchTargetFields.RotationY |
|
||||
MatchTargetFields.RotationZ;
|
||||
|
||||
public static bool HasAny(this MatchTargetFields me, MatchTargetFields fields)
|
||||
{
|
||||
return (me & fields) != None;
|
||||
}
|
||||
|
||||
public static MatchTargetFields Toggle(this MatchTargetFields me, MatchTargetFields flag)
|
||||
{
|
||||
return me ^ flag;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Timeline track used for playing back animations on an Animator.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[TrackClipType(typeof(AnimationPlayableAsset), false)]
|
||||
[TrackBindingType(typeof(Animator))]
|
||||
[ExcludeFromPreset]
|
||||
[TimelineHelpURL(typeof(AnimationTrack))]
|
||||
public partial class AnimationTrack : TrackAsset, ILayerable
|
||||
{
|
||||
const string k_DefaultInfiniteClipName = "Recorded";
|
||||
const string k_DefaultRecordableClipName = "Recorded";
|
||||
|
||||
[SerializeField, FormerlySerializedAs("m_OpenClipPreExtrapolation")]
|
||||
TimelineClip.ClipExtrapolation m_InfiniteClipPreExtrapolation = TimelineClip.ClipExtrapolation.None;
|
||||
|
||||
[SerializeField, FormerlySerializedAs("m_OpenClipPostExtrapolation")]
|
||||
TimelineClip.ClipExtrapolation m_InfiniteClipPostExtrapolation = TimelineClip.ClipExtrapolation.None;
|
||||
|
||||
[SerializeField, FormerlySerializedAs("m_OpenClipOffsetPosition")]
|
||||
Vector3 m_InfiniteClipOffsetPosition = Vector3.zero;
|
||||
|
||||
[SerializeField, FormerlySerializedAs("m_OpenClipOffsetEulerAngles")]
|
||||
Vector3 m_InfiniteClipOffsetEulerAngles = Vector3.zero;
|
||||
|
||||
[SerializeField, FormerlySerializedAs("m_OpenClipTimeOffset")]
|
||||
double m_InfiniteClipTimeOffset;
|
||||
|
||||
[SerializeField, FormerlySerializedAs("m_OpenClipRemoveOffset")]
|
||||
bool m_InfiniteClipRemoveOffset; // cached value for remove offset
|
||||
|
||||
[SerializeField]
|
||||
bool m_InfiniteClipApplyFootIK = true;
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
AnimationPlayableAsset.LoopMode mInfiniteClipLoop = AnimationPlayableAsset.LoopMode.UseSourceAsset;
|
||||
|
||||
[SerializeField]
|
||||
MatchTargetFields m_MatchTargetFields = MatchTargetFieldConstants.All;
|
||||
[SerializeField]
|
||||
Vector3 m_Position = Vector3.zero;
|
||||
[SerializeField]
|
||||
Vector3 m_EulerAngles = Vector3.zero;
|
||||
|
||||
|
||||
[SerializeField] AvatarMask m_AvatarMask;
|
||||
[SerializeField] bool m_ApplyAvatarMask = true;
|
||||
|
||||
[SerializeField] TrackOffset m_TrackOffset = TrackOffset.ApplyTransformOffsets;
|
||||
|
||||
[SerializeField, HideInInspector] AnimationClip m_InfiniteClip;
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private AnimationClip m_DefaultPoseClip;
|
||||
private AnimationClip m_CachedPropertiesClip;
|
||||
private int m_CachedHash;
|
||||
private EditorCurveBinding[] m_CachedBindings;
|
||||
|
||||
AnimationOffsetPlayable m_ClipOffset;
|
||||
|
||||
private Vector3 m_SceneOffsetPosition = Vector3.zero;
|
||||
private Vector3 m_SceneOffsetRotation = Vector3.zero;
|
||||
|
||||
private bool m_HasPreviewComponents = false;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The translation offset of the entire track.
|
||||
/// </summary>
|
||||
public Vector3 position
|
||||
{
|
||||
get { return m_Position; }
|
||||
set { m_Position = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rotation offset of the entire track, expressed as a quaternion.
|
||||
/// </summary>
|
||||
public Quaternion rotation
|
||||
{
|
||||
get { return Quaternion.Euler(m_EulerAngles); }
|
||||
set { m_EulerAngles = value.eulerAngles; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The euler angle representation of the rotation offset of the entire track.
|
||||
/// </summary>
|
||||
public Vector3 eulerAngles
|
||||
{
|
||||
get { return m_EulerAngles; }
|
||||
set { m_EulerAngles = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether to apply track offsets to all clips on the track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used to offset all clips on a track, in addition to the clips individual offsets.
|
||||
/// </remarks>
|
||||
[Obsolete("applyOffset is deprecated. Use trackOffset instead", true)]
|
||||
public bool applyOffsets
|
||||
{
|
||||
get { return false; }
|
||||
set { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies what is used to set the starting position and orientation of an Animation Track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Track Offset is only applied when the Animation Track contains animation that modifies the root Transform.
|
||||
/// </remarks>
|
||||
public TrackOffset trackOffset
|
||||
{
|
||||
get { return m_TrackOffset; }
|
||||
set { m_TrackOffset = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which fields to match when aligning offsets of clips.
|
||||
/// </summary>
|
||||
public MatchTargetFields matchTargetFields
|
||||
{
|
||||
get { return m_MatchTargetFields; }
|
||||
set { m_MatchTargetFields = value & MatchTargetFieldConstants.All; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An AnimationClip storing the data for an infinite track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value of this property is null when the AnimationTrack is in Clip Mode.
|
||||
/// </remarks>
|
||||
public AnimationClip infiniteClip
|
||||
{
|
||||
get { return m_InfiniteClip; }
|
||||
internal set { m_InfiniteClip = value; }
|
||||
}
|
||||
|
||||
// saved value for converting to/from infinite mode
|
||||
internal bool infiniteClipRemoveOffset
|
||||
{
|
||||
get { return m_InfiniteClipRemoveOffset; }
|
||||
set { m_InfiniteClipRemoveOffset = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the AvatarMask to be applied to all clips on the track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Applying an AvatarMask to an animation track will allow discarding portions of the animation being applied on the track.
|
||||
/// </remarks>
|
||||
public AvatarMask avatarMask
|
||||
{
|
||||
get { return m_AvatarMask; }
|
||||
set { m_AvatarMask = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether to apply the AvatarMask to the track.
|
||||
/// </summary>
|
||||
public bool applyAvatarMask
|
||||
{
|
||||
get { return m_ApplyAvatarMask; }
|
||||
set { m_ApplyAvatarMask = value; }
|
||||
}
|
||||
|
||||
// is this track compilable
|
||||
|
||||
internal override bool CanCompileClips()
|
||||
{
|
||||
return !muted && (m_Clips.Count > 0 || (m_InfiniteClip != null && !m_InfiniteClip.empty));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<PlayableBinding> outputs
|
||||
{
|
||||
get { yield return AnimationPlayableBinding.Create(name, this); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether the Animation Track has clips, or is in infinite mode.
|
||||
/// </summary>
|
||||
public bool inClipMode
|
||||
{
|
||||
get { return clips != null && clips.Length != 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The translation offset of a track in infinite mode.
|
||||
/// </summary>
|
||||
public Vector3 infiniteClipOffsetPosition
|
||||
{
|
||||
get { return m_InfiniteClipOffsetPosition; }
|
||||
set { m_InfiniteClipOffsetPosition = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rotation offset of a track in infinite mode.
|
||||
/// </summary>
|
||||
public Quaternion infiniteClipOffsetRotation
|
||||
{
|
||||
get { return Quaternion.Euler(m_InfiniteClipOffsetEulerAngles); }
|
||||
set { m_InfiniteClipOffsetEulerAngles = value.eulerAngles; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The euler angle representation of the rotation offset of the track when in infinite mode.
|
||||
/// </summary>
|
||||
public Vector3 infiniteClipOffsetEulerAngles
|
||||
{
|
||||
get { return m_InfiniteClipOffsetEulerAngles; }
|
||||
set { m_InfiniteClipOffsetEulerAngles = value; }
|
||||
}
|
||||
|
||||
internal bool infiniteClipApplyFootIK
|
||||
{
|
||||
get { return m_InfiniteClipApplyFootIK; }
|
||||
set { m_InfiniteClipApplyFootIK = value; }
|
||||
}
|
||||
|
||||
internal double infiniteClipTimeOffset
|
||||
{
|
||||
get { return m_InfiniteClipTimeOffset; }
|
||||
set { m_InfiniteClipTimeOffset = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The saved state of pre-extrapolation for clips converted to infinite mode.
|
||||
/// </summary>
|
||||
public TimelineClip.ClipExtrapolation infiniteClipPreExtrapolation
|
||||
{
|
||||
get { return m_InfiniteClipPreExtrapolation; }
|
||||
set { m_InfiniteClipPreExtrapolation = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The saved state of post-extrapolation for clips when converted to infinite mode.
|
||||
/// </summary>
|
||||
public TimelineClip.ClipExtrapolation infiniteClipPostExtrapolation
|
||||
{
|
||||
get { return m_InfiniteClipPostExtrapolation; }
|
||||
set { m_InfiniteClipPostExtrapolation = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The saved state of animation clip loop state when converted to infinite mode
|
||||
/// </summary>
|
||||
internal AnimationPlayableAsset.LoopMode infiniteClipLoop
|
||||
{
|
||||
get { return mInfiniteClipLoop; }
|
||||
set { mInfiniteClipLoop = value; }
|
||||
}
|
||||
|
||||
[ContextMenu("Reset Offsets")]
|
||||
void ResetOffsets()
|
||||
{
|
||||
m_Position = Vector3.zero;
|
||||
m_EulerAngles = Vector3.zero;
|
||||
UpdateClipOffsets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TimelineClip on this track that uses an AnimationClip.
|
||||
/// </summary>
|
||||
/// <param name="clip">Source animation clip of the resulting TimelineClip.</param>
|
||||
/// <returns>A new TimelineClip which has an AnimationPlayableAsset asset attached.</returns>
|
||||
public TimelineClip CreateClip(AnimationClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return null;
|
||||
|
||||
var newClip = CreateClip<AnimationPlayableAsset>();
|
||||
AssignAnimationClip(newClip, clip);
|
||||
return newClip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AnimationClip that stores the data for an infinite track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If an infiniteClip already exists, this method produces no result, even if you provide a different value
|
||||
/// for infiniteClipName.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// This method can't create an infinite clip for an AnimationTrack that contains one or more Timeline clips.
|
||||
/// Use AnimationTrack.inClipMode to determine whether it is possible to create an infinite clip on an AnimationTrack.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// When used from the editor, this method attempts to save the created infinite clip to the TimelineAsset.
|
||||
/// The TimelineAsset must already exist in the AssetDatabase to save the infinite clip. If the TimelineAsset
|
||||
/// does not exist, the infinite clip is still created but it is not saved.
|
||||
/// </remarks>
|
||||
/// <param name="infiniteClipName">
|
||||
/// The name of the AnimationClip to create.
|
||||
/// This method does not ensure unique names. If you want a unique clip name, you must provide one.
|
||||
/// See ObjectNames.GetUniqueName for information on a method that creates unique names.
|
||||
/// </param>
|
||||
public void CreateInfiniteClip(string infiniteClipName)
|
||||
{
|
||||
if (inClipMode)
|
||||
{
|
||||
Debug.LogWarning("CreateInfiniteClip cannot create an infinite clip for an AnimationTrack that contains one or more Timeline Clips.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_InfiniteClip != null)
|
||||
return;
|
||||
|
||||
m_InfiniteClip = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(infiniteClipName) ? k_DefaultInfiniteClipName : infiniteClipName, this, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TimelineClip, AnimationPlayableAsset and an AnimationClip. Use this clip to record in a timeline.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When used from the editor, this method attempts to save the created recordable clip to the TimelineAsset.
|
||||
/// The TimelineAsset must already exist in the AssetDatabase to save the recordable clip. If the TimelineAsset
|
||||
/// does not exist, the recordable clip is still created but it is not saved.
|
||||
/// </remarks>
|
||||
/// <param name="animClipName">
|
||||
/// The name of the AnimationClip to create.
|
||||
/// This method does not ensure unique names. If you want a unique clip name, you must provide one.
|
||||
/// See ObjectNames.GetUniqueName for information on a method that creates unique names.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns a new TimelineClip with an AnimationPlayableAsset asset attached.
|
||||
/// </returns>
|
||||
public TimelineClip CreateRecordableClip(string animClipName)
|
||||
{
|
||||
var clip = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(animClipName) ? k_DefaultRecordableClipName : animClipName, this, false);
|
||||
|
||||
var timelineClip = CreateClip(clip);
|
||||
timelineClip.displayName = animClipName;
|
||||
timelineClip.recordable = true;
|
||||
timelineClip.start = 0;
|
||||
timelineClip.duration = 1;
|
||||
|
||||
var apa = timelineClip.asset as AnimationPlayableAsset;
|
||||
if (apa != null)
|
||||
apa.removeStartOffset = false;
|
||||
|
||||
return timelineClip;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal Vector3 sceneOffsetPosition
|
||||
{
|
||||
get { return m_SceneOffsetPosition; }
|
||||
set { m_SceneOffsetPosition = value; }
|
||||
}
|
||||
|
||||
internal Vector3 sceneOffsetRotation
|
||||
{
|
||||
get { return m_SceneOffsetRotation; }
|
||||
set { m_SceneOffsetRotation = value; }
|
||||
}
|
||||
|
||||
internal bool hasPreviewComponents
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_HasPreviewComponents)
|
||||
return true;
|
||||
|
||||
var parentTrack = parent as AnimationTrack;
|
||||
if (parentTrack != null)
|
||||
{
|
||||
return parentTrack.hasPreviewComponents;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Used to initialize default values on a newly created clip
|
||||
/// </summary>
|
||||
/// <param name="clip">The clip added to the track</param>
|
||||
protected override void OnCreateClip(TimelineClip clip)
|
||||
{
|
||||
var extrapolation = TimelineClip.ClipExtrapolation.None;
|
||||
if (!isSubTrack)
|
||||
extrapolation = TimelineClip.ClipExtrapolation.Hold;
|
||||
clip.preExtrapolationMode = extrapolation;
|
||||
clip.postExtrapolationMode = extrapolation;
|
||||
}
|
||||
|
||||
protected internal override int CalculateItemsHash()
|
||||
{
|
||||
return GetAnimationClipHash(m_InfiniteClip).CombineHash(base.CalculateItemsHash());
|
||||
}
|
||||
|
||||
internal void UpdateClipOffsets()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (m_ClipOffset.IsValid())
|
||||
{
|
||||
m_ClipOffset.SetPosition(position);
|
||||
m_ClipOffset.SetRotation(rotation);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Playable CompileTrackPlayable(PlayableGraph graph, AnimationTrack track, GameObject go, IntervalTree<RuntimeElement> tree, AppliedOffsetMode mode)
|
||||
{
|
||||
var mixer = AnimationMixerPlayable.Create(graph, track.clips.Length);
|
||||
for (int i = 0; i < track.clips.Length; i++)
|
||||
{
|
||||
var c = track.clips[i];
|
||||
var asset = c.asset as PlayableAsset;
|
||||
if (asset == null)
|
||||
continue;
|
||||
|
||||
var animationAsset = asset as AnimationPlayableAsset;
|
||||
if (animationAsset != null)
|
||||
animationAsset.appliedOffsetMode = mode;
|
||||
|
||||
var source = asset.CreatePlayable(graph, go);
|
||||
if (source.IsValid())
|
||||
{
|
||||
var clip = new RuntimeClip(c, source, mixer);
|
||||
tree.Add(clip);
|
||||
graph.Connect(source, 0, mixer, i);
|
||||
mixer.SetInputWeight(i, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!track.AnimatesRootTransform())
|
||||
return mixer;
|
||||
|
||||
return ApplyTrackOffset(graph, mixer, go, mode);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILayerable.CreateLayerMixer"/>
|
||||
/// <returns>Returns <c>Playable.Null</c></returns>
|
||||
Playable ILayerable.CreateLayerMixer(PlayableGraph graph, GameObject go, int inputCount)
|
||||
{
|
||||
return Playable.Null;
|
||||
}
|
||||
|
||||
internal override Playable CreateMixerPlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree)
|
||||
{
|
||||
if (isSubTrack)
|
||||
throw new InvalidOperationException("Nested animation tracks should never be asked to create a graph directly");
|
||||
|
||||
List<AnimationTrack> flattenTracks = new List<AnimationTrack>();
|
||||
if (CanCompileClips())
|
||||
flattenTracks.Add(this);
|
||||
|
||||
var genericRoot = GetGenericRootNode(go);
|
||||
var animatesRootTransformNoMask = AnimatesRootTransform();
|
||||
var animatesRootTransform = animatesRootTransformNoMask && !IsRootTransformDisabledByMask(go, genericRoot);
|
||||
foreach (var subTrack in GetChildTracks())
|
||||
{
|
||||
var child = subTrack as AnimationTrack;
|
||||
if (child != null && child.CanCompileClips())
|
||||
{
|
||||
var childAnimatesRoot = child.AnimatesRootTransform();
|
||||
animatesRootTransformNoMask |= child.AnimatesRootTransform();
|
||||
animatesRootTransform |= (childAnimatesRoot && !child.IsRootTransformDisabledByMask(go, genericRoot));
|
||||
flattenTracks.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
// figure out which mode to apply
|
||||
AppliedOffsetMode mode = GetOffsetMode(go, animatesRootTransform);
|
||||
int defaultBlendCount = GetDefaultBlendCount();
|
||||
var layerMixer = CreateGroupMixer(graph, go, flattenTracks.Count + defaultBlendCount);
|
||||
for (int c = 0; c < flattenTracks.Count; c++)
|
||||
{
|
||||
int blendIndex = c + defaultBlendCount;
|
||||
// if the child is masking the root transform, compile it as if we are non-root mode
|
||||
var childMode = mode;
|
||||
if (mode != AppliedOffsetMode.NoRootTransform && flattenTracks[c].IsRootTransformDisabledByMask(go, genericRoot))
|
||||
childMode = AppliedOffsetMode.NoRootTransform;
|
||||
|
||||
var compiledTrackPlayable = flattenTracks[c].inClipMode ?
|
||||
CompileTrackPlayable(graph, flattenTracks[c], go, tree, childMode) :
|
||||
flattenTracks[c].CreateInfiniteTrackPlayable(graph, go, tree, childMode);
|
||||
graph.Connect(compiledTrackPlayable, 0, layerMixer, blendIndex);
|
||||
layerMixer.SetInputWeight(blendIndex, flattenTracks[c].inClipMode ? 0 : 1);
|
||||
if (flattenTracks[c].applyAvatarMask && flattenTracks[c].avatarMask != null)
|
||||
{
|
||||
layerMixer.SetLayerMaskFromAvatarMask((uint)blendIndex, flattenTracks[c].avatarMask);
|
||||
}
|
||||
}
|
||||
|
||||
var requiresMotionXPlayable = RequiresMotionXPlayable(mode, go);
|
||||
|
||||
// In the editor, we may require the motion X playable if we are animating the root transform but it is masked out, because the default poses
|
||||
// need to properly update root motion
|
||||
requiresMotionXPlayable |= (defaultBlendCount > 0 && RequiresMotionXPlayable(GetOffsetMode(go, animatesRootTransformNoMask), go));
|
||||
|
||||
// Attach the default poses
|
||||
AttachDefaultBlend(graph, layerMixer, requiresMotionXPlayable);
|
||||
|
||||
// motionX playable not required in scene offset mode, or root transform mode
|
||||
Playable mixer = layerMixer;
|
||||
if (requiresMotionXPlayable)
|
||||
{
|
||||
// If we are animating a root transform, add the motionX to delta playable as the root node
|
||||
var motionXToDelta = AnimationMotionXToDeltaPlayable.Create(graph);
|
||||
graph.Connect(mixer, 0, motionXToDelta, 0);
|
||||
motionXToDelta.SetInputWeight(0, 1.0f);
|
||||
motionXToDelta.SetAbsoluteMotion(UsesAbsoluteMotion(mode));
|
||||
mixer = (Playable)motionXToDelta;
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
var animator = GetBinding(go != null ? go.GetComponent<PlayableDirector>() : null);
|
||||
if (animator != null)
|
||||
{
|
||||
GameObject targetGO = animator.gameObject;
|
||||
IAnimationWindowPreview[] previewComponents = targetGO.GetComponents<IAnimationWindowPreview>();
|
||||
|
||||
m_HasPreviewComponents = previewComponents.Length > 0;
|
||||
if (m_HasPreviewComponents)
|
||||
{
|
||||
foreach (var component in previewComponents)
|
||||
{
|
||||
mixer = component.BuildPreviewGraph(graph, mixer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return mixer;
|
||||
}
|
||||
|
||||
private int GetDefaultBlendCount()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
return 0;
|
||||
|
||||
return ((m_CachedPropertiesClip != null) ? 1 : 0) + ((m_DefaultPoseClip != null) ? 1 : 0);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Attaches the default blends to the layer mixer
|
||||
// the base layer is a default clip of all driven properties
|
||||
// the next layer is optionally the desired default pose (in the case of humanoid, the TPose)
|
||||
private void AttachDefaultBlend(PlayableGraph graph, AnimationLayerMixerPlayable mixer, bool requireOffset)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
int mixerInput = 0;
|
||||
if (m_CachedPropertiesClip)
|
||||
{
|
||||
var cachedPropertiesClip = AnimationClipPlayable.Create(graph, m_CachedPropertiesClip);
|
||||
cachedPropertiesClip.SetApplyFootIK(false);
|
||||
var defaults = (Playable)cachedPropertiesClip;
|
||||
if (requireOffset)
|
||||
defaults = AttachOffsetPlayable(graph, defaults, m_SceneOffsetPosition, Quaternion.Euler(m_SceneOffsetRotation));
|
||||
graph.Connect(defaults, 0, mixer, mixerInput);
|
||||
mixer.SetInputWeight(mixerInput, 1.0f);
|
||||
mixerInput++;
|
||||
}
|
||||
|
||||
if (m_DefaultPoseClip)
|
||||
{
|
||||
var defaultPose = AnimationClipPlayable.Create(graph, m_DefaultPoseClip);
|
||||
defaultPose.SetApplyFootIK(false);
|
||||
var blendDefault = (Playable)defaultPose;
|
||||
if (requireOffset)
|
||||
blendDefault = AttachOffsetPlayable(graph, blendDefault, m_SceneOffsetPosition, Quaternion.Euler(m_SceneOffsetRotation));
|
||||
graph.Connect(blendDefault, 0, mixer, mixerInput);
|
||||
mixer.SetInputWeight(mixerInput, 1.0f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private Playable AttachOffsetPlayable(PlayableGraph graph, Playable playable, Vector3 pos, Quaternion rot)
|
||||
{
|
||||
var offsetPlayable = AnimationOffsetPlayable.Create(graph, pos, rot, 1);
|
||||
offsetPlayable.SetInputWeight(0, 1.0f);
|
||||
graph.Connect(playable, 0, offsetPlayable, 0);
|
||||
return offsetPlayable;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static string k_DefaultHumanoidClipPath = "Packages/com.unity.timeline/Editor/StyleSheets/res/HumanoidDefault.anim";
|
||||
private static AnimationClip s_DefaultHumanoidClip = null;
|
||||
|
||||
AnimationClip GetDefaultHumanoidClip()
|
||||
{
|
||||
if (s_DefaultHumanoidClip == null)
|
||||
{
|
||||
s_DefaultHumanoidClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(k_DefaultHumanoidClipPath);
|
||||
if (s_DefaultHumanoidClip == null)
|
||||
Debug.LogError("Could not load default humanoid animation clip for Timeline");
|
||||
}
|
||||
|
||||
return s_DefaultHumanoidClip;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool RequiresMotionXPlayable(AppliedOffsetMode mode, GameObject gameObject)
|
||||
{
|
||||
if (mode == AppliedOffsetMode.NoRootTransform)
|
||||
return false;
|
||||
if (mode == AppliedOffsetMode.SceneOffsetLegacy)
|
||||
{
|
||||
var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null);
|
||||
return animator != null && animator.hasRootMotion;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool UsesAbsoluteMotion(AppliedOffsetMode mode)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// in editor, previewing is always done in absolute motion
|
||||
if (!Application.isPlaying)
|
||||
return true;
|
||||
#endif
|
||||
return mode != AppliedOffsetMode.SceneOffset &&
|
||||
mode != AppliedOffsetMode.SceneOffsetLegacy;
|
||||
}
|
||||
|
||||
bool HasController(GameObject gameObject)
|
||||
{
|
||||
var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null);
|
||||
|
||||
return animator != null && animator.runtimeAnimatorController != null;
|
||||
}
|
||||
|
||||
internal Animator GetBinding(PlayableDirector director)
|
||||
{
|
||||
if (director == null)
|
||||
return null;
|
||||
|
||||
UnityEngine.Object key = this;
|
||||
if (isSubTrack)
|
||||
key = parent;
|
||||
|
||||
UnityEngine.Object binding = null;
|
||||
if (director != null)
|
||||
binding = director.GetGenericBinding(key);
|
||||
|
||||
Animator animator = null;
|
||||
if (binding != null) // the binding can be an animator or game object
|
||||
{
|
||||
animator = binding as Animator;
|
||||
var gameObject = binding as GameObject;
|
||||
if (animator == null && gameObject != null)
|
||||
animator = gameObject.GetComponent<Animator>();
|
||||
}
|
||||
|
||||
return animator;
|
||||
}
|
||||
|
||||
static AnimationLayerMixerPlayable CreateGroupMixer(PlayableGraph graph, GameObject go, int inputCount)
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
return AnimationLayerMixerPlayable.Create(graph, inputCount, false);
|
||||
#else
|
||||
return AnimationLayerMixerPlayable.Create(graph, inputCount);
|
||||
#endif
|
||||
}
|
||||
|
||||
Playable CreateInfiniteTrackPlayable(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree, AppliedOffsetMode mode)
|
||||
{
|
||||
if (m_InfiniteClip == null)
|
||||
return Playable.Null;
|
||||
|
||||
var mixer = AnimationMixerPlayable.Create(graph, 1);
|
||||
|
||||
// In infinite mode, we always force the loop mode of the clip off because the clip keys are offset in infinite mode
|
||||
// which causes loop to behave different.
|
||||
// The inline curve editor never shows loops in infinite mode.
|
||||
var playable = AnimationPlayableAsset.CreatePlayable(graph, m_InfiniteClip, m_InfiniteClipOffsetPosition, m_InfiniteClipOffsetEulerAngles, false, mode, infiniteClipApplyFootIK, AnimationPlayableAsset.LoopMode.Off);
|
||||
if (playable.IsValid())
|
||||
{
|
||||
tree.Add(new InfiniteRuntimeClip(playable));
|
||||
graph.Connect(playable, 0, mixer, 0);
|
||||
mixer.SetInputWeight(0, 1.0f);
|
||||
}
|
||||
|
||||
if (!AnimatesRootTransform())
|
||||
return mixer;
|
||||
|
||||
var rootTrack = isSubTrack ? (AnimationTrack)parent : this;
|
||||
return rootTrack.ApplyTrackOffset(graph, mixer, go, mode);
|
||||
}
|
||||
|
||||
Playable ApplyTrackOffset(PlayableGraph graph, Playable root, GameObject go, AppliedOffsetMode mode)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
m_ClipOffset = AnimationOffsetPlayable.Null;
|
||||
#endif
|
||||
|
||||
// offsets don't apply in scene offset, or if there is no root transform (globally or on this track)
|
||||
if (mode == AppliedOffsetMode.SceneOffsetLegacy ||
|
||||
mode == AppliedOffsetMode.SceneOffset ||
|
||||
mode == AppliedOffsetMode.NoRootTransform
|
||||
)
|
||||
return root;
|
||||
|
||||
|
||||
var pos = position;
|
||||
var rot = rotation;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// in the editor use the preview position to playback from if available
|
||||
if (mode == AppliedOffsetMode.SceneOffsetEditor)
|
||||
{
|
||||
pos = m_SceneOffsetPosition;
|
||||
rot = Quaternion.Euler(m_SceneOffsetRotation);
|
||||
}
|
||||
#endif
|
||||
|
||||
var offsetPlayable = AnimationOffsetPlayable.Create(graph, pos, rot, 1);
|
||||
#if UNITY_EDITOR
|
||||
m_ClipOffset = offsetPlayable;
|
||||
#endif
|
||||
graph.Connect(root, 0, offsetPlayable, 0);
|
||||
offsetPlayable.SetInputWeight(0, 1);
|
||||
|
||||
return offsetPlayable;
|
||||
}
|
||||
|
||||
// the evaluation time is large so that the properties always get evaluated
|
||||
internal override void GetEvaluationTime(out double outStart, out double outDuration)
|
||||
{
|
||||
if (inClipMode)
|
||||
{
|
||||
base.GetEvaluationTime(out outStart, out outDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
outStart = 0;
|
||||
outDuration = TimelineClip.kMaxTimeValue;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void GetSequenceTime(out double outStart, out double outDuration)
|
||||
{
|
||||
if (inClipMode)
|
||||
{
|
||||
base.GetSequenceTime(out outStart, out outDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
outStart = 0;
|
||||
outDuration = Math.Max(GetNotificationDuration(), TimeUtility.GetAnimationClipLength(m_InfiniteClip));
|
||||
}
|
||||
}
|
||||
|
||||
void AssignAnimationClip(TimelineClip clip, AnimationClip animClip)
|
||||
{
|
||||
if (clip == null || animClip == null)
|
||||
return;
|
||||
|
||||
if (animClip.legacy)
|
||||
throw new InvalidOperationException("Legacy Animation Clips are not supported");
|
||||
|
||||
AnimationPlayableAsset asset = clip.asset as AnimationPlayableAsset;
|
||||
if (asset != null)
|
||||
{
|
||||
asset.clip = animClip;
|
||||
asset.name = animClip.name;
|
||||
var duration = asset.duration;
|
||||
if (!double.IsInfinity(duration) && duration >= TimelineClip.kMinDuration && duration < TimelineClip.kMaxTimeValue)
|
||||
clip.duration = duration;
|
||||
}
|
||||
clip.displayName = animClip.name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the Timeline Editor to gather properties requiring preview.
|
||||
/// </summary>
|
||||
/// <param name="director">The PlayableDirector invoking the preview</param>
|
||||
/// <param name="driver">PropertyCollector used to gather previewable properties</param>
|
||||
public override void GatherProperties(PlayableDirector director, IPropertyCollector driver)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
m_SceneOffsetPosition = Vector3.zero;
|
||||
m_SceneOffsetRotation = Vector3.zero;
|
||||
|
||||
var animator = GetBinding(director);
|
||||
if (animator == null)
|
||||
return;
|
||||
|
||||
var animClips = new List<AnimationClip>(this.clips.Length + 2);
|
||||
GetAnimationClips(animClips);
|
||||
|
||||
var hasHumanMotion = animClips.Exists(clip => clip.humanMotion);
|
||||
// case 1174752 - recording root transform on humanoid clips clips cause invalid pose. This will apply the default T-Pose, only if it not already driven by another track
|
||||
if (!hasHumanMotion && animator.isHuman && AnimatesRootTransform() &&
|
||||
!DrivenPropertyManagerInternal.IsDriven(animator.transform, "m_LocalPosition.x") &&
|
||||
!DrivenPropertyManagerInternal.IsDriven(animator.transform, "m_LocalRotation.x"))
|
||||
hasHumanMotion = true;
|
||||
|
||||
m_SceneOffsetPosition = animator.transform.localPosition;
|
||||
m_SceneOffsetRotation = animator.transform.localEulerAngles;
|
||||
|
||||
// Create default pose clip from collected properties
|
||||
if (hasHumanMotion)
|
||||
animClips.Add(GetDefaultHumanoidClip());
|
||||
|
||||
m_DefaultPoseClip = hasHumanMotion ? GetDefaultHumanoidClip() : null;
|
||||
var hash = AnimationPreviewUtilities.GetClipHash(animClips);
|
||||
if (m_CachedBindings == null || m_CachedHash != hash)
|
||||
{
|
||||
m_CachedBindings = AnimationPreviewUtilities.GetBindings(animator.gameObject, animClips);
|
||||
m_CachedPropertiesClip = AnimationPreviewUtilities.CreateDefaultClip(animator.gameObject, m_CachedBindings);
|
||||
m_CachedHash = hash;
|
||||
}
|
||||
|
||||
AnimationPreviewUtilities.PreviewFromCurves(animator.gameObject, m_CachedBindings); // faster to preview from curves then an animation clip
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gather all the animation clips for this track
|
||||
/// </summary>
|
||||
/// <param name="animClips"></param>
|
||||
private void GetAnimationClips(List<AnimationClip> animClips)
|
||||
{
|
||||
foreach (var c in clips)
|
||||
{
|
||||
var a = c.asset as AnimationPlayableAsset;
|
||||
if (a != null && a.clip != null)
|
||||
animClips.Add(a.clip);
|
||||
}
|
||||
|
||||
if (m_InfiniteClip != null)
|
||||
animClips.Add(m_InfiniteClip);
|
||||
|
||||
foreach (var childTrack in GetChildTracks())
|
||||
{
|
||||
var animChildTrack = childTrack as AnimationTrack;
|
||||
if (animChildTrack != null)
|
||||
animChildTrack.GetAnimationClips(animClips);
|
||||
}
|
||||
}
|
||||
|
||||
// calculate which offset mode to apply
|
||||
AppliedOffsetMode GetOffsetMode(GameObject go, bool animatesRootTransform)
|
||||
{
|
||||
if (!animatesRootTransform)
|
||||
return AppliedOffsetMode.NoRootTransform;
|
||||
|
||||
if (m_TrackOffset == TrackOffset.ApplyTransformOffsets)
|
||||
return AppliedOffsetMode.TransformOffset;
|
||||
|
||||
if (m_TrackOffset == TrackOffset.ApplySceneOffsets)
|
||||
return (Application.isPlaying) ? AppliedOffsetMode.SceneOffset : AppliedOffsetMode.SceneOffsetEditor;
|
||||
|
||||
if (HasController(go))
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
return AppliedOffsetMode.SceneOffsetLegacyEditor;
|
||||
return AppliedOffsetMode.SceneOffsetLegacy;
|
||||
}
|
||||
|
||||
return AppliedOffsetMode.TransformOffsetLegacy;
|
||||
}
|
||||
|
||||
private bool IsRootTransformDisabledByMask(GameObject gameObject, Transform genericRootNode)
|
||||
{
|
||||
if (avatarMask == null || !applyAvatarMask)
|
||||
return false;
|
||||
|
||||
var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null);
|
||||
if (animator == null)
|
||||
return false;
|
||||
|
||||
if (animator.isHuman)
|
||||
return !avatarMask.GetHumanoidBodyPartActive(AvatarMaskBodyPart.Root);
|
||||
|
||||
if (avatarMask.transformCount == 0)
|
||||
return false;
|
||||
|
||||
// no special root supplied
|
||||
if (genericRootNode == null)
|
||||
return string.IsNullOrEmpty(avatarMask.GetTransformPath(0)) && !avatarMask.GetTransformActive(0);
|
||||
|
||||
// walk the avatar list to find the matching transform
|
||||
for (int i = 0; i < avatarMask.transformCount; i++)
|
||||
{
|
||||
if (genericRootNode == animator.transform.Find(avatarMask.GetTransformPath(i)))
|
||||
return !avatarMask.GetTransformActive(i);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the generic root transform node. Returns null if it is the root node, OR if it not a generic node
|
||||
private Transform GetGenericRootNode(GameObject gameObject)
|
||||
{
|
||||
var animator = GetBinding(gameObject != null ? gameObject.GetComponent<PlayableDirector>() : null);
|
||||
if (animator == null)
|
||||
return null;
|
||||
|
||||
if (animator.isHuman)
|
||||
return null;
|
||||
|
||||
if (animator.avatar == null)
|
||||
return null;
|
||||
|
||||
// this returns the bone name, but not the full path
|
||||
var rootName = animator.avatar.humanDescription.m_RootMotionBoneName;
|
||||
if (rootName == animator.name || string.IsNullOrEmpty(rootName))
|
||||
return null;
|
||||
|
||||
// walk the hierarchy to find the first bone with this name
|
||||
return FindInHierarchyBreadthFirst(animator.transform, rootName);
|
||||
}
|
||||
|
||||
internal bool AnimatesRootTransform()
|
||||
{
|
||||
// infinite mode
|
||||
if (AnimationPlayableAsset.HasRootTransforms(m_InfiniteClip))
|
||||
return true;
|
||||
|
||||
// clip mode
|
||||
foreach (var c in GetClips())
|
||||
{
|
||||
var apa = c.asset as AnimationPlayableAsset;
|
||||
if (apa != null && apa.hasRootTransforms)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly Queue<Transform> s_CachedQueue = new Queue<Transform>(100);
|
||||
private static Transform FindInHierarchyBreadthFirst(Transform t, string name)
|
||||
{
|
||||
s_CachedQueue.Clear();
|
||||
s_CachedQueue.Enqueue(t);
|
||||
while (s_CachedQueue.Count > 0)
|
||||
{
|
||||
var r = s_CachedQueue.Dequeue();
|
||||
if (r.name == name)
|
||||
return r;
|
||||
for (int i = 0; i < r.childCount; i++)
|
||||
s_CachedQueue.Enqueue(r.GetChild(i));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d21dcc2386d650c4597f3633c75a1f98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,15 @@
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
interface ICurvesOwner
|
||||
{
|
||||
AnimationClip curves { get; }
|
||||
bool hasCurves { get; }
|
||||
double duration { get; }
|
||||
void CreateCurves(string curvesClipName);
|
||||
|
||||
string defaultCurvesName { get; }
|
||||
Object asset { get; }
|
||||
Object assetOwner { get; }
|
||||
TrackAsset targetTrack { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89b31ff5ca0a5eb4797ac65d43949807
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c04c8cb23b78e04492e0f310cdee93e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
partial class AnimationPlayableAsset : ISerializationCallbackReceiver
|
||||
{
|
||||
enum Versions
|
||||
{
|
||||
Initial = 0,
|
||||
RotationAsEuler = 1,
|
||||
}
|
||||
static readonly int k_LatestVersion = (int)Versions.RotationAsEuler;
|
||||
[SerializeField, HideInInspector] int m_Version;
|
||||
|
||||
[SerializeField, Obsolete("Use m_RotationEuler Instead", false), HideInInspector]
|
||||
private Quaternion m_Rotation = Quaternion.identity; // deprecated. now saves in euler angles
|
||||
|
||||
/// <summary>
|
||||
/// Called before Unity serializes this object.
|
||||
/// </summary>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
m_Version = k_LatestVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after Unity deserializes this object.
|
||||
/// </summary>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
if (m_Version < k_LatestVersion)
|
||||
{
|
||||
OnUpgradeFromVersion(m_Version); //upgrade derived classes
|
||||
}
|
||||
}
|
||||
|
||||
void OnUpgradeFromVersion(int oldVersion)
|
||||
{
|
||||
if (oldVersion < (int)Versions.RotationAsEuler)
|
||||
AnimationPlayableAssetUpgrade.ConvertRotationToEuler(this);
|
||||
}
|
||||
|
||||
static class AnimationPlayableAssetUpgrade
|
||||
{
|
||||
public static void ConvertRotationToEuler(AnimationPlayableAsset asset)
|
||||
{
|
||||
#pragma warning disable 618
|
||||
asset.m_EulerAngles = asset.m_Rotation.eulerAngles;
|
||||
#pragma warning restore 618
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6773203120b27984d9a8572fa3564f03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
partial class AnimationTrack
|
||||
{
|
||||
// 649 is value is only assigned to. they can be updated from old files being serialized
|
||||
#pragma warning disable 649
|
||||
//fields that are used for upgrading should be put here, ideally as read-only
|
||||
[SerializeField, Obsolete("Use m_InfiniteClipOffsetEulerAngles Instead", false), HideInInspector]
|
||||
Quaternion m_OpenClipOffsetRotation = Quaternion.identity;
|
||||
|
||||
[SerializeField, Obsolete("Use m_RotationEuler Instead", false), HideInInspector]
|
||||
Quaternion m_Rotation = Quaternion.identity;
|
||||
|
||||
[SerializeField, Obsolete("Use m_RootTransformOffsetMode", false), HideInInspector]
|
||||
bool m_ApplyOffsets;
|
||||
#pragma warning restore 649
|
||||
|
||||
/// <summary>
|
||||
/// Translation offset of a track in infinite mode.
|
||||
/// This property is obsolete. Use <see cref="UnityEngine.Timeline.AnimationTrack.infiniteClipOffsetPosition"/> instead.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("openClipOffsetPosition has been deprecated. Use infiniteClipOffsetPosition instead. (UnityUpgradable) -> infiniteClipOffsetPosition", true)]
|
||||
public Vector3 openClipOffsetPosition
|
||||
{
|
||||
get { return infiniteClipOffsetPosition; }
|
||||
set { infiniteClipOffsetPosition = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation offset of a track in infinite mode.
|
||||
/// This property is obsolete. Use <see cref="UnityEngine.Timeline.AnimationTrack.infiniteClipOffsetRotation"/> instead.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("openClipOffsetRotation has been deprecated. Use infiniteClipOffsetRotation instead. (UnityUpgradable) -> infiniteClipOffsetRotation", true)]
|
||||
public Quaternion openClipOffsetRotation
|
||||
{
|
||||
get { return infiniteClipOffsetRotation; }
|
||||
set { infiniteClipOffsetRotation = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Euler angle representation of the rotation offset of the track when in infinite mode.
|
||||
/// This property is obsolete. Use <see cref="UnityEngine.Timeline.AnimationTrack.infiniteClipOffsetEulerAngles"/> instead.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("openClipOffsetEulerAngles has been deprecated. Use infiniteClipOffsetEulerAngles instead. (UnityUpgradable) -> infiniteClipOffsetEulerAngles", true)]
|
||||
public Vector3 openClipOffsetEulerAngles
|
||||
{
|
||||
get { return infiniteClipOffsetEulerAngles; }
|
||||
set { infiniteClipOffsetEulerAngles = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saved state of pre-extrapolation for clips converted to infinite mode.
|
||||
/// This property is obsolete. Use <see cref="UnityEngine.Timeline.AnimationTrack.infiniteClipPreExtrapolation"/> instead.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("openClipPreExtrapolation has been deprecated. Use infiniteClipPreExtrapolation instead. (UnityUpgradable) -> infiniteClipPreExtrapolation", true)]
|
||||
public TimelineClip.ClipExtrapolation openClipPreExtrapolation
|
||||
{
|
||||
get { return infiniteClipPreExtrapolation; }
|
||||
set { infiniteClipPreExtrapolation = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saved state of post-extrapolation for clips converted to infinite mode.
|
||||
/// This property is obsolete. Use <see cref="UnityEngine.Timeline.AnimationTrack.infiniteClipPostExtrapolation"/> instead.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("openClipPostExtrapolation has been deprecated. Use infiniteClipPostExtrapolation instead. (UnityUpgradable) -> infiniteClipPostExtrapolation", true)]
|
||||
public TimelineClip.ClipExtrapolation openClipPostExtrapolation
|
||||
{
|
||||
get { return infiniteClipPostExtrapolation; }
|
||||
set { infiniteClipPostExtrapolation = value; }
|
||||
}
|
||||
|
||||
internal override void OnUpgradeFromVersion(int oldVersion)
|
||||
{
|
||||
if (oldVersion < (int)Versions.RotationAsEuler)
|
||||
AnimationTrackUpgrade.ConvertRotationsToEuler(this);
|
||||
if (oldVersion < (int)Versions.RootMotionUpgrade)
|
||||
AnimationTrackUpgrade.ConvertRootMotion(this);
|
||||
if (oldVersion < (int)Versions.AnimatedTrackProperties)
|
||||
AnimationTrackUpgrade.ConvertInfiniteTrack(this);
|
||||
}
|
||||
|
||||
// 612 is Property is Obsolete
|
||||
// 618 is Field is Obsolete
|
||||
#pragma warning disable 612, 618
|
||||
static class AnimationTrackUpgrade
|
||||
{
|
||||
public static void ConvertRotationsToEuler(AnimationTrack track)
|
||||
{
|
||||
track.m_EulerAngles = track.m_Rotation.eulerAngles;
|
||||
track.m_InfiniteClipOffsetEulerAngles = track.m_OpenClipOffsetRotation.eulerAngles;
|
||||
}
|
||||
|
||||
public static void ConvertRootMotion(AnimationTrack track)
|
||||
{
|
||||
track.m_TrackOffset = TrackOffset.Auto; // loaded tracks should use legacy mode
|
||||
|
||||
// reset offsets if not applied
|
||||
if (!track.m_ApplyOffsets)
|
||||
{
|
||||
track.m_Position = Vector3.zero;
|
||||
track.m_EulerAngles = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ConvertInfiniteTrack(AnimationTrack track)
|
||||
{
|
||||
track.m_InfiniteClip = track.m_AnimClip;
|
||||
track.m_AnimClip = null;
|
||||
}
|
||||
}
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b0c53b13a1539949b3b212e049151d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,34 @@
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
partial class TimelineClip
|
||||
{
|
||||
enum Versions
|
||||
{
|
||||
Initial = 0,
|
||||
ClipInFromGlobalToLocal = 1
|
||||
}
|
||||
const int k_LatestVersion = (int)Versions.ClipInFromGlobalToLocal;
|
||||
[SerializeField, HideInInspector] int m_Version;
|
||||
|
||||
//fields that are used for upgrading should be put here, ideally as read-only
|
||||
|
||||
void UpgradeToLatestVersion()
|
||||
{
|
||||
if (m_Version < (int)Versions.ClipInFromGlobalToLocal)
|
||||
{
|
||||
TimelineClipUpgrade.UpgradeClipInFromGlobalToLocal(this);
|
||||
}
|
||||
}
|
||||
|
||||
static class TimelineClipUpgrade
|
||||
{
|
||||
// version 0->1, clipIn move from global to local
|
||||
public static void UpgradeClipInFromGlobalToLocal(TimelineClip clip)
|
||||
{
|
||||
// case 936751 -- clipIn was serialized in global, not local offset
|
||||
if (clip.m_ClipIn > 0 && clip.m_TimeScale > float.Epsilon)
|
||||
clip.m_ClipIn *= clip.m_TimeScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a4f0c91a28ece04198b200dd55145d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,21 @@
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
partial class TimelineAsset
|
||||
{
|
||||
enum Versions
|
||||
{
|
||||
Initial = 0
|
||||
}
|
||||
const int k_LatestVersion = (int)Versions.Initial;
|
||||
[SerializeField, HideInInspector] int m_Version;
|
||||
|
||||
//fields that are used for upgrading should be put here, ideally as read-only
|
||||
|
||||
void UpgradeToLatestVersion()
|
||||
{ }
|
||||
|
||||
//upgrade code should go into this class
|
||||
static class TimelineAssetUpgrade
|
||||
{ }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95c91abdcc1ea03458c2ea4e9626a5d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
partial class TrackAsset : ISerializationCallbackReceiver
|
||||
{
|
||||
internal enum Versions
|
||||
{
|
||||
Initial = 0,
|
||||
RotationAsEuler = 1,
|
||||
RootMotionUpgrade = 2,
|
||||
AnimatedTrackProperties = 3
|
||||
}
|
||||
|
||||
const int k_LatestVersion = (int)Versions.AnimatedTrackProperties;
|
||||
|
||||
[SerializeField, HideInInspector] int m_Version;
|
||||
|
||||
[Obsolete("Please use m_InfiniteClip (on AnimationTrack) instead.", false)]
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("m_animClip")]
|
||||
internal AnimationClip m_AnimClip;
|
||||
|
||||
/// <summary>
|
||||
/// Called before a track is serialized.
|
||||
/// </summary>
|
||||
protected virtual void OnBeforeTrackSerialize() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called after a track has been deserialized.
|
||||
/// </summary>
|
||||
protected virtual void OnAfterTrackDeserialize() { }
|
||||
|
||||
internal virtual void OnUpgradeFromVersion(int oldVersion) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called before Unity serializes this object.
|
||||
/// </summary>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
m_Version = k_LatestVersion;
|
||||
|
||||
//make sure children are correctly parented
|
||||
if (m_Children != null)
|
||||
{
|
||||
for (var i = m_Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var asset = m_Children[i] as TrackAsset;
|
||||
if (asset != null && asset.parent != this)
|
||||
asset.parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
OnBeforeTrackSerialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after Unity deserializes this object.
|
||||
/// </summary>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
// Clear the clip cache when a deserialize is performed, or
|
||||
// we can get out of sync when performing Undo
|
||||
m_ClipsCache = null;
|
||||
Invalidate();
|
||||
|
||||
if (m_Version < k_LatestVersion)
|
||||
{
|
||||
UpgradeToLatestVersion(); //upgrade TrackAsset
|
||||
OnUpgradeFromVersion(m_Version); //upgrade derived classes
|
||||
}
|
||||
|
||||
foreach (var marker in GetMarkers())
|
||||
{
|
||||
marker.Initialize(this);
|
||||
}
|
||||
|
||||
OnAfterTrackDeserialize();
|
||||
}
|
||||
|
||||
//fields that are used for upgrading should be put here, ideally as read-only
|
||||
void UpgradeToLatestVersion()
|
||||
{ }
|
||||
|
||||
//upgrade code should go into this class
|
||||
static class TrackAssetUpgrade
|
||||
{ }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c68f34993bfe85e489158a29c99a20b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec817e5e5781e0a4983a1dc8875d1974
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,46 @@
|
||||
#if UNITY_EDITOR && UNITY_2021_1_OR_NEWER
|
||||
#define CAN_USE_CUSTOM_HELP_URL
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
#if CAN_USE_CUSTOM_HELP_URL
|
||||
|
||||
using UnityEditor.PackageManager;
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
class TimelineHelpURLAttribute : HelpURLAttribute
|
||||
{
|
||||
const string k_BaseURL = "https://docs.unity3d.com/Packages/com.unity.timeline@";
|
||||
const string k_MidURL = "/api/";
|
||||
const string k_EndURL = ".html";
|
||||
const string k_FallbackVersion = "latest";
|
||||
|
||||
static readonly string k_PackageVersion;
|
||||
|
||||
static TimelineHelpURLAttribute()
|
||||
{
|
||||
PackageInfo packageInfo = PackageInfo.FindForAssembly(typeof(TimelineAsset).Assembly);
|
||||
k_PackageVersion = packageInfo == null ? k_FallbackVersion : packageInfo.version.Substring(0, 3);
|
||||
}
|
||||
|
||||
public TimelineHelpURLAttribute(Type type)
|
||||
: base(HelpURL(type)) {}
|
||||
|
||||
static string HelpURL(Type type)
|
||||
{
|
||||
return $"{k_BaseURL}{k_PackageVersion}{k_MidURL}{type.FullName}{k_EndURL}";
|
||||
}
|
||||
}
|
||||
#else //HelpURL attribute is `sealed` in previous Unity versions
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
class TimelineHelpURLAttribute : Attribute
|
||||
{
|
||||
public TimelineHelpURLAttribute(Type type) { }
|
||||
}
|
||||
#endif
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c35b6ea5f1d7cd74eac79413eb70670c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute used to specify the color of the track and its clips inside the Timeline Editor.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class TrackColorAttribute : Attribute
|
||||
{
|
||||
Color m_Color;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Color color
|
||||
{
|
||||
get { return m_Color; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the track color using [0-1] R,G,B values.
|
||||
/// </summary>
|
||||
/// <param name="r">Red value [0-1].</param>
|
||||
/// <param name="g">Green value [0-1].</param>
|
||||
/// <param name="b">Blue value [0-1].</param>
|
||||
public TrackColorAttribute(float r, float g, float b)
|
||||
{
|
||||
m_Color = new Color(r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c3d52cc5c46d7946a920e21901ff38e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d19b75372f4e44d4fa4b2cffbb54124b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
[Serializable]
|
||||
[NotKeyable]
|
||||
class AudioClipProperties : PlayableBehaviour
|
||||
{
|
||||
[Range(0.0f, 1.0f)]
|
||||
public float volume = 1.0f;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d60a406ab64c434e9d731914e11a51e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using UnityEngine.Audio;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
[Serializable]
|
||||
class AudioMixerProperties : PlayableBehaviour
|
||||
{
|
||||
[Range(0.0f, 1.0f)]
|
||||
public float volume = 1.0f;
|
||||
|
||||
[Range(-1.0f, 1.0f)]
|
||||
public float stereoPan = 0.0f;
|
||||
|
||||
[Range(0.0f, 1.0f)]
|
||||
public float spatialBlend = 0.0f;
|
||||
|
||||
public override void PrepareFrame(Playable playable, FrameData info)
|
||||
{
|
||||
if (!playable.IsValid() || !playable.IsPlayableOfType<AudioMixerPlayable>())
|
||||
return;
|
||||
|
||||
var inputCount = playable.GetInputCount();
|
||||
|
||||
for (int i = 0; i < inputCount; ++i)
|
||||
{
|
||||
if (playable.GetInputWeight(i) > 0.0f)
|
||||
{
|
||||
var input = playable.GetInput(i);
|
||||
|
||||
if (input.IsValid() && input.IsPlayableOfType<AudioClipPlayable>())
|
||||
{
|
||||
var audioClipPlayable = (AudioClipPlayable)input;
|
||||
var audioClipProperties = input.GetHandle().GetObject<AudioClipProperties>();
|
||||
|
||||
audioClipPlayable.SetVolume(Mathf.Clamp01(volume * audioClipProperties.volume));
|
||||
audioClipPlayable.SetStereoPan(Mathf.Clamp(stereoPan, -1.0f, 1.0f));
|
||||
audioClipPlayable.SetSpatialBlend(Mathf.Clamp01(spatialBlend));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8c4a920f001ca64680ed6fdb52d1753
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Audio;
|
||||
#if UNITY_EDITOR
|
||||
using System.ComponentModel;
|
||||
#endif
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// PlayableAsset wrapper for an AudioClip in Timeline.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
#if UNITY_EDITOR
|
||||
[DisplayName("Audio Clip")]
|
||||
#endif
|
||||
public class AudioPlayableAsset : PlayableAsset, ITimelineClipAsset
|
||||
{
|
||||
[SerializeField] AudioClip m_Clip;
|
||||
#pragma warning disable 649 //Field is never assigned to and will always have its default value
|
||||
[SerializeField] bool m_Loop;
|
||||
[SerializeField, HideInInspector] float m_bufferingTime = 0.1f;
|
||||
[SerializeField] AudioClipProperties m_ClipProperties = new AudioClipProperties();
|
||||
|
||||
// the amount of time to give the clip to load prior to it's start time
|
||||
internal float bufferingTime
|
||||
{
|
||||
get { return m_bufferingTime; }
|
||||
set { m_bufferingTime = value; }
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Playable m_LiveClipPlayable = Playable.Null;
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The audio clip to be played
|
||||
/// </summary>
|
||||
public AudioClip clip
|
||||
{
|
||||
get { return m_Clip; }
|
||||
set { m_Clip = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the audio clip loops.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this to loop the audio clip when the duration of the timeline clip exceeds that of the audio clip.
|
||||
/// </remarks>
|
||||
public bool loop
|
||||
{
|
||||
get { return m_Loop; }
|
||||
set { m_Loop = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the duration required to play the audio clip exactly once
|
||||
/// </summary>
|
||||
public override double duration
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Clip == null)
|
||||
return base.duration;
|
||||
|
||||
// use this instead of length to avoid rounding precision errors,
|
||||
return (double)m_Clip.samples / m_Clip.frequency;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a description of the PlayableOutputs that may be created for this asset.
|
||||
/// </summary>
|
||||
public override IEnumerable<PlayableBinding> outputs
|
||||
{
|
||||
get { yield return AudioPlayableBinding.Create(name, this); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the root of a Playable subgraph to play the audio clip.
|
||||
/// </summary>
|
||||
/// <param name="graph">PlayableGraph that will own the playable</param>
|
||||
/// <param name="go">The GameObject that triggered the graph build</param>
|
||||
/// <returns>The root playable of the subgraph</returns>
|
||||
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
|
||||
{
|
||||
if (m_Clip == null)
|
||||
return Playable.Null;
|
||||
|
||||
var audioClipPlayable = AudioClipPlayable.Create(graph, m_Clip, m_Loop);
|
||||
audioClipPlayable.GetHandle().SetScriptInstance(m_ClipProperties.Clone());
|
||||
|
||||
#if UNITY_EDITOR
|
||||
m_LiveClipPlayable = audioClipPlayable;
|
||||
#endif
|
||||
|
||||
return audioClipPlayable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the capabilities of TimelineClips that contain an AudioPlayableAsset
|
||||
/// </summary>
|
||||
public ClipCaps clipCaps
|
||||
{
|
||||
get
|
||||
{
|
||||
return ClipCaps.ClipIn |
|
||||
ClipCaps.SpeedMultiplier |
|
||||
ClipCaps.Blending |
|
||||
(m_Loop ? ClipCaps.Looping : ClipCaps.None);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal void LiveLink()
|
||||
{
|
||||
if (!m_LiveClipPlayable.IsValid())
|
||||
return;
|
||||
|
||||
var audioMixerProperties = m_LiveClipPlayable.GetHandle().GetObject<AudioClipProperties>();
|
||||
|
||||
if (audioMixerProperties == null)
|
||||
return;
|
||||
|
||||
audioMixerProperties.volume = m_ClipProperties.volume;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f10dd60657c6004587f237a7e90f8e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Audio;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// A Timeline track that can play AudioClips.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[TrackClipType(typeof(AudioPlayableAsset), false)]
|
||||
[TrackBindingType(typeof(AudioSource))]
|
||||
[ExcludeFromPreset]
|
||||
[TimelineHelpURL(typeof(AudioTrack))]
|
||||
public class AudioTrack : TrackAsset
|
||||
{
|
||||
[SerializeField]
|
||||
AudioMixerProperties m_TrackProperties = new AudioMixerProperties();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Playable m_LiveMixerPlayable = Playable.Null;
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Create an TimelineClip for playing an AudioClip on this track.
|
||||
/// </summary>
|
||||
/// <param name="clip">The audio clip to play</param>
|
||||
/// <returns>A TimelineClip with an AudioPlayableAsset asset.</returns>
|
||||
public TimelineClip CreateClip(AudioClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return null;
|
||||
|
||||
var newClip = CreateDefaultClip();
|
||||
|
||||
var audioAsset = newClip.asset as AudioPlayableAsset;
|
||||
if (audioAsset != null)
|
||||
audioAsset.clip = clip;
|
||||
|
||||
newClip.duration = clip.length;
|
||||
newClip.displayName = clip.name;
|
||||
|
||||
return newClip;
|
||||
}
|
||||
|
||||
internal override Playable CompileClips(PlayableGraph graph, GameObject go, IList<TimelineClip> timelineClips, IntervalTree<RuntimeElement> tree)
|
||||
{
|
||||
var clipBlender = AudioMixerPlayable.Create(graph, timelineClips.Count);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
clipBlender.GetHandle().SetScriptInstance(m_TrackProperties.Clone());
|
||||
m_LiveMixerPlayable = clipBlender;
|
||||
#else
|
||||
if (hasCurves)
|
||||
clipBlender.GetHandle().SetScriptInstance(m_TrackProperties.Clone());
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < timelineClips.Count; i++)
|
||||
{
|
||||
var c = timelineClips[i];
|
||||
var asset = c.asset as PlayableAsset;
|
||||
if (asset == null)
|
||||
continue;
|
||||
|
||||
var buffer = 0.1f;
|
||||
var audioAsset = c.asset as AudioPlayableAsset;
|
||||
if (audioAsset != null)
|
||||
buffer = audioAsset.bufferingTime;
|
||||
|
||||
var source = asset.CreatePlayable(graph, go);
|
||||
if (!source.IsValid())
|
||||
continue;
|
||||
|
||||
if (source.IsPlayableOfType<AudioClipPlayable>())
|
||||
{
|
||||
// Enforce initial values on all clips
|
||||
var audioClipPlayable = (AudioClipPlayable)source;
|
||||
var audioClipProperties = audioClipPlayable.GetHandle().GetObject<AudioClipProperties>();
|
||||
|
||||
audioClipPlayable.SetVolume(Mathf.Clamp01(m_TrackProperties.volume * audioClipProperties.volume));
|
||||
audioClipPlayable.SetStereoPan(Mathf.Clamp(m_TrackProperties.stereoPan, -1.0f, 1.0f));
|
||||
audioClipPlayable.SetSpatialBlend(Mathf.Clamp01(m_TrackProperties.spatialBlend));
|
||||
}
|
||||
|
||||
tree.Add(new ScheduleRuntimeClip(c, source, clipBlender, buffer));
|
||||
graph.Connect(source, 0, clipBlender, i);
|
||||
source.SetSpeed(c.timeScale);
|
||||
source.SetDuration(c.extrapolatedDuration);
|
||||
clipBlender.SetInputWeight(source, 1.0f);
|
||||
}
|
||||
|
||||
ConfigureTrackAnimation(tree, go, clipBlender);
|
||||
|
||||
return clipBlender;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<PlayableBinding> outputs
|
||||
{
|
||||
get { yield return AudioPlayableBinding.Create(name, this); }
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal void LiveLink()
|
||||
{
|
||||
if (!m_LiveMixerPlayable.IsValid())
|
||||
return;
|
||||
|
||||
var audioMixerProperties = m_LiveMixerPlayable.GetHandle().GetObject<AudioMixerProperties>();
|
||||
|
||||
if (audioMixerProperties == null)
|
||||
return;
|
||||
|
||||
audioMixerProperties.volume = m_TrackProperties.volume;
|
||||
audioMixerProperties.stereoPan = m_TrackProperties.stereoPan;
|
||||
audioMixerProperties.spatialBlend = m_TrackProperties.spatialBlend;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
m_TrackProperties.volume = Mathf.Clamp01(m_TrackProperties.volume);
|
||||
m_TrackProperties.stereoPan = Mathf.Clamp(m_TrackProperties.stereoPan, -1.0f, 1.0f);
|
||||
m_TrackProperties.spatialBlend = Mathf.Clamp01(m_TrackProperties.spatialBlend);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b22792c3b570444eb18cb78c2af3a74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,90 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the timeline features supported by a clip
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ClipCaps
|
||||
{
|
||||
/// <summary>
|
||||
/// No features are supported.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The clip supports loops.
|
||||
/// </summary>
|
||||
Looping = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The clip supports clip extrapolation.
|
||||
/// </summary>
|
||||
Extrapolation = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The clip supports initial local times greater than zero.
|
||||
/// </summary>
|
||||
ClipIn = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// The clip supports time scaling.
|
||||
/// </summary>
|
||||
SpeedMultiplier = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// The clip supports blending between clips.
|
||||
/// </summary>
|
||||
Blending = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// The clip supports time scaling, and sets the default trim mode in the editor to scale the clip
|
||||
/// (speed multiplier) when the start/end of the clip is trimmed.
|
||||
/// </summary>
|
||||
AutoScale = 1 << 5 | SpeedMultiplier,
|
||||
|
||||
/// <summary>
|
||||
/// All features are supported.
|
||||
/// </summary>
|
||||
All = ~None
|
||||
}
|
||||
|
||||
static class TimelineClipCapsExtensions
|
||||
{
|
||||
public static bool SupportsLooping(this TimelineClip clip)
|
||||
{
|
||||
return clip != null && (clip.clipCaps & ClipCaps.Looping) != ClipCaps.None;
|
||||
}
|
||||
|
||||
public static bool SupportsExtrapolation(this TimelineClip clip)
|
||||
{
|
||||
return clip != null && (clip.clipCaps & ClipCaps.Extrapolation) != ClipCaps.None;
|
||||
}
|
||||
|
||||
public static bool SupportsClipIn(this TimelineClip clip)
|
||||
{
|
||||
return clip != null && (clip.clipCaps & ClipCaps.ClipIn) != ClipCaps.None;
|
||||
}
|
||||
|
||||
public static bool SupportsSpeedMultiplier(this TimelineClip clip)
|
||||
{
|
||||
return clip != null && (clip.clipCaps & ClipCaps.SpeedMultiplier) != ClipCaps.None;
|
||||
}
|
||||
|
||||
public static bool SupportsBlending(this TimelineClip clip)
|
||||
{
|
||||
return clip != null && (clip.clipCaps & ClipCaps.Blending) != ClipCaps.None;
|
||||
}
|
||||
|
||||
public static bool HasAll(this ClipCaps caps, ClipCaps flags)
|
||||
{
|
||||
return (caps & flags) == flags;
|
||||
}
|
||||
|
||||
public static bool HasAny(this ClipCaps caps, ClipCaps flags)
|
||||
{
|
||||
return (caps & flags) != 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 667a99762bdf5484fbaa02573fd396e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea998292f45ea494d9e100f5f6362f91
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,440 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Playable Asset that generates playables for controlling time-related elements on a GameObject.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[NotKeyable]
|
||||
public class ControlPlayableAsset : PlayableAsset, IPropertyPreview, ITimelineClipAsset
|
||||
{
|
||||
const int k_MaxRandInt = 10000;
|
||||
static readonly List<PlayableDirector> k_EmptyDirectorsList = new List<PlayableDirector>(0);
|
||||
static readonly List<ParticleSystem> k_EmptyParticlesList = new List<ParticleSystem>(0);
|
||||
static readonly HashSet<ParticleSystem> s_SubEmitterCollector = new HashSet<ParticleSystem>();
|
||||
|
||||
/// <summary>
|
||||
/// GameObject in the scene to control, or the parent of the instantiated prefab.
|
||||
/// </summary>
|
||||
[SerializeField] public ExposedReference<GameObject> sourceGameObject;
|
||||
|
||||
/// <summary>
|
||||
/// Prefab object that will be instantiated.
|
||||
/// </summary>
|
||||
[SerializeField] public GameObject prefabGameObject;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether Particle Systems will be controlled.
|
||||
/// </summary>
|
||||
[SerializeField] public bool updateParticle = true;
|
||||
|
||||
/// <summary>
|
||||
/// Random seed to supply particle systems that are set to use autoRandomSeed
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used to maintain determinism when playing back in timeline. Sub emitters will be assigned incrementing random seeds to maintain determinism and distinction.
|
||||
/// </remarks>
|
||||
[SerializeField] public uint particleRandomSeed;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether playableDirectors are controlled.
|
||||
/// </summary>
|
||||
[SerializeField] public bool updateDirector = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether Monobehaviours implementing ITimeControl will be controlled.
|
||||
/// </summary>
|
||||
[SerializeField] public bool updateITimeControl = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether to search the entire hierarchy for controllable components.
|
||||
/// </summary>
|
||||
[SerializeField] public bool searchHierarchy;
|
||||
|
||||
/// <summary>
|
||||
/// Indicate whether GameObject activation is controlled
|
||||
/// </summary>
|
||||
[SerializeField] public bool active = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the active state of the GameObject when Timeline is stopped.
|
||||
/// </summary>
|
||||
[SerializeField] public ActivationControlPlayable.PostPlaybackState postPlayback = ActivationControlPlayable.PostPlaybackState.Revert;
|
||||
|
||||
PlayableAsset m_ControlDirectorAsset;
|
||||
double m_Duration = PlayableBinding.DefaultDuration;
|
||||
bool m_SupportLoop;
|
||||
|
||||
private static HashSet<PlayableDirector> s_ProcessedDirectors = new HashSet<PlayableDirector>();
|
||||
private static HashSet<GameObject> s_CreatedPrefabs = new HashSet<GameObject>();
|
||||
|
||||
// does the last instance created control directors and/or particles
|
||||
internal bool controllingDirectors { get; private set; }
|
||||
internal bool controllingParticles { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This function is called when the object is loaded.
|
||||
/// </summary>
|
||||
public void OnEnable()
|
||||
{
|
||||
// can't be set in a constructor
|
||||
if (particleRandomSeed == 0)
|
||||
particleRandomSeed = (uint)Random.Range(1, k_MaxRandInt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the duration in seconds needed to play the underlying director or particle system exactly once.
|
||||
/// </summary>
|
||||
public override double duration { get { return m_Duration; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the capabilities of TimelineClips that contain a ControlPlayableAsset
|
||||
/// </summary>
|
||||
public ClipCaps clipCaps
|
||||
{
|
||||
get { return ClipCaps.ClipIn | ClipCaps.SpeedMultiplier | (m_SupportLoop ? ClipCaps.Looping : ClipCaps.None); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the root of a Playable subgraph to control the contents of the game object.
|
||||
/// </summary>
|
||||
/// <param name="graph">PlayableGraph that will own the playable</param>
|
||||
/// <param name="go">The GameObject that triggered the graph build</param>
|
||||
/// <returns>The root playable of the subgraph</returns>
|
||||
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
|
||||
{
|
||||
// case 989856
|
||||
if (prefabGameObject != null)
|
||||
{
|
||||
if (s_CreatedPrefabs.Contains(prefabGameObject))
|
||||
{
|
||||
Debug.LogWarningFormat("Control Track Clip ({0}) is causing a prefab to instantiate itself recursively. Aborting further instances.", name);
|
||||
return Playable.Create(graph);
|
||||
}
|
||||
s_CreatedPrefabs.Add(prefabGameObject);
|
||||
}
|
||||
|
||||
Playable root = Playable.Null;
|
||||
var playables = new List<Playable>();
|
||||
|
||||
GameObject sourceObject = sourceGameObject.Resolve(graph.GetResolver());
|
||||
if (prefabGameObject != null)
|
||||
{
|
||||
Transform parenTransform = sourceObject != null ? sourceObject.transform : null;
|
||||
var controlPlayable = PrefabControlPlayable.Create(graph, prefabGameObject, parenTransform);
|
||||
|
||||
sourceObject = controlPlayable.GetBehaviour().prefabInstance;
|
||||
playables.Add(controlPlayable);
|
||||
}
|
||||
|
||||
m_Duration = PlayableBinding.DefaultDuration;
|
||||
m_SupportLoop = false;
|
||||
|
||||
controllingParticles = false;
|
||||
controllingDirectors = false;
|
||||
|
||||
if (sourceObject != null)
|
||||
{
|
||||
var directors = updateDirector ? GetComponent<PlayableDirector>(sourceObject) : k_EmptyDirectorsList;
|
||||
var particleSystems = updateParticle ? GetControllableParticleSystems(sourceObject) : k_EmptyParticlesList;
|
||||
|
||||
// update the duration and loop values (used for UI purposes) here
|
||||
// so they are tied to the latest gameObject bound
|
||||
UpdateDurationAndLoopFlag(directors, particleSystems);
|
||||
|
||||
var director = go.GetComponent<PlayableDirector>();
|
||||
if (director != null)
|
||||
m_ControlDirectorAsset = director.playableAsset;
|
||||
|
||||
if (go == sourceObject && prefabGameObject == null)
|
||||
{
|
||||
Debug.LogWarningFormat("Control Playable ({0}) is referencing the same PlayableDirector component than the one in which it is playing.", name);
|
||||
active = false;
|
||||
if (!searchHierarchy)
|
||||
updateDirector = false;
|
||||
}
|
||||
|
||||
if (active)
|
||||
CreateActivationPlayable(sourceObject, graph, playables);
|
||||
|
||||
if (updateDirector)
|
||||
SearchHierarchyAndConnectDirector(directors, graph, playables, prefabGameObject != null);
|
||||
|
||||
if (updateParticle)
|
||||
SearchHierarchyAndConnectParticleSystem(particleSystems, graph, playables);
|
||||
|
||||
if (updateITimeControl)
|
||||
SearchHierarchyAndConnectControlableScripts(GetControlableScripts(sourceObject), graph, playables);
|
||||
|
||||
// Connect Playables to Generic to Mixer
|
||||
root = ConnectPlayablesToMixer(graph, playables);
|
||||
}
|
||||
|
||||
if (prefabGameObject != null)
|
||||
s_CreatedPrefabs.Remove(prefabGameObject);
|
||||
|
||||
if (!root.IsValid())
|
||||
root = Playable.Create(graph);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
static Playable ConnectPlayablesToMixer(PlayableGraph graph, List<Playable> playables)
|
||||
{
|
||||
var mixer = Playable.Create(graph, playables.Count);
|
||||
|
||||
for (int i = 0; i != playables.Count; ++i)
|
||||
{
|
||||
ConnectMixerAndPlayable(graph, mixer, playables[i], i);
|
||||
}
|
||||
|
||||
mixer.SetPropagateSetTime(true);
|
||||
|
||||
return mixer;
|
||||
}
|
||||
|
||||
void CreateActivationPlayable(GameObject root, PlayableGraph graph,
|
||||
List<Playable> outplayables)
|
||||
{
|
||||
var activation = ActivationControlPlayable.Create(graph, root, postPlayback);
|
||||
if (activation.IsValid())
|
||||
outplayables.Add(activation);
|
||||
}
|
||||
|
||||
void SearchHierarchyAndConnectParticleSystem(IEnumerable<ParticleSystem> particleSystems, PlayableGraph graph,
|
||||
List<Playable> outplayables)
|
||||
{
|
||||
foreach (var particleSystem in particleSystems)
|
||||
{
|
||||
if (particleSystem != null)
|
||||
{
|
||||
controllingParticles = true;
|
||||
outplayables.Add(ParticleControlPlayable.Create(graph, particleSystem, particleRandomSeed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SearchHierarchyAndConnectDirector(IEnumerable<PlayableDirector> directors, PlayableGraph graph,
|
||||
List<Playable> outplayables, bool disableSelfReferences)
|
||||
{
|
||||
foreach (var director in directors)
|
||||
{
|
||||
if (director != null)
|
||||
{
|
||||
if (director.playableAsset != m_ControlDirectorAsset)
|
||||
{
|
||||
outplayables.Add(DirectorControlPlayable.Create(graph, director));
|
||||
controllingDirectors = true;
|
||||
}
|
||||
// if this self references, disable the director.
|
||||
else if (disableSelfReferences)
|
||||
{
|
||||
director.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SearchHierarchyAndConnectControlableScripts(IEnumerable<MonoBehaviour> controlableScripts, PlayableGraph graph, List<Playable> outplayables)
|
||||
{
|
||||
foreach (var script in controlableScripts)
|
||||
{
|
||||
outplayables.Add(TimeControlPlayable.Create(graph, (ITimeControl)script));
|
||||
}
|
||||
}
|
||||
|
||||
static void ConnectMixerAndPlayable(PlayableGraph graph, Playable mixer, Playable playable,
|
||||
int portIndex)
|
||||
{
|
||||
graph.Connect(playable, 0, mixer, portIndex);
|
||||
mixer.SetInputWeight(playable, 1.0f);
|
||||
}
|
||||
|
||||
internal IList<T> GetComponent<T>(GameObject gameObject)
|
||||
{
|
||||
var components = new List<T>();
|
||||
if (gameObject != null)
|
||||
{
|
||||
if (searchHierarchy)
|
||||
{
|
||||
gameObject.GetComponentsInChildren<T>(true, components);
|
||||
}
|
||||
else
|
||||
{
|
||||
gameObject.GetComponents<T>(components);
|
||||
}
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
internal static IEnumerable<MonoBehaviour> GetControlableScripts(GameObject root)
|
||||
{
|
||||
if (root == null)
|
||||
yield break;
|
||||
|
||||
foreach (var script in root.GetComponentsInChildren<MonoBehaviour>())
|
||||
{
|
||||
if (script is ITimeControl)
|
||||
yield return script;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateDurationAndLoopFlag(IList<PlayableDirector> directors, IList<ParticleSystem> particleSystems)
|
||||
{
|
||||
if (directors.Count == 0 && particleSystems.Count == 0)
|
||||
return;
|
||||
|
||||
const double invalidDuration = double.NegativeInfinity;
|
||||
|
||||
var maxDuration = invalidDuration;
|
||||
var supportsLoop = false;
|
||||
|
||||
foreach (var director in directors)
|
||||
{
|
||||
if (director.playableAsset != null)
|
||||
{
|
||||
var assetDuration = director.playableAsset.duration;
|
||||
|
||||
if (director.playableAsset is TimelineAsset && assetDuration > 0.0)
|
||||
// Timeline assets report being one tick shorter than they actually are, unless they are empty
|
||||
assetDuration = (double)((DiscreteTime)assetDuration).OneTickAfter();
|
||||
|
||||
maxDuration = Math.Max(maxDuration, assetDuration);
|
||||
supportsLoop = supportsLoop || director.extrapolationMode == DirectorWrapMode.Loop;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var particleSystem in particleSystems)
|
||||
{
|
||||
maxDuration = Math.Max(maxDuration, particleSystem.main.duration);
|
||||
supportsLoop = supportsLoop || particleSystem.main.loop;
|
||||
}
|
||||
|
||||
m_Duration = double.IsNegativeInfinity(maxDuration) ? PlayableBinding.DefaultDuration : maxDuration;
|
||||
m_SupportLoop = supportsLoop;
|
||||
}
|
||||
|
||||
IList<ParticleSystem> GetControllableParticleSystems(GameObject go)
|
||||
{
|
||||
var roots = new List<ParticleSystem>();
|
||||
|
||||
// searchHierarchy will look for particle systems on child objects.
|
||||
// once a particle system is found, all child particle systems are controlled with playables
|
||||
// unless they are subemitters
|
||||
|
||||
if (searchHierarchy || go.GetComponent<ParticleSystem>() != null)
|
||||
{
|
||||
GetControllableParticleSystems(go.transform, roots, s_SubEmitterCollector);
|
||||
s_SubEmitterCollector.Clear();
|
||||
}
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
static void GetControllableParticleSystems(Transform t, ICollection<ParticleSystem> roots, HashSet<ParticleSystem> subEmitters)
|
||||
{
|
||||
var ps = t.GetComponent<ParticleSystem>();
|
||||
if (ps != null)
|
||||
{
|
||||
if (!subEmitters.Contains(ps))
|
||||
{
|
||||
roots.Add(ps);
|
||||
CacheSubEmitters(ps, subEmitters);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < t.childCount; ++i)
|
||||
{
|
||||
GetControllableParticleSystems(t.GetChild(i), roots, subEmitters);
|
||||
}
|
||||
}
|
||||
|
||||
static void CacheSubEmitters(ParticleSystem ps, HashSet<ParticleSystem> subEmitters)
|
||||
{
|
||||
if (ps == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < ps.subEmitters.subEmittersCount; i++)
|
||||
{
|
||||
subEmitters.Add(ps.subEmitters.GetSubEmitterSystem(i));
|
||||
// don't call this recursively. subEmitters are only simulated one level deep.
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
|
||||
{
|
||||
// This method is no longer called by Control Tracks.
|
||||
if (director == null)
|
||||
return;
|
||||
|
||||
// prevent infinite recursion
|
||||
if (s_ProcessedDirectors.Contains(director))
|
||||
return;
|
||||
s_ProcessedDirectors.Add(director);
|
||||
|
||||
var gameObject = sourceGameObject.Resolve(director);
|
||||
if (gameObject != null)
|
||||
{
|
||||
if (updateParticle)// case 1076850 -- drive all emitters, not just roots.
|
||||
PreviewParticles(driver, gameObject.GetComponentsInChildren<ParticleSystem>(true));
|
||||
|
||||
if (active)
|
||||
PreviewActivation(driver, new[] { gameObject });
|
||||
|
||||
if (updateITimeControl)
|
||||
PreviewTimeControl(driver, director, GetControlableScripts(gameObject));
|
||||
|
||||
if (updateDirector)
|
||||
PreviewDirectors(driver, GetComponent<PlayableDirector>(gameObject));
|
||||
}
|
||||
s_ProcessedDirectors.Remove(director);
|
||||
}
|
||||
|
||||
internal static void PreviewParticles(IPropertyCollector driver, IEnumerable<ParticleSystem> particles)
|
||||
{
|
||||
foreach (var ps in particles)
|
||||
{
|
||||
driver.AddFromName<ParticleSystem>(ps.gameObject, "randomSeed");
|
||||
driver.AddFromName<ParticleSystem>(ps.gameObject, "autoRandomSeed");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void PreviewActivation(IPropertyCollector driver, IEnumerable<GameObject> objects)
|
||||
{
|
||||
foreach (var gameObject in objects)
|
||||
driver.AddFromName(gameObject, "m_IsActive");
|
||||
}
|
||||
|
||||
internal static void PreviewTimeControl(IPropertyCollector driver, PlayableDirector director, IEnumerable<MonoBehaviour> scripts)
|
||||
{
|
||||
foreach (var script in scripts)
|
||||
{
|
||||
var propertyPreview = script as IPropertyPreview;
|
||||
if (propertyPreview != null)
|
||||
propertyPreview.GatherProperties(director, driver);
|
||||
else
|
||||
driver.AddFromComponent(script.gameObject, script);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void PreviewDirectors(IPropertyCollector driver, IEnumerable<PlayableDirector> directors)
|
||||
{
|
||||
foreach (var childDirector in directors)
|
||||
{
|
||||
if (childDirector == null)
|
||||
continue;
|
||||
|
||||
var timeline = childDirector.playableAsset as TimelineAsset;
|
||||
if (timeline == null)
|
||||
continue;
|
||||
|
||||
timeline.GatherProperties(childDirector, driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48853ae485fa386428341ac1ea122570
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// A Track whose clips control time-related elements on a GameObject.
|
||||
/// </summary>
|
||||
[TrackClipType(typeof(ControlPlayableAsset), false)]
|
||||
[ExcludeFromPreset]
|
||||
[TimelineHelpURL(typeof(ControlTrack))]
|
||||
public class ControlTrack : TrackAsset
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
private static readonly HashSet<PlayableDirector> s_ProcessedDirectors = new HashSet<PlayableDirector>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GatherProperties(PlayableDirector director, IPropertyCollector driver)
|
||||
{
|
||||
if (director == null)
|
||||
return;
|
||||
|
||||
// avoid recursion
|
||||
if (s_ProcessedDirectors.Contains(director))
|
||||
return;
|
||||
|
||||
s_ProcessedDirectors.Add(director);
|
||||
|
||||
var particlesToPreview = new HashSet<ParticleSystem>();
|
||||
var activationToPreview = new HashSet<GameObject>();
|
||||
var timeControlToPreview = new HashSet<MonoBehaviour>();
|
||||
var subDirectorsToPreview = new HashSet<PlayableDirector>();
|
||||
|
||||
foreach (var clip in GetClips())
|
||||
{
|
||||
var controlPlayableAsset = clip.asset as ControlPlayableAsset;
|
||||
if (controlPlayableAsset == null)
|
||||
continue;
|
||||
|
||||
var gameObject = controlPlayableAsset.sourceGameObject.Resolve(director);
|
||||
if (gameObject == null)
|
||||
continue;
|
||||
|
||||
if (controlPlayableAsset.updateParticle)
|
||||
particlesToPreview.UnionWith(gameObject.GetComponentsInChildren<ParticleSystem>(true));
|
||||
if (controlPlayableAsset.active)
|
||||
activationToPreview.Add(gameObject);
|
||||
if (controlPlayableAsset.updateITimeControl)
|
||||
timeControlToPreview.UnionWith(ControlPlayableAsset.GetControlableScripts(gameObject));
|
||||
if (controlPlayableAsset.updateDirector)
|
||||
subDirectorsToPreview.UnionWith(controlPlayableAsset.GetComponent<PlayableDirector>(gameObject));
|
||||
}
|
||||
|
||||
ControlPlayableAsset.PreviewParticles(driver, particlesToPreview);
|
||||
ControlPlayableAsset.PreviewActivation(driver, activationToPreview);
|
||||
ControlPlayableAsset.PreviewTimeControl(driver, director, timeControlToPreview);
|
||||
ControlPlayableAsset.PreviewDirectors(driver, subDirectorsToPreview);
|
||||
|
||||
s_ProcessedDirectors.Remove(director);
|
||||
|
||||
particlesToPreview.Clear();
|
||||
activationToPreview.Clear();
|
||||
timeControlToPreview.Clear();
|
||||
subDirectorsToPreview.Clear();
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15e0374501f39d54eb30235764636e0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,226 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
struct DiscreteTime : IComparable
|
||||
{
|
||||
const double k_Tick = 1e-12;
|
||||
public static readonly DiscreteTime kMaxTime = new DiscreteTime(Int64.MaxValue);
|
||||
|
||||
readonly Int64 m_DiscreteTime;
|
||||
|
||||
public static double tickValue { get { return k_Tick; } }
|
||||
|
||||
public DiscreteTime(DiscreteTime time)
|
||||
{
|
||||
m_DiscreteTime = time.m_DiscreteTime;
|
||||
}
|
||||
|
||||
DiscreteTime(Int64 time)
|
||||
{
|
||||
m_DiscreteTime = time;
|
||||
}
|
||||
|
||||
public DiscreteTime(double time)
|
||||
{
|
||||
m_DiscreteTime = DoubleToDiscreteTime(time);
|
||||
}
|
||||
|
||||
public DiscreteTime(float time)
|
||||
{
|
||||
m_DiscreteTime = FloatToDiscreteTime(time);
|
||||
}
|
||||
|
||||
public DiscreteTime(int time)
|
||||
{
|
||||
m_DiscreteTime = IntToDiscreteTime(time);
|
||||
}
|
||||
|
||||
public DiscreteTime(int frame, double fps)
|
||||
{
|
||||
m_DiscreteTime = DoubleToDiscreteTime(frame * fps);
|
||||
}
|
||||
|
||||
public DiscreteTime OneTickBefore()
|
||||
{
|
||||
return new DiscreteTime(m_DiscreteTime - 1);
|
||||
}
|
||||
|
||||
public DiscreteTime OneTickAfter()
|
||||
{
|
||||
return new DiscreteTime(m_DiscreteTime + 1);
|
||||
}
|
||||
|
||||
public Int64 GetTick()
|
||||
{
|
||||
return m_DiscreteTime;
|
||||
}
|
||||
|
||||
public static DiscreteTime FromTicks(Int64 ticks)
|
||||
{
|
||||
return new DiscreteTime(ticks);
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is DiscreteTime)
|
||||
return m_DiscreteTime.CompareTo(((DiscreteTime)obj).m_DiscreteTime);
|
||||
return 1;
|
||||
}
|
||||
|
||||
public bool Equals(DiscreteTime other)
|
||||
{
|
||||
return m_DiscreteTime == other.m_DiscreteTime;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is DiscreteTime)
|
||||
return Equals((DiscreteTime)obj);
|
||||
return false;
|
||||
}
|
||||
|
||||
static Int64 DoubleToDiscreteTime(double time)
|
||||
{
|
||||
double number = (time / k_Tick) + 0.5;
|
||||
if (number < Int64.MaxValue && number > Int64.MinValue)
|
||||
return (Int64)number;
|
||||
throw new ArgumentOutOfRangeException("Time is over the discrete range.");
|
||||
}
|
||||
|
||||
static Int64 FloatToDiscreteTime(float time)
|
||||
{
|
||||
float number = (time / (float)k_Tick) + 0.5f;
|
||||
if (number < Int64.MaxValue && number > Int64.MinValue)
|
||||
return (Int64)number;
|
||||
throw new ArgumentOutOfRangeException("Time is over the discrete range.");
|
||||
}
|
||||
|
||||
static Int64 IntToDiscreteTime(int time)
|
||||
{
|
||||
return DoubleToDiscreteTime(time);
|
||||
}
|
||||
|
||||
static double ToDouble(Int64 time)
|
||||
{
|
||||
return time * k_Tick;
|
||||
}
|
||||
|
||||
static float ToFloat(Int64 time)
|
||||
{
|
||||
return (float)ToDouble(time);
|
||||
}
|
||||
|
||||
public static explicit operator double(DiscreteTime b)
|
||||
{
|
||||
return ToDouble(b.m_DiscreteTime);
|
||||
}
|
||||
|
||||
public static explicit operator float(DiscreteTime b)
|
||||
{
|
||||
return ToFloat(b.m_DiscreteTime);
|
||||
}
|
||||
|
||||
public static explicit operator Int64(DiscreteTime b)
|
||||
{
|
||||
return b.m_DiscreteTime;
|
||||
}
|
||||
|
||||
public static explicit operator DiscreteTime(double time)
|
||||
{
|
||||
return new DiscreteTime(time);
|
||||
}
|
||||
|
||||
public static explicit operator DiscreteTime(float time)
|
||||
{
|
||||
return new DiscreteTime(time);
|
||||
}
|
||||
|
||||
public static implicit operator DiscreteTime(Int32 time)
|
||||
{
|
||||
return new DiscreteTime(time);
|
||||
}
|
||||
|
||||
public static explicit operator DiscreteTime(Int64 time)
|
||||
{
|
||||
return new DiscreteTime(time);
|
||||
}
|
||||
|
||||
public static bool operator ==(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return lhs.m_DiscreteTime == rhs.m_DiscreteTime;
|
||||
}
|
||||
|
||||
public static bool operator !=(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
public static bool operator >(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return lhs.m_DiscreteTime > rhs.m_DiscreteTime;
|
||||
}
|
||||
|
||||
public static bool operator <(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return lhs.m_DiscreteTime < rhs.m_DiscreteTime;
|
||||
}
|
||||
|
||||
public static bool operator <=(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return lhs.m_DiscreteTime <= rhs.m_DiscreteTime;
|
||||
}
|
||||
|
||||
public static bool operator >=(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return lhs.m_DiscreteTime >= rhs.m_DiscreteTime;
|
||||
}
|
||||
|
||||
public static DiscreteTime operator +(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return new DiscreteTime(lhs.m_DiscreteTime + rhs.m_DiscreteTime);
|
||||
}
|
||||
|
||||
public static DiscreteTime operator -(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return new DiscreteTime(lhs.m_DiscreteTime - rhs.m_DiscreteTime);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return m_DiscreteTime.ToString();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return m_DiscreteTime.GetHashCode();
|
||||
}
|
||||
|
||||
public static DiscreteTime Min(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return new DiscreteTime(Math.Min(lhs.m_DiscreteTime, rhs.m_DiscreteTime));
|
||||
}
|
||||
|
||||
public static DiscreteTime Max(DiscreteTime lhs, DiscreteTime rhs)
|
||||
{
|
||||
return new DiscreteTime(Math.Max(lhs.m_DiscreteTime, rhs.m_DiscreteTime));
|
||||
}
|
||||
|
||||
public static double SnapToNearestTick(double time)
|
||||
{
|
||||
Int64 discreteTime = DoubleToDiscreteTime(time);
|
||||
return ToDouble(discreteTime);
|
||||
}
|
||||
|
||||
public static float SnapToNearestTick(float time)
|
||||
{
|
||||
Int64 discreteTime = FloatToDiscreteTime(time);
|
||||
return ToFloat(discreteTime);
|
||||
}
|
||||
|
||||
public static Int64 GetNearestTick(double time)
|
||||
{
|
||||
return DoubleToDiscreteTime(time);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8beed9aab74505d488e6befe54c3f6ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c6f60d349ea37048af03504fc872f33
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Runtime clip customized for 'infinite' tracks playables.
|
||||
/// Used for clips whose time needs to match the timelines exactly
|
||||
/// </summary>
|
||||
class InfiniteRuntimeClip : RuntimeElement
|
||||
{
|
||||
private Playable m_Playable;
|
||||
private static readonly Int64 kIntervalEnd = DiscreteTime.GetNearestTick(TimelineClip.kMaxTimeValue);
|
||||
|
||||
public InfiniteRuntimeClip(Playable playable)
|
||||
{
|
||||
m_Playable = playable;
|
||||
}
|
||||
|
||||
public override Int64 intervalStart
|
||||
{
|
||||
get { return 0; }
|
||||
}
|
||||
|
||||
public override Int64 intervalEnd
|
||||
{
|
||||
get { return kIntervalEnd; }
|
||||
}
|
||||
|
||||
public override bool enable
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
m_Playable.Play();
|
||||
else
|
||||
m_Playable.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
public override void EvaluateAt(double localTime, FrameData frameData)
|
||||
{
|
||||
m_Playable.SetTime(localTime);
|
||||
}
|
||||
|
||||
public override void DisableAt(double localTime, double rootDuration, FrameData frameData)
|
||||
{
|
||||
m_Playable.SetTime(localTime);
|
||||
enable = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b5abcb38bac0c54794ad732a3fa0de3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
interface IInterval
|
||||
{
|
||||
Int64 intervalStart { get; }
|
||||
Int64 intervalEnd { get; }
|
||||
}
|
||||
|
||||
struct IntervalTreeNode // interval node,
|
||||
{
|
||||
public Int64 center; // midpoint for this node
|
||||
public int first; // index of first element of this node in m_Entries
|
||||
public int last; // index of the last element of this node in m_Entries
|
||||
public int left; // index in m_Nodes of the left subnode
|
||||
public int right; // index in m_Nodes of the right subnode
|
||||
}
|
||||
|
||||
class IntervalTree<T> where T : IInterval
|
||||
{
|
||||
internal struct Entry
|
||||
{
|
||||
public Int64 intervalStart;
|
||||
public Int64 intervalEnd;
|
||||
public T item;
|
||||
}
|
||||
|
||||
const int kMinNodeSize = 10; // the minimum number of entries to have subnodes
|
||||
const int kInvalidNode = -1;
|
||||
const Int64 kCenterUnknown = Int64.MaxValue; // center hasn't been calculated. indicates no children
|
||||
|
||||
readonly List<Entry> m_Entries = new List<Entry>();
|
||||
readonly List<IntervalTreeNode> m_Nodes = new List<IntervalTreeNode>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the tree will be rebuilt on the next query
|
||||
/// </summary>
|
||||
public bool dirty { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Add an IInterval to the tree
|
||||
/// </summary>
|
||||
public void Add(T item)
|
||||
{
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
m_Entries.Add(
|
||||
new Entry()
|
||||
{
|
||||
intervalStart = item.intervalStart,
|
||||
intervalEnd = item.intervalEnd,
|
||||
item = item
|
||||
}
|
||||
);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query the tree at a particular time
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="results"></param>
|
||||
public void IntersectsWith(Int64 value, List<T> results)
|
||||
{
|
||||
if (m_Entries.Count == 0)
|
||||
return;
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
Rebuild();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
if (m_Nodes.Count > 0)
|
||||
Query(m_Nodes[0], value, results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query the tree at a particular range of time
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="end"></param>
|
||||
/// <param name="results"></param>
|
||||
public void IntersectsWithRange(Int64 start, Int64 end, List<T> results)
|
||||
{
|
||||
if (start > end)
|
||||
return;
|
||||
|
||||
if (m_Entries.Count == 0)
|
||||
return;
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
Rebuild();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
if (m_Nodes.Count > 0)
|
||||
QueryRange(m_Nodes[0], start, end, results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the intervals from their source. Use this to detect if the data in the tree
|
||||
/// has changed.
|
||||
/// </summary>
|
||||
public void UpdateIntervals()
|
||||
{
|
||||
bool isDirty = false;
|
||||
for (int i = 0; i < m_Entries.Count; i++)
|
||||
{
|
||||
var n = m_Entries[i];
|
||||
var s = n.item.intervalStart;
|
||||
var e = n.item.intervalEnd;
|
||||
|
||||
isDirty |= n.intervalStart != s;
|
||||
isDirty |= n.intervalEnd != e;
|
||||
|
||||
m_Entries[i] = new Entry()
|
||||
{
|
||||
intervalStart = s,
|
||||
intervalEnd = e,
|
||||
item = n.item
|
||||
};
|
||||
}
|
||||
|
||||
dirty |= isDirty;
|
||||
}
|
||||
|
||||
private void Query(IntervalTreeNode intervalTreeNode, Int64 value, List<T> results)
|
||||
{
|
||||
for (int i = intervalTreeNode.first; i <= intervalTreeNode.last; i++)
|
||||
{
|
||||
var entry = m_Entries[i];
|
||||
if (value >= entry.intervalStart && value < entry.intervalEnd)
|
||||
{
|
||||
results.Add(entry.item);
|
||||
}
|
||||
}
|
||||
|
||||
if (intervalTreeNode.center == kCenterUnknown)
|
||||
return;
|
||||
if (intervalTreeNode.left != kInvalidNode && value < intervalTreeNode.center)
|
||||
Query(m_Nodes[intervalTreeNode.left], value, results);
|
||||
if (intervalTreeNode.right != kInvalidNode && value > intervalTreeNode.center)
|
||||
Query(m_Nodes[intervalTreeNode.right], value, results);
|
||||
}
|
||||
|
||||
private void QueryRange(IntervalTreeNode intervalTreeNode, Int64 start, Int64 end, List<T> results)
|
||||
{
|
||||
for (int i = intervalTreeNode.first; i <= intervalTreeNode.last; i++)
|
||||
{
|
||||
var entry = m_Entries[i];
|
||||
if (end >= entry.intervalStart && start < entry.intervalEnd)
|
||||
{
|
||||
results.Add(entry.item);
|
||||
}
|
||||
}
|
||||
|
||||
if (intervalTreeNode.center == kCenterUnknown)
|
||||
return;
|
||||
if (intervalTreeNode.left != kInvalidNode && start < intervalTreeNode.center)
|
||||
QueryRange(m_Nodes[intervalTreeNode.left], start, end, results);
|
||||
if (intervalTreeNode.right != kInvalidNode && end > intervalTreeNode.center)
|
||||
QueryRange(m_Nodes[intervalTreeNode.right], start, end, results);
|
||||
}
|
||||
|
||||
private void Rebuild()
|
||||
{
|
||||
m_Nodes.Clear();
|
||||
m_Nodes.Capacity = m_Entries.Capacity;
|
||||
Rebuild(0, m_Entries.Count - 1);
|
||||
}
|
||||
|
||||
private int Rebuild(int start, int end)
|
||||
{
|
||||
IntervalTreeNode intervalTreeNode = new IntervalTreeNode();
|
||||
|
||||
// minimum size, don't subdivide
|
||||
int count = end - start + 1;
|
||||
if (count < kMinNodeSize)
|
||||
{
|
||||
intervalTreeNode = new IntervalTreeNode() { center = kCenterUnknown, first = start, last = end, left = kInvalidNode, right = kInvalidNode };
|
||||
m_Nodes.Add(intervalTreeNode);
|
||||
return m_Nodes.Count - 1;
|
||||
}
|
||||
|
||||
var min = Int64.MaxValue;
|
||||
var max = Int64.MinValue;
|
||||
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
var o = m_Entries[i];
|
||||
min = Math.Min(min, o.intervalStart);
|
||||
max = Math.Max(max, o.intervalEnd);
|
||||
}
|
||||
|
||||
var center = (max + min) / 2;
|
||||
intervalTreeNode.center = center;
|
||||
|
||||
// first pass, put every thing left of center, left
|
||||
int x = start;
|
||||
int y = end;
|
||||
while (true)
|
||||
{
|
||||
while (x <= end && m_Entries[x].intervalEnd < center)
|
||||
x++;
|
||||
|
||||
while (y >= start && m_Entries[y].intervalEnd >= center)
|
||||
y--;
|
||||
|
||||
if (x > y)
|
||||
break;
|
||||
|
||||
var nodeX = m_Entries[x];
|
||||
var nodeY = m_Entries[y];
|
||||
|
||||
m_Entries[y] = nodeX;
|
||||
m_Entries[x] = nodeY;
|
||||
}
|
||||
|
||||
intervalTreeNode.first = x;
|
||||
|
||||
// second pass, put every start passed the center right
|
||||
y = end;
|
||||
while (true)
|
||||
{
|
||||
while (x <= end && m_Entries[x].intervalStart <= center)
|
||||
x++;
|
||||
|
||||
while (y >= start && m_Entries[y].intervalStart > center)
|
||||
y--;
|
||||
|
||||
if (x > y)
|
||||
break;
|
||||
|
||||
var nodeX = m_Entries[x];
|
||||
var nodeY = m_Entries[y];
|
||||
|
||||
m_Entries[y] = nodeX;
|
||||
m_Entries[x] = nodeY;
|
||||
}
|
||||
|
||||
intervalTreeNode.last = y;
|
||||
|
||||
// reserve a place
|
||||
m_Nodes.Add(new IntervalTreeNode());
|
||||
int index = m_Nodes.Count - 1;
|
||||
|
||||
intervalTreeNode.left = kInvalidNode;
|
||||
intervalTreeNode.right = kInvalidNode;
|
||||
|
||||
if (start < intervalTreeNode.first)
|
||||
intervalTreeNode.left = Rebuild(start, intervalTreeNode.first - 1);
|
||||
|
||||
if (end > intervalTreeNode.last)
|
||||
intervalTreeNode.right = Rebuild(intervalTreeNode.last + 1, end);
|
||||
|
||||
m_Nodes[index] = intervalTreeNode;
|
||||
return index;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_Entries.Clear();
|
||||
m_Nodes.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f74c99a65464bb4b86ccb314ee95a7f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
// The RuntimeClip wraps a single clip in an instantiated sequence.
|
||||
// It supports the IInterval interface so that it can be stored in the interval tree
|
||||
// It is this class that is returned by an interval tree query.
|
||||
class RuntimeClip : RuntimeClipBase
|
||||
{
|
||||
TimelineClip m_Clip;
|
||||
Playable m_Playable;
|
||||
Playable m_ParentMixer;
|
||||
|
||||
public override double start
|
||||
{
|
||||
get { return m_Clip.extrapolatedStart; }
|
||||
}
|
||||
|
||||
public override double duration
|
||||
{
|
||||
get { return m_Clip.extrapolatedDuration; }
|
||||
}
|
||||
|
||||
public RuntimeClip(TimelineClip clip, Playable clipPlayable, Playable parentMixer)
|
||||
{
|
||||
Create(clip, clipPlayable, parentMixer);
|
||||
}
|
||||
|
||||
void Create(TimelineClip clip, Playable clipPlayable, Playable parentMixer)
|
||||
{
|
||||
m_Clip = clip;
|
||||
m_Playable = clipPlayable;
|
||||
m_ParentMixer = parentMixer;
|
||||
clipPlayable.Pause();
|
||||
}
|
||||
|
||||
public TimelineClip clip
|
||||
{
|
||||
get { return m_Clip; }
|
||||
}
|
||||
|
||||
public Playable mixer
|
||||
{
|
||||
get { return m_ParentMixer; }
|
||||
}
|
||||
|
||||
public Playable playable
|
||||
{
|
||||
get { return m_Playable; }
|
||||
}
|
||||
|
||||
public override bool enable
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value && m_Playable.GetPlayState() != PlayState.Playing)
|
||||
{
|
||||
m_Playable.Play();
|
||||
SetTime(m_Clip.clipIn);
|
||||
}
|
||||
else if (!value && m_Playable.GetPlayState() != PlayState.Paused)
|
||||
{
|
||||
m_Playable.Pause();
|
||||
if (m_ParentMixer.IsValid())
|
||||
m_ParentMixer.SetInputWeight(m_Playable, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTime(double time)
|
||||
{
|
||||
m_Playable.SetTime(time);
|
||||
}
|
||||
|
||||
public void SetDuration(double duration)
|
||||
{
|
||||
m_Playable.SetDuration(duration);
|
||||
}
|
||||
|
||||
public override void EvaluateAt(double localTime, FrameData frameData)
|
||||
{
|
||||
enable = true;
|
||||
if (frameData.timeLooped)
|
||||
{
|
||||
// case 1184106 - animation playables require setTime to be called twice to 'reset' event.
|
||||
SetTime(clip.clipIn);
|
||||
SetTime(clip.clipIn);
|
||||
}
|
||||
|
||||
float weight = 1.0f;
|
||||
if (clip.IsPreExtrapolatedTime(localTime))
|
||||
weight = clip.EvaluateMixIn((float)clip.start);
|
||||
else if (clip.IsPostExtrapolatedTime(localTime))
|
||||
weight = clip.EvaluateMixOut((float)clip.end);
|
||||
else
|
||||
weight = clip.EvaluateMixIn(localTime) * clip.EvaluateMixOut(localTime);
|
||||
|
||||
if (mixer.IsValid())
|
||||
mixer.SetInputWeight(playable, weight);
|
||||
|
||||
// localTime of the sequence to localtime of the clip
|
||||
double clipTime = clip.ToLocalTime(localTime);
|
||||
if (clipTime >= -DiscreteTime.tickValue / 2)
|
||||
{
|
||||
SetTime(clipTime);
|
||||
}
|
||||
|
||||
SetDuration(clip.extrapolatedDuration);
|
||||
}
|
||||
|
||||
public override void DisableAt(double localTime, double rootDuration, FrameData frameData)
|
||||
{
|
||||
var time = Math.Min(localTime, (double)DiscreteTime.FromTicks(intervalEnd));
|
||||
if (frameData.timeLooped)
|
||||
time = Math.Min(time, rootDuration);
|
||||
|
||||
var clipTime = clip.ToLocalTime(time);
|
||||
if (clipTime > -DiscreteTime.tickValue / 2)
|
||||
{
|
||||
SetTime(clipTime);
|
||||
}
|
||||
enable = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70a190a1b304d1e43995af35d09231d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
internal abstract class RuntimeClipBase : RuntimeElement
|
||||
{
|
||||
public abstract double start { get; }
|
||||
public abstract double duration { get; }
|
||||
|
||||
public override Int64 intervalStart
|
||||
{
|
||||
get { return DiscreteTime.GetNearestTick(start); }
|
||||
}
|
||||
|
||||
public override Int64 intervalEnd
|
||||
{
|
||||
get { return DiscreteTime.GetNearestTick(start + duration); }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70f955bbb437a494888ef54d97abb474
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
abstract class RuntimeElement : IInterval
|
||||
{
|
||||
public abstract Int64 intervalStart { get; }
|
||||
public abstract Int64 intervalEnd { get; }
|
||||
public int intervalBit { get; set; }
|
||||
|
||||
public abstract bool enable { set; }
|
||||
public abstract void EvaluateAt(double localTime, FrameData frameData);
|
||||
public abstract void DisableAt(double localTime, double rootDuration, FrameData frameData);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76b6bf32a6fcf934aab8c529bddccc81
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Audio;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
// Special runtime clip implementation that handles playables that use a scheduling system
|
||||
// such as Audio
|
||||
internal class ScheduleRuntimeClip : RuntimeClipBase
|
||||
{
|
||||
private TimelineClip m_Clip;
|
||||
private Playable m_Playable;
|
||||
private Playable m_ParentMixer;
|
||||
private double m_StartDelay;
|
||||
private double m_FinishTail;
|
||||
private bool m_Started = false;
|
||||
|
||||
// represents the start point when we want to start getting updated
|
||||
public override double start
|
||||
{
|
||||
get { return Math.Max(0, m_Clip.start - m_StartDelay); }
|
||||
}
|
||||
|
||||
public override double duration
|
||||
{
|
||||
get { return m_Clip.duration + m_FinishTail + m_Clip.start - start; }
|
||||
}
|
||||
|
||||
public void SetTime(double time)
|
||||
{
|
||||
m_Playable.SetTime(time);
|
||||
}
|
||||
|
||||
public TimelineClip clip { get { return m_Clip; } }
|
||||
public Playable mixer { get { return m_ParentMixer; } }
|
||||
public Playable playable { get { return m_Playable; } }
|
||||
|
||||
public ScheduleRuntimeClip(TimelineClip clip, Playable clipPlayable,
|
||||
Playable parentMixer, double startDelay = 0.2, double finishTail = 0.1)
|
||||
{
|
||||
Create(clip, clipPlayable, parentMixer, startDelay, finishTail);
|
||||
}
|
||||
|
||||
private void Create(TimelineClip clip, Playable clipPlayable, Playable parentMixer,
|
||||
double startDelay, double finishTail)
|
||||
{
|
||||
m_Clip = clip;
|
||||
m_Playable = clipPlayable;
|
||||
m_ParentMixer = parentMixer;
|
||||
m_StartDelay = startDelay;
|
||||
m_FinishTail = finishTail;
|
||||
clipPlayable.Pause();
|
||||
}
|
||||
|
||||
public override bool enable
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value && m_Playable.GetPlayState() != PlayState.Playing)
|
||||
{
|
||||
m_Playable.Play();
|
||||
}
|
||||
else if (!value && m_Playable.GetPlayState() != PlayState.Paused)
|
||||
{
|
||||
m_Playable.Pause();
|
||||
if (m_ParentMixer.IsValid())
|
||||
m_ParentMixer.SetInputWeight(m_Playable, 0.0f);
|
||||
}
|
||||
|
||||
m_Started &= value;
|
||||
}
|
||||
}
|
||||
|
||||
public override void EvaluateAt(double localTime, FrameData frameData)
|
||||
{
|
||||
if (frameData.timeHeld)
|
||||
{
|
||||
enable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
bool forceSeek = frameData.seekOccurred || frameData.timeLooped || frameData.evaluationType == FrameData.EvaluationType.Evaluate;
|
||||
|
||||
// If we are in the tail region of the clip, then dont do anything
|
||||
if (localTime > start + duration - m_FinishTail)
|
||||
return;
|
||||
|
||||
// this may set the weight to 1 in a delay, but it will avoid missing the start
|
||||
float weight = clip.EvaluateMixIn(localTime) * clip.EvaluateMixOut(localTime);
|
||||
if (mixer.IsValid())
|
||||
mixer.SetInputWeight(playable, weight);
|
||||
|
||||
// localTime of the sequence to localtime of the clip
|
||||
if (!m_Started || forceSeek)
|
||||
{
|
||||
// accounts for clip in and speed
|
||||
double clipTime = clip.ToLocalTime(Math.Max(localTime, clip.start));
|
||||
// multiply by the time scale so the delay is local to the clip
|
||||
// Audio will rescale based on it's effective time scale (which includes the parent)
|
||||
double startDelay = Math.Max(clip.start - localTime, 0) * clip.timeScale;
|
||||
double durationLocal = m_Clip.duration * clip.timeScale;
|
||||
if (m_Playable.IsPlayableOfType<AudioClipPlayable>())
|
||||
((AudioClipPlayable)m_Playable).Seek(clipTime, startDelay, durationLocal);
|
||||
|
||||
m_Started = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void DisableAt(double localTime, double rootDuration, FrameData frameData)
|
||||
{
|
||||
enable = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b250be9db55288b48ac121c074d795e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8c5993172f27e4419d7d4ed5ef77840
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,31 @@
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface implemented by markers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A marker is a point in time.
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnityEngine.Timeline.Marker"/>
|
||||
public interface IMarker
|
||||
{
|
||||
/// <summary>
|
||||
/// The time set for the marker, in seconds.
|
||||
/// </summary>
|
||||
double time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The track that contains the marker.
|
||||
/// </summary>
|
||||
TrackAsset parent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This method is called when the marker is initialized.
|
||||
/// </summary>
|
||||
/// <param name="parent">The track that contains the marker.</param>
|
||||
/// <remarks>
|
||||
/// This method is called after each deserialization of the Timeline Asset.
|
||||
/// </remarks>
|
||||
void Initialize(TrackAsset parent);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4cb169caa67eddf4d83b39fd0917a945
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,15 @@
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement this interface to change the behaviour of an INotification.
|
||||
/// </summary>
|
||||
/// This interface must be implemented along with <see cref="UnityEngine.Playables.INotification"/> to modify the default behaviour of a notification.
|
||||
/// <seealso cref="UnityEngine.Timeline.NotificationFlags"/>
|
||||
public interface INotificationOptionProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The flags that change the triggering behaviour.
|
||||
/// </summary>
|
||||
NotificationFlags flags { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5082cb99a8f99b84d84dd8b4c5233a9e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Use Marker as a base class when creating a custom marker.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A marker is a point in time.
|
||||
/// </remarks>
|
||||
public abstract class Marker : ScriptableObject, IMarker
|
||||
{
|
||||
[SerializeField, TimeField, Tooltip("Time for the marker")] double m_Time;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TrackAsset parent { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// The marker time cannot be negative.
|
||||
/// </remarks>
|
||||
public double time
|
||||
{
|
||||
get { return m_Time; }
|
||||
set { m_Time = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IMarker.Initialize(TrackAsset parentTrack)
|
||||
{
|
||||
// We only really want to update the parent when the object is first deserialized
|
||||
// If not a cloned track would "steal" the source's markers
|
||||
if (parent == null)
|
||||
{
|
||||
parent = parentTrack;
|
||||
try
|
||||
{
|
||||
OnInitialize(parentTrack);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(e.Message, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to receive a callback when the marker is initialized.
|
||||
/// </summary>
|
||||
/// <param name="aPent">The track that contains the marker.</param>
|
||||
public virtual void OnInitialize(TrackAsset aPent)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89b48a03f6f43e94e87cc8d2104d3d4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
[Serializable]
|
||||
struct MarkerList : ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField, HideInInspector] List<ScriptableObject> m_Objects;
|
||||
|
||||
[HideInInspector, NonSerialized] List<IMarker> m_Cache;
|
||||
bool m_CacheDirty;
|
||||
bool m_HasNotifications;
|
||||
public List<IMarker> markers
|
||||
{
|
||||
get
|
||||
{
|
||||
BuildCache();
|
||||
return m_Cache;
|
||||
}
|
||||
}
|
||||
|
||||
public MarkerList(int capacity)
|
||||
{
|
||||
m_Objects = new List<ScriptableObject>(capacity);
|
||||
m_Cache = new List<IMarker>(capacity);
|
||||
m_CacheDirty = true;
|
||||
m_HasNotifications = false;
|
||||
}
|
||||
|
||||
public void Add(ScriptableObject item)
|
||||
{
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
m_Objects.Add(item);
|
||||
m_CacheDirty = true;
|
||||
}
|
||||
|
||||
public bool Remove(IMarker item)
|
||||
{
|
||||
if (!(item is ScriptableObject))
|
||||
throw new InvalidOperationException("Supplied type must be a ScriptableObject");
|
||||
return Remove((ScriptableObject)item, item.parent.timelineAsset, item.parent);
|
||||
}
|
||||
|
||||
public bool Remove(ScriptableObject item, TimelineAsset timelineAsset, PlayableAsset thingToDirty)
|
||||
{
|
||||
if (!m_Objects.Contains(item)) return false;
|
||||
|
||||
TimelineUndo.PushUndo(thingToDirty, "Delete Marker");
|
||||
m_Objects.Remove(item);
|
||||
m_CacheDirty = true;
|
||||
TimelineUndo.PushDestroyUndo(timelineAsset, thingToDirty, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_Objects.Clear();
|
||||
m_CacheDirty = true;
|
||||
}
|
||||
|
||||
public bool Contains(ScriptableObject item)
|
||||
{
|
||||
return m_Objects.Contains(item);
|
||||
}
|
||||
|
||||
public IEnumerable<IMarker> GetMarkers()
|
||||
{
|
||||
return markers;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return markers.Count; }
|
||||
}
|
||||
|
||||
public IMarker this[int idx]
|
||||
{
|
||||
get
|
||||
{
|
||||
return markers[idx];
|
||||
}
|
||||
}
|
||||
|
||||
public List<ScriptableObject> GetRawMarkerList()
|
||||
{
|
||||
return m_Objects;
|
||||
}
|
||||
|
||||
public IMarker CreateMarker(Type type, double time, TrackAsset owner)
|
||||
{
|
||||
if (!typeof(ScriptableObject).IsAssignableFrom(type) || !typeof(IMarker).IsAssignableFrom(type))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The requested type needs to inherit from ScriptableObject and implement IMarker");
|
||||
}
|
||||
if (!owner.supportsNotifications && typeof(INotification).IsAssignableFrom(type))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Markers implementing the INotification interface cannot be added on tracks that do not support notifications");
|
||||
}
|
||||
|
||||
var markerSO = ScriptableObject.CreateInstance(type);
|
||||
var marker = (IMarker)markerSO;
|
||||
marker.time = time;
|
||||
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(markerSO, owner);
|
||||
TimelineUndo.RegisterCreatedObjectUndo(markerSO, "Create " + type.Name);
|
||||
TimelineUndo.PushUndo(owner, "Create " + type.Name);
|
||||
|
||||
Add(markerSO);
|
||||
marker.Initialize(owner);
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
public bool HasNotifications()
|
||||
{
|
||||
BuildCache();
|
||||
return m_HasNotifications;
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
for (int i = m_Objects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
object o = m_Objects[i];
|
||||
if (o == null)
|
||||
{
|
||||
Debug.LogWarning("Empty marker found while loading timeline. It will be removed.");
|
||||
m_Objects.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
m_CacheDirty = true;
|
||||
}
|
||||
|
||||
void BuildCache()
|
||||
{
|
||||
if (m_CacheDirty)
|
||||
{
|
||||
m_Cache = new List<IMarker>(m_Objects.Count);
|
||||
m_HasNotifications = false;
|
||||
foreach (var o in m_Objects)
|
||||
{
|
||||
if (o != null)
|
||||
{
|
||||
m_Cache.Add(o as IMarker);
|
||||
if (o is INotification)
|
||||
{
|
||||
m_HasNotifications = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_CacheDirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4335a164bb763104c8805212c23d795f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this track to add Markers bound to a GameObject.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[TrackBindingType(typeof(GameObject))]
|
||||
[HideInMenu]
|
||||
[ExcludeFromPreset]
|
||||
[TimelineHelpURL(typeof(MarkerTrack))]
|
||||
public class MarkerTrack : TrackAsset
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<PlayableBinding> outputs
|
||||
{
|
||||
get
|
||||
{
|
||||
return this == timelineAsset?.markerTrack ?
|
||||
new List<PlayableBinding> { ScriptPlayableBinding.Create(name, null, typeof(GameObject)) } :
|
||||
base.outputs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a16748d9461eae46a725db9776d5390
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this track to emit signals to a bound SignalReceiver.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This track cannot contain clips.
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnityEngine.Timeline.SignalEmitter"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.SignalReceiver"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.SignalAsset"/>
|
||||
[Serializable]
|
||||
[TrackBindingType(typeof(SignalReceiver))]
|
||||
[TrackColor(0.25f, 0.25f, 0.25f)]
|
||||
[ExcludeFromPreset]
|
||||
[TimelineHelpURL(typeof(SignalTrack))]
|
||||
public class SignalTrack : MarkerTrack { }
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b46e36075dd1c124a8422c228e75e1fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b00473355622524394628f7ec51808d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,5 @@
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
//used to tell Signal Handler inspector to use a special drawer for UnityEvent
|
||||
class CustomSignalEventDrawer : PropertyAttribute { }
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7ebd1239373d5f41af65ef32d67f445
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// An asset representing an emitted signal. A SignalAsset connects a SignalEmitter with a SignalReceiver.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnityEngine.Timeline.SignalEmitter"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.SignalReceiver"/>
|
||||
[AssetFileNameExtension("signal")]
|
||||
[TimelineHelpURL(typeof(SignalAsset))]
|
||||
public class SignalAsset : ScriptableObject
|
||||
{
|
||||
internal static event Action<SignalAsset> OnEnableCallback;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (OnEnableCallback != null)
|
||||
OnEnableCallback(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6fa2d92fc1b3f34da284357edf89c3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker that emits a signal to a SignalReceiver.
|
||||
/// </summary>
|
||||
/// A SignalEmitter emits a notification through the playable system. A SignalEmitter is used with a SignalReceiver and a SignalAsset.
|
||||
/// <seealso cref="UnityEngine.Timeline.SignalAsset"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.SignalReceiver"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.Marker"/>
|
||||
[Serializable]
|
||||
[CustomStyle("SignalEmitter")]
|
||||
[ExcludeFromPreset]
|
||||
[TimelineHelpURL(typeof(SignalEmitter))]
|
||||
public class SignalEmitter : Marker, INotification, INotificationOptionProvider
|
||||
{
|
||||
[SerializeField] bool m_Retroactive;
|
||||
[SerializeField] bool m_EmitOnce;
|
||||
[SerializeField] SignalAsset m_Asset;
|
||||
|
||||
/// <summary>
|
||||
/// Use retroactive to emit the signal if playback starts after the SignalEmitter time.
|
||||
/// </summary>
|
||||
public bool retroactive
|
||||
{
|
||||
get { return m_Retroactive; }
|
||||
set { m_Retroactive = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use emitOnce to emit this signal once during loops.
|
||||
/// </summary>
|
||||
public bool emitOnce
|
||||
{
|
||||
get { return m_EmitOnce; }
|
||||
set { m_EmitOnce = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asset representing the signal being emitted.
|
||||
/// </summary>
|
||||
public SignalAsset asset
|
||||
{
|
||||
get { return m_Asset; }
|
||||
set { m_Asset = value; }
|
||||
}
|
||||
|
||||
PropertyName INotification.id
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Asset != null)
|
||||
{
|
||||
return new PropertyName(m_Asset.name);
|
||||
}
|
||||
return new PropertyName(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
NotificationFlags INotificationOptionProvider.flags
|
||||
{
|
||||
get
|
||||
{
|
||||
return (retroactive ? NotificationFlags.Retroactive : default(NotificationFlags)) |
|
||||
(emitOnce ? NotificationFlags.TriggerOnce : default(NotificationFlags)) |
|
||||
NotificationFlags.TriggerInEditMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15c38f6fa1940124db1ab7f6fe7268d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Listens for emitted signals and reacts depending on its defined reactions.
|
||||
/// </summary>
|
||||
/// A SignalReceiver contains a list of reactions. Each reaction is bound to a SignalAsset.
|
||||
/// When a SignalEmitter emits a signal, the SignalReceiver invokes the corresponding reaction.
|
||||
/// <seealso cref="UnityEngine.Timeline.SignalEmitter"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.SignalAsset"/>
|
||||
[TimelineHelpURL(typeof(SignalReceiver))]
|
||||
public class SignalReceiver : MonoBehaviour, INotificationReceiver
|
||||
{
|
||||
[SerializeField]
|
||||
EventKeyValue m_Events = new EventKeyValue();
|
||||
|
||||
/// <summary>
|
||||
/// Called when a notification is sent.
|
||||
/// </summary>
|
||||
/// <param name="origin">The playable that sent the notification.</param>
|
||||
/// <param name="notification">The received notification. Only notifications of type <see cref="SignalEmitter"/> will be processed.</param>
|
||||
/// <param name="context">User defined data that depends on the type of notification. Uses this to pass necessary information that can change with each invocation.</param>
|
||||
public void OnNotify(Playable origin, INotification notification, object context)
|
||||
{
|
||||
var signal = notification as SignalEmitter;
|
||||
if (signal != null && signal.asset != null)
|
||||
{
|
||||
UnityEvent evt;
|
||||
if (m_Events.TryGetValue(signal.asset, out evt) && evt != null)
|
||||
{
|
||||
evt.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a new reaction for a SignalAsset.
|
||||
/// </summary>
|
||||
/// <param name="asset">The SignalAsset for which the reaction is being defined.</param>
|
||||
/// <param name="reaction">The UnityEvent that describes the reaction.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the asset is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when the SignalAsset is already registered with this receiver.</exception>
|
||||
public void AddReaction(SignalAsset asset, UnityEvent reaction)
|
||||
{
|
||||
if (asset == null)
|
||||
throw new ArgumentNullException("asset");
|
||||
|
||||
if (m_Events.signals.Contains(asset))
|
||||
throw new ArgumentException("SignalAsset already used.");
|
||||
m_Events.Append(asset, reaction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a null SignalAsset with a reaction specified by the UnityEvent.
|
||||
/// </summary>
|
||||
/// <param name="reaction">The new reaction to be appended.</param>
|
||||
/// <returns>The index of the appended reaction.</returns>
|
||||
/// <remarks>Multiple null assets are valid.</remarks>
|
||||
public int AddEmptyReaction(UnityEvent reaction)
|
||||
{
|
||||
m_Events.Append(null, reaction);
|
||||
return m_Events.events.Count - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a SignalAsset.
|
||||
/// </summary>
|
||||
/// <param name="asset">The SignalAsset to be removed.</param>
|
||||
public void Remove(SignalAsset asset)
|
||||
{
|
||||
if (!m_Events.signals.Contains(asset))
|
||||
{
|
||||
throw new ArgumentException("The SignalAsset is not registered with this receiver.");
|
||||
}
|
||||
|
||||
m_Events.Remove(asset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all registered SignalAssets.
|
||||
/// </summary>
|
||||
/// <returns>Returns a list of SignalAssets.</returns>
|
||||
public IEnumerable<SignalAsset> GetRegisteredSignals()
|
||||
{
|
||||
return m_Events.signals;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first UnityEvent associated with a SignalAsset.
|
||||
/// </summary>
|
||||
/// <param name="key">A SignalAsset defining the signal.</param>
|
||||
/// <returns>Returns the reaction associated with a SignalAsset. Returns null if the signal asset does not exist.</returns>
|
||||
public UnityEvent GetReaction(SignalAsset key)
|
||||
{
|
||||
UnityEvent ret;
|
||||
if (m_Events.TryGetValue(key, out ret))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the count of registered SignalAssets.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int Count()
|
||||
{
|
||||
return m_Events.signals.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the SignalAsset associated with a reaction at a specific index.
|
||||
/// </summary>
|
||||
/// <param name="idx">The index of the reaction.</param>
|
||||
/// <param name="newKey">The replacement SignalAsset.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the replacement SignalAsset is already registered to this SignalReceiver.</exception>
|
||||
/// <remarks>The new SignalAsset can be null.</remarks>
|
||||
public void ChangeSignalAtIndex(int idx, SignalAsset newKey)
|
||||
{
|
||||
if (idx < 0 || idx > m_Events.signals.Count - 1)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
if (m_Events.signals[idx] == newKey)
|
||||
return;
|
||||
var alreadyUsed = m_Events.signals.Contains(newKey);
|
||||
if (newKey == null || m_Events.signals[idx] == null || !alreadyUsed)
|
||||
m_Events.signals[idx] = newKey;
|
||||
|
||||
if (newKey != null && alreadyUsed)
|
||||
throw new ArgumentException("SignalAsset already used.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the SignalAsset and reaction at a specific index.
|
||||
/// </summary>
|
||||
/// <param name="idx">The index of the SignalAsset to be removed.</param>
|
||||
public void RemoveAtIndex(int idx)
|
||||
{
|
||||
if (idx < 0 || idx > m_Events.signals.Count - 1)
|
||||
throw new IndexOutOfRangeException();
|
||||
m_Events.Remove(idx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the reaction at a specific index with a new UnityEvent.
|
||||
/// </summary>
|
||||
/// <param name="idx">The index of the reaction to be replaced.</param>
|
||||
/// <param name="reaction">The replacement reaction.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the replacement reaction is null.</exception>
|
||||
public void ChangeReactionAtIndex(int idx, UnityEvent reaction)
|
||||
{
|
||||
if (idx < 0 || idx > m_Events.events.Count - 1)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
m_Events.events[idx] = reaction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reaction at a specific index.
|
||||
/// </summary>
|
||||
/// <param name="idx">The index of the reaction.</param>
|
||||
/// <returns>Returns a reaction.</returns>
|
||||
public UnityEvent GetReactionAtIndex(int idx)
|
||||
{
|
||||
if (idx < 0 || idx > m_Events.events.Count - 1)
|
||||
throw new IndexOutOfRangeException();
|
||||
return m_Events.events[idx];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SignalAsset at a specific index
|
||||
/// </summary>
|
||||
/// <param name="idx">The index of the SignalAsset.</param>
|
||||
/// <returns>Returns a SignalAsset.</returns>
|
||||
public SignalAsset GetSignalAssetAtIndex(int idx)
|
||||
{
|
||||
if (idx < 0 || idx > m_Events.signals.Count - 1)
|
||||
throw new IndexOutOfRangeException();
|
||||
return m_Events.signals[idx];
|
||||
}
|
||||
|
||||
// Required by Unity for the MonoBehaviour to have an enabled state
|
||||
private void OnEnable()
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class EventKeyValue
|
||||
{
|
||||
[SerializeField]
|
||||
List<SignalAsset> m_Signals = new List<SignalAsset>();
|
||||
|
||||
[SerializeField, CustomSignalEventDrawer]
|
||||
List<UnityEvent> m_Events = new List<UnityEvent>();
|
||||
|
||||
public bool TryGetValue(SignalAsset key, out UnityEvent value)
|
||||
{
|
||||
var index = m_Signals.IndexOf(key);
|
||||
if (index != -1)
|
||||
{
|
||||
value = m_Events[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Append(SignalAsset key, UnityEvent value)
|
||||
{
|
||||
m_Signals.Add(key);
|
||||
m_Events.Add(value);
|
||||
}
|
||||
|
||||
public void Remove(int idx)
|
||||
{
|
||||
if (idx != -1)
|
||||
{
|
||||
m_Signals.RemoveAt(idx);
|
||||
m_Events.RemoveAt(idx);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(SignalAsset key)
|
||||
{
|
||||
var idx = m_Signals.IndexOf(key);
|
||||
if (idx != -1)
|
||||
{
|
||||
m_Signals.RemoveAt(idx);
|
||||
m_Events.RemoveAt(idx);
|
||||
}
|
||||
}
|
||||
|
||||
public List<SignalAsset> signals
|
||||
{
|
||||
get { return m_Signals; }
|
||||
}
|
||||
|
||||
public List<UnityEvent> events
|
||||
{
|
||||
get { return m_Events; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e52de21a22b6dd44c9cc19f810c65059
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c61ba0c209bcc74f83e3650039ebdf9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for TrackAssets
|
||||
/// </summary>
|
||||
public static class TrackAssetExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the GroupTrack this track belongs to.
|
||||
/// </summary>
|
||||
/// <param name="asset">The track asset to find the group of</param>
|
||||
/// <returns>The parent GroupTrack or null if the Track is an override track, or root track.</returns>
|
||||
public static GroupTrack GetGroup(this TrackAsset asset)
|
||||
{
|
||||
if (asset == null)
|
||||
return null;
|
||||
|
||||
return asset.parent as GroupTrack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns the track to the specified group track.
|
||||
/// </summary>
|
||||
/// <param name="asset">The track to assign.</param>
|
||||
/// <param name="group">The GroupTrack to assign the track to.</param>
|
||||
/// <remarks>
|
||||
/// Does not support assigning to a group in a different timeline.
|
||||
/// </remarks>
|
||||
public static void SetGroup(this TrackAsset asset, GroupTrack group)
|
||||
{
|
||||
const string undoString = "Reparent";
|
||||
|
||||
if (asset == null || asset == group || asset.parent == group)
|
||||
return;
|
||||
|
||||
if (group != null && asset.timelineAsset != group.timelineAsset)
|
||||
throw new InvalidOperationException("Cannot assign to a group in a different timeline");
|
||||
|
||||
|
||||
TimelineUndo.PushUndo(asset, undoString);
|
||||
|
||||
var timeline = asset.timelineAsset;
|
||||
var parentTrack = asset.parent as TrackAsset;
|
||||
var parentTimeline = asset.parent as TimelineAsset;
|
||||
if (parentTrack != null || parentTimeline != null)
|
||||
{
|
||||
TimelineUndo.PushUndo(asset.parent, undoString);
|
||||
if (parentTimeline != null)
|
||||
{
|
||||
parentTimeline.RemoveTrack(asset);
|
||||
}
|
||||
else
|
||||
{
|
||||
parentTrack.RemoveSubTrack(asset);
|
||||
}
|
||||
}
|
||||
|
||||
if (group == null)
|
||||
{
|
||||
TimelineUndo.PushUndo(timeline, undoString);
|
||||
asset.parent = asset.timelineAsset;
|
||||
timeline.AddTrackInternal(asset);
|
||||
}
|
||||
else
|
||||
{
|
||||
TimelineUndo.PushUndo(group, undoString);
|
||||
group.AddChild(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3721d5c6afa8e545995dfaada328476
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// A group track is a container that allows tracks to be arranged in a hierarchical manner.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[TrackClipType(typeof(TrackAsset))]
|
||||
[SupportsChildTracks]
|
||||
[ExcludeFromPreset]
|
||||
[TimelineHelpURL(typeof(GroupTrack))]
|
||||
public class GroupTrack : TrackAsset
|
||||
{
|
||||
internal override bool CanCompileClips()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<PlayableBinding> outputs
|
||||
{
|
||||
get { return PlayableBinding.None; }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0fc6f5187a81dc47999eefade6f0935
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement this interface on a TrackAsset derived class to support layers
|
||||
/// </summary>
|
||||
public interface ILayerable
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a mixer that blends track mixers.
|
||||
/// </summary>
|
||||
/// <param name="graph">The graph where the mixer playable will be added.</param>
|
||||
/// <param name="go">The GameObject that requested the graph.</param>
|
||||
/// <param name="inputCount">The number of inputs on the mixer. There should be an input for each playable from each clip.</param>
|
||||
/// <returns>Returns a playable that is used as a mixer. If this method returns Playable.Null, it indicates that a layer mixer is not needed. In this case, a single track mixer blends all playables generated from all layers.</returns>
|
||||
Playable CreateLayerMixer(PlayableGraph graph, GameObject go, int inputCount);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1dc9fdfe61a6a8749a0f6b89b45e887d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b7d06780fca6fc4384580d3ebed9219
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,145 @@
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GameObject to control.
|
||||
/// </summary>
|
||||
public GameObject gameObject = null;
|
||||
|
||||
/// <inheritdoc cref="ActivationControlPlayable.PostPlaybackState"/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d20e4e177b86a2843805dd3894f41b42
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is deprecated. It is recommended to use Playable Asset and Playable Behaviour derived classes instead.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[Obsolete("For best performance use PlayableAsset and PlayableBehaviour.")]
|
||||
public class BasicPlayableBehaviour : ScriptableObject, IPlayableAsset, IPlayableBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The playback duration in seconds of the instantiated Playable.
|
||||
/// </summary>
|
||||
public virtual double duration { get { return PlayableBinding.DefaultDuration; } }
|
||||
|
||||
/// <summary>
|
||||
///A description of the outputs of the instantiated Playable.
|
||||
/// </summary>
|
||||
public virtual IEnumerable<PlayableBinding> outputs { get { return PlayableBinding.None; } }
|
||||
|
||||
/// <summary>
|
||||
/// <para>This function is called when the PlayableGraph that owns this PlayableBehaviour starts.</para>
|
||||
/// </summary>
|
||||
/// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
|
||||
public virtual void OnGraphStart(Playable playable) { }
|
||||
|
||||
/// <summary>
|
||||
/// <para>This function is called when the PlayableGraph that owns this PlayableBehaviour stops.</para>
|
||||
/// </summary>
|
||||
/// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
|
||||
public virtual void OnGraphStop(Playable playable) { }
|
||||
|
||||
/// <summary>
|
||||
/// <para>This function is called when the Playable that owns the PlayableBehaviour is created.</para>
|
||||
/// </summary>
|
||||
/// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
|
||||
public virtual void OnPlayableCreate(Playable playable) { }
|
||||
|
||||
/// <summary>
|
||||
/// <para>This function is called when the Playable that owns the PlayableBehaviour is destroyed.</para>
|
||||
/// </summary>
|
||||
/// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
|
||||
public virtual void OnPlayableDestroy(Playable playable) { }
|
||||
|
||||
/// <summary>
|
||||
/// <para>This function is called when the Playable play state is changed to Playables.PlayState.Playing.</para>
|
||||
/// </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 virtual void OnBehaviourPlay(Playable playable, FrameData info) { }
|
||||
|
||||
/// <summary>
|
||||
/// <para>This function is called when the Playable play state is changed to Playables.PlayState.Paused.</para>
|
||||
/// </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 virtual void OnBehaviourPause(Playable playable, FrameData info) { }
|
||||
|
||||
/// <summary>
|
||||
/// <para>This function is called during the PrepareFrame phase of the PlayableGraph.</para>
|
||||
/// </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 virtual void PrepareFrame(Playable playable, FrameData info) { }
|
||||
|
||||
/// <summary>
|
||||
/// <para>This function is called during the ProcessFrame phase of the PlayableGraph.</para>
|
||||
/// </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>
|
||||
/// <param name="playerData">The user data of the ScriptPlayableOutput that initiated the process pass.</param>
|
||||
public virtual void ProcessFrame(Playable playable, FrameData info, object playerData) { }
|
||||
|
||||
/// <summary>
|
||||
/// Implement this method to have your asset inject playables into the given graph.
|
||||
/// </summary>
|
||||
/// <param name="graph">The graph to inject playables into.</param>
|
||||
/// <param name="owner">The game object which initiated the build.</param>
|
||||
/// <returns>The playable injected into the graph, or the root playable if multiple playables are injected.</returns>
|
||||
public virtual Playable CreatePlayable(PlayableGraph graph, GameObject owner)
|
||||
{
|
||||
return ScriptPlayable<BasicPlayableBehaviour>.Create(graph, this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe03a7b0ba57a4d488b6c327ae16c335
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Playable Behaviour used to control a PlayableDirector.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This playable is used to control other PlayableDirector components from a Timeline sequence.
|
||||
/// </remarks>
|
||||
public class DirectorControlPlayable : PlayableBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The PlayableDirector being controlled by this PlayableBehaviour
|
||||
/// </summary>
|
||||
public PlayableDirector director;
|
||||
|
||||
bool m_SyncTime = false;
|
||||
double m_AssetDuration = double.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Playable with a DirectorControlPlayable attached
|
||||
/// </summary>
|
||||
/// <param name="graph">The graph to inject the playable into</param>
|
||||
/// <param name="director">The director to control</param>
|
||||
/// <returns>Returns a Playable with a DirectorControlPlayable attached</returns>
|
||||
public static ScriptPlayable<DirectorControlPlayable> Create(PlayableGraph graph, PlayableDirector director)
|
||||
{
|
||||
if (director == null)
|
||||
return ScriptPlayable<DirectorControlPlayable>.Null;
|
||||
|
||||
var handle = ScriptPlayable<DirectorControlPlayable>.Create(graph);
|
||||
handle.GetBehaviour().director = director;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying && UnityEditor.PrefabUtility.IsPartOfPrefabInstance(director))
|
||||
UnityEditor.PrefabUtility.prefabInstanceUpdated += handle.GetBehaviour().OnPrefabUpdated;
|
||||
#endif
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is called when this PlayableBehaviour is destroyed.
|
||||
/// </summary>
|
||||
/// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
|
||||
public override void OnPlayableDestroy(Playable playable)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabUpdated;
|
||||
#endif
|
||||
if (director != null && director.playableAsset != null)
|
||||
director.Stop();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (director == null || !director.isActiveAndEnabled || director.playableAsset == null)
|
||||
return;
|
||||
|
||||
// resync the time on an evaluate or a time jump (caused by loops, or some setTime calls)
|
||||
m_SyncTime |= (info.evaluationType == FrameData.EvaluationType.Evaluate) ||
|
||||
DetectDiscontinuity(playable, info);
|
||||
|
||||
SyncSpeed(info.effectiveSpeed);
|
||||
SyncStart(playable.GetGraph(), playable.GetTime());
|
||||
#if !UNITY_2021_2_OR_NEWER
|
||||
SyncStop(playable.GetGraph(), playable.GetTime());
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
m_SyncTime = true;
|
||||
|
||||
if (director != null && director.playableAsset != null)
|
||||
m_AssetDuration = director.playableAsset.duration;
|
||||
}
|
||||
|
||||
/// <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 (director != null && director.playableAsset != null)
|
||||
{
|
||||
if (info.effectivePlayState == PlayState.Playing) // graph was paused
|
||||
director.Pause();
|
||||
else
|
||||
director.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="playerData">unused</param>
|
||||
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
|
||||
{
|
||||
if (director == null || !director.isActiveAndEnabled || director.playableAsset == null)
|
||||
return;
|
||||
|
||||
if (m_SyncTime || DetectOutOfSync(playable))
|
||||
{
|
||||
UpdateTime(playable);
|
||||
if (director.playableGraph.IsValid())
|
||||
{
|
||||
director.playableGraph.Evaluate();
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
director.playableGraph.SynchronizeEvaluation(playable.GetGraph());
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
director.Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
m_SyncTime = false;
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
SyncStop(playable.GetGraph(), playable.GetTime());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnPrefabUpdated(GameObject go)
|
||||
{
|
||||
// When the prefab asset is updated, we rebuild the graph to reflect the changes in editor
|
||||
if (UnityEditor.PrefabUtility.GetRootGameObject(director) == go)
|
||||
director.RebuildGraph();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void SyncSpeed(double speed)
|
||||
{
|
||||
if (director.playableGraph.IsValid())
|
||||
{
|
||||
int roots = director.playableGraph.GetRootPlayableCount();
|
||||
for (int i = 0; i < roots; i++)
|
||||
{
|
||||
var rootPlayable = director.playableGraph.GetRootPlayable(i);
|
||||
if (rootPlayable.IsValid())
|
||||
{
|
||||
rootPlayable.SetSpeed(speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SyncStart(PlayableGraph graph, double time)
|
||||
{
|
||||
if (director.state == PlayState.Playing
|
||||
|| !graph.IsPlaying()
|
||||
|| (director.extrapolationMode == DirectorWrapMode.None && time > m_AssetDuration))
|
||||
return;
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
if (graph.IsMatchFrameRateEnabled())
|
||||
director.Play(graph.GetFrameRate());
|
||||
else
|
||||
director.Play();
|
||||
#else
|
||||
director.Play();
|
||||
#endif
|
||||
}
|
||||
|
||||
void SyncStop(PlayableGraph graph, double time)
|
||||
{
|
||||
if (director.state == PlayState.Paused
|
||||
|| (graph.IsPlaying() && (director.extrapolationMode != DirectorWrapMode.None || time < m_AssetDuration)))
|
||||
return;
|
||||
if (director.state == PlayState.Paused)
|
||||
return;
|
||||
|
||||
bool expectedFinished = director.extrapolationMode == DirectorWrapMode.None && time > m_AssetDuration;
|
||||
if (expectedFinished || !graph.IsPlaying())
|
||||
director.Pause();
|
||||
}
|
||||
|
||||
bool DetectDiscontinuity(Playable playable, FrameData info)
|
||||
{
|
||||
return Math.Abs(playable.GetTime() - playable.GetPreviousTime() - info.m_DeltaTime * info.m_EffectiveSpeed) > DiscreteTime.tickValue;
|
||||
}
|
||||
|
||||
bool DetectOutOfSync(Playable playable)
|
||||
{
|
||||
double expectedTime = playable.GetTime();
|
||||
if (playable.GetTime() >= m_AssetDuration)
|
||||
{
|
||||
switch (director.extrapolationMode)
|
||||
{
|
||||
case DirectorWrapMode.None:
|
||||
expectedTime = m_AssetDuration;
|
||||
break;
|
||||
case DirectorWrapMode.Hold:
|
||||
expectedTime = m_AssetDuration;
|
||||
break;
|
||||
case DirectorWrapMode.Loop:
|
||||
expectedTime %= m_AssetDuration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Mathf.Approximately((float)expectedTime, (float)director.time))
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
double lastDelta = playable.GetTime() - playable.GetPreviousTime();
|
||||
if (UnityEditor.Unsupported.IsDeveloperBuild())
|
||||
Debug.LogWarningFormat("Internal Warning - Control track desync detected on {2} ({0:F10} vs {1:F10} with delta {3:F10}). Time will be resynchronized. Known to happen with nested control tracks", playable.GetTime(), director.time, director.name, lastDelta);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// We need to handle loop modes explicitly since we are setting the time directly
|
||||
void UpdateTime(Playable playable)
|
||||
{
|
||||
double duration = Math.Max(0.1, director.playableAsset.duration);
|
||||
switch (director.extrapolationMode)
|
||||
{
|
||||
case DirectorWrapMode.Hold:
|
||||
director.time = Math.Min(duration, Math.Max(0, playable.GetTime()));
|
||||
break;
|
||||
case DirectorWrapMode.Loop:
|
||||
director.time = Math.Max(0, playable.GetTime() % duration);
|
||||
break;
|
||||
case DirectorWrapMode.None:
|
||||
director.time = Math.Min(duration, Math.Max(0, playable.GetTime()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be156cc527d606b4aaac403e9843186e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,27 @@
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface that can be implemented by MonoBehaviours indicating that they receive time-related control calls from a PlayableGraph.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementing this interface on MonoBehaviours attached to GameObjects under control by control-tracks will cause them to be notified when associated Timeline clips are active.
|
||||
/// </remarks>
|
||||
public interface ITimeControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Called each frame the Timeline clip is active.
|
||||
/// </summary>
|
||||
/// <param name="time">The local time of the associated Timeline clip.</param>
|
||||
void SetTime(double time);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the associated Timeline clip becomes active.
|
||||
/// </summary>
|
||||
void OnControlTimeStart();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the associated Timeline clip becomes deactivated.
|
||||
/// </summary>
|
||||
void OnControlTimeStop();
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5415c904c4fbc3e498253bc2866b37cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Use these flags to specify the notification behaviour.
|
||||
/// </summary>
|
||||
/// <see cref="UnityEngine.Playables.INotification"/>
|
||||
[Flags]
|
||||
[Serializable]
|
||||
public enum NotificationFlags : short
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this flag to send the notification in Edit Mode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sent on discontinuous jumps in time.
|
||||
/// </remarks>
|
||||
TriggerInEditMode = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Use this flag to send the notification if playback starts after the notification time.
|
||||
/// </summary>
|
||||
Retroactive = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Use this flag to send the notification only once when looping.
|
||||
/// </summary>
|
||||
TriggerOnce = 1 << 2,
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 983c76d87fb6f4f4597a526a4b2b5fd7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Playable that synchronizes a particle system simulation.
|
||||
/// </summary>
|
||||
public class ParticleControlPlayable : PlayableBehaviour
|
||||
{
|
||||
const float kUnsetTime = float.MaxValue;
|
||||
float m_LastPlayableTime = kUnsetTime;
|
||||
float m_LastParticleTime = kUnsetTime;
|
||||
uint m_RandomSeed = 1;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Playable with a ParticleControlPlayable behaviour attached
|
||||
/// </summary>
|
||||
/// <param name="graph">The PlayableGraph to inject the Playable into.</param>
|
||||
/// <param name="component">The particle systtem to control</param>
|
||||
/// <param name="randomSeed">A random seed to use for particle simulation</param>
|
||||
/// <returns>Returns the created Playable.</returns>
|
||||
public static ScriptPlayable<ParticleControlPlayable> Create(PlayableGraph graph, ParticleSystem component, uint randomSeed)
|
||||
{
|
||||
if (component == null)
|
||||
return ScriptPlayable<ParticleControlPlayable>.Null;
|
||||
|
||||
var handle = ScriptPlayable<ParticleControlPlayable>.Create(graph);
|
||||
handle.GetBehaviour().Initialize(component, randomSeed);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The particle system to control
|
||||
/// </summary>
|
||||
public ParticleSystem particleSystem { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the behaviour with a particle system and random seed.
|
||||
/// </summary>
|
||||
/// <param name="ps"></param>
|
||||
/// <param name="randomSeed"></param>
|
||||
public void Initialize(ParticleSystem ps, uint randomSeed)
|
||||
{
|
||||
m_RandomSeed = Math.Max(1, randomSeed);
|
||||
particleSystem = ps;
|
||||
SetRandomSeed(particleSystem, m_RandomSeed);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying && UnityEditor.PrefabUtility.IsPartOfPrefabInstance(ps))
|
||||
UnityEditor.PrefabUtility.prefabInstanceUpdated += OnPrefabUpdated;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <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 (!Application.isPlaying)
|
||||
UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabUpdated;
|
||||
}
|
||||
|
||||
void OnPrefabUpdated(GameObject go)
|
||||
{
|
||||
// When the instance is updated from, this will cause the next evaluate to resimulate.
|
||||
if (UnityEditor.PrefabUtility.GetRootGameObject(particleSystem) == go)
|
||||
m_LastPlayableTime = kUnsetTime;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void SetRandomSeed(ParticleSystem particleSystem, uint randomSeed)
|
||||
{
|
||||
if (particleSystem == null)
|
||||
return;
|
||||
|
||||
particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
if (particleSystem.useAutoRandomSeed)
|
||||
{
|
||||
particleSystem.useAutoRandomSeed = false;
|
||||
particleSystem.randomSeed = randomSeed;
|
||||
}
|
||||
|
||||
for (int i = 0; i < particleSystem.subEmitters.subEmittersCount; i++)
|
||||
{
|
||||
SetRandomSeed(particleSystem.subEmitters.GetSubEmitterSystem(i), ++randomSeed);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="data">A FrameData structure that contains information about the current frame context.</param>
|
||||
public override void PrepareFrame(Playable playable, FrameData data)
|
||||
{
|
||||
if (particleSystem == null || !particleSystem.gameObject.activeInHierarchy)
|
||||
{
|
||||
// case 1212943
|
||||
m_LastPlayableTime = kUnsetTime;
|
||||
return;
|
||||
}
|
||||
|
||||
var time = (float)playable.GetTime();
|
||||
var particleTime = particleSystem.time;
|
||||
|
||||
// if particle system time has changed externally, a re-sync is needed
|
||||
if (m_LastPlayableTime > time || !Mathf.Approximately(particleTime, m_LastParticleTime))
|
||||
Simulate(time, true);
|
||||
else if (m_LastPlayableTime < time)
|
||||
Simulate(time - m_LastPlayableTime, false);
|
||||
|
||||
m_LastPlayableTime = time;
|
||||
m_LastParticleTime = particleSystem.time;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
m_LastPlayableTime = kUnsetTime;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
m_LastPlayableTime = kUnsetTime;
|
||||
}
|
||||
|
||||
private void Simulate(float time, bool restart)
|
||||
{
|
||||
const bool withChildren = false;
|
||||
const bool fixedTimeStep = false;
|
||||
float maxTime = Time.maximumDeltaTime;
|
||||
|
||||
if (restart)
|
||||
particleSystem.Simulate(0, withChildren, true, fixedTimeStep);
|
||||
|
||||
// simulating by too large a time-step causes sub-emitters not to work, and loops not to
|
||||
// simulate correctly
|
||||
while (time > maxTime)
|
||||
{
|
||||
particleSystem.Simulate(maxTime, withChildren, false, fixedTimeStep);
|
||||
time -= maxTime;
|
||||
}
|
||||
|
||||
if (time > 0)
|
||||
particleSystem.Simulate(time, withChildren, false, fixedTimeStep);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f603edd7163537f44927ad2808147a25
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Playable that controls and instantiates a Prefab.
|
||||
/// </summary>
|
||||
public class PrefabControlPlayable : PlayableBehaviour
|
||||
{
|
||||
GameObject m_Instance;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private bool m_IsActiveCached;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Playable with a PrefabControlPlayable behaviour attached
|
||||
/// </summary>
|
||||
/// <param name="graph">The PlayableGraph to inject the Playable into.</param>
|
||||
/// <param name="prefabGameObject">The prefab to instantiate from</param>
|
||||
/// <param name="parentTransform">Transform to parent instance to. Can be null.</param>
|
||||
/// <returns>Returns a Playabe with PrefabControlPlayable behaviour attached.</returns>
|
||||
public static ScriptPlayable<PrefabControlPlayable> Create(PlayableGraph graph, GameObject prefabGameObject, Transform parentTransform)
|
||||
{
|
||||
if (prefabGameObject == null)
|
||||
return ScriptPlayable<PrefabControlPlayable>.Null;
|
||||
|
||||
var handle = ScriptPlayable<PrefabControlPlayable>.Create(graph);
|
||||
handle.GetBehaviour().Initialize(prefabGameObject, parentTransform);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The instance of the prefab created by this behaviour
|
||||
/// </summary>
|
||||
public GameObject prefabInstance
|
||||
{
|
||||
get { return m_Instance; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the behaviour with a prefab and parent transform
|
||||
/// </summary>
|
||||
/// <param name="prefabGameObject">The prefab to instantiate from</param>
|
||||
/// <param name="parentTransform">Transform to parent instance to. Can be null.</param>
|
||||
/// <returns>The created instance</returns>
|
||||
public GameObject Initialize(GameObject prefabGameObject, Transform parentTransform)
|
||||
{
|
||||
if (prefabGameObject == null)
|
||||
throw new ArgumentNullException("Prefab cannot be null");
|
||||
|
||||
if (m_Instance != null)
|
||||
{
|
||||
Debug.LogWarningFormat("Prefab Control Playable ({0}) has already been initialized with a Prefab ({1}).", prefabGameObject.name, m_Instance.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
m_Instance = (GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(prefabGameObject, parentTransform);
|
||||
UnityEditor.PrefabUtility.prefabInstanceUpdated += OnPrefabUpdated;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
m_Instance = Object.Instantiate(prefabGameObject, parentTransform, false);
|
||||
}
|
||||
m_Instance.name = prefabGameObject.name + " [Timeline]";
|
||||
m_Instance.SetActive(false);
|
||||
SetHideFlagsRecursive(m_Instance);
|
||||
}
|
||||
return m_Instance;
|
||||
}
|
||||
|
||||
/// <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 (m_Instance)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
Object.Destroy(m_Instance);
|
||||
else
|
||||
Object.DestroyImmediate(m_Instance);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabUpdated;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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_Instance == null)
|
||||
return;
|
||||
|
||||
m_Instance.SetActive(true);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
m_IsActiveCached = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// 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 (m_Instance != null && info.effectivePlayState == PlayState.Paused)
|
||||
{
|
||||
m_Instance.SetActive(false);
|
||||
#if UNITY_EDITOR
|
||||
m_IsActiveCached = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnPrefabUpdated(GameObject go)
|
||||
{
|
||||
if (go == m_Instance)
|
||||
{
|
||||
SetHideFlagsRecursive(go);
|
||||
go.SetActive(m_IsActiveCached);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void SetHideFlagsRecursive(GameObject gameObject)
|
||||
{
|
||||
if (gameObject == null)
|
||||
return;
|
||||
|
||||
gameObject.hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
|
||||
if (!Application.isPlaying)
|
||||
gameObject.hideFlags |= HideFlags.HideInHierarchy;
|
||||
foreach (Transform child in gameObject.transform)
|
||||
{
|
||||
SetHideFlagsRecursive(child.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 439c018cf4619e94d9a92110ce0aa188
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,85 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1db879070d9a45f4c86cdf5e59616df5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this PlayableBehaviour to send notifications at a given time.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnityEngine.Timeline.NotificationFlags"/>
|
||||
public class TimeNotificationBehaviour : PlayableBehaviour
|
||||
{
|
||||
struct NotificationEntry
|
||||
{
|
||||
public double time;
|
||||
public INotification payload;
|
||||
public bool notificationFired;
|
||||
public NotificationFlags flags;
|
||||
|
||||
public bool triggerInEditor
|
||||
{
|
||||
get { return (flags & NotificationFlags.TriggerInEditMode) != 0; }
|
||||
}
|
||||
public bool prewarm
|
||||
{
|
||||
get { return (flags & NotificationFlags.Retroactive) != 0; }
|
||||
}
|
||||
public bool triggerOnce
|
||||
{
|
||||
get { return (flags & NotificationFlags.TriggerOnce) != 0; }
|
||||
}
|
||||
}
|
||||
|
||||
readonly List<NotificationEntry> m_Notifications = new List<NotificationEntry>();
|
||||
double m_PreviousTime;
|
||||
bool m_NeedSortNotifications;
|
||||
|
||||
Playable m_TimeSource;
|
||||
|
||||
/// <summary>
|
||||
/// Sets an optional Playable that provides duration and Wrap mode information.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// timeSource is optional. By default, the duration and Wrap mode will come from the current Playable.
|
||||
/// </remarks>
|
||||
public Playable timeSource
|
||||
{
|
||||
set { m_TimeSource = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and initializes a ScriptPlayable with a TimeNotificationBehaviour.
|
||||
/// </summary>
|
||||
/// <param name="graph">The playable graph.</param>
|
||||
/// <param name="duration">The duration of the playable.</param>
|
||||
/// <param name="loopMode">The loop mode of the playable.</param>
|
||||
/// <returns>A new TimeNotificationBehaviour linked to the PlayableGraph.</returns>
|
||||
public static ScriptPlayable<TimeNotificationBehaviour> Create(PlayableGraph graph, double duration, DirectorWrapMode loopMode)
|
||||
{
|
||||
var notificationsPlayable = ScriptPlayable<TimeNotificationBehaviour>.Create(graph);
|
||||
notificationsPlayable.SetDuration(duration);
|
||||
notificationsPlayable.SetTimeWrapMode(loopMode);
|
||||
notificationsPlayable.SetPropagateSetTime(true);
|
||||
return notificationsPlayable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a notification to be sent with flags, at a specific time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to send the notification.</param>
|
||||
/// <param name="payload">The notification.</param>
|
||||
/// <param name="flags">The notification flags that determine the notification behaviour. This parameter is set to Retroactive by default.</param>
|
||||
/// <seealso cref="UnityEngine.Timeline.NotificationFlags"/>
|
||||
public void AddNotification(double time, INotification payload, NotificationFlags flags = NotificationFlags.Retroactive)
|
||||
{
|
||||
m_Notifications.Add(new NotificationEntry
|
||||
{
|
||||
time = time,
|
||||
payload = payload,
|
||||
flags = flags
|
||||
});
|
||||
m_NeedSortNotifications = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called when the PlayableGraph that owns this PlayableBehaviour starts.
|
||||
/// </summary>
|
||||
/// <param name="playable">The reference to the playable associated with this PlayableBehaviour.</param>
|
||||
public override void OnGraphStart(Playable playable)
|
||||
{
|
||||
SortNotifications();
|
||||
var currentTime = playable.GetTime();
|
||||
for (var i = 0; i < m_Notifications.Count; i++)
|
||||
{
|
||||
// case 1257208 - when a timeline is _resumed_, only reset notifications after the resumed time
|
||||
if (m_Notifications[i].time > currentTime && !m_Notifications[i].triggerOnce)
|
||||
{
|
||||
var notification = m_Notifications[i];
|
||||
notification.notificationFired = false;
|
||||
m_Notifications[i] = notification;
|
||||
}
|
||||
}
|
||||
m_PreviousTime = playable.GetTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called when the Playable play state is changed to PlayState.Paused
|
||||
/// </summary>
|
||||
/// <param name="playable">The reference to the playable associated with this PlayableBehaviour.</param>
|
||||
/// <param name="info">Playable context information such as weight, evaluationType, and so on.</param>
|
||||
public override void OnBehaviourPause(Playable playable, FrameData info)
|
||||
{
|
||||
if (playable.IsDone())
|
||||
{
|
||||
SortNotifications();
|
||||
for (var i = 0; i < m_Notifications.Count; i++)
|
||||
{
|
||||
var e = m_Notifications[i];
|
||||
if (!e.notificationFired)
|
||||
{
|
||||
var duration = playable.GetDuration();
|
||||
var canTrigger = m_PreviousTime <= e.time && e.time <= duration;
|
||||
if (canTrigger)
|
||||
{
|
||||
Trigger_internal(playable, info.output, ref e);
|
||||
m_Notifications[i] = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called during the PrepareFrame phase of the PlayableGraph.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Called once before processing starts.
|
||||
/// </remarks>
|
||||
/// <param name="playable">The reference to the playable associated with this PlayableBehaviour.</param>
|
||||
/// <param name="info">Playable context information such as weight, evaluationType, and so on.</param>
|
||||
public override void PrepareFrame(Playable playable, FrameData info)
|
||||
{
|
||||
// Never trigger on scrub
|
||||
if (info.evaluationType == FrameData.EvaluationType.Evaluate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SyncDurationWithExternalSource(playable);
|
||||
SortNotifications();
|
||||
var currentTime = playable.GetTime();
|
||||
|
||||
// Fire notifications from previousTime till the end
|
||||
if (info.timeLooped)
|
||||
{
|
||||
var duration = playable.GetDuration();
|
||||
TriggerNotificationsInRange(m_PreviousTime, duration, info, playable, true);
|
||||
var dx = playable.GetDuration() - m_PreviousTime;
|
||||
var nFullTimelines = (int)((info.deltaTime * info.effectiveSpeed - dx) / playable.GetDuration());
|
||||
for (var i = 0; i < nFullTimelines; i++)
|
||||
{
|
||||
TriggerNotificationsInRange(0, duration, info, playable, false);
|
||||
}
|
||||
TriggerNotificationsInRange(0, currentTime, info, playable, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var pt = playable.GetTime();
|
||||
TriggerNotificationsInRange(m_PreviousTime, pt, info,
|
||||
playable, true);
|
||||
}
|
||||
|
||||
for (var i = 0; i < m_Notifications.Count; ++i)
|
||||
{
|
||||
var e = m_Notifications[i];
|
||||
if (e.notificationFired && CanRestoreNotification(e, info, currentTime, m_PreviousTime))
|
||||
{
|
||||
Restore_internal(ref e);
|
||||
m_Notifications[i] = e;
|
||||
}
|
||||
}
|
||||
|
||||
m_PreviousTime = playable.GetTime();
|
||||
}
|
||||
|
||||
void SortNotifications()
|
||||
{
|
||||
if (m_NeedSortNotifications)
|
||||
{
|
||||
m_Notifications.Sort((x, y) => x.time.CompareTo(y.time));
|
||||
m_NeedSortNotifications = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool CanRestoreNotification(NotificationEntry e, FrameData info, double currentTime, double previousTime)
|
||||
{
|
||||
if (e.triggerOnce)
|
||||
return false;
|
||||
if (info.timeLooped)
|
||||
return true;
|
||||
|
||||
//case 1111595: restore the notification if the time is manually set before it
|
||||
return previousTime > currentTime && currentTime <= e.time;
|
||||
}
|
||||
|
||||
void TriggerNotificationsInRange(double start, double end, FrameData info, Playable playable, bool checkState)
|
||||
{
|
||||
if (start <= end)
|
||||
{
|
||||
var playMode = Application.isPlaying;
|
||||
for (var i = 0; i < m_Notifications.Count; i++)
|
||||
{
|
||||
var e = m_Notifications[i];
|
||||
if (e.notificationFired && (checkState || e.triggerOnce))
|
||||
continue;
|
||||
|
||||
var notificationTime = e.time;
|
||||
if (e.prewarm && notificationTime < end && (e.triggerInEditor || playMode))
|
||||
{
|
||||
Trigger_internal(playable, info.output, ref e);
|
||||
m_Notifications[i] = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (notificationTime < start || notificationTime > end)
|
||||
continue;
|
||||
|
||||
if (e.triggerInEditor || playMode)
|
||||
{
|
||||
Trigger_internal(playable, info.output, ref e);
|
||||
m_Notifications[i] = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SyncDurationWithExternalSource(Playable playable)
|
||||
{
|
||||
if (m_TimeSource.IsValid())
|
||||
{
|
||||
playable.SetDuration(m_TimeSource.GetDuration());
|
||||
playable.SetTimeWrapMode(m_TimeSource.GetTimeWrapMode());
|
||||
}
|
||||
}
|
||||
|
||||
static void Trigger_internal(Playable playable, PlayableOutput output, ref NotificationEntry e)
|
||||
{
|
||||
output.PushNotification(playable, e.payload);
|
||||
e.notificationFired = true;
|
||||
}
|
||||
|
||||
static void Restore_internal(ref NotificationEntry e)
|
||||
{
|
||||
e.notificationFired = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afeb55855d7a63b45ba6f8bd97599202
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff97302ee78d6ad478b433ec557ee303
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,28 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("UnityEngine.Timeline")]
|
||||
[assembly: AssemblyDescription("Unity Timeline")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Unity Technologies")]
|
||||
[assembly: AssemblyProduct("UnityEngine.Timeline")]
|
||||
[assembly: AssemblyCopyright("Copyright <20> 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: InternalsVisibleTo("Unity.Timeline.Editor")]
|
||||
[assembly: ComVisible(false)]
|
||||
#if UNITY_EDITOR // RuntimeEditor version
|
||||
[assembly: Guid("844F8153-91DB-42D4-9455-CBF1A7BFC434")]
|
||||
#else // Runtime version
|
||||
[assembly: Guid("6A10B290-9283-487F-913B-00D94CD3FAF5")]
|
||||
#endif
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: InternalsVisibleTo("Assembly-CSharp-testable")]
|
||||
[assembly: InternalsVisibleTo("Assembly-CSharp-Editor-testable")]
|
||||
[assembly: InternalsVisibleTo("Unity.Timeline.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Timeline.Tests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Timeline.Tests.Common")]
|
||||
[assembly: InternalsVisibleTo("Unity.Timeline.Tests.Performance")]
|
||||
[assembly: InternalsVisibleTo("Unity.Timeline.Tests.Performance.Editor")]
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bb74b1c097396c49b1691e6a938f814
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdb4f6935641b574b984da8dc27cab45
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// A PlayableTrack is a track whose clips are custom playables.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a track that can contain PlayableAssets that are found in the project and do not have their own specified track type.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[TimelineHelpURL(typeof(PlayableTrack))]
|
||||
public class PlayableTrack : TrackAsset
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void OnCreateClip(TimelineClip clip)
|
||||
{
|
||||
if (clip.asset != null)
|
||||
clip.displayName = clip.asset.GetType().Name;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82cd92ffc29383742932b27ca414c80f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
public partial class TimelineAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum to specify the type of a track. This enum is obsolete.
|
||||
/// </summary>
|
||||
[Obsolete("MediaType has been deprecated. It is no longer required, and will be removed in a future release.", false)]
|
||||
public enum MediaType
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that a track is used for animation.
|
||||
/// <see cref="UnityEngine.Timeline.TimelineAsset.MediaType"/> is obsolete.
|
||||
/// </summary>
|
||||
Animation,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that a track is used for audio.
|
||||
/// <see cref="UnityEngine.Timeline.TimelineAsset.MediaType"/> is obsolete.
|
||||
/// </summary>
|
||||
Audio,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that a track is used for a texture.
|
||||
/// <see cref="UnityEngine.Timeline.TimelineAsset.MediaType"/> is obsolete.
|
||||
/// </summary>
|
||||
Texture = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that a track is used for video.
|
||||
/// <see cref="UnityEngine.Timeline.TimelineAsset.MediaType"/> is obsolete.
|
||||
/// </summary>
|
||||
[Obsolete("Use Texture MediaType instead. (UnityUpgradable) -> UnityEngine.Timeline.TimelineAsset/MediaType.Texture", false)]
|
||||
Video = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that a track is used for scripting.
|
||||
/// <see cref="UnityEngine.Timeline.TimelineAsset.MediaType"/> is obsolete.
|
||||
/// </summary>
|
||||
Script,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that a track is used for multiple media types.
|
||||
/// <see cref="UnityEngine.Timeline.TimelineAsset.MediaType"/> is obsolete.
|
||||
/// </summary>
|
||||
Hybrid,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that a track is used for a group.
|
||||
/// <see cref="UnityEngine.Timeline.TimelineAsset.MediaType"/> is obsolete.
|
||||
/// </summary>
|
||||
Group
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// TrackMediaType defines the type of a track. This attribute is obsolete; it will have no effect.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
[Obsolete("TrackMediaType has been deprecated. It is no longer required, and will be removed in a future release.", false)]
|
||||
public class TrackMediaType : Attribute // Defines the type of a track
|
||||
{
|
||||
/// <summary>
|
||||
/// MediaType of a track.
|
||||
/// <see cref="UnityEngine.Timeline.TrackMediaType"/> is obsolete; it will have no effect.
|
||||
/// </summary>
|
||||
public readonly TimelineAsset.MediaType m_MediaType;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a MediaType attribute.
|
||||
/// <see cref="UnityEngine.Timeline.TrackMediaType"/> is obsolete; it will have no effect.
|
||||
/// </summary>
|
||||
/// <param name="mt"><inheritdoc cref="m_MediaType"/></param>
|
||||
public TrackMediaType(TimelineAsset.MediaType mt)
|
||||
{
|
||||
m_MediaType = mt;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7748a1d3701ac824ea7f366ba0388f5d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,549 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// A PlayableAsset that represents a timeline.
|
||||
/// </summary>
|
||||
[ExcludeFromPreset]
|
||||
[Serializable]
|
||||
[TimelineHelpURL(typeof(TimelineAsset))]
|
||||
public partial class TimelineAsset : PlayableAsset, ISerializationCallbackReceiver, ITimelineClipAsset, IPropertyPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// How the duration of the timeline is determined.
|
||||
/// </summary>
|
||||
public enum DurationMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The duration of the timeline is determined based on the clips present.
|
||||
/// </summary>
|
||||
BasedOnClips,
|
||||
/// <summary>
|
||||
/// The duration of the timeline is a fixed length.
|
||||
/// </summary>
|
||||
FixedLength
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Properties of the timeline that are used by the editor
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class EditorSettings
|
||||
{
|
||||
internal static readonly double kMinFrameRate = TimeUtility.kFrameRateEpsilon;
|
||||
internal static readonly double kMaxFrameRate = 1000.0;
|
||||
internal static readonly double kDefaultFrameRate = 60.0;
|
||||
[HideInInspector, SerializeField, FrameRateField] double m_Framerate = kDefaultFrameRate;
|
||||
[HideInInspector, SerializeField] bool m_ScenePreview = true;
|
||||
|
||||
/// <summary>
|
||||
/// The frames per second used for snapping and time ruler display
|
||||
/// </summary>
|
||||
[Obsolete("EditorSettings.fps has been deprecated. Use editorSettings.frameRate instead.", false)]
|
||||
public float fps
|
||||
{
|
||||
get
|
||||
{
|
||||
return (float)m_Framerate;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_Framerate = Mathf.Clamp(value, (float)kMinFrameRate, (float)kMaxFrameRate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The frames per second used for framelocked preview, frame snapping and time ruler display,
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If frameRate is set to a non-standard custom frame rate, Timeline playback
|
||||
/// may give incorrect results when playbackLockedToFrame is true.
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnityEngine.Timeline.TimelineAsset"/>
|
||||
public double frameRate
|
||||
{
|
||||
get { return m_Framerate; }
|
||||
set { m_Framerate = GetValidFrameRate(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the EditorSetting frameRate to one of the provided standard frame rates.
|
||||
/// </summary>
|
||||
/// <param name="enumValue"> StandardFrameRates value, used to set the current EditorSettings frameRate value.</param>
|
||||
/// <remarks>
|
||||
/// When specifying drop frame values, it is recommended to select one of the provided standard frame rates.
|
||||
/// Specifying a non-standard custom frame rate may give incorrect results when playbackLockedToFrame
|
||||
/// is enabled during Timeline playback.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentException">Thrown when the enumValue is not a valid member of StandardFrameRates.</exception>
|
||||
/// <seealso cref="UnityEngine.Timeline.TimelineAsset"/>
|
||||
public void SetStandardFrameRate(StandardFrameRates enumValue)
|
||||
{
|
||||
FrameRate rate = TimeUtility.ToFrameRate(enumValue);
|
||||
if (rate.IsValid())
|
||||
throw new ArgumentException(String.Format("StandardFrameRates {0}, is not defined",
|
||||
enumValue.ToString()));
|
||||
m_Framerate = rate.rate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set to false to ignore scene preview when this timeline is played by the Timeline window.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When set to false, this setting will
|
||||
/// - Disable scene preview when this timeline is played by the Timeline window.
|
||||
/// - Disable recording for all recordable tracks.
|
||||
/// - Disable play range in the Timeline window.
|
||||
/// - `Stop()` is not called on the `PlayableDirector` when switching between different `TimelineAsset`s in the TimelineWindow.
|
||||
///
|
||||
/// `scenePreview` will only be applied if the asset is the master timeline.
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnityEngine.Timeline.TimelineAsset"/>
|
||||
public bool scenePreview
|
||||
{
|
||||
get => m_ScenePreview;
|
||||
set => m_ScenePreview = value;
|
||||
}
|
||||
}
|
||||
|
||||
[HideInInspector, SerializeField] List<ScriptableObject> m_Tracks;
|
||||
[HideInInspector, SerializeField] double m_FixedDuration; // only applied if duration mode is Fixed
|
||||
[HideInInspector, NonSerialized] TrackAsset[] m_CacheOutputTracks;
|
||||
[HideInInspector, NonSerialized] List<TrackAsset> m_CacheRootTracks;
|
||||
[HideInInspector, NonSerialized] TrackAsset[] m_CacheFlattenedTracks;
|
||||
[HideInInspector, SerializeField] EditorSettings m_EditorSettings = new EditorSettings();
|
||||
[SerializeField] DurationMode m_DurationMode;
|
||||
|
||||
[HideInInspector, SerializeField] MarkerTrack m_MarkerTrack;
|
||||
|
||||
/// <summary>
|
||||
/// Settings used by timeline for editing purposes
|
||||
/// </summary>
|
||||
public EditorSettings editorSettings
|
||||
{
|
||||
get { return m_EditorSettings; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The length, in seconds, of the timeline
|
||||
/// </summary>
|
||||
public override double duration
|
||||
{
|
||||
get
|
||||
{
|
||||
// @todo cache this value when rebuilt
|
||||
if (m_DurationMode == DurationMode.BasedOnClips)
|
||||
{
|
||||
//avoid having no clip evaluated at the end by removing a tick from the total duration
|
||||
var discreteDuration = CalculateItemsDuration();
|
||||
if (discreteDuration <= 0)
|
||||
return 0.0;
|
||||
return (double)discreteDuration.OneTickBefore();
|
||||
}
|
||||
|
||||
return m_FixedDuration;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The length of the timeline when durationMode is set to fixed length.
|
||||
/// </summary>
|
||||
public double fixedDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
DiscreteTime discreteDuration = (DiscreteTime)m_FixedDuration;
|
||||
if (discreteDuration <= 0)
|
||||
return 0.0;
|
||||
|
||||
//avoid having no clip evaluated at the end by removing a tick from the total duration
|
||||
return (double)discreteDuration.OneTickBefore();
|
||||
}
|
||||
set { m_FixedDuration = Math.Max(0.0, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mode used to determine the duration of the Timeline
|
||||
/// </summary>
|
||||
public DurationMode durationMode
|
||||
{
|
||||
get { return m_DurationMode; }
|
||||
set { m_DurationMode = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A description of the PlayableOutputs that will be created by the timeline when instantiated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each track will create an PlayableOutput
|
||||
/// </remarks>
|
||||
public override IEnumerable<PlayableBinding> outputs
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var outputTracks in GetOutputTracks())
|
||||
foreach (var output in outputTracks.outputs)
|
||||
yield return output;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The capabilities supported by all clips in the timeline.
|
||||
/// </summary>
|
||||
public ClipCaps clipCaps
|
||||
{
|
||||
get
|
||||
{
|
||||
var caps = ClipCaps.All;
|
||||
foreach (var track in GetRootTracks())
|
||||
{
|
||||
foreach (var clip in track.clips)
|
||||
caps &= clip.clipCaps;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the the number of output tracks in the Timeline.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An output track is a track the generates a PlayableOutput. In general, an output track is any track that is not a GroupTrack, a subtrack, or override track.
|
||||
/// </remarks>
|
||||
public int outputTrackCount
|
||||
{
|
||||
get
|
||||
{
|
||||
UpdateOutputTrackCache(); // updates the cache if necessary
|
||||
return m_CacheOutputTracks.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of tracks at the root level of the timeline.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A root track refers to all tracks that occur at the root of the timeline. These are the outmost level GroupTracks, and output tracks that do not belong to any group
|
||||
/// </remarks>
|
||||
public int rootTrackCount
|
||||
{
|
||||
get
|
||||
{
|
||||
UpdateRootTrackCache();
|
||||
return m_CacheRootTracks.Count;
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
editorSettings.frameRate = GetValidFrameRate(editorSettings.frameRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves at root track at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the root track to get. Must be between 0 and rootTrackCount</param>
|
||||
/// <remarks>
|
||||
/// A root track refers to all tracks that occur at the root of the timeline. These are the outmost level GroupTracks, and output tracks that do not belong to any group.
|
||||
/// </remarks>
|
||||
/// <returns>Root track at the specified index.</returns>
|
||||
public TrackAsset GetRootTrack(int index)
|
||||
{
|
||||
UpdateRootTrackCache();
|
||||
return m_CacheRootTracks[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an enumerable list of all root tracks.
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerable of all root tracks.</returns>
|
||||
/// <remarks>A root track refers to all tracks that occur at the root of the timeline. These are the outmost level GroupTracks, and output tracks that do not belong to any group.</remarks>
|
||||
public IEnumerable<TrackAsset> GetRootTracks()
|
||||
{
|
||||
UpdateRootTrackCache();
|
||||
return m_CacheRootTracks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrives the output track from the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the output track to retrieve. Must be between 0 and outputTrackCount</param>
|
||||
/// <returns>The output track from the given index</returns>
|
||||
public TrackAsset GetOutputTrack(int index)
|
||||
{
|
||||
UpdateOutputTrackCache();
|
||||
return m_CacheOutputTracks[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all output tracks in the Timeline.
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerable of all output tracks</returns>
|
||||
/// <remarks>
|
||||
/// An output track is a track the generates a PlayableOutput. In general, an output track is any track that is not a GroupTrack or subtrack.
|
||||
/// </remarks>
|
||||
public IEnumerable<TrackAsset> GetOutputTracks()
|
||||
{
|
||||
UpdateOutputTrackCache();
|
||||
return m_CacheOutputTracks;
|
||||
}
|
||||
|
||||
static double GetValidFrameRate(double frameRate)
|
||||
{
|
||||
return Math.Min(Math.Max(frameRate, EditorSettings.kMinFrameRate), EditorSettings.kMaxFrameRate);
|
||||
}
|
||||
|
||||
void UpdateRootTrackCache()
|
||||
{
|
||||
if (m_CacheRootTracks == null)
|
||||
{
|
||||
if (m_Tracks == null)
|
||||
m_CacheRootTracks = new List<TrackAsset>();
|
||||
else
|
||||
{
|
||||
m_CacheRootTracks = new List<TrackAsset>(m_Tracks.Count);
|
||||
if (markerTrack != null)
|
||||
{
|
||||
m_CacheRootTracks.Add(markerTrack);
|
||||
}
|
||||
|
||||
foreach (var t in m_Tracks)
|
||||
{
|
||||
var trackAsset = t as TrackAsset;
|
||||
if (trackAsset != null)
|
||||
m_CacheRootTracks.Add(trackAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOutputTrackCache()
|
||||
{
|
||||
if (m_CacheOutputTracks == null)
|
||||
{
|
||||
var outputTracks = new List<TrackAsset>();
|
||||
foreach (var flattenedTrack in flattenedTracks)
|
||||
{
|
||||
if (flattenedTrack != null && flattenedTrack.GetType() != typeof(GroupTrack) && !flattenedTrack.isSubTrack)
|
||||
outputTracks.Add(flattenedTrack);
|
||||
}
|
||||
m_CacheOutputTracks = outputTracks.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal TrackAsset[] flattenedTracks
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_CacheFlattenedTracks == null)
|
||||
{
|
||||
var list = new List<TrackAsset>(m_Tracks.Count * 2);
|
||||
UpdateRootTrackCache();
|
||||
|
||||
list.AddRange(m_CacheRootTracks);
|
||||
for (int i = 0; i < m_CacheRootTracks.Count; i++)
|
||||
{
|
||||
AddSubTracksRecursive(m_CacheRootTracks[i], ref list);
|
||||
}
|
||||
|
||||
m_CacheFlattenedTracks = list.ToArray();
|
||||
}
|
||||
return m_CacheFlattenedTracks;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the marker track for this TimelineAsset.
|
||||
/// </summary>
|
||||
/// <returns>Returns the marker track.</returns>
|
||||
/// <remarks>
|
||||
/// Use <see cref="TrackAsset.GetMarkers"/> to get a list of the markers on the returned track.
|
||||
/// </remarks>
|
||||
public MarkerTrack markerTrack
|
||||
{
|
||||
get { return m_MarkerTrack; }
|
||||
}
|
||||
|
||||
// access to the track list as scriptable object
|
||||
internal List<ScriptableObject> trackObjects
|
||||
{
|
||||
get { return m_Tracks; }
|
||||
}
|
||||
|
||||
internal void AddTrackInternal(TrackAsset track)
|
||||
{
|
||||
m_Tracks.Add(track);
|
||||
track.parent = this;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
internal void RemoveTrack(TrackAsset track)
|
||||
{
|
||||
m_Tracks.Remove(track);
|
||||
Invalidate();
|
||||
var parentTrack = track.parent as TrackAsset;
|
||||
if (parentTrack != null)
|
||||
{
|
||||
parentTrack.RemoveSubTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the timeline
|
||||
/// </summary>
|
||||
/// <param name="graph">PlayableGraph that will own the playable</param>
|
||||
/// <param name="go">The gameobject that triggered the graph build</param>
|
||||
/// <returns>The Root Playable of the Timeline</returns>
|
||||
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
|
||||
{
|
||||
bool autoRebalanceTree = false;
|
||||
#if UNITY_EDITOR
|
||||
autoRebalanceTree = true;
|
||||
#endif
|
||||
|
||||
// only create outputs if we are not nested
|
||||
bool createOutputs = graph.GetPlayableCount() == 0;
|
||||
var timeline = TimelinePlayable.Create(graph, GetOutputTracks(), go, autoRebalanceTree, createOutputs);
|
||||
timeline.SetDuration(this.duration);
|
||||
timeline.SetPropagateSetTime(true);
|
||||
return timeline.IsValid() ? timeline : Playable.Null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before Unity serializes this object.
|
||||
/// </summary>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
m_Version = k_LatestVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after Unity deserializes this object.
|
||||
/// </summary>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
// resets cache on an Undo
|
||||
Invalidate(); // resets cache on an Undo
|
||||
if (m_Version < k_LatestVersion)
|
||||
{
|
||||
UpgradeToLatestVersion();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal event Action AssetModifiedOnDisk;
|
||||
#endif
|
||||
void __internalAwake()
|
||||
{
|
||||
if (m_Tracks == null)
|
||||
m_Tracks = new List<ScriptableObject>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// case 1280331 -- embedding the timeline asset inside a prefab will create a temporary non-persistent version of an asset
|
||||
// setting the track parents to this will change persistent tracks
|
||||
if (!UnityEditor.EditorUtility.IsPersistent(this))
|
||||
return;
|
||||
#endif
|
||||
|
||||
// validate the array. DON'T remove Unity null objects, just actual null objects
|
||||
for (int i = m_Tracks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
TrackAsset asset = m_Tracks[i] as TrackAsset;
|
||||
if (asset != null)
|
||||
asset.parent = this;
|
||||
#if UNITY_EDITOR
|
||||
object o = m_Tracks[i];
|
||||
if (o == null)
|
||||
{
|
||||
Debug.LogWarning("Empty track found while loading timeline. It will be removed.");
|
||||
m_Tracks.RemoveAt(i);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
AssetModifiedOnDisk?.Invoke();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the Timeline Editor to gather properties requiring preview.
|
||||
/// </summary>
|
||||
/// <param name="director">The PlayableDirector invoking the preview</param>
|
||||
/// <param name="driver">PropertyCollector used to gather previewable properties</param>
|
||||
public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
|
||||
{
|
||||
var outputTracks = GetOutputTracks();
|
||||
foreach (var track in outputTracks)
|
||||
{
|
||||
if (!track.mutedInHierarchy)
|
||||
track.GatherProperties(director, driver);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a marker track for the TimelineAsset.
|
||||
/// </summary>
|
||||
/// In the editor, the marker track appears under the Timeline ruler.
|
||||
/// <remarks>
|
||||
/// This track is always bound to the GameObject that contains the PlayableDirector component for the current timeline.
|
||||
/// The marker track is created the first time this method is called. If the marker track is already created, this method does nothing.
|
||||
/// </remarks>
|
||||
public void CreateMarkerTrack()
|
||||
{
|
||||
if (m_MarkerTrack == null)
|
||||
{
|
||||
m_MarkerTrack = CreateInstance<MarkerTrack>();
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(m_MarkerTrack, this);
|
||||
m_MarkerTrack.parent = this;
|
||||
m_MarkerTrack.name = "Markers"; // This name will show up in the bindings list if it contains signals
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidates the asset, call this if changing the asset data
|
||||
internal void Invalidate()
|
||||
{
|
||||
m_CacheRootTracks = null;
|
||||
m_CacheOutputTracks = null;
|
||||
m_CacheFlattenedTracks = null;
|
||||
}
|
||||
|
||||
internal void UpdateFixedDurationWithItemsDuration()
|
||||
{
|
||||
m_FixedDuration = (double)CalculateItemsDuration();
|
||||
}
|
||||
|
||||
DiscreteTime CalculateItemsDuration()
|
||||
{
|
||||
var discreteDuration = new DiscreteTime(0);
|
||||
foreach (var track in flattenedTracks)
|
||||
{
|
||||
if (track.muted)
|
||||
continue;
|
||||
|
||||
discreteDuration = DiscreteTime.Max(discreteDuration, (DiscreteTime)track.end);
|
||||
}
|
||||
|
||||
if (discreteDuration <= 0)
|
||||
return new DiscreteTime(0);
|
||||
|
||||
return discreteDuration;
|
||||
}
|
||||
|
||||
static void AddSubTracksRecursive(TrackAsset track, ref List<TrackAsset> allTracks)
|
||||
{
|
||||
if (track == null)
|
||||
return;
|
||||
|
||||
allTracks.AddRange(track.GetChildTracks());
|
||||
foreach (TrackAsset subTrack in track.GetChildTracks())
|
||||
{
|
||||
AddSubTracksRecursive(subTrack, ref allTracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfda56da833e2384a9677cd3c976a436
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngineInternal; // for metro type extensions
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
public partial class TimelineAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to create a track and add it to the Timeline.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of track to create. Must derive from TrackAsset.</param>
|
||||
/// <param name="parent">Track to parent to. This can be null.</param>
|
||||
/// <param name="name">Name to give the track.</param>
|
||||
/// <returns>The created track.</returns>
|
||||
/// <remarks>
|
||||
/// This method will throw an InvalidOperationException if the parent is not valid. The parent can be any GroupTrack, or a supported parent type of track. For example, this can be used to create override tracks in AnimationTracks.
|
||||
/// </remarks>
|
||||
public TrackAsset CreateTrack(Type type, TrackAsset parent, string name)
|
||||
{
|
||||
if (parent != null && parent.timelineAsset != this)
|
||||
throw new InvalidOperationException("Addtrack cannot parent to a track not in the Timeline");
|
||||
|
||||
if (!typeof(TrackAsset).IsAssignableFrom(type))
|
||||
throw new InvalidOperationException("Supplied type must be a track asset");
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
if (!TimelineCreateUtilities.ValidateParentTrack(parent, type))
|
||||
throw new InvalidOperationException("Cannot assign a child of type " + type.Name + " to a parent of type " + parent.GetType().Name);
|
||||
}
|
||||
|
||||
var baseName = name;
|
||||
if (string.IsNullOrEmpty(baseName))
|
||||
{
|
||||
baseName = type.Name;
|
||||
#if UNITY_EDITOR
|
||||
baseName = UnityEditor.ObjectNames.NicifyVariableName(baseName);
|
||||
#endif
|
||||
}
|
||||
|
||||
var trackName = baseName;
|
||||
if (parent != null)
|
||||
trackName = TimelineCreateUtilities.GenerateUniqueActorName(parent.subTracksObjects, baseName);
|
||||
else
|
||||
trackName = TimelineCreateUtilities.GenerateUniqueActorName(trackObjects, baseName);
|
||||
|
||||
TrackAsset newTrack = AllocateTrack(parent, trackName, type);
|
||||
|
||||
return newTrack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a track and adds it to the Timeline Asset.
|
||||
/// </summary>
|
||||
/// <param name="parent">Track to parent to. This can be null.</param>
|
||||
/// <param name="trackName">The name of the track being created.</param>
|
||||
/// <typeparam name="T">The type of track being created. The track type must be derived from TrackAsset.</typeparam>
|
||||
/// <returns>Returns the created track.</returns>
|
||||
/// <remarks>
|
||||
/// This method will throw an InvalidOperationException if the parent is not valid. The parent can be any GroupTrack, or a supported parent type of track. For example, this can be used to create override tracks in AnimationTracks.
|
||||
/// </remarks>
|
||||
public T CreateTrack<T>(TrackAsset parent, string trackName) where T : TrackAsset, new()
|
||||
{
|
||||
return (T)CreateTrack(typeof(T), parent, trackName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a track and adds it to the Timeline Asset.
|
||||
/// </summary>
|
||||
/// <param name="trackName">The name of the track being created.</param>
|
||||
/// <typeparam name="T">The type of track being created. The track type must be derived from TrackAsset.</typeparam>
|
||||
/// <returns>Returns the created track.</returns>
|
||||
public T CreateTrack<T>(string trackName) where T : TrackAsset, new()
|
||||
{
|
||||
return (T)CreateTrack(typeof(T), null, trackName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a track and adds it to the Timeline Asset.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of track being created. The track type must be derived from TrackAsset.</typeparam>
|
||||
/// <returns>Returns the created track.</returns>
|
||||
public T CreateTrack<T>() where T : TrackAsset, new()
|
||||
{
|
||||
return (T)CreateTrack(typeof(T), null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a clip from this timeline.
|
||||
/// </summary>
|
||||
/// <param name="clip">The clip to delete.</param>
|
||||
/// <returns>Returns true if the removal was successful</returns>
|
||||
/// <remarks>
|
||||
/// This method will delete a clip and any assets owned by the clip.
|
||||
/// </remarks>
|
||||
public bool DeleteClip(TimelineClip clip)
|
||||
{
|
||||
if (clip == null || clip.GetParentTrack() == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (this != clip.GetParentTrack().timelineAsset)
|
||||
{
|
||||
Debug.LogError("Cannot delete a clip from this timeline");
|
||||
return false;
|
||||
}
|
||||
|
||||
TimelineUndo.PushUndo(clip.GetParentTrack(), "Delete Clip");
|
||||
if (clip.curves != null)
|
||||
{
|
||||
TimelineUndo.PushDestroyUndo(this, clip.GetParentTrack(), clip.curves);
|
||||
}
|
||||
|
||||
// handle wrapped assets
|
||||
if (clip.asset != null)
|
||||
{
|
||||
DeleteRecordedAnimation(clip);
|
||||
|
||||
// TODO -- we should flag assets and owned, instead of this check...
|
||||
#if UNITY_EDITOR
|
||||
string path = UnityEditor.AssetDatabase.GetAssetPath(clip.asset);
|
||||
if (path == UnityEditor.AssetDatabase.GetAssetPath(this))
|
||||
#endif
|
||||
{
|
||||
TimelineUndo.PushDestroyUndo(this, clip.GetParentTrack(), clip.asset);
|
||||
}
|
||||
}
|
||||
|
||||
var clipParentTrack = clip.GetParentTrack();
|
||||
clipParentTrack.RemoveClip(clip);
|
||||
clipParentTrack.CalculateExtrapolationTimes();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a track from a timeline, including all clips and subtracks.
|
||||
/// </summary>
|
||||
/// <param name="track">The track to delete. It must be owned by this Timeline.</param>
|
||||
/// <returns>True if the track was deleted successfully.</returns>
|
||||
public bool DeleteTrack(TrackAsset track)
|
||||
{
|
||||
if (track.timelineAsset != this)
|
||||
return false;
|
||||
|
||||
// push before we modify properties
|
||||
TimelineUndo.PushUndo(track, "Delete Track");
|
||||
TimelineUndo.PushUndo(this, "Delete Track");
|
||||
|
||||
TrackAsset parent = track.parent as TrackAsset;
|
||||
if (parent != null)
|
||||
TimelineUndo.PushUndo(parent, "Delete Track");
|
||||
|
||||
var children = track.GetChildTracks();
|
||||
foreach (var child in children)
|
||||
{
|
||||
DeleteTrack(child);
|
||||
}
|
||||
|
||||
DeleteRecordedAnimation(track);
|
||||
|
||||
var clipsToDelete = new List<TimelineClip>(track.clips);
|
||||
foreach (var clip in clipsToDelete)
|
||||
{
|
||||
DeleteClip(clip);
|
||||
}
|
||||
RemoveTrack(track);
|
||||
|
||||
TimelineUndo.PushDestroyUndo(this, this, track);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void MoveLastTrackBefore(TrackAsset asset)
|
||||
{
|
||||
if (m_Tracks == null || m_Tracks.Count < 2 || asset == null)
|
||||
return;
|
||||
|
||||
var lastTrack = m_Tracks[m_Tracks.Count - 1];
|
||||
if (lastTrack == asset)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < m_Tracks.Count - 1; i++)
|
||||
{
|
||||
if (m_Tracks[i] == asset)
|
||||
{
|
||||
for (int j = m_Tracks.Count - 1; j > i; j--)
|
||||
m_Tracks[j] = m_Tracks[j - 1];
|
||||
m_Tracks[i] = lastTrack;
|
||||
Invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TrackAsset AllocateTrack(TrackAsset trackAssetParent, string trackName, Type trackType)
|
||||
{
|
||||
if (trackAssetParent != null && trackAssetParent.timelineAsset != this)
|
||||
throw new InvalidOperationException("Addtrack cannot parent to a track not in the Timeline");
|
||||
|
||||
if (!typeof(TrackAsset).IsAssignableFrom(trackType))
|
||||
throw new InvalidOperationException("Supplied type must be a track asset");
|
||||
|
||||
var asset = (TrackAsset)CreateInstance(trackType);
|
||||
asset.name = trackName;
|
||||
|
||||
const string createTrackUndoName = "Create Track";
|
||||
|
||||
PlayableAsset parent = trackAssetParent != null ? trackAssetParent as PlayableAsset : this;
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(asset, parent);
|
||||
TimelineUndo.RegisterCreatedObjectUndo(asset, createTrackUndoName);
|
||||
TimelineUndo.PushUndo(parent, createTrackUndoName);
|
||||
|
||||
if (trackAssetParent != null)
|
||||
trackAssetParent.AddChild(asset);
|
||||
else //TimelineAsset is the parent
|
||||
AddTrackInternal(asset);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
void DeleteRecordedAnimation(TrackAsset track)
|
||||
{
|
||||
var animTrack = track as AnimationTrack;
|
||||
if (animTrack != null && animTrack.infiniteClip != null)
|
||||
TimelineUndo.PushDestroyUndo(this, track, animTrack.infiniteClip);
|
||||
|
||||
if (track.curves != null)
|
||||
TimelineUndo.PushDestroyUndo(this, track, track.curves);
|
||||
}
|
||||
|
||||
void DeleteRecordedAnimation(TimelineClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return;
|
||||
|
||||
if (clip.curves != null)
|
||||
TimelineUndo.PushDestroyUndo(this, clip.GetParentTrack(), clip.curves);
|
||||
|
||||
if (!clip.recordable)
|
||||
return;
|
||||
|
||||
AnimationPlayableAsset asset = clip.asset as AnimationPlayableAsset;
|
||||
if (asset == null || asset.clip == null)
|
||||
return;
|
||||
|
||||
TimelineUndo.PushDestroyUndo(this, asset, asset.clip);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93665e8b67658804d99c4487228cc050
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the type of PlayableAsset that a TrackAsset derived class can create clips of.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class TrackClipTypeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the clip class associate with this track
|
||||
/// </summary>
|
||||
public readonly Type inspectedType;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to allow automatic creation of these types.
|
||||
/// </summary>
|
||||
public readonly bool allowAutoCreate; // true will make it show up in menus
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="clipClass">The type of the clip class to associate with this track. Must derive from PlayableAsset.</param>
|
||||
public TrackClipTypeAttribute(Type clipClass)
|
||||
{
|
||||
inspectedType = clipClass;
|
||||
allowAutoCreate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="clipClass">The type of the clip class to associate with this track. Must derive from PlayableAsset.</param>
|
||||
/// <param name="allowAutoCreate">Whether to allow automatic creation of these types. Default value is true.</param>
|
||||
/// <remarks>Setting allowAutoCreate to false will cause Timeline to not show menu items for creating clips of this type.</remarks>
|
||||
public TrackClipTypeAttribute(Type clipClass, bool allowAutoCreate)
|
||||
{
|
||||
inspectedType = clipClass;
|
||||
allowAutoCreate = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this to a PlayableBehaviour class or field to indicate that it is not animatable.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class)]
|
||||
public class NotKeyableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Options for track binding
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TrackBindingFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// No options specified
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Allow automatic creating of component during gameObject drag and drop
|
||||
/// </summary>
|
||||
AllowCreateComponent = 1,
|
||||
|
||||
/// <summary>
|
||||
/// All options specified
|
||||
/// </summary>
|
||||
All = AllowCreateComponent
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type of object that should be bound to a TrackAsset.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code source="../DocCodeExamples/TimelineAttributesExamples.cs" region="declare-sampleTrackBindingAttr" title="SampleTrackBindingAttr"/>
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// Use this attribute when creating Custom Tracks to specify the type of object the track requires a binding to.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class TrackBindingTypeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of binding for the associate track.
|
||||
/// </summary>
|
||||
public readonly Type type;
|
||||
|
||||
/// <summary>
|
||||
/// Options for the the track binding
|
||||
/// </summary>
|
||||
public readonly TrackBindingFlags flags;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new TrackBindingTypeAttribute.
|
||||
/// </summary>
|
||||
/// <param name="type"><inheritdoc cref="TrackBindingTypeAttribute.type"/></param>
|
||||
public TrackBindingTypeAttribute(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
this.flags = TrackBindingFlags.All;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new TrackBindingTypeAttribute.
|
||||
/// </summary>
|
||||
/// <param name="type"><inheritdoc cref="TrackBindingTypeAttribute.type"/></param>
|
||||
/// <param name="flags"><inheritdoc cref="TrackBindingTypeAttribute.flags"/></param>
|
||||
public TrackBindingTypeAttribute(Type type, TrackBindingFlags flags)
|
||||
{
|
||||
this.type = type;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
// indicates that child tracks are permitted on a track
|
||||
// internal because not fully supported on custom classes yet
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
class SupportsChildTracksAttribute : Attribute
|
||||
{
|
||||
public readonly Type childType;
|
||||
public readonly int levels;
|
||||
|
||||
public SupportsChildTracksAttribute(Type childType = null, int levels = Int32.MaxValue)
|
||||
{
|
||||
this.childType = childType;
|
||||
this.levels = levels;
|
||||
}
|
||||
}
|
||||
|
||||
// indicates that the type should not be put on a PlayableTrack
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
class IgnoreOnPlayableTrackAttribute : System.Attribute { }
|
||||
|
||||
// used to flag properties as using a time field (second/frames) display
|
||||
class TimeFieldAttribute : PropertyAttribute
|
||||
{
|
||||
public enum UseEditMode
|
||||
{
|
||||
None,
|
||||
ApplyEditMode
|
||||
}
|
||||
public UseEditMode useEditMode { get; }
|
||||
|
||||
public TimeFieldAttribute(UseEditMode useEditMode = UseEditMode.ApplyEditMode)
|
||||
{
|
||||
this.useEditMode = useEditMode;
|
||||
}
|
||||
}
|
||||
|
||||
class FrameRateFieldAttribute : PropertyAttribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Use this attribute to hide a class from Timeline menus.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class HideInMenuAttribute : Attribute { }
|
||||
|
||||
///<summary>
|
||||
/// Use this attribute to customize the appearance of a Marker.
|
||||
/// </summary>
|
||||
/// Specify the style to use to draw a Marker.
|
||||
/// <example>
|
||||
/// <code source="../DocCodeExamples/TimelineAttributesExamples.cs" region="declare-customStyleMarkerAttr" title="CustomStyleMarkerAttr"/>
|
||||
/// </example>
|
||||
/// How to create a custom style rule:
|
||||
/// 1) Create a 'common.uss' USS file in an Editor folder in a StyleSheets/Extensions folder hierarchy.
|
||||
/// Example of valid folder paths:
|
||||
/// - Assets/Editor/StyleSheets/Extensions
|
||||
/// - Assets/Editor/Markers/StyleSheets/Extensions
|
||||
/// - Assets/Timeline/Editor/MyMarkers/StyleSheets/Extensions
|
||||
/// Rules in 'dark.uss' are used if you use the Pro Skin and rules in 'light.uss' are used otherwise.
|
||||
///
|
||||
/// 2)In the USS file, create a styling rule to customize the appearance of the marker.
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// MyStyle
|
||||
/// {
|
||||
/// /* Specify the appearance of the marker in the collapsed state here. */
|
||||
/// }
|
||||
///
|
||||
/// MyStyle:checked
|
||||
/// {
|
||||
/// /* Specify the appearance of the marker in the expanded state here. */
|
||||
/// }
|
||||
///
|
||||
/// MyStyle:focused:checked
|
||||
/// {
|
||||
/// /* Specify the appearance of the marker in the selected state here. */
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="UnityEngine.Timeline.Marker"/>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class CustomStyleAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the USS style.
|
||||
/// </summary>
|
||||
public readonly string ussStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CustomStyleAttribute.
|
||||
/// </summary>
|
||||
/// <param name="ussStyle"><inheritdoc cref="CustomStyleAttribute.ussStyle"/></param>
|
||||
public CustomStyleAttribute(string ussStyle)
|
||||
{
|
||||
this.ussStyle = ussStyle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this attribute to assign a clip, marker or track to a category in a submenu
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class MenuCategoryAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The menu name of the category
|
||||
/// </summary>
|
||||
public readonly string category;
|
||||
|
||||
public MenuCategoryAttribute(string category)
|
||||
{
|
||||
this.category = category ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e99141cd5dbef844a4338bb87930b89
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,901 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement this interface to support advanced features of timeline clips.
|
||||
/// </summary>
|
||||
public interface ITimelineClipAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a description of the features supported by clips with PlayableAssets implementing this interface.
|
||||
/// </summary>
|
||||
ClipCaps clipCaps { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a clip on the timeline.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public partial class TimelineClip : ICurvesOwner, ISerializationCallbackReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// The default capabilities for a clip
|
||||
/// </summary>
|
||||
public static readonly ClipCaps kDefaultClipCaps = ClipCaps.Blending;
|
||||
|
||||
/// <summary>
|
||||
/// The default length of a clip in seconds.
|
||||
/// </summary>
|
||||
public static readonly float kDefaultClipDurationInSeconds = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum timescale allowed on a clip
|
||||
/// </summary>
|
||||
public static readonly double kTimeScaleMin = 1.0 / 1000;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum timescale allowed on a clip
|
||||
/// </summary>
|
||||
public static readonly double kTimeScaleMax = 1000;
|
||||
|
||||
internal static readonly string kDefaultCurvesName = "Clip Parameters";
|
||||
|
||||
internal static readonly double kMinDuration = 1 / 60.0;
|
||||
|
||||
// constant representing the longest possible sequence duration
|
||||
internal static readonly double kMaxTimeValue = 1000000; // more than a week's time, and within numerical precision boundaries
|
||||
|
||||
/// <summary>
|
||||
/// How the clip handles time outside its start and end range.
|
||||
/// </summary>
|
||||
public enum ClipExtrapolation
|
||||
{
|
||||
/// <summary>
|
||||
/// No extrapolation is applied.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Hold the time at the end value of the clip.
|
||||
/// </summary>
|
||||
Hold,
|
||||
|
||||
/// <summary>
|
||||
/// Repeat time values outside the start/end range.
|
||||
/// </summary>
|
||||
Loop,
|
||||
|
||||
/// <summary>
|
||||
/// Repeat time values outside the start/end range, reversing direction at each loop
|
||||
/// </summary>
|
||||
PingPong,
|
||||
|
||||
/// <summary>
|
||||
/// Time values are passed in without modification, extending beyond the clips range
|
||||
/// </summary>
|
||||
Continue
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// How blend curves are treated in an overlap
|
||||
/// </summary>
|
||||
public enum BlendCurveMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The curve is normalized against the opposing clip
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// The blend curve is fixed.
|
||||
/// </summary>
|
||||
Manual
|
||||
};
|
||||
|
||||
internal TimelineClip(TrackAsset parent)
|
||||
{
|
||||
// parent clip into track
|
||||
SetParentTrack_Internal(parent);
|
||||
}
|
||||
|
||||
[SerializeField] double m_Start;
|
||||
[SerializeField] double m_ClipIn;
|
||||
[SerializeField] Object m_Asset;
|
||||
[SerializeField] [FormerlySerializedAs("m_HackDuration")] double m_Duration;
|
||||
[SerializeField] double m_TimeScale = 1.0;
|
||||
[SerializeField] TrackAsset m_ParentTrack;
|
||||
|
||||
// for mixing out scripts - default is no mix out (i.e. flat)
|
||||
[SerializeField] double m_EaseInDuration;
|
||||
[SerializeField] double m_EaseOutDuration;
|
||||
|
||||
// the blend durations override ease in / out durations
|
||||
[SerializeField] double m_BlendInDuration = -1.0f;
|
||||
[SerializeField] double m_BlendOutDuration = -1.0f;
|
||||
|
||||
// doubles as ease in/out and blend in/out curves
|
||||
[SerializeField] AnimationCurve m_MixInCurve;
|
||||
[SerializeField] AnimationCurve m_MixOutCurve;
|
||||
|
||||
[SerializeField] BlendCurveMode m_BlendInCurveMode = BlendCurveMode.Auto;
|
||||
[SerializeField] BlendCurveMode m_BlendOutCurveMode = BlendCurveMode.Auto;
|
||||
|
||||
[SerializeField] List<string> m_ExposedParameterNames;
|
||||
[SerializeField] AnimationClip m_AnimationCurves;
|
||||
|
||||
[SerializeField] bool m_Recordable;
|
||||
|
||||
// extrapolation
|
||||
[SerializeField] ClipExtrapolation m_PostExtrapolationMode;
|
||||
[SerializeField] ClipExtrapolation m_PreExtrapolationMode;
|
||||
[SerializeField] double m_PostExtrapolationTime;
|
||||
[SerializeField] double m_PreExtrapolationTime;
|
||||
|
||||
[SerializeField] string m_DisplayName;
|
||||
|
||||
/// <summary>
|
||||
/// Is the clip being extrapolated before its start time?
|
||||
/// </summary>
|
||||
public bool hasPreExtrapolation
|
||||
{
|
||||
get { return m_PreExtrapolationMode != ClipExtrapolation.None && m_PreExtrapolationTime > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the clip being extrapolated past its end time?
|
||||
/// </summary>
|
||||
public bool hasPostExtrapolation
|
||||
{
|
||||
get { return m_PostExtrapolationMode != ClipExtrapolation.None && m_PostExtrapolationTime > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A speed multiplier for the clip;
|
||||
/// </summary>
|
||||
public double timeScale
|
||||
{
|
||||
get { return clipCaps.HasAny(ClipCaps.SpeedMultiplier) ? Math.Max(kTimeScaleMin, Math.Min(m_TimeScale, kTimeScaleMax)) : 1.0; }
|
||||
set
|
||||
{
|
||||
UpdateDirty(m_TimeScale, value);
|
||||
m_TimeScale = clipCaps.HasAny(ClipCaps.SpeedMultiplier) ? Math.Max(kTimeScaleMin, Math.Min(value, kTimeScaleMax)) : 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The start time, in seconds, of the clip
|
||||
/// </summary>
|
||||
public double start
|
||||
{
|
||||
get { return m_Start; }
|
||||
set
|
||||
{
|
||||
UpdateDirty(value, m_Start);
|
||||
var newValue = Math.Max(SanitizeTimeValue(value, m_Start), 0);
|
||||
if (m_ParentTrack != null && m_Start != newValue)
|
||||
{
|
||||
m_ParentTrack.OnClipMove();
|
||||
}
|
||||
m_Start = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The length, in seconds, of the clip
|
||||
/// </summary>
|
||||
public double duration
|
||||
{
|
||||
get { return m_Duration; }
|
||||
set
|
||||
{
|
||||
UpdateDirty(m_Duration, value);
|
||||
m_Duration = Math.Max(SanitizeTimeValue(value, m_Duration), double.Epsilon);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The end time, in seconds of the clip
|
||||
/// </summary>
|
||||
public double end
|
||||
{
|
||||
get { return m_Start + m_Duration; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Local offset time of the clip.
|
||||
/// </summary>
|
||||
public double clipIn
|
||||
{
|
||||
get { return clipCaps.HasAny(ClipCaps.ClipIn) ? m_ClipIn : 0; }
|
||||
set
|
||||
{
|
||||
UpdateDirty(m_ClipIn, value);
|
||||
m_ClipIn = clipCaps.HasAny(ClipCaps.ClipIn) ? Math.Max(Math.Min(SanitizeTimeValue(value, m_ClipIn), kMaxTimeValue), 0.0) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name displayed on the clip
|
||||
/// </summary>
|
||||
public string displayName
|
||||
{
|
||||
get { return m_DisplayName; }
|
||||
set { m_DisplayName = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The length, in seconds, of the PlayableAsset attached to the clip.
|
||||
/// </summary>
|
||||
public double clipAssetDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
var playableAsset = m_Asset as IPlayableAsset;
|
||||
return playableAsset != null ? playableAsset.duration : double.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An animation clip containing animated properties of the attached PlayableAsset
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is where animated clip properties are stored.
|
||||
/// </remarks>
|
||||
public AnimationClip curves
|
||||
{
|
||||
get { return m_AnimationCurves; }
|
||||
internal set { m_AnimationCurves = value; }
|
||||
}
|
||||
|
||||
string ICurvesOwner.defaultCurvesName
|
||||
{
|
||||
get { return kDefaultCurvesName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this clip contains animated properties for the attached PlayableAsset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is false if the curves property is null or if it contains no information.
|
||||
/// </remarks>
|
||||
public bool hasCurves
|
||||
{
|
||||
get { return m_AnimationCurves != null && !m_AnimationCurves.empty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The PlayableAsset attached to the clip.
|
||||
/// </summary>
|
||||
public Object asset
|
||||
{
|
||||
get { return m_Asset; }
|
||||
set { m_Asset = value; }
|
||||
}
|
||||
|
||||
Object ICurvesOwner.assetOwner
|
||||
{
|
||||
get { return GetParentTrack(); }
|
||||
}
|
||||
|
||||
TrackAsset ICurvesOwner.targetTrack
|
||||
{
|
||||
get { return GetParentTrack(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// underlyingAsset property is obsolete. Use asset property instead
|
||||
/// </summary>
|
||||
[Obsolete("underlyingAsset property is obsolete. Use asset property instead", true)]
|
||||
public Object underlyingAsset
|
||||
{
|
||||
get { return null; }
|
||||
set { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the TrackAsset to which this clip is attached.
|
||||
/// </summary>
|
||||
[Obsolete("parentTrack is deprecated and will be removed in a future release. Use " + nameof(GetParentTrack) + "() and " + nameof(TimelineClipExtensions) + "::" + nameof(TimelineClipExtensions.MoveToTrack) + "() or " + nameof(TimelineClipExtensions) + "::" + nameof(TimelineClipExtensions.TryMoveToTrack) + "() instead.", false)]
|
||||
public TrackAsset parentTrack
|
||||
{
|
||||
get { return m_ParentTrack; }
|
||||
set { SetParentTrack_Internal(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the TrackAsset to which this clip is attached.
|
||||
/// </summary>
|
||||
/// <returns>the parent TrackAsset</returns>
|
||||
public TrackAsset GetParentTrack()
|
||||
{
|
||||
return m_ParentTrack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the parent track without performing any validation. To ensure a valid change use TimelineClipExtensions.TrySetParentTrack(TrackAsset) instead.
|
||||
/// </summary>
|
||||
/// <param name="newParentTrack"></param>
|
||||
internal void SetParentTrack_Internal(TrackAsset newParentTrack)
|
||||
{
|
||||
if (m_ParentTrack == newParentTrack)
|
||||
return;
|
||||
|
||||
if (m_ParentTrack != null)
|
||||
m_ParentTrack.RemoveClip(this);
|
||||
|
||||
m_ParentTrack = newParentTrack;
|
||||
|
||||
if (m_ParentTrack != null)
|
||||
m_ParentTrack.AddClip(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ease in duration of the timeline clip in seconds. This only applies if the start of the clip is not overlapping.
|
||||
/// </summary>
|
||||
public double easeInDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
var availableDuration = hasBlendOut ? duration - m_BlendOutDuration : duration;
|
||||
return clipCaps.HasAny(ClipCaps.Blending) ? Math.Min(Math.Max(m_EaseInDuration, 0), availableDuration) : 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
var availableDuration = hasBlendOut ? duration - m_BlendOutDuration : duration;
|
||||
m_EaseInDuration = clipCaps.HasAny(ClipCaps.Blending) ? Math.Max(0, Math.Min(SanitizeTimeValue(value, m_EaseInDuration), availableDuration)) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ease out duration of the timeline clip in seconds. This only applies if the end of the clip is not overlapping.
|
||||
/// </summary>
|
||||
public double easeOutDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
var availableDuration = hasBlendIn ? duration - m_BlendInDuration : duration;
|
||||
return clipCaps.HasAny(ClipCaps.Blending) ? Math.Min(Math.Max(m_EaseOutDuration, 0), availableDuration) : 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
var availableDuration = hasBlendIn ? duration - m_BlendInDuration : duration;
|
||||
m_EaseOutDuration = clipCaps.HasAny(ClipCaps.Blending) ? Math.Max(0, Math.Min(SanitizeTimeValue(value, m_EaseOutDuration), availableDuration)) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// eastOutTime property is obsolete use easeOutTime property instead
|
||||
/// </summary>
|
||||
[Obsolete("Use easeOutTime instead (UnityUpgradable) -> easeOutTime", true)]
|
||||
public double eastOutTime
|
||||
{
|
||||
get { return duration - easeOutDuration + m_Start; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The time in seconds that the ease out begins
|
||||
/// </summary>
|
||||
public double easeOutTime
|
||||
{
|
||||
get { return duration - easeOutDuration + m_Start; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of overlap in seconds on the start of a clip.
|
||||
/// </summary>
|
||||
public double blendInDuration
|
||||
{
|
||||
get { return clipCaps.HasAny(ClipCaps.Blending) ? m_BlendInDuration : 0; }
|
||||
set { m_BlendInDuration = clipCaps.HasAny(ClipCaps.Blending) ? SanitizeTimeValue(value, m_BlendInDuration) : 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of overlap in seconds at the end of a clip.
|
||||
/// </summary>
|
||||
public double blendOutDuration
|
||||
{
|
||||
get { return clipCaps.HasAny(ClipCaps.Blending) ? m_BlendOutDuration : 0; }
|
||||
set { m_BlendOutDuration = clipCaps.HasAny(ClipCaps.Blending) ? SanitizeTimeValue(value, m_BlendOutDuration) : 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mode for calculating the blend curve of the overlap at the start of the clip
|
||||
/// </summary>
|
||||
public BlendCurveMode blendInCurveMode
|
||||
{
|
||||
get { return m_BlendInCurveMode; }
|
||||
set { m_BlendInCurveMode = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mode for calculating the blend curve of the overlap at the end of the clip
|
||||
/// </summary>
|
||||
public BlendCurveMode blendOutCurveMode
|
||||
{
|
||||
get { return m_BlendOutCurveMode; }
|
||||
set { m_BlendOutCurveMode = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the clip is blending in
|
||||
/// </summary>
|
||||
public bool hasBlendIn { get { return clipCaps.HasAny(ClipCaps.Blending) && m_BlendInDuration > 0; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the clip is blending out
|
||||
/// </summary>
|
||||
public bool hasBlendOut { get { return clipCaps.HasAny(ClipCaps.Blending) && m_BlendOutDuration > 0; } }
|
||||
|
||||
/// <summary>
|
||||
/// The animation curve used for calculating weights during an ease in or a blend in.
|
||||
/// </summary>
|
||||
public AnimationCurve mixInCurve
|
||||
{
|
||||
get
|
||||
{
|
||||
// auto fix broken curves
|
||||
if (m_MixInCurve == null || m_MixInCurve.length < 2)
|
||||
m_MixInCurve = GetDefaultMixInCurve();
|
||||
|
||||
return m_MixInCurve;
|
||||
}
|
||||
set { m_MixInCurve = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of the clip being used for ease or blend in as a percentage
|
||||
/// </summary>
|
||||
public float mixInPercentage
|
||||
{
|
||||
get { return (float)(mixInDuration / duration); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of the clip blending or easing in, in seconds
|
||||
/// </summary>
|
||||
public double mixInDuration
|
||||
{
|
||||
get { return hasBlendIn ? blendInDuration : easeInDuration; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation curve used for calculating weights during an ease out or a blend out.
|
||||
/// </summary>
|
||||
public AnimationCurve mixOutCurve
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_MixOutCurve == null || m_MixOutCurve.length < 2)
|
||||
m_MixOutCurve = GetDefaultMixOutCurve();
|
||||
return m_MixOutCurve;
|
||||
}
|
||||
set { m_MixOutCurve = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The time in seconds that an ease out or blend out starts
|
||||
/// </summary>
|
||||
public double mixOutTime
|
||||
{
|
||||
get { return duration - mixOutDuration + m_Start; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of the clip blending or easing out, in seconds
|
||||
/// </summary>
|
||||
public double mixOutDuration
|
||||
{
|
||||
get { return hasBlendOut ? blendOutDuration : easeOutDuration; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of the clip being used for ease or blend out as a percentage
|
||||
/// </summary>
|
||||
public float mixOutPercentage
|
||||
{
|
||||
get { return (float)(mixOutDuration / duration); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this clip is recordable in editor
|
||||
/// </summary>
|
||||
public bool recordable
|
||||
{
|
||||
get { return m_Recordable; }
|
||||
internal set { m_Recordable = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// exposedParameter is deprecated and will be removed in a future release
|
||||
/// </summary>
|
||||
[Obsolete("exposedParameter is deprecated and will be removed in a future release", true)]
|
||||
public List<string> exposedParameters
|
||||
{
|
||||
get { return m_ExposedParameterNames ?? (m_ExposedParameterNames = new List<string>()); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the capabilities supported by this clip.
|
||||
/// </summary>
|
||||
public ClipCaps clipCaps
|
||||
{
|
||||
get
|
||||
{
|
||||
var clipAsset = asset as ITimelineClipAsset;
|
||||
return (clipAsset != null) ? clipAsset.clipCaps : kDefaultClipCaps;
|
||||
}
|
||||
}
|
||||
|
||||
internal int Hash()
|
||||
{
|
||||
return HashUtility.CombineHash(m_Start.GetHashCode(),
|
||||
m_Duration.GetHashCode(),
|
||||
m_TimeScale.GetHashCode(),
|
||||
m_ClipIn.GetHashCode(),
|
||||
((int)m_PreExtrapolationMode).GetHashCode(),
|
||||
((int)m_PostExtrapolationMode).GetHashCode());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a time, returns the weight from the mix out
|
||||
/// </summary>
|
||||
/// <param name="time">Time (relative to the timeline)</param>
|
||||
/// <returns></returns>
|
||||
public float EvaluateMixOut(double time)
|
||||
{
|
||||
if (!clipCaps.HasAny(ClipCaps.Blending))
|
||||
return 1.0f;
|
||||
|
||||
if (mixOutDuration > Mathf.Epsilon)
|
||||
{
|
||||
var perc = (float)(time - mixOutTime) / (float)mixOutDuration;
|
||||
perc = Mathf.Clamp01(mixOutCurve.Evaluate(perc));
|
||||
return perc;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a time, returns the weight from the mix in
|
||||
/// </summary>
|
||||
/// <param name="time">Time (relative to the timeline)</param>
|
||||
/// <returns></returns>
|
||||
public float EvaluateMixIn(double time)
|
||||
{
|
||||
if (!clipCaps.HasAny(ClipCaps.Blending))
|
||||
return 1.0f;
|
||||
|
||||
if (mixInDuration > Mathf.Epsilon)
|
||||
{
|
||||
var perc = (float)(time - m_Start) / (float)mixInDuration;
|
||||
perc = Mathf.Clamp01(mixInCurve.Evaluate(perc));
|
||||
return perc;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
static AnimationCurve GetDefaultMixInCurve()
|
||||
{
|
||||
return AnimationCurve.EaseInOut(0, 0, 1, 1);
|
||||
}
|
||||
|
||||
static AnimationCurve GetDefaultMixOutCurve()
|
||||
{
|
||||
return AnimationCurve.EaseInOut(0, 1, 1, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from global time to a clips local time.
|
||||
/// </summary>
|
||||
/// <param name="time">time relative to the timeline</param>
|
||||
/// <returns>
|
||||
/// The local time with extrapolation applied
|
||||
/// </returns>
|
||||
public double ToLocalTime(double time)
|
||||
{
|
||||
if (time < 0)
|
||||
return time;
|
||||
|
||||
// handle Extrapolation
|
||||
if (IsPreExtrapolatedTime(time))
|
||||
time = GetExtrapolatedTime(time - m_Start, m_PreExtrapolationMode, m_Duration);
|
||||
else if (IsPostExtrapolatedTime(time))
|
||||
time = GetExtrapolatedTime(time - m_Start, m_PostExtrapolationMode, m_Duration);
|
||||
else
|
||||
time -= m_Start;
|
||||
|
||||
// handle looping and time scale within the clip
|
||||
time *= timeScale;
|
||||
time += clipIn;
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from global time to local time of the clip
|
||||
/// </summary>
|
||||
/// <param name="time">The time relative to the timeline</param>
|
||||
/// <returns>The local time, ignoring any extrapolation or bounds</returns>
|
||||
public double ToLocalTimeUnbound(double time)
|
||||
{
|
||||
return (time - m_Start) * timeScale + clipIn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from local time of the clip to global time
|
||||
/// </summary>
|
||||
/// <param name="time">Time relative to the clip</param>
|
||||
/// <returns>The time relative to the timeline</returns>
|
||||
internal double FromLocalTimeUnbound(double time)
|
||||
{
|
||||
return (time - clipIn) / timeScale + m_Start;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this contains an animation asset, returns the animation clip attached. Otherwise returns null.
|
||||
/// </summary>
|
||||
public AnimationClip animationClip
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Asset == null)
|
||||
return null;
|
||||
|
||||
var playableAsset = m_Asset as AnimationPlayableAsset;
|
||||
return playableAsset != null ? playableAsset.clip : null;
|
||||
}
|
||||
}
|
||||
|
||||
static double SanitizeTimeValue(double value, double defaultValue)
|
||||
{
|
||||
if (double.IsInfinity(value) || double.IsNaN(value))
|
||||
{
|
||||
Debug.LogError("Invalid time value assigned");
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return Math.Max(-kMaxTimeValue, Math.Min(kMaxTimeValue, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the clip is being extrapolated past the end time.
|
||||
/// </summary>
|
||||
public ClipExtrapolation postExtrapolationMode
|
||||
{
|
||||
get { return clipCaps.HasAny(ClipCaps.Extrapolation) ? m_PostExtrapolationMode : ClipExtrapolation.None; }
|
||||
internal set { m_PostExtrapolationMode = clipCaps.HasAny(ClipCaps.Extrapolation) ? value : ClipExtrapolation.None; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the clip is being extrapolated before the start time.
|
||||
/// </summary>
|
||||
public ClipExtrapolation preExtrapolationMode
|
||||
{
|
||||
get { return clipCaps.HasAny(ClipCaps.Extrapolation) ? m_PreExtrapolationMode : ClipExtrapolation.None; }
|
||||
internal set { m_PreExtrapolationMode = clipCaps.HasAny(ClipCaps.Extrapolation) ? value : ClipExtrapolation.None; }
|
||||
}
|
||||
|
||||
internal void SetPostExtrapolationTime(double time)
|
||||
{
|
||||
m_PostExtrapolationTime = time;
|
||||
}
|
||||
|
||||
internal void SetPreExtrapolationTime(double time)
|
||||
{
|
||||
m_PreExtrapolationTime = time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a time, returns whether it falls within the clip's extrapolation
|
||||
/// </summary>
|
||||
/// <param name="sequenceTime">The time relative to the timeline</param>
|
||||
/// <returns>True if <paramref name="sequenceTime"/> is within the clip extrapolation</returns>
|
||||
public bool IsExtrapolatedTime(double sequenceTime)
|
||||
{
|
||||
return IsPreExtrapolatedTime(sequenceTime) || IsPostExtrapolatedTime(sequenceTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a time, returns whether it falls within the clip's pre-extrapolation
|
||||
/// </summary>
|
||||
/// <param name="sequenceTime">The time relative to the timeline</param>
|
||||
/// <returns>True if <paramref name="sequenceTime"/> is within the clip pre-extrapolation</returns>
|
||||
public bool IsPreExtrapolatedTime(double sequenceTime)
|
||||
{
|
||||
return preExtrapolationMode != ClipExtrapolation.None &&
|
||||
sequenceTime < m_Start && sequenceTime >= m_Start - m_PreExtrapolationTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a time, returns whether it falls within the clip's post-extrapolation
|
||||
/// </summary>
|
||||
/// <param name="sequenceTime">The time relative to the timeline</param>
|
||||
/// <returns>True if <paramref name="sequenceTime"/> is within the clip post-extrapolation</returns>
|
||||
public bool IsPostExtrapolatedTime(double sequenceTime)
|
||||
{
|
||||
return postExtrapolationMode != ClipExtrapolation.None &&
|
||||
(sequenceTime > end) && (sequenceTime - end < m_PostExtrapolationTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The start time of the clip, accounting for pre-extrapolation
|
||||
/// </summary>
|
||||
public double extrapolatedStart
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_PreExtrapolationMode != ClipExtrapolation.None)
|
||||
return m_Start - m_PreExtrapolationTime;
|
||||
|
||||
return m_Start;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The length of the clip in seconds, including extrapolation.
|
||||
/// </summary>
|
||||
public double extrapolatedDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
double length = m_Duration;
|
||||
|
||||
if (m_PostExtrapolationMode != ClipExtrapolation.None)
|
||||
length += Math.Min(m_PostExtrapolationTime, kMaxTimeValue);
|
||||
|
||||
if (m_PreExtrapolationMode != ClipExtrapolation.None)
|
||||
length += m_PreExtrapolationTime;
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
static double GetExtrapolatedTime(double time, ClipExtrapolation mode, double duration)
|
||||
{
|
||||
if (duration == 0)
|
||||
return 0;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case ClipExtrapolation.None:
|
||||
break;
|
||||
|
||||
case ClipExtrapolation.Loop:
|
||||
if (time < 0)
|
||||
time = duration - (-time % duration);
|
||||
else if (time > duration)
|
||||
time %= duration;
|
||||
break;
|
||||
|
||||
case ClipExtrapolation.Hold:
|
||||
if (time < 0)
|
||||
return 0;
|
||||
if (time > duration)
|
||||
return duration;
|
||||
break;
|
||||
|
||||
case ClipExtrapolation.PingPong:
|
||||
if (time < 0)
|
||||
{
|
||||
time = duration * 2 - (-time % (duration * 2));
|
||||
time = duration - Math.Abs(time - duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
time = time % (duration * 2.0);
|
||||
time = duration - Math.Abs(time - duration);
|
||||
}
|
||||
break;
|
||||
|
||||
case ClipExtrapolation.Continue:
|
||||
break;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AnimationClip to store animated properties for the attached PlayableAsset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If curves already exists for this clip, this method produces no result regardless of the
|
||||
/// value specified for curvesClipName.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// When used from the editor, this method attempts to save the created curves clip to the TimelineAsset.
|
||||
/// The TimelineAsset must already exist in the AssetDatabase to save the curves clip. If the TimelineAsset
|
||||
/// does not exist, the curves clip is still created but it is not saved.
|
||||
/// </remarks>
|
||||
/// <param name="curvesClipName">
|
||||
/// The name of the AnimationClip to create.
|
||||
/// This method does not ensure unique names. If you want a unique clip name, you must provide one.
|
||||
/// See ObjectNames.GetUniqueName for information on a method that creates unique names.
|
||||
/// </param>
|
||||
public void CreateCurves(string curvesClipName)
|
||||
{
|
||||
if (m_AnimationCurves != null)
|
||||
return;
|
||||
|
||||
m_AnimationCurves = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(curvesClipName) ? kDefaultCurvesName : curvesClipName, GetParentTrack(), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before Unity serializes this object.
|
||||
/// </summary>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
m_Version = k_LatestVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after Unity deserializes this object.
|
||||
/// </summary>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
if (m_Version < k_LatestVersion)
|
||||
{
|
||||
UpgradeToLatestVersion();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a more readable representation of the timeline clip as a string
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return UnityString.Format("{0} ({1:F2}, {2:F2}):{3:F2} | {4}", displayName, start, end, clipIn, GetParentTrack());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this method to adjust ease in and ease out values to avoid overlapping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Ease values will be adjusted to respect the ratio between ease in and ease out.
|
||||
/// </remarks>
|
||||
public void ConformEaseValues()
|
||||
{
|
||||
if (m_EaseInDuration + m_EaseOutDuration > duration)
|
||||
{
|
||||
var ratio = CalculateEasingRatio(m_EaseInDuration, m_EaseOutDuration);
|
||||
m_EaseInDuration = duration * ratio;
|
||||
m_EaseOutDuration = duration * (1.0 - ratio);
|
||||
}
|
||||
}
|
||||
|
||||
static double CalculateEasingRatio(double easeIn, double easeOut)
|
||||
{
|
||||
if (Math.Abs(easeIn - easeOut) < TimeUtility.kTimeEpsilon)
|
||||
return 0.5;
|
||||
|
||||
if (easeIn == 0.0)
|
||||
return 0.0;
|
||||
|
||||
if (easeOut == 0.0)
|
||||
return 1.0;
|
||||
|
||||
return easeIn / (easeIn + easeOut);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal int DirtyIndex { get; private set; }
|
||||
internal void MarkDirty()
|
||||
{
|
||||
DirtyIndex++;
|
||||
}
|
||||
|
||||
void UpdateDirty(double oldValue, double newValue)
|
||||
{
|
||||
if (oldValue != newValue)
|
||||
DirtyIndex++;
|
||||
}
|
||||
|
||||
#else
|
||||
void UpdateDirty(double oldValue, double newValue) { }
|
||||
#endif
|
||||
};
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65f3a4c67e4927a478b7036bae1da0e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Audio;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
// Generic evaluation callback called after all the clips have been processed
|
||||
internal interface ITimelineEvaluateCallback
|
||||
{
|
||||
void Evaluate();
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// This Rebalancer class ensures that the interval tree structures stays balance regardless of whether the intervals inside change.
|
||||
/// </summary>
|
||||
class IntervalTreeRebalancer
|
||||
{
|
||||
private IntervalTree<RuntimeElement> m_Tree;
|
||||
public IntervalTreeRebalancer(IntervalTree<RuntimeElement> tree)
|
||||
{
|
||||
m_Tree = tree;
|
||||
}
|
||||
|
||||
public bool Rebalance()
|
||||
{
|
||||
m_Tree.UpdateIntervals();
|
||||
return m_Tree.dirty;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// The TimelinePlayable Playable
|
||||
// This is the actual runtime playable that gets evaluated as part of a playable graph.
|
||||
// It "compiles" a list of tracks into an IntervalTree of Runtime clips.
|
||||
// At each frame, it advances time, then fetches the "intersection: of various time interval
|
||||
// using the interval tree.
|
||||
// Finally, on each intersecting clip, it will calculate each clips' local time, as well as
|
||||
// blend weight and set them accordingly
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The root Playable generated by timeline.
|
||||
/// </summary>
|
||||
public class TimelinePlayable : PlayableBehaviour
|
||||
{
|
||||
private IntervalTree<RuntimeElement> m_IntervalTree = new IntervalTree<RuntimeElement>();
|
||||
private List<RuntimeElement> m_ActiveClips = new List<RuntimeElement>();
|
||||
private List<RuntimeElement> m_CurrentListOfActiveClips;
|
||||
private int m_ActiveBit = 0;
|
||||
|
||||
private List<ITimelineEvaluateCallback> m_EvaluateCallbacks = new List<ITimelineEvaluateCallback>();
|
||||
|
||||
private Dictionary<TrackAsset, Playable> m_PlayableCache = new Dictionary<TrackAsset, Playable>();
|
||||
|
||||
internal static bool muteAudioScrubbing = true;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private IntervalTreeRebalancer m_Rebalancer;
|
||||
internal static event Action<Playable> playableLooped;
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Creates an instance of a Timeline
|
||||
/// </summary>
|
||||
/// <param name="graph">The playable graph to inject the timeline.</param>
|
||||
/// <param name="tracks">The list of tracks to compile</param>
|
||||
/// <param name="go">The GameObject that initiated the compilation</param>
|
||||
/// <param name="autoRebalance">In the editor, whether the graph should account for the possibility of changing clip times</param>
|
||||
/// <param name="createOutputs">Whether to create PlayableOutputs in the graph</param>
|
||||
/// <returns>A subgraph with the playable containing a TimelinePlayable behaviour as the root</returns>
|
||||
public static ScriptPlayable<TimelinePlayable> Create(PlayableGraph graph, IEnumerable<TrackAsset> tracks, GameObject go, bool autoRebalance, bool createOutputs)
|
||||
{
|
||||
if (tracks == null)
|
||||
throw new ArgumentNullException("Tracks list is null", "tracks");
|
||||
|
||||
if (go == null)
|
||||
throw new ArgumentNullException("GameObject parameter is null", "go");
|
||||
|
||||
var playable = ScriptPlayable<TimelinePlayable>.Create(graph);
|
||||
playable.SetTraversalMode(PlayableTraversalMode.Passthrough);
|
||||
var sequence = playable.GetBehaviour();
|
||||
sequence.Compile(graph, playable, tracks, go, autoRebalance, createOutputs);
|
||||
return playable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles the subgraph of this timeline
|
||||
/// </summary>
|
||||
/// <param name="graph">The playable graph to inject the timeline.</param>
|
||||
/// <param name="timelinePlayable"></param>
|
||||
/// <param name="tracks">The list of tracks to compile</param>
|
||||
/// <param name="go">The GameObject that initiated the compilation</param>
|
||||
/// <param name="autoRebalance">In the editor, whether the graph should account for the possibility of changing clip times</param>
|
||||
/// <param name="createOutputs">Whether to create PlayableOutputs in the graph</param>
|
||||
public void Compile(PlayableGraph graph, Playable timelinePlayable, IEnumerable<TrackAsset> tracks, GameObject go, bool autoRebalance, bool createOutputs)
|
||||
{
|
||||
if (tracks == null)
|
||||
throw new ArgumentNullException("Tracks list is null", "tracks");
|
||||
|
||||
if (go == null)
|
||||
throw new ArgumentNullException("GameObject parameter is null", "go");
|
||||
|
||||
var outputTrackList = new List<TrackAsset>(tracks);
|
||||
var maximumNumberOfIntersections = outputTrackList.Count * 2 + outputTrackList.Count; // worse case: 2 overlapping clips per track + each track
|
||||
m_CurrentListOfActiveClips = new List<RuntimeElement>(maximumNumberOfIntersections);
|
||||
m_ActiveClips = new List<RuntimeElement>(maximumNumberOfIntersections);
|
||||
|
||||
m_EvaluateCallbacks.Clear();
|
||||
m_PlayableCache.Clear();
|
||||
|
||||
CompileTrackList(graph, timelinePlayable, outputTrackList, go, createOutputs);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (autoRebalance)
|
||||
{
|
||||
m_Rebalancer = new IntervalTreeRebalancer(m_IntervalTree);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void CompileTrackList(PlayableGraph graph, Playable timelinePlayable, IEnumerable<TrackAsset> tracks, GameObject go, bool createOutputs)
|
||||
{
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (!track.IsCompilable())
|
||||
continue;
|
||||
|
||||
if (!m_PlayableCache.ContainsKey(track))
|
||||
{
|
||||
track.SortClips();
|
||||
CreateTrackPlayable(graph, timelinePlayable, track, go, createOutputs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateTrackOutput(PlayableGraph graph, TrackAsset track, GameObject go, Playable playable, int port)
|
||||
{
|
||||
if (track.isSubTrack)
|
||||
return;
|
||||
|
||||
var bindings = track.outputs;
|
||||
foreach (var binding in bindings)
|
||||
{
|
||||
var playableOutput = binding.CreateOutput(graph);
|
||||
playableOutput.SetReferenceObject(binding.sourceObject);
|
||||
playableOutput.SetSourcePlayable(playable, port);
|
||||
playableOutput.SetWeight(1.0f);
|
||||
|
||||
// only apply this on our animation track
|
||||
if (track as AnimationTrack != null)
|
||||
{
|
||||
EvaluateWeightsForAnimationPlayableOutput(track, (AnimationPlayableOutput)playableOutput);
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
EvaluateAnimationPreviewUpdateCallback(track, (AnimationPlayableOutput)playableOutput);
|
||||
#endif
|
||||
}
|
||||
if (playableOutput.IsPlayableOutputOfType<AudioPlayableOutput>())
|
||||
((AudioPlayableOutput)playableOutput).SetEvaluateOnSeek(!muteAudioScrubbing);
|
||||
|
||||
// If the track is the timeline marker track, assume binding is the PlayableDirector
|
||||
if (track.timelineAsset.markerTrack == track)
|
||||
{
|
||||
var director = go.GetComponent<PlayableDirector>();
|
||||
playableOutput.SetUserData(director);
|
||||
foreach (var c in go.GetComponents<INotificationReceiver>())
|
||||
{
|
||||
playableOutput.AddNotificationReceiver(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EvaluateWeightsForAnimationPlayableOutput(TrackAsset track, AnimationPlayableOutput animOutput)
|
||||
{
|
||||
m_EvaluateCallbacks.Add(new AnimationOutputWeightProcessor(animOutput));
|
||||
}
|
||||
|
||||
void EvaluateAnimationPreviewUpdateCallback(TrackAsset track, AnimationPlayableOutput animOutput)
|
||||
{
|
||||
m_EvaluateCallbacks.Add(new AnimationPreviewUpdateCallback(animOutput));
|
||||
}
|
||||
|
||||
Playable CreateTrackPlayable(PlayableGraph graph, Playable timelinePlayable, TrackAsset track, GameObject go, bool createOutputs)
|
||||
{
|
||||
if (!track.IsCompilable()) // where parents are not compilable (group tracks)
|
||||
return timelinePlayable;
|
||||
|
||||
Playable playable;
|
||||
if (m_PlayableCache.TryGetValue(track, out playable))
|
||||
return playable;
|
||||
|
||||
if (track.name == "root")
|
||||
return timelinePlayable;
|
||||
|
||||
TrackAsset parentActor = track.parent as TrackAsset;
|
||||
var parentPlayable = parentActor != null ? CreateTrackPlayable(graph, timelinePlayable, parentActor, go, createOutputs) : timelinePlayable;
|
||||
var actorPlayable = track.CreatePlayableGraph(graph, go, m_IntervalTree, timelinePlayable);
|
||||
bool connected = false;
|
||||
|
||||
if (!actorPlayable.IsValid())
|
||||
{
|
||||
// if a track says it's compilable, but returns Playable.Null, that can screw up the whole graph.
|
||||
throw new InvalidOperationException(track.name + "(" + track.GetType() + ") did not produce a valid playable.");
|
||||
}
|
||||
|
||||
|
||||
// Special case for animation tracks
|
||||
if (parentPlayable.IsValid() && actorPlayable.IsValid())
|
||||
{
|
||||
int port = parentPlayable.GetInputCount();
|
||||
parentPlayable.SetInputCount(port + 1);
|
||||
connected = graph.Connect(actorPlayable, 0, parentPlayable, port);
|
||||
parentPlayable.SetInputWeight(port, 1.0f);
|
||||
}
|
||||
|
||||
if (createOutputs && connected)
|
||||
{
|
||||
CreateTrackOutput(graph, track, go, parentPlayable, parentPlayable.GetInputCount() - 1);
|
||||
}
|
||||
|
||||
CacheTrack(track, actorPlayable, connected ? (parentPlayable.GetInputCount() - 1) : -1, parentPlayable);
|
||||
return actorPlayable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridden to handle synchronizing time on the timeline instance.
|
||||
/// </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)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (m_Rebalancer != null)
|
||||
m_Rebalancer.Rebalance();
|
||||
// avoids loop creating a time offset during framelocked playback
|
||||
// if the timeline duration does not fall on a frame boundary.
|
||||
if (playableLooped != null && info.timeLooped)
|
||||
playableLooped.Invoke(playable);
|
||||
#endif
|
||||
|
||||
// force seek if we are being evaluated
|
||||
// or if our time has jumped. This is used to
|
||||
// resynchronize
|
||||
Evaluate(playable, info);
|
||||
}
|
||||
|
||||
private void Evaluate(Playable playable, FrameData frameData)
|
||||
{
|
||||
if (m_IntervalTree == null)
|
||||
return;
|
||||
|
||||
double localTime = playable.GetTime();
|
||||
m_ActiveBit = m_ActiveBit == 0 ? 1 : 0;
|
||||
|
||||
m_CurrentListOfActiveClips.Clear();
|
||||
m_IntervalTree.IntersectsWith(DiscreteTime.GetNearestTick(localTime), m_CurrentListOfActiveClips);
|
||||
|
||||
foreach (var c in m_CurrentListOfActiveClips)
|
||||
{
|
||||
c.intervalBit = m_ActiveBit;
|
||||
}
|
||||
|
||||
// all previously active clips having a different intervalBit flag are not
|
||||
// in the current intersection, therefore are considered becoming disabled at this frame
|
||||
var timelineEnd = (double)new DiscreteTime(playable.GetDuration());
|
||||
foreach (var c in m_ActiveClips)
|
||||
{
|
||||
if (c.intervalBit != m_ActiveBit)
|
||||
c.DisableAt(localTime, timelineEnd, frameData);
|
||||
}
|
||||
|
||||
m_ActiveClips.Clear();
|
||||
// case 998642 - don't use m_ActiveClips.AddRange, as in 4.6 .Net scripting it causes GC allocs
|
||||
for (var a = 0; a < m_CurrentListOfActiveClips.Count; a++)
|
||||
{
|
||||
m_CurrentListOfActiveClips[a].EvaluateAt(localTime, frameData);
|
||||
m_ActiveClips.Add(m_CurrentListOfActiveClips[a]);
|
||||
}
|
||||
|
||||
int count = m_EvaluateCallbacks.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
m_EvaluateCallbacks[i].Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
private void CacheTrack(TrackAsset track, Playable playable, int port, Playable parent)
|
||||
{
|
||||
m_PlayableCache[track] = playable;
|
||||
}
|
||||
|
||||
//necessary to build on AOT platforms
|
||||
static void ForAOTCompilationOnly()
|
||||
{
|
||||
new List<IntervalTree<RuntimeElement>.Entry>();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80b10e1c58509a449a3c5aecc07d4455
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,1329 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// A PlayableAsset representing a track inside a timeline.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Derive from TrackAsset to implement custom timeline tracks. TrackAsset derived classes support the following attributes:
|
||||
/// <seealso cref="UnityEngine.Timeline.HideInMenuAttribute"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.TrackColorAttribute"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.TrackClipTypeAttribute"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.TrackBindingTypeAttribute"/>
|
||||
/// <seealso cref="System.ComponentModel.DisplayNameAttribute"/>
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// <code source="../../DocCodeExamples/TrackAssetExamples.cs" region="declare-trackAssetExample" title="TrackAssetExample"/>
|
||||
/// </example>
|
||||
[Serializable]
|
||||
[IgnoreOnPlayableTrack]
|
||||
public abstract partial class TrackAsset : PlayableAsset, IPropertyPreview, ICurvesOwner
|
||||
{
|
||||
// Internal caches used to avoid memory allocation during graph construction
|
||||
private struct TransientBuildData
|
||||
{
|
||||
public List<TrackAsset> trackList;
|
||||
public List<TimelineClip> clipList;
|
||||
public List<IMarker> markerList;
|
||||
|
||||
public static TransientBuildData Create()
|
||||
{
|
||||
return new TransientBuildData()
|
||||
{
|
||||
trackList = new List<TrackAsset>(20),
|
||||
clipList = new List<TimelineClip>(500),
|
||||
markerList = new List<IMarker>(100),
|
||||
};
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
trackList.Clear();
|
||||
clipList.Clear();
|
||||
markerList.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static TransientBuildData s_BuildData = TransientBuildData.Create();
|
||||
|
||||
internal const string kDefaultCurvesName = "Track Parameters";
|
||||
|
||||
internal static event Action<TimelineClip, GameObject, Playable> OnClipPlayableCreate;
|
||||
internal static event Action<TrackAsset, GameObject, Playable> OnTrackAnimationPlayableCreate;
|
||||
|
||||
[SerializeField, HideInInspector] bool m_Locked;
|
||||
[SerializeField, HideInInspector] bool m_Muted;
|
||||
[SerializeField, HideInInspector] string m_CustomPlayableFullTypename = string.Empty;
|
||||
[SerializeField, HideInInspector] AnimationClip m_Curves;
|
||||
[SerializeField, HideInInspector] PlayableAsset m_Parent;
|
||||
[SerializeField, HideInInspector] List<ScriptableObject> m_Children;
|
||||
|
||||
[NonSerialized] int m_ItemsHash;
|
||||
[NonSerialized] TimelineClip[] m_ClipsCache;
|
||||
|
||||
DiscreteTime m_Start;
|
||||
DiscreteTime m_End;
|
||||
bool m_CacheSorted;
|
||||
bool? m_SupportsNotifications;
|
||||
|
||||
static TrackAsset[] s_EmptyCache = new TrackAsset[0];
|
||||
IEnumerable<TrackAsset> m_ChildTrackCache;
|
||||
|
||||
static Dictionary<Type, TrackBindingTypeAttribute> s_TrackBindingTypeAttributeCache = new Dictionary<Type, TrackBindingTypeAttribute>();
|
||||
|
||||
[SerializeField, HideInInspector] protected internal List<TimelineClip> m_Clips = new List<TimelineClip>();
|
||||
|
||||
[SerializeField, HideInInspector] MarkerList m_Markers = new MarkerList(0);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal int DirtyIndex { get; private set; }
|
||||
internal void MarkDirtyTrackAndClips()
|
||||
{
|
||||
DirtyIndex++;
|
||||
foreach (var clip in GetClips())
|
||||
{
|
||||
if (clip != null)
|
||||
clip.MarkDirty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The start time, in seconds, of this track
|
||||
/// </summary>
|
||||
public double start
|
||||
{
|
||||
get
|
||||
{
|
||||
UpdateDuration();
|
||||
return (double)m_Start;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The end time, in seconds, of this track
|
||||
/// </summary>
|
||||
public double end
|
||||
{
|
||||
get
|
||||
{
|
||||
UpdateDuration();
|
||||
return (double)m_End;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The length, in seconds, of this track
|
||||
/// </summary>
|
||||
public sealed override double duration
|
||||
{
|
||||
get
|
||||
{
|
||||
UpdateDuration();
|
||||
return (double)(m_End - m_Start);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the track is muted or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A muted track is excluded from the generated PlayableGraph
|
||||
/// </remarks>
|
||||
public bool muted
|
||||
{
|
||||
get { return m_Muted; }
|
||||
set { m_Muted = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The muted state of a track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A track is also muted when one of its parent tracks are muted.
|
||||
/// </remarks>
|
||||
public bool mutedInHierarchy
|
||||
{
|
||||
get
|
||||
{
|
||||
if (muted)
|
||||
return true;
|
||||
|
||||
TrackAsset p = this;
|
||||
while (p.parent as TrackAsset != null)
|
||||
{
|
||||
p = (TrackAsset)p.parent;
|
||||
if (p as GroupTrack != null)
|
||||
return p.mutedInHierarchy;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The TimelineAsset that this track belongs to.
|
||||
/// </summary>
|
||||
public TimelineAsset timelineAsset
|
||||
{
|
||||
get
|
||||
{
|
||||
var node = this;
|
||||
while (node != null)
|
||||
{
|
||||
if (node.parent == null)
|
||||
return null;
|
||||
|
||||
var seq = node.parent as TimelineAsset;
|
||||
if (seq != null)
|
||||
return seq;
|
||||
|
||||
node = node.parent as TrackAsset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The owner of this track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this track is a subtrack, the parent is a TrackAsset. Otherwise the parent is a TimelineAsset.
|
||||
/// </remarks>
|
||||
public PlayableAsset parent
|
||||
{
|
||||
get { return m_Parent; }
|
||||
internal set { m_Parent = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of clips owned by this track
|
||||
/// </summary>
|
||||
/// <returns>Returns an enumerable list of clips owned by the track.</returns>
|
||||
public IEnumerable<TimelineClip> GetClips()
|
||||
{
|
||||
return clips;
|
||||
}
|
||||
|
||||
internal TimelineClip[] clips
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Clips == null)
|
||||
m_Clips = new List<TimelineClip>();
|
||||
|
||||
if (m_ClipsCache == null)
|
||||
{
|
||||
m_CacheSorted = false;
|
||||
m_ClipsCache = m_Clips.ToArray();
|
||||
}
|
||||
|
||||
return m_ClipsCache;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this track is considered empty.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A track is considered empty when it does not contain a TimelineClip, Marker, or Curve.
|
||||
/// </remarks>
|
||||
public virtual bool isEmpty
|
||||
{
|
||||
get { return !hasClips && !hasCurves && GetMarkerCount() == 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this track contains any TimelineClip.
|
||||
/// </summary>
|
||||
public bool hasClips
|
||||
{
|
||||
get { return m_Clips != null && m_Clips.Count != 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this track contains animated properties for the attached PlayableAsset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is false if the curves property is null or if it contains no information.
|
||||
/// </remarks>
|
||||
public bool hasCurves
|
||||
{
|
||||
get { return m_Curves != null && !m_Curves.empty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this track is a subtrack
|
||||
/// </summary>
|
||||
public bool isSubTrack
|
||||
{
|
||||
get
|
||||
{
|
||||
var owner = parent as TrackAsset;
|
||||
return owner != null && owner.GetType() == GetType();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a description of the PlayableOutputs that will be created by this track.
|
||||
/// </summary>
|
||||
public override IEnumerable<PlayableBinding> outputs
|
||||
{
|
||||
get
|
||||
{
|
||||
TrackBindingTypeAttribute attribute;
|
||||
if (!s_TrackBindingTypeAttributeCache.TryGetValue(GetType(), out attribute))
|
||||
{
|
||||
attribute = (TrackBindingTypeAttribute)Attribute.GetCustomAttribute(GetType(), typeof(TrackBindingTypeAttribute));
|
||||
s_TrackBindingTypeAttributeCache.Add(GetType(), attribute);
|
||||
}
|
||||
|
||||
var trackBindingType = attribute != null ? attribute.type : null;
|
||||
yield return ScriptPlayableBinding.Create(name, this, trackBindingType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of subtracks or child tracks attached to this track.
|
||||
/// </summary>
|
||||
/// <returns>Returns an enumerable list of child tracks owned directly by this track.</returns>
|
||||
/// <remarks>
|
||||
/// In the case of GroupTracks, this returns all tracks contained in the group. This will return the all subtracks or override tracks, if supported by the track.
|
||||
/// </remarks>
|
||||
public IEnumerable<TrackAsset> GetChildTracks()
|
||||
{
|
||||
UpdateChildTrackCache();
|
||||
return m_ChildTrackCache;
|
||||
}
|
||||
|
||||
internal string customPlayableTypename
|
||||
{
|
||||
get { return m_CustomPlayableFullTypename; }
|
||||
set { m_CustomPlayableFullTypename = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An animation clip storing animated properties of the attached PlayableAsset
|
||||
/// </summary>
|
||||
public AnimationClip curves
|
||||
{
|
||||
get { return m_Curves; }
|
||||
internal set { m_Curves = value; }
|
||||
}
|
||||
|
||||
string ICurvesOwner.defaultCurvesName
|
||||
{
|
||||
get { return kDefaultCurvesName; }
|
||||
}
|
||||
|
||||
Object ICurvesOwner.asset
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
Object ICurvesOwner.assetOwner
|
||||
{
|
||||
get { return timelineAsset; }
|
||||
}
|
||||
|
||||
TrackAsset ICurvesOwner.targetTrack
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
// for UI where we need to detect 'null' objects
|
||||
internal List<ScriptableObject> subTracksObjects
|
||||
{
|
||||
get { return m_Children; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The local locked state of the track.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that locking a track only affects operations in the Timeline Editor. It does not prevent other API calls from changing a track or it's clips.
|
||||
///
|
||||
/// This returns or sets the local locked state of the track. A track may still be locked for editing because one or more of it's parent tracks in the hierarchy is locked. Use lockedInHierarchy to test if a track is locked because of it's own locked state or because of a parent tracks locked state.
|
||||
/// </remarks>
|
||||
public bool locked
|
||||
{
|
||||
get { return m_Locked; }
|
||||
set { m_Locked = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The locked state of a track. (RO)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that locking a track only affects operations in the Timeline Editor. It does not prevent other API calls from changing a track or it's clips.
|
||||
///
|
||||
/// This indicates whether a track is locked in the Timeline Editor because either it's locked property is enabled or a parent track is locked.
|
||||
/// </remarks>
|
||||
public bool lockedInHierarchy
|
||||
{
|
||||
get
|
||||
{
|
||||
if (locked)
|
||||
return true;
|
||||
|
||||
TrackAsset p = this;
|
||||
while (p.parent as TrackAsset != null)
|
||||
{
|
||||
p = (TrackAsset)p.parent;
|
||||
if (p as GroupTrack != null)
|
||||
return p.lockedInHierarchy;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a track accepts markers that implement <see cref="UnityEngine.Playables.INotification"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only tracks with a bound object of type <see cref="UnityEngine.GameObject"/> or <see cref="UnityEngine.Component"/> can accept notifications.
|
||||
/// </remarks>
|
||||
public bool supportsNotifications
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_SupportsNotifications.HasValue)
|
||||
{
|
||||
m_SupportsNotifications = NotificationUtilities.TrackTypeSupportsNotifications(GetType());
|
||||
}
|
||||
|
||||
return m_SupportsNotifications.Value;
|
||||
}
|
||||
}
|
||||
|
||||
void __internalAwake() //do not use OnEnable, since users will want it to initialize their class
|
||||
{
|
||||
if (m_Clips == null)
|
||||
m_Clips = new List<TimelineClip>();
|
||||
|
||||
m_ChildTrackCache = null;
|
||||
if (m_Children == null)
|
||||
m_Children = new List<ScriptableObject>();
|
||||
#if UNITY_EDITOR
|
||||
// validate the array. DON'T remove Unity null objects, just actual null objects
|
||||
for (int i = m_Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
object o = m_Children[i];
|
||||
if (o == null)
|
||||
{
|
||||
Debug.LogWarning("Empty child track found while loading timeline. It will be removed.");
|
||||
m_Children.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AnimationClip to store animated properties for the attached PlayableAsset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If curves already exists for this track, this method produces no result regardless of
|
||||
/// the value specified for curvesClipName.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// When used from the editor, this method attempts to save the created curves clip to the TimelineAsset.
|
||||
/// The TimelineAsset must already exist in the AssetDatabase to save the curves clip. If the TimelineAsset
|
||||
/// does not exist, the curves clip is still created but it is not saved.
|
||||
/// </remarks>
|
||||
/// <param name="curvesClipName">
|
||||
/// The name of the AnimationClip to create.
|
||||
/// This method does not ensure unique names. If you want a unique clip name, you must provide one.
|
||||
/// See ObjectNames.GetUniqueName for information on a method that creates unique names.
|
||||
/// </param>
|
||||
public void CreateCurves(string curvesClipName)
|
||||
{
|
||||
if (m_Curves != null)
|
||||
return;
|
||||
|
||||
m_Curves = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(curvesClipName) ? kDefaultCurvesName : curvesClipName, this, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a mixer used to blend playables generated by clips on the track.
|
||||
/// </summary>
|
||||
/// <param name="graph">The graph to inject playables into</param>
|
||||
/// <param name="go">The GameObject that requested the graph.</param>
|
||||
/// <param name="inputCount">The number of playables from clips that will be inputs to the returned mixer</param>
|
||||
/// <returns>A handle to the [[Playable]] representing the mixer.</returns>
|
||||
/// <remarks>
|
||||
/// Override this method to provide a custom playable for mixing clips on a graph.
|
||||
/// </remarks>
|
||||
public virtual Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
|
||||
{
|
||||
return Playable.Create(graph, inputCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides PlayableAsset.CreatePlayable(). Not used in Timeline.
|
||||
/// </summary>
|
||||
/// <param name="graph"><inheritdoc/></param>
|
||||
/// <param name="go"><inheritdoc/></param>
|
||||
/// <returns><inheritDoc/></returns>
|
||||
public sealed override Playable CreatePlayable(PlayableGraph graph, GameObject go)
|
||||
{
|
||||
return Playable.Null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TimelineClip on this track.
|
||||
/// </summary>
|
||||
/// <returns>Returns a new TimelineClip that is attached to the track.</returns>
|
||||
/// <remarks>
|
||||
/// The type of the playable asset attached to the clip is determined by TrackClip attributes that decorate the TrackAsset derived class
|
||||
/// </remarks>
|
||||
public TimelineClip CreateDefaultClip()
|
||||
{
|
||||
var trackClipTypeAttributes = GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
|
||||
Type playableAssetType = null;
|
||||
foreach (var trackClipTypeAttribute in trackClipTypeAttributes)
|
||||
{
|
||||
var attribute = trackClipTypeAttribute as TrackClipTypeAttribute;
|
||||
if (attribute != null && typeof(IPlayableAsset).IsAssignableFrom(attribute.inspectedType) && typeof(ScriptableObject).IsAssignableFrom(attribute.inspectedType))
|
||||
{
|
||||
playableAssetType = attribute.inspectedType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (playableAssetType == null)
|
||||
{
|
||||
Debug.LogWarning("Cannot create a default clip for type " + GetType());
|
||||
return null;
|
||||
}
|
||||
return CreateAndAddNewClipOfType(playableAssetType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a clip on the track with a playable asset attached, whose derived type is specified by T
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A PlayableAsset derived type</typeparam>
|
||||
/// <returns>Returns a TimelineClip whose asset is of type T</returns>
|
||||
/// <remarks>
|
||||
/// Throws <exception cref="System.InvalidOperationException"/> if <typeparamref name="T"/> is not supported by the track.
|
||||
/// Supported types are determined by TrackClip attributes that decorate the TrackAsset derived class
|
||||
/// </remarks>
|
||||
public TimelineClip CreateClip<T>() where T : ScriptableObject, IPlayableAsset
|
||||
{
|
||||
return CreateClip(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a clip from this track.
|
||||
/// </summary>
|
||||
/// <param name="clip">The clip to delete.</param>
|
||||
/// <returns>Returns true if the removal was successful</returns>
|
||||
/// <remarks>
|
||||
/// This method will delete a clip and any assets owned by the clip.
|
||||
/// </remarks>
|
||||
/// <exception>
|
||||
/// Throws <exception cref="System.InvalidOperationException"/> if <paramref name="clip"/> is not a child of the TrackAsset.
|
||||
/// </exception>
|
||||
public bool DeleteClip(TimelineClip clip)
|
||||
{
|
||||
if (!m_Clips.Contains(clip))
|
||||
throw new InvalidOperationException("Cannot delete clip since it is not a child of the TrackAsset.");
|
||||
|
||||
return timelineAsset != null && timelineAsset.DeleteClip(clip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a marker of the requested type, at a specific time, and adds the marker to the current asset.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of marker.</param>
|
||||
/// <param name="time">The time where the marker is created.</param>
|
||||
/// <returns>Returns the instance of the created marker.</returns>
|
||||
/// <remarks>
|
||||
/// All markers that implement IMarker and inherit from <see cref="UnityEngine.ScriptableObject"/> are supported.
|
||||
/// Markers that implement the INotification interface cannot be added to tracks that do not support notifications.
|
||||
/// CreateMarker will throw <exception cref="System.InvalidOperationException"/> with tracks that do not support notifications if <paramref name="type"/> implements the INotification interface.
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnityEngine.Timeline.Marker"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.TrackAsset.supportsNotifications"/>
|
||||
public IMarker CreateMarker(Type type, double time)
|
||||
{
|
||||
return m_Markers.CreateMarker(type, time, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a marker of the requested type, at a specific time, and adds the marker to the current asset.
|
||||
/// </summary>
|
||||
/// <param name="time">The time where the marker is created.</param>
|
||||
/// <typeparam name="T">The type of marker to create.</typeparam>
|
||||
/// <returns>Returns the instance of the created marker.</returns>
|
||||
/// <remarks>
|
||||
/// All markers that implement IMarker and inherit from <see cref="UnityEngine.ScriptableObject"/> are supported.
|
||||
/// CreateMarker will throw <exception cref="System.InvalidOperationException"/> with tracks that do not support notifications if <typeparamref name="T"/> implements the INotification interface.
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnityEngine.Timeline.Marker"/>
|
||||
/// <seealso cref="UnityEngine.Timeline.TrackAsset.supportsNotifications"/>
|
||||
public T CreateMarker<T>(double time) where T : ScriptableObject, IMarker
|
||||
{
|
||||
return (T)CreateMarker(typeof(T), time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a marker from the current asset.
|
||||
/// </summary>
|
||||
/// <param name="marker">The marker instance to be removed.</param>
|
||||
/// <returns>Returns true if the marker instance was successfully removed. Returns false otherwise.</returns>
|
||||
public bool DeleteMarker(IMarker marker)
|
||||
{
|
||||
return m_Markers.Remove(marker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerable list of markers on the current asset.
|
||||
/// </summary>
|
||||
/// <returns>The list of markers on the asset.
|
||||
/// </returns>
|
||||
public IEnumerable<IMarker> GetMarkers()
|
||||
{
|
||||
return m_Markers.GetMarkers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of markers on the current asset.
|
||||
/// </summary>
|
||||
/// <returns>The number of markers.</returns>
|
||||
public int GetMarkerCount()
|
||||
{
|
||||
return m_Markers.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the marker at a given position, on the current asset.
|
||||
/// </summary>
|
||||
/// <param name="idx">The index of the marker to be returned.</param>
|
||||
/// <returns>The marker.</returns>
|
||||
/// <remarks>The ordering of the markers is not guaranteed.
|
||||
/// </remarks>
|
||||
public IMarker GetMarker(int idx)
|
||||
{
|
||||
return m_Markers[idx];
|
||||
}
|
||||
|
||||
internal TimelineClip CreateClip(System.Type requestedType)
|
||||
{
|
||||
if (ValidateClipType(requestedType))
|
||||
return CreateAndAddNewClipOfType(requestedType);
|
||||
|
||||
throw new InvalidOperationException("Clips of type " + requestedType + " are not permitted on tracks of type " + GetType());
|
||||
}
|
||||
|
||||
internal TimelineClip CreateAndAddNewClipOfType(Type requestedType)
|
||||
{
|
||||
var newClip = CreateClipOfType(requestedType);
|
||||
AddClip(newClip);
|
||||
return newClip;
|
||||
}
|
||||
|
||||
internal TimelineClip CreateClipOfType(Type requestedType)
|
||||
{
|
||||
if (!ValidateClipType(requestedType))
|
||||
throw new System.InvalidOperationException("Clips of type " + requestedType + " are not permitted on tracks of type " + GetType());
|
||||
|
||||
var playableAsset = CreateInstance(requestedType);
|
||||
if (playableAsset == null)
|
||||
{
|
||||
throw new System.InvalidOperationException("Could not create an instance of the ScriptableObject type " + requestedType.Name);
|
||||
}
|
||||
playableAsset.name = requestedType.Name;
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(playableAsset, this);
|
||||
TimelineUndo.RegisterCreatedObjectUndo(playableAsset, "Create Clip");
|
||||
|
||||
return CreateClipFromAsset(playableAsset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a timeline clip from an existing playable asset.
|
||||
/// </summary>
|
||||
/// <param name="asset"></param>
|
||||
/// <returns></returns>
|
||||
internal TimelineClip CreateClipFromPlayableAsset(IPlayableAsset asset)
|
||||
{
|
||||
if (asset == null)
|
||||
throw new ArgumentNullException("asset");
|
||||
|
||||
if ((asset as ScriptableObject) == null)
|
||||
throw new System.ArgumentException("CreateClipFromPlayableAsset " + " only supports ScriptableObject-derived Types");
|
||||
|
||||
if (!ValidateClipType(asset.GetType()))
|
||||
throw new System.InvalidOperationException("Clips of type " + asset.GetType() + " are not permitted on tracks of type " + GetType());
|
||||
|
||||
return CreateClipFromAsset(asset as ScriptableObject);
|
||||
}
|
||||
|
||||
private TimelineClip CreateClipFromAsset(ScriptableObject playableAsset)
|
||||
{
|
||||
TimelineUndo.PushUndo(this, "Create Clip");
|
||||
|
||||
var newClip = CreateNewClipContainerInternal();
|
||||
newClip.displayName = playableAsset.name;
|
||||
newClip.asset = playableAsset;
|
||||
|
||||
IPlayableAsset iPlayableAsset = playableAsset as IPlayableAsset;
|
||||
if (iPlayableAsset != null)
|
||||
{
|
||||
var candidateDuration = iPlayableAsset.duration;
|
||||
|
||||
if (!double.IsInfinity(candidateDuration) && candidateDuration > 0)
|
||||
newClip.duration = Math.Min(Math.Max(candidateDuration, TimelineClip.kMinDuration), TimelineClip.kMaxTimeValue);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
OnCreateClip(newClip);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(e.Message, playableAsset);
|
||||
return null;
|
||||
}
|
||||
|
||||
return newClip;
|
||||
}
|
||||
|
||||
internal IEnumerable<ScriptableObject> GetMarkersRaw()
|
||||
{
|
||||
return m_Markers.GetRawMarkerList();
|
||||
}
|
||||
|
||||
internal void ClearMarkers()
|
||||
{
|
||||
m_Markers.Clear();
|
||||
}
|
||||
|
||||
internal void AddMarker(ScriptableObject e)
|
||||
{
|
||||
m_Markers.Add(e);
|
||||
}
|
||||
|
||||
internal bool DeleteMarkerRaw(ScriptableObject marker)
|
||||
{
|
||||
return m_Markers.Remove(marker, timelineAsset, this);
|
||||
}
|
||||
|
||||
int GetTimeRangeHash()
|
||||
{
|
||||
double start = double.MaxValue, end = double.MinValue;
|
||||
int count = m_Markers.Count;
|
||||
for (int i = 0; i < m_Markers.Count; i++)
|
||||
{
|
||||
var marker = m_Markers[i];
|
||||
if (!(marker is INotification))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (marker.time < start)
|
||||
start = marker.time;
|
||||
if (marker.time > end)
|
||||
end = marker.time;
|
||||
}
|
||||
|
||||
return start.GetHashCode().CombineHash(end.GetHashCode());
|
||||
}
|
||||
|
||||
internal void AddClip(TimelineClip newClip)
|
||||
{
|
||||
if (!m_Clips.Contains(newClip))
|
||||
{
|
||||
m_Clips.Add(newClip);
|
||||
m_ClipsCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
Playable CreateNotificationsPlayable(PlayableGraph graph, Playable mixerPlayable, GameObject go, Playable timelinePlayable)
|
||||
{
|
||||
s_BuildData.markerList.Clear();
|
||||
GatherNotifications(s_BuildData.markerList);
|
||||
|
||||
ScriptPlayable<TimeNotificationBehaviour> notificationPlayable;
|
||||
if (go.TryGetComponent(out PlayableDirector director))
|
||||
notificationPlayable = NotificationUtilities.CreateNotificationsPlayable(graph, s_BuildData.markerList, director);
|
||||
else
|
||||
notificationPlayable = NotificationUtilities.CreateNotificationsPlayable(graph, s_BuildData.markerList, timelineAsset);
|
||||
|
||||
if (notificationPlayable.IsValid())
|
||||
{
|
||||
notificationPlayable.GetBehaviour().timeSource = timelinePlayable;
|
||||
if (mixerPlayable.IsValid())
|
||||
{
|
||||
notificationPlayable.SetInputCount(1);
|
||||
graph.Connect(mixerPlayable, 0, notificationPlayable, 0);
|
||||
notificationPlayable.SetInputWeight(mixerPlayable, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return notificationPlayable;
|
||||
}
|
||||
|
||||
internal Playable CreatePlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree, Playable timelinePlayable)
|
||||
{
|
||||
UpdateDuration();
|
||||
var mixerPlayable = Playable.Null;
|
||||
if (CanCreateMixerRecursive())
|
||||
mixerPlayable = CreateMixerPlayableGraph(graph, go, tree);
|
||||
|
||||
Playable notificationsPlayable = CreateNotificationsPlayable(graph, mixerPlayable, go, timelinePlayable);
|
||||
|
||||
// clear the temporary build data to avoid holding references
|
||||
// case 1253974
|
||||
s_BuildData.Clear();
|
||||
if (!notificationsPlayable.IsValid() && !mixerPlayable.IsValid())
|
||||
{
|
||||
Debug.LogErrorFormat("Track {0} of type {1} has no notifications and returns an invalid mixer Playable", name,
|
||||
GetType().FullName);
|
||||
|
||||
return Playable.Create(graph);
|
||||
}
|
||||
|
||||
return notificationsPlayable.IsValid() ? notificationsPlayable : mixerPlayable;
|
||||
}
|
||||
|
||||
internal virtual Playable CompileClips(PlayableGraph graph, GameObject go, IList<TimelineClip> timelineClips, IntervalTree<RuntimeElement> tree)
|
||||
{
|
||||
var blend = CreateTrackMixer(graph, go, timelineClips.Count);
|
||||
for (var c = 0; c < timelineClips.Count; c++)
|
||||
{
|
||||
var source = CreatePlayable(graph, go, timelineClips[c]);
|
||||
if (source.IsValid())
|
||||
{
|
||||
source.SetDuration(timelineClips[c].duration);
|
||||
var clip = new RuntimeClip(timelineClips[c], source, blend);
|
||||
tree.Add(clip);
|
||||
graph.Connect(source, 0, blend, c);
|
||||
blend.SetInputWeight(c, 0.0f);
|
||||
}
|
||||
}
|
||||
ConfigureTrackAnimation(tree, go, blend);
|
||||
return blend;
|
||||
}
|
||||
|
||||
void GatherCompilableTracks(IList<TrackAsset> tracks)
|
||||
{
|
||||
if (!muted && CanCreateTrackMixer())
|
||||
tracks.Add(this);
|
||||
|
||||
foreach (var c in GetChildTracks())
|
||||
{
|
||||
if (c != null)
|
||||
c.GatherCompilableTracks(tracks);
|
||||
}
|
||||
}
|
||||
|
||||
void GatherNotifications(List<IMarker> markers)
|
||||
{
|
||||
if (!muted && CanCompileNotifications())
|
||||
markers.AddRange(GetMarkers());
|
||||
foreach (var c in GetChildTracks())
|
||||
{
|
||||
if (c != null)
|
||||
c.GatherNotifications(markers);
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual Playable CreateMixerPlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree)
|
||||
{
|
||||
if (tree == null)
|
||||
throw new ArgumentException("IntervalTree argument cannot be null", "tree");
|
||||
|
||||
if (go == null)
|
||||
throw new ArgumentException("GameObject argument cannot be null", "go");
|
||||
|
||||
s_BuildData.Clear();
|
||||
GatherCompilableTracks(s_BuildData.trackList);
|
||||
|
||||
// nothing to compile
|
||||
if (s_BuildData.trackList.Count == 0)
|
||||
return Playable.Null;
|
||||
|
||||
// check if layers are supported
|
||||
Playable layerMixer = Playable.Null;
|
||||
ILayerable layerable = this as ILayerable;
|
||||
if (layerable != null)
|
||||
layerMixer = layerable.CreateLayerMixer(graph, go, s_BuildData.trackList.Count);
|
||||
|
||||
if (layerMixer.IsValid())
|
||||
{
|
||||
for (int i = 0; i < s_BuildData.trackList.Count; i++)
|
||||
{
|
||||
var mixer = s_BuildData.trackList[i].CompileClips(graph, go, s_BuildData.trackList[i].clips, tree);
|
||||
if (mixer.IsValid())
|
||||
{
|
||||
graph.Connect(mixer, 0, layerMixer, i);
|
||||
layerMixer.SetInputWeight(i, 1.0f);
|
||||
}
|
||||
}
|
||||
return layerMixer;
|
||||
}
|
||||
|
||||
// one track compiles. Add track mixer and clips
|
||||
if (s_BuildData.trackList.Count == 1)
|
||||
return s_BuildData.trackList[0].CompileClips(graph, go, s_BuildData.trackList[0].clips, tree);
|
||||
|
||||
// no layer mixer provided. merge down all clips.
|
||||
for (int i = 0; i < s_BuildData.trackList.Count; i++)
|
||||
s_BuildData.clipList.AddRange(s_BuildData.trackList[i].clips);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
bool applyWarning = false;
|
||||
for (int i = 0; i < s_BuildData.trackList.Count; i++)
|
||||
applyWarning |= i > 0 && s_BuildData.trackList[i].hasCurves;
|
||||
|
||||
if (applyWarning)
|
||||
Debug.LogWarning("A layered track contains animated fields, but no layer mixer has been provided. Animated fields on layers will be ignored. Override CreateLayerMixer in " + s_BuildData.trackList[0].GetType().Name + " and return a valid playable to support animated fields on layered tracks.");
|
||||
#endif
|
||||
// compile all the clips into a single mixer
|
||||
return CompileClips(graph, go, s_BuildData.clipList, tree);
|
||||
}
|
||||
|
||||
internal void ConfigureTrackAnimation(IntervalTree<RuntimeElement> tree, GameObject go, Playable blend)
|
||||
{
|
||||
if (!hasCurves)
|
||||
return;
|
||||
|
||||
blend.SetAnimatedProperties(m_Curves);
|
||||
tree.Add(new InfiniteRuntimeClip(blend));
|
||||
|
||||
if (OnTrackAnimationPlayableCreate != null)
|
||||
OnTrackAnimationPlayableCreate.Invoke(this, go, blend);
|
||||
}
|
||||
|
||||
// sorts clips by start time
|
||||
internal void SortClips()
|
||||
{
|
||||
var clipsAsArray = clips; // will alloc
|
||||
if (!m_CacheSorted)
|
||||
{
|
||||
Array.Sort(clips, (clip1, clip2) => clip1.start.CompareTo(clip2.start));
|
||||
m_CacheSorted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// clears the clips after a clone
|
||||
internal void ClearClipsInternal()
|
||||
{
|
||||
m_Clips = new List<TimelineClip>();
|
||||
m_ClipsCache = null;
|
||||
}
|
||||
|
||||
internal void ClearSubTracksInternal()
|
||||
{
|
||||
m_Children = new List<ScriptableObject>();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
// called by an owned clip when it moves
|
||||
internal void OnClipMove()
|
||||
{
|
||||
m_CacheSorted = false;
|
||||
}
|
||||
|
||||
internal TimelineClip CreateNewClipContainerInternal()
|
||||
{
|
||||
var clipContainer = new TimelineClip(this);
|
||||
clipContainer.asset = null;
|
||||
|
||||
// position clip at end of sequence
|
||||
var newClipStart = 0.0;
|
||||
for (var a = 0; a < m_Clips.Count - 1; a++)
|
||||
{
|
||||
var clipDuration = m_Clips[a].duration;
|
||||
if (double.IsInfinity(clipDuration))
|
||||
clipDuration = TimelineClip.kDefaultClipDurationInSeconds;
|
||||
newClipStart = Math.Max(newClipStart, m_Clips[a].start + clipDuration);
|
||||
}
|
||||
|
||||
clipContainer.mixInCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
|
||||
clipContainer.mixOutCurve = AnimationCurve.EaseInOut(0, 1, 1, 0);
|
||||
clipContainer.start = newClipStart;
|
||||
clipContainer.duration = TimelineClip.kDefaultClipDurationInSeconds;
|
||||
clipContainer.displayName = "untitled";
|
||||
return clipContainer;
|
||||
}
|
||||
|
||||
internal void AddChild(TrackAsset child)
|
||||
{
|
||||
if (child == null)
|
||||
return;
|
||||
|
||||
m_Children.Add(child);
|
||||
child.parent = this;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
internal void MoveLastTrackBefore(TrackAsset asset)
|
||||
{
|
||||
if (m_Children == null || m_Children.Count < 2 || asset == null)
|
||||
return;
|
||||
|
||||
var lastTrack = m_Children[m_Children.Count - 1];
|
||||
if (lastTrack == asset)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < m_Children.Count - 1; i++)
|
||||
{
|
||||
if (m_Children[i] == asset)
|
||||
{
|
||||
for (int j = m_Children.Count - 1; j > i; j--)
|
||||
m_Children[j] = m_Children[j - 1];
|
||||
m_Children[i] = lastTrack;
|
||||
Invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool RemoveSubTrack(TrackAsset child)
|
||||
{
|
||||
if (m_Children.Remove(child))
|
||||
{
|
||||
Invalidate();
|
||||
child.parent = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void RemoveClip(TimelineClip clip)
|
||||
{
|
||||
m_Clips.Remove(clip);
|
||||
m_ClipsCache = null;
|
||||
}
|
||||
|
||||
// Is this track compilable for the sequence
|
||||
// calculate the time interval that this track will be evaluated in.
|
||||
internal virtual void GetEvaluationTime(out double outStart, out double outDuration)
|
||||
{
|
||||
outStart = 0;
|
||||
outDuration = 1;
|
||||
|
||||
outStart = double.PositiveInfinity;
|
||||
var outEnd = double.NegativeInfinity;
|
||||
|
||||
if (hasCurves)
|
||||
{
|
||||
outStart = 0.0;
|
||||
outEnd = TimeUtility.GetAnimationClipLength(curves);
|
||||
}
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
outStart = Math.Min(clip.start, outStart);
|
||||
outEnd = Math.Max(clip.end, outEnd);
|
||||
}
|
||||
|
||||
if (HasNotifications())
|
||||
{
|
||||
var notificationDuration = GetNotificationDuration();
|
||||
outStart = Math.Min(notificationDuration, outStart);
|
||||
outEnd = Math.Max(notificationDuration, outEnd);
|
||||
}
|
||||
|
||||
if (double.IsInfinity(outStart) || double.IsInfinity(outEnd))
|
||||
outStart = outDuration = 0.0;
|
||||
else
|
||||
outDuration = outEnd - outStart;
|
||||
}
|
||||
|
||||
// calculate the time interval that the sequence will use to determine length.
|
||||
// by default this is the same as the evaluation, but subclasses can have different
|
||||
// behaviour
|
||||
internal virtual void GetSequenceTime(out double outStart, out double outDuration)
|
||||
{
|
||||
GetEvaluationTime(out outStart, out outDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the Timeline Editor to gather properties requiring preview.
|
||||
/// </summary>
|
||||
/// <param name="director">The PlayableDirector invoking the preview</param>
|
||||
/// <param name="driver">PropertyCollector used to gather previewable properties</param>
|
||||
public virtual void GatherProperties(PlayableDirector director, IPropertyCollector driver)
|
||||
{
|
||||
// only push on game objects if there is a binding. Subtracks
|
||||
// will use objects on the stack
|
||||
var gameObject = GetGameObjectBinding(director);
|
||||
if (gameObject != null)
|
||||
driver.PushActiveGameObject(gameObject);
|
||||
|
||||
if (hasCurves)
|
||||
driver.AddObjectProperties(this, m_Curves);
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip.curves != null && clip.asset != null)
|
||||
driver.AddObjectProperties(clip.asset, clip.curves);
|
||||
|
||||
IPropertyPreview modifier = clip.asset as IPropertyPreview;
|
||||
if (modifier != null)
|
||||
modifier.GatherProperties(director, driver);
|
||||
}
|
||||
|
||||
foreach (var subtrack in GetChildTracks())
|
||||
{
|
||||
if (subtrack != null)
|
||||
subtrack.GatherProperties(director, driver);
|
||||
}
|
||||
|
||||
if (gameObject != null)
|
||||
driver.PopActiveGameObject();
|
||||
}
|
||||
|
||||
internal GameObject GetGameObjectBinding(PlayableDirector director)
|
||||
{
|
||||
if (director == null)
|
||||
return null;
|
||||
|
||||
var binding = director.GetGenericBinding(this);
|
||||
|
||||
var gameObject = binding as GameObject;
|
||||
if (gameObject != null)
|
||||
return gameObject;
|
||||
|
||||
var comp = binding as Component;
|
||||
if (comp != null)
|
||||
return comp.gameObject;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal bool ValidateClipType(Type clipType)
|
||||
{
|
||||
var attrs = GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
|
||||
for (var c = 0; c < attrs.Length; ++c)
|
||||
{
|
||||
var attr = (TrackClipTypeAttribute)attrs[c];
|
||||
if (attr.inspectedType.IsAssignableFrom(clipType))
|
||||
return true;
|
||||
}
|
||||
|
||||
// special case for playable tracks, they accept all clips (in the runtime)
|
||||
return typeof(PlayableTrack).IsAssignableFrom(GetType()) &&
|
||||
typeof(IPlayableAsset).IsAssignableFrom(clipType) &&
|
||||
typeof(ScriptableObject).IsAssignableFrom(clipType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a clip is created on a track.
|
||||
/// </summary>
|
||||
/// <param name="clip">The timeline clip added to this track</param>
|
||||
/// <remarks>Use this method to set default values on a timeline clip, or it's PlayableAsset.</remarks>
|
||||
protected virtual void OnCreateClip(TimelineClip clip) { }
|
||||
|
||||
void UpdateDuration()
|
||||
{
|
||||
// check if something changed in the clips that require a re-calculation of the evaluation times.
|
||||
var itemsHash = CalculateItemsHash();
|
||||
if (itemsHash == m_ItemsHash)
|
||||
return;
|
||||
m_ItemsHash = itemsHash;
|
||||
|
||||
double trackStart, trackDuration;
|
||||
GetSequenceTime(out trackStart, out trackDuration);
|
||||
|
||||
m_Start = (DiscreteTime)trackStart;
|
||||
m_End = (DiscreteTime)(trackStart + trackDuration);
|
||||
|
||||
// calculate the extrapolations time.
|
||||
// TODO Extrapolation time should probably be extracted from the SequenceClip so only a track is aware of it.
|
||||
this.CalculateExtrapolationTimes();
|
||||
}
|
||||
|
||||
protected internal virtual int CalculateItemsHash()
|
||||
{
|
||||
return HashUtility.CombineHash(GetClipsHash(), GetAnimationClipHash(m_Curves), GetTimeRangeHash());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a Playable from a TimelineClip.
|
||||
/// </summary>
|
||||
/// <param name="graph">PlayableGraph that will own the playable.</param>
|
||||
/// <param name="gameObject">The GameObject that builds the PlayableGraph.</param>
|
||||
/// <param name="clip">The TimelineClip to construct a playable for.</param>
|
||||
/// <returns>A playable that will be set as an input to the Track Mixer playable, or Playable.Null if the clip does not have a valid PlayableAsset</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if the specified PlayableGraph is not valid.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if the specified TimelineClip is not valid.</exception>
|
||||
/// <remarks>
|
||||
/// By default, this method invokes Playable.CreatePlayable, sets animated properties, and sets the speed of the created playable. Override this method to change this default implementation.
|
||||
/// </remarks>
|
||||
protected virtual Playable CreatePlayable(PlayableGraph graph, GameObject gameObject, TimelineClip clip)
|
||||
{
|
||||
if (!graph.IsValid())
|
||||
throw new ArgumentException("graph must be a valid PlayableGraph");
|
||||
if (clip == null)
|
||||
throw new ArgumentNullException("clip");
|
||||
|
||||
var asset = clip.asset as IPlayableAsset;
|
||||
if (asset != null)
|
||||
{
|
||||
var handle = asset.CreatePlayable(graph, gameObject);
|
||||
if (handle.IsValid())
|
||||
{
|
||||
handle.SetAnimatedProperties(clip.curves);
|
||||
handle.SetSpeed(clip.timeScale);
|
||||
if (OnClipPlayableCreate != null)
|
||||
OnClipPlayableCreate(clip, gameObject, handle);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
return Playable.Null;
|
||||
}
|
||||
|
||||
internal void Invalidate()
|
||||
{
|
||||
m_ChildTrackCache = null;
|
||||
var timeline = timelineAsset;
|
||||
if (timeline != null)
|
||||
{
|
||||
timeline.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
internal double GetNotificationDuration()
|
||||
{
|
||||
if (!supportsNotifications)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var maxTime = 0.0;
|
||||
int count = m_Markers.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var marker = m_Markers[i];
|
||||
if (!(marker is INotification))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
maxTime = Math.Max(maxTime, marker.time);
|
||||
}
|
||||
|
||||
return maxTime;
|
||||
}
|
||||
|
||||
internal virtual bool CanCompileClips()
|
||||
{
|
||||
return hasClips || hasCurves;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the track can create a mixer for its own contents.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the track's mixer should be included in the playable graph.</returns>
|
||||
/// <remarks>A return value of true does not guarantee that the mixer will be included in the playable graph. GroupTracks and muted tracks are never included in the graph</remarks>
|
||||
/// <remarks>A return value of false does not guarantee that the mixer will not be included in the playable graph. If a child track returns true for CanCreateTrackMixer, the parent track will generate the mixer but its own playables will not be included.</remarks>
|
||||
/// <remarks>Override this method to change the conditions for a track to be included in the playable graph.</remarks>
|
||||
public virtual bool CanCreateTrackMixer()
|
||||
{
|
||||
return CanCompileClips();
|
||||
}
|
||||
|
||||
internal bool IsCompilable()
|
||||
{
|
||||
bool isContainer = typeof(GroupTrack).IsAssignableFrom(GetType());
|
||||
|
||||
if (isContainer)
|
||||
return false;
|
||||
|
||||
var ret = !mutedInHierarchy && (CanCreateTrackMixer() || CanCompileNotifications());
|
||||
if (!ret)
|
||||
{
|
||||
foreach (var t in GetChildTracks())
|
||||
{
|
||||
if (t.IsCompilable())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void UpdateChildTrackCache()
|
||||
{
|
||||
if (m_ChildTrackCache == null)
|
||||
{
|
||||
if (m_Children == null || m_Children.Count == 0)
|
||||
m_ChildTrackCache = s_EmptyCache;
|
||||
else
|
||||
{
|
||||
var childTracks = new List<TrackAsset>(m_Children.Count);
|
||||
for (int i = 0; i < m_Children.Count; i++)
|
||||
{
|
||||
var subTrack = m_Children[i] as TrackAsset;
|
||||
if (subTrack != null)
|
||||
childTracks.Add(subTrack);
|
||||
}
|
||||
m_ChildTrackCache = childTracks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual int Hash()
|
||||
{
|
||||
return clips.Length + (m_Markers.Count << 16);
|
||||
}
|
||||
|
||||
int GetClipsHash()
|
||||
{
|
||||
var hash = 0;
|
||||
foreach (var clip in m_Clips)
|
||||
{
|
||||
hash = hash.CombineHash(clip.Hash());
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hash code for an AnimationClip.
|
||||
/// </summary>
|
||||
/// <param name="clip">The animation clip.</param>
|
||||
/// <returns>A 32-bit signed integer that is the hash code for <paramref name="clip"/>. Returns 0 if <paramref name="clip"/> is null or empty.</returns>
|
||||
protected static int GetAnimationClipHash(AnimationClip clip)
|
||||
{
|
||||
var hash = 0;
|
||||
if (clip != null && !clip.empty)
|
||||
hash = hash.CombineHash(clip.frameRate.GetHashCode())
|
||||
.CombineHash(clip.length.GetHashCode());
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool HasNotifications()
|
||||
{
|
||||
return m_Markers.HasNotifications();
|
||||
}
|
||||
|
||||
bool CanCompileNotifications()
|
||||
{
|
||||
return supportsNotifications && m_Markers.HasNotifications();
|
||||
}
|
||||
|
||||
bool CanCreateMixerRecursive()
|
||||
{
|
||||
if (CanCreateTrackMixer())
|
||||
return true;
|
||||
foreach (var track in GetChildTracks())
|
||||
{
|
||||
if (track.CanCreateMixerRecursive())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ad53269c7421084ab67f804591994e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "Unity.Timeline",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "Unity",
|
||||
"expression": "2021.2.0a17",
|
||||
"define": "TIMELINE_FRAMEACCURATE"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f06555f75b070af458a003d92f9efb00
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8730045d7da0f84cb11c0d868899577
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,301 @@
|
||||
using System;
|
||||
#if UNITY_EDITOR
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class AnimationPreviewUtilities
|
||||
{
|
||||
private const string k_PosX = "m_LocalPosition.x";
|
||||
private const string k_PosY = "m_LocalPosition.y";
|
||||
private const string k_PosZ = "m_LocalPosition.z";
|
||||
private const string k_RotX = "m_LocalRotation.x";
|
||||
private const string k_RotY = "m_LocalRotation.y";
|
||||
private const string k_RotZ = "m_LocalRotation.z";
|
||||
private const string k_RotW = "m_LocalRotation.w";
|
||||
private const string k_ScaleX = "m_LocalScale.x";
|
||||
private const string k_ScaleY = "m_LocalScale.y";
|
||||
private const string k_ScaleZ = "m_LocalScale.z";
|
||||
private const string k_EulerAnglesRaw = "localEulerAnglesRaw";
|
||||
private const string k_EulerHint = "m_LocalEulerAnglesHint";
|
||||
private const string k_Pos = "m_LocalPosition";
|
||||
private const string k_Rot = "m_LocalRotation";
|
||||
private const string k_MotionT = "MotionT";
|
||||
private const string k_MotionQ = "MotionQ";
|
||||
private const string k_RootT = "RootT";
|
||||
private const string k_RootQ = "RootQ";
|
||||
|
||||
|
||||
internal static Object s_PreviewDriver;
|
||||
|
||||
|
||||
internal class EditorCurveBindingComparer : IEqualityComparer<EditorCurveBinding>
|
||||
{
|
||||
public bool Equals(EditorCurveBinding x, EditorCurveBinding y) { return x.path.Equals(y.path) && x.type == y.type && x.propertyName == y.propertyName; }
|
||||
public int GetHashCode(EditorCurveBinding obj)
|
||||
{
|
||||
return obj.propertyName.GetHashCode() ^ obj.path.GetHashCode();
|
||||
}
|
||||
|
||||
public static readonly EditorCurveBindingComparer Instance = new EditorCurveBindingComparer();
|
||||
}
|
||||
|
||||
// a dictionary is faster than a hashset, because the capacity can be pre-set
|
||||
private static readonly Dictionary<EditorCurveBinding, int> s_CurveSet = new Dictionary<EditorCurveBinding, int>(10000, EditorCurveBindingComparer.Instance);
|
||||
private static readonly AnimatorBindingCache s_BindingCache = new AnimatorBindingCache();
|
||||
|
||||
// string.StartsWith is slow (https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity5.html)
|
||||
// hand rolled version has best performance.
|
||||
private static bool FastStartsWith(string a, string toCompare)
|
||||
{
|
||||
int aLen = a.Length;
|
||||
int bLen = toCompare.Length;
|
||||
|
||||
int ap = 0;
|
||||
int bp = 0;
|
||||
|
||||
while (ap < aLen && bp < bLen && a[ap] == toCompare[bp])
|
||||
{
|
||||
ap++;
|
||||
bp++;
|
||||
}
|
||||
|
||||
return (bp == bLen);
|
||||
}
|
||||
|
||||
public static void ClearCaches()
|
||||
{
|
||||
s_BindingCache.Clear();
|
||||
s_CurveSet.Clear();
|
||||
}
|
||||
|
||||
public static EditorCurveBinding[] GetBindings(GameObject animatorRoot, IEnumerable<AnimationClip> clips)
|
||||
{
|
||||
s_CurveSet.Clear();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
AddBindings(s_BindingCache.GetCurveBindings(clip));
|
||||
}
|
||||
|
||||
// if we have a transform binding, bind the entire skeleton
|
||||
if (NeedsSkeletonBindings(s_CurveSet.Keys))
|
||||
AddBindings(s_BindingCache.GetAnimatorBindings(animatorRoot));
|
||||
|
||||
var bindings = new EditorCurveBinding[s_CurveSet.Keys.Count];
|
||||
s_CurveSet.Keys.CopyTo(bindings, 0);
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public static int GetClipHash(List<AnimationClip> clips)
|
||||
{
|
||||
int hash = 0;
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var stats = AnimationUtility.GetAnimationClipStats(clip);
|
||||
hash = HashUtility.CombineHash(hash, clip.GetHashCode(), stats.clips, stats.size, stats.totalCurves);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static void PreviewFromCurves(GameObject animatorRoot, IEnumerable<EditorCurveBinding> keys)
|
||||
{
|
||||
if (!AnimationMode.InAnimationMode())
|
||||
return;
|
||||
|
||||
var avatarRoot = GetAvatarRoot(animatorRoot);
|
||||
foreach (var binding in keys)
|
||||
{
|
||||
if (IsAvatarBinding(binding) || IsEuler(binding))
|
||||
continue;
|
||||
|
||||
bool isTransform = typeof(Transform).IsAssignableFrom(binding.type);
|
||||
if (isTransform && binding.propertyName.Equals(AnimatorBindingCache.TRPlaceHolder))
|
||||
AddTRBinding(animatorRoot, binding);
|
||||
else if (isTransform && binding.propertyName.Equals(AnimatorBindingCache.ScalePlaceholder))
|
||||
AddScaleBinding(animatorRoot, binding);
|
||||
else
|
||||
AnimationMode.AddEditorCurveBinding(avatarRoot, binding);
|
||||
}
|
||||
}
|
||||
|
||||
public static AnimationClip CreateDefaultClip(GameObject animatorRoot, IEnumerable<EditorCurveBinding> keys)
|
||||
{
|
||||
AnimationClip animClip = new AnimationClip() { name = "DefaultPose" };
|
||||
var keyFrames = new[] {new Keyframe(0, 0)};
|
||||
var curve = new AnimationCurve(keyFrames);
|
||||
bool rootMotion = false;
|
||||
var avatarRoot = GetAvatarRoot(animatorRoot);
|
||||
|
||||
foreach (var binding in keys)
|
||||
{
|
||||
if (IsRootMotion(binding))
|
||||
{
|
||||
rootMotion = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.Equals(AnimatorBindingCache.TRPlaceHolder))
|
||||
{
|
||||
if (string.IsNullOrEmpty(binding.path))
|
||||
rootMotion = true;
|
||||
else
|
||||
{
|
||||
var transform = animatorRoot.transform.Find(binding.path);
|
||||
if (transform != null)
|
||||
{
|
||||
var pos = transform.localPosition;
|
||||
var rot = transform.localRotation;
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_PosX, SetZeroKey(curve, keyFrames, pos.x));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_PosY, SetZeroKey(curve, keyFrames, pos.y));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_PosZ, SetZeroKey(curve, keyFrames, pos.z));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_RotX, SetZeroKey(curve, keyFrames, rot.x));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_RotY, SetZeroKey(curve, keyFrames, rot.y));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_RotZ, SetZeroKey(curve, keyFrames, rot.z));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_RotW, SetZeroKey(curve, keyFrames, rot.w));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName == AnimatorBindingCache.ScalePlaceholder)
|
||||
{
|
||||
var transform = animatorRoot.transform.Find(binding.path);
|
||||
if (transform != null)
|
||||
{
|
||||
var scale = transform.localScale;
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_ScaleX, SetZeroKey(curve, keyFrames, scale.x));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_ScaleY, SetZeroKey(curve, keyFrames, scale.y));
|
||||
animClip.SetCurve(binding.path, typeof(Transform), k_ScaleZ, SetZeroKey(curve, keyFrames, scale.z));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not setting curves through AnimationUtility.SetEditorCurve to avoid reentrant
|
||||
// onCurveWasModified calls in timeline. This means we don't get sprite curves
|
||||
// in the default clip right now.
|
||||
if (IsAvatarBinding(binding) || IsEulerHint(binding) || binding.isPPtrCurve)
|
||||
continue;
|
||||
|
||||
float floatValue;
|
||||
AnimationUtility.GetFloatValue(avatarRoot, binding, out floatValue);
|
||||
animClip.SetCurve(binding.path, binding.type, binding.propertyName, SetZeroKey(curve, keyFrames, floatValue));
|
||||
}
|
||||
|
||||
// add root motion explicitly.
|
||||
if (rootMotion)
|
||||
{
|
||||
var pos = Vector3.zero; // the appropriate root motion offsets are applied by timeline
|
||||
var rot = Quaternion.identity;
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_PosX, SetZeroKey(curve, keyFrames, pos.x));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_PosY, SetZeroKey(curve, keyFrames, pos.y));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_PosZ, SetZeroKey(curve, keyFrames, pos.z));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_RotX, SetZeroKey(curve, keyFrames, rot.x));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_RotY, SetZeroKey(curve, keyFrames, rot.y));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_RotZ, SetZeroKey(curve, keyFrames, rot.z));
|
||||
animClip.SetCurve(string.Empty, typeof(Transform), k_RotW, SetZeroKey(curve, keyFrames, rot.w));
|
||||
}
|
||||
|
||||
return animClip;
|
||||
}
|
||||
|
||||
public static bool IsRootMotion(EditorCurveBinding binding)
|
||||
{
|
||||
// Root Transform TR.
|
||||
if (typeof(Transform).IsAssignableFrom(binding.type) && string.IsNullOrEmpty(binding.path))
|
||||
{
|
||||
return FastStartsWith(binding.propertyName, k_Pos) || FastStartsWith(binding.propertyName, k_Rot);
|
||||
}
|
||||
|
||||
// MotionCurves/RootCurves.
|
||||
if (binding.type == typeof(Animator))
|
||||
{
|
||||
return FastStartsWith(binding.propertyName, k_MotionT) ||
|
||||
FastStartsWith(binding.propertyName, k_MotionQ) ||
|
||||
FastStartsWith(binding.propertyName, k_RootT) ||
|
||||
FastStartsWith(binding.propertyName, k_RootQ);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool NeedsSkeletonBindings(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
foreach (var b in bindings)
|
||||
{
|
||||
if (IsSkeletalBinding(b))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void AddBindings(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
foreach (var b in bindings)
|
||||
{
|
||||
if (!s_CurveSet.ContainsKey(b))
|
||||
s_CurveSet[b] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTRBinding(GameObject root, EditorCurveBinding binding)
|
||||
{
|
||||
var t = root.transform.Find(binding.path);
|
||||
if (t != null)
|
||||
{
|
||||
DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalPosition");
|
||||
DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalRotation");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddScaleBinding(GameObject root, EditorCurveBinding binding)
|
||||
{
|
||||
var t = root.transform.Find(binding.path);
|
||||
if (t != null)
|
||||
DrivenPropertyManager.RegisterProperty(s_PreviewDriver, t, "m_LocalScale");
|
||||
}
|
||||
|
||||
private static bool IsEuler(EditorCurveBinding binding)
|
||||
{
|
||||
return FastStartsWith(binding.propertyName, k_EulerAnglesRaw) &&
|
||||
typeof(Transform).IsAssignableFrom(binding.type);
|
||||
}
|
||||
|
||||
private static bool IsAvatarBinding(EditorCurveBinding binding)
|
||||
{
|
||||
return string.IsNullOrEmpty(binding.path) && typeof(Animator) == binding.type;
|
||||
}
|
||||
|
||||
private static bool IsSkeletalBinding(EditorCurveBinding binding)
|
||||
{
|
||||
// skin mesh incorporates blend shapes
|
||||
return typeof(Transform).IsAssignableFrom(binding.type) || typeof(SkinnedMeshRenderer).IsAssignableFrom(binding.type);
|
||||
}
|
||||
|
||||
private static AnimationCurve SetZeroKey(AnimationCurve curve, Keyframe[] keys, float val)
|
||||
{
|
||||
keys[0].value = val;
|
||||
curve.keys = keys;
|
||||
return curve;
|
||||
}
|
||||
|
||||
private static bool IsEulerHint(EditorCurveBinding binding)
|
||||
{
|
||||
return typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.StartsWith(k_EulerHint);
|
||||
}
|
||||
|
||||
private static GameObject GetAvatarRoot(GameObject animatorRoot)
|
||||
{
|
||||
var animator = animatorRoot.GetComponent<Animator>();
|
||||
if (animator != null && animator.avatarRoot != animatorRoot.transform)
|
||||
return animator.avatarRoot.gameObject;
|
||||
return animatorRoot;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01da6c5b7c781174d818662ce6f39b8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,133 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Animator to Editor Curve Binding cache. Used to prevent frequent calls to GetAnimatorBindings which can be costly
|
||||
/// </summary>
|
||||
class AnimatorBindingCache
|
||||
{
|
||||
public const string TRPlaceHolder = "TransformTR";
|
||||
public const string ScalePlaceholder = "TransformScale";
|
||||
|
||||
struct AnimatorEntry
|
||||
{
|
||||
public int animatorID;
|
||||
public bool applyRootMotion;
|
||||
public bool humanoid;
|
||||
}
|
||||
|
||||
class AnimatorEntryComparer : IEqualityComparer<AnimatorEntry>
|
||||
{
|
||||
public bool Equals(AnimatorEntry x, AnimatorEntry y) { return x.animatorID == y.animatorID && x.applyRootMotion == y.applyRootMotion && x.humanoid == y.humanoid; }
|
||||
public int GetHashCode(AnimatorEntry obj) { return HashUtility.CombineHash(obj.animatorID, obj.applyRootMotion.GetHashCode(), obj.humanoid.GetHashCode()); }
|
||||
public static readonly AnimatorEntryComparer Instance = new AnimatorEntryComparer();
|
||||
}
|
||||
|
||||
readonly Dictionary<AnimatorEntry, EditorCurveBinding[]> m_AnimatorCache = new Dictionary<AnimatorEntry, EditorCurveBinding[]>(AnimatorEntryComparer.Instance);
|
||||
readonly Dictionary<AnimationClip, EditorCurveBinding[]> m_ClipCache = new Dictionary<AnimationClip, EditorCurveBinding[]>();
|
||||
|
||||
private static readonly EditorCurveBinding[] kEmptyArray = new EditorCurveBinding[0];
|
||||
private static readonly List<EditorCurveBinding> s_BindingScratchPad = new List<EditorCurveBinding>(1000);
|
||||
|
||||
public AnimatorBindingCache()
|
||||
{
|
||||
AnimationUtility.onCurveWasModified += OnCurveWasModified;
|
||||
}
|
||||
|
||||
public EditorCurveBinding[] GetAnimatorBindings(GameObject gameObject)
|
||||
{
|
||||
if (gameObject == null)
|
||||
return kEmptyArray;
|
||||
|
||||
Animator animator = gameObject.GetComponent<Animator>();
|
||||
if (animator == null)
|
||||
return kEmptyArray;
|
||||
|
||||
AnimatorEntry entry = new AnimatorEntry()
|
||||
{
|
||||
animatorID = animator.GetInstanceID(),
|
||||
applyRootMotion = animator.applyRootMotion,
|
||||
humanoid = animator.isHuman
|
||||
};
|
||||
|
||||
EditorCurveBinding[] result = null;
|
||||
if (m_AnimatorCache.TryGetValue(entry, out result))
|
||||
return result;
|
||||
|
||||
s_BindingScratchPad.Clear();
|
||||
|
||||
// Replacement for AnimationMode.GetAnimatorBinding - this is faster and allocates kB instead of MB
|
||||
var transforms = animator.GetComponentsInChildren<Transform>();
|
||||
foreach (var t in transforms)
|
||||
{
|
||||
if (animator.IsBoneTransform(t))
|
||||
s_BindingScratchPad.Add(EditorCurveBinding.FloatCurve(AnimationUtility.CalculateTransformPath(t, animator.transform), typeof(Transform), TRPlaceHolder));
|
||||
}
|
||||
|
||||
var streamBindings = AnimationUtility.GetAnimationStreamBindings(animator.gameObject);
|
||||
UpdateTransformBindings(streamBindings);
|
||||
s_BindingScratchPad.AddRange(streamBindings);
|
||||
|
||||
result = new EditorCurveBinding[s_BindingScratchPad.Count];
|
||||
s_BindingScratchPad.CopyTo(result);
|
||||
m_AnimatorCache[entry] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public EditorCurveBinding[] GetCurveBindings(AnimationClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return kEmptyArray;
|
||||
|
||||
EditorCurveBinding[] result;
|
||||
if (!m_ClipCache.TryGetValue(clip, out result))
|
||||
{
|
||||
result = AnimationMode.GetCurveBindings(clip);
|
||||
UpdateTransformBindings(result);
|
||||
m_ClipCache[clip] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void UpdateTransformBindings(EditorCurveBinding[] bindings)
|
||||
{
|
||||
for (int i = 0; i < bindings.Length; i++)
|
||||
{
|
||||
var binding = bindings[i];
|
||||
if (AnimationPreviewUtilities.IsRootMotion(binding))
|
||||
{
|
||||
binding.type = typeof(Transform);
|
||||
binding.propertyName = TRPlaceHolder;
|
||||
}
|
||||
else if (typeof(Transform).IsAssignableFrom(binding.type) && (binding.propertyName.StartsWith("m_LocalRotation.") || binding.propertyName.StartsWith("m_LocalPosition.")))
|
||||
{
|
||||
binding.propertyName = TRPlaceHolder;
|
||||
}
|
||||
else if (typeof(Transform).IsAssignableFrom(binding.type) && binding.propertyName.StartsWith("m_LocalScale."))
|
||||
{
|
||||
binding.propertyName = ScalePlaceholder;
|
||||
}
|
||||
bindings[i] = binding;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_AnimatorCache.Clear();
|
||||
m_ClipCache.Clear();
|
||||
}
|
||||
|
||||
void OnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType modification)
|
||||
{
|
||||
m_ClipCache.Remove(clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0080567f62c3f94cb75b2927a349e22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
// Extension methods responsible for managing extrapolation time
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class Extrapolation
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum amount of extrapolation time to apply
|
||||
/// </summary>
|
||||
internal static readonly double kMinExtrapolationTime = TimeUtility.kTimeEpsilon * 1000;
|
||||
|
||||
// Calculates the extrapolation times
|
||||
internal static void CalculateExtrapolationTimes(this TrackAsset asset)
|
||||
{
|
||||
TimelineClip[] clips = asset.clips;
|
||||
if (clips == null || clips.Length == 0)
|
||||
return;
|
||||
|
||||
// extrapolation not supported
|
||||
if (!clips[0].SupportsExtrapolation())
|
||||
return;
|
||||
|
||||
var orderedClips = SortClipsByStartTime(clips);
|
||||
if (orderedClips.Length > 0)
|
||||
{
|
||||
// post extrapolation is the minimum time to the next clip
|
||||
for (int i = 0; i < orderedClips.Length; i++)
|
||||
{
|
||||
double minTime = double.PositiveInfinity;
|
||||
for (int j = 0; j < orderedClips.Length; j++)
|
||||
{
|
||||
if (i == j)
|
||||
continue;
|
||||
|
||||
double deltaTime = orderedClips[j].start - orderedClips[i].end;
|
||||
if (deltaTime >= -TimeUtility.kTimeEpsilon && deltaTime < minTime)
|
||||
minTime = Math.Min(minTime, deltaTime);
|
||||
// check for overlapped clips
|
||||
if (orderedClips[j].start <= orderedClips[i].end && orderedClips[j].end > orderedClips[i].end)
|
||||
minTime = 0;
|
||||
}
|
||||
minTime = minTime <= kMinExtrapolationTime ? 0 : minTime;
|
||||
orderedClips[i].SetPostExtrapolationTime(minTime);
|
||||
}
|
||||
|
||||
// the first clip gets pre-extrapolation, then it's only respected if there is no post extrapolation
|
||||
orderedClips[0].SetPreExtrapolationTime(Math.Max(0, orderedClips[0].start));
|
||||
for (int i = 1; i < orderedClips.Length; i++)
|
||||
{
|
||||
double preTime = 0;
|
||||
int prevClip = -1;
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
// overlap, no pre-time
|
||||
if (orderedClips[j].end > orderedClips[i].start)
|
||||
{
|
||||
prevClip = -1;
|
||||
preTime = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
double gap = orderedClips[i].start - orderedClips[j].end;
|
||||
if (prevClip == -1 || gap < preTime)
|
||||
{
|
||||
preTime = gap;
|
||||
prevClip = j;
|
||||
}
|
||||
}
|
||||
// check for a post extrapolation time
|
||||
if (prevClip >= 0)
|
||||
{
|
||||
if (orderedClips[prevClip].postExtrapolationMode != TimelineClip.ClipExtrapolation.None)
|
||||
preTime = 0;
|
||||
}
|
||||
|
||||
preTime = preTime <= kMinExtrapolationTime ? 0 : preTime;
|
||||
orderedClips[i].SetPreExtrapolationTime(preTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TimelineClip[] SortClipsByStartTime(TimelineClip[] clips)
|
||||
{
|
||||
var orderedClips = new TimelineClip[clips.Length];
|
||||
Array.Copy(clips, orderedClips, clips.Length);
|
||||
Array.Sort(orderedClips, (clip1, clip2) => clip1.start.CompareTo(clip2.start));
|
||||
return orderedClips;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32535dd294c621e4297fba34b15b1c52
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,30 @@
|
||||
#if !TIMELINE_FRAMEACCURATE
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
internal readonly struct FrameRate : IEquatable<FrameRate>
|
||||
{
|
||||
public readonly double rate;
|
||||
|
||||
public static readonly FrameRate k_23_976Fps = new FrameRate(23.976023976024);
|
||||
public static readonly FrameRate k_24Fps = new FrameRate(24);
|
||||
public static readonly FrameRate k_25Fps = new FrameRate(25);
|
||||
public static readonly FrameRate k_30Fps = new FrameRate(30);
|
||||
public static readonly FrameRate k_29_97Fps = new FrameRate(29.97002997003);
|
||||
public static readonly FrameRate k_50Fps = new FrameRate(50);
|
||||
public static readonly FrameRate k_59_94Fps = new FrameRate(59.9400599400599);
|
||||
public static readonly FrameRate k_60Fps = new FrameRate(60);
|
||||
|
||||
FrameRate(double framerate) { rate = framerate; }
|
||||
public bool IsValid() => rate > TimeUtility.kTimeEpsilon;
|
||||
public bool Equals(FrameRate other) => Math.Abs(rate - other.rate) < TimeUtility.kFrameRateEpsilon;
|
||||
public override bool Equals(object obj) => obj is FrameRate other && Equals(other);
|
||||
public override int GetHashCode() => rate.GetHashCode();
|
||||
public static bool operator ==(FrameRate a, FrameRate b) => a.Equals(b);
|
||||
public static bool operator !=(FrameRate a, FrameRate b) => !a.Equals(b);
|
||||
|
||||
public static FrameRate DoubleToFrameRate(double rate) => new FrameRate(Math.Ceiling(rate) - rate < TimeUtility.kFrameRateEpsilon ? rate : Math.Ceiling(rate) * 1000.0 / 1001.0);
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0949a3b7ed32e8744a07b31328a66567
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,51 @@
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class HashUtility
|
||||
{
|
||||
// Note. We could have used "params int[] hashes" but we want to avoid allocating.
|
||||
|
||||
public static int CombineHash(this int h1, int h2)
|
||||
{
|
||||
return h1 ^ (int)(h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); // Similar to c++ boost::hash_combine
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3)
|
||||
{
|
||||
return CombineHash(h1, h2).CombineHash(h3);
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3, int h4)
|
||||
{
|
||||
return CombineHash(h1, h2, h3).CombineHash(h4);
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3, int h4, int h5)
|
||||
{
|
||||
return CombineHash(h1, h2, h3, h4).CombineHash(h5);
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3, int h4, int h5, int h6)
|
||||
{
|
||||
return CombineHash(h1, h2, h3, h4, h5).CombineHash(h6);
|
||||
}
|
||||
|
||||
public static int CombineHash(int h1, int h2, int h3, int h4, int h5, int h6, int h7)
|
||||
{
|
||||
return CombineHash(h1, h2, h3, h4, h5, h6).CombineHash(h7);
|
||||
}
|
||||
|
||||
public static int CombineHash(int[] hashes)
|
||||
{
|
||||
if (hashes == null || hashes.Length == 0)
|
||||
return 0;
|
||||
|
||||
var h = hashes[0];
|
||||
for (int i = 1; i < hashes.Length; ++i)
|
||||
{
|
||||
h = CombineHash(h, hashes[i]);
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0ca7b2e84542bf4ab9987087e8d79ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,102 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface used to inform the Timeline Editor about potential property modifications that may occur while previewing.
|
||||
/// </summary>
|
||||
public interface IPropertyCollector
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the active game object for subsequent property modifications.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject to push.</param>
|
||||
void PushActiveGameObject(GameObject gameObject);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the active GameObject from the modification stack, restoring the previous value.
|
||||
/// </summary>
|
||||
void PopActiveGameObject();
|
||||
|
||||
/// <summary>
|
||||
/// Add properties modified by an animation clip.
|
||||
/// </summary>
|
||||
/// <param name="clip">The animation clip that contains the properties</param>
|
||||
void AddFromClip(AnimationClip clip);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications specified by a list of animation clips.
|
||||
/// </summary>
|
||||
/// <param name="clips">The list of animation clips used to determine which property modifications to apply.</param>
|
||||
void AddFromClips(IEnumerable<AnimationClip> clips);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
/// <typeparam name="T">The type of the component the property exists on</typeparam>
|
||||
/// <remarks>
|
||||
/// This method uses the most recent gameObject from PushActiveGameObject
|
||||
/// </remarks>
|
||||
void AddFromName<T>(string name) where T : Component;
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
/// <remarks>
|
||||
/// This method uses the most recent gameObject from PushActiveGameObject
|
||||
/// </remarks>
|
||||
void AddFromName(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications modified by an animation clip.
|
||||
/// </summary>
|
||||
/// <param name="obj">The GameObject where the properties exist</param>
|
||||
/// <param name="clip">The animation clip that contains the properties</param>
|
||||
void AddFromClip(GameObject obj, AnimationClip clip);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications specified by a list of animation clips.
|
||||
/// </summary>
|
||||
/// <param name="obj">The gameObject that will be animated</param>
|
||||
/// <param name="clips">The list of animation clips used to determine which property modifications to apply.</param>
|
||||
void AddFromClips(GameObject obj, IEnumerable<AnimationClip> clips);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
/// <param name="obj">The gameObject where the properties exist</param>
|
||||
/// <typeparam name="T">The type of the component the property exists on</typeparam>>
|
||||
void AddFromName<T>(GameObject obj, string name) where T : Component;
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="obj">The gameObject where the properties exist</param>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
void AddFromName(GameObject obj, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications using the serialized property name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the serialized property</param>
|
||||
/// <param name="component">The component where the properties exist</param>
|
||||
void AddFromName(Component component, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Set all serializable properties on a component to be under preview control.
|
||||
/// </summary>
|
||||
/// <param name="obj">The gameObject where the properties exist</param>
|
||||
/// <param name="component">The component to set in preview mode</param>
|
||||
void AddFromComponent(GameObject obj, Component component);
|
||||
|
||||
/// <summary>
|
||||
/// Add property modifications modified by an animation clip.
|
||||
/// </summary>
|
||||
/// <param name="obj">The Object where the properties exist</param>
|
||||
/// <param name="clip">The animation clip that contains the properties</param>
|
||||
void AddObjectProperties(Object obj, AnimationClip clip);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66b2b8fd1d9b4bc4c96b07335ad822f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,17 @@
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement this interface in a PlayableAsset to specify which properties will be modified when Timeline is in preview mode.
|
||||
/// </summary>
|
||||
public interface IPropertyPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Called by the Timeline Editor to gather properties requiring preview.
|
||||
/// </summary>
|
||||
/// <param name="director">The PlayableDirector invoking the preview</param>
|
||||
/// <param name="driver">PropertyCollector used to gather previewable properties</param>
|
||||
void GatherProperties(PlayableDirector director, IPropertyCollector driver);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5f0881228e5827438f74e9b7b33c2dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class NotificationUtilities
|
||||
{
|
||||
public static ScriptPlayable<TimeNotificationBehaviour> CreateNotificationsPlayable(PlayableGraph graph, IEnumerable<IMarker> markers, PlayableDirector director)
|
||||
{
|
||||
return CreateNotificationsPlayable(graph, markers, null, director);
|
||||
}
|
||||
|
||||
public static ScriptPlayable<TimeNotificationBehaviour> CreateNotificationsPlayable(PlayableGraph graph, IEnumerable<IMarker> markers, TimelineAsset timelineAsset)
|
||||
{
|
||||
return CreateNotificationsPlayable(graph, markers, timelineAsset, null);
|
||||
}
|
||||
|
||||
static ScriptPlayable<TimeNotificationBehaviour> CreateNotificationsPlayable(PlayableGraph graph, IEnumerable<IMarker> markers, IPlayableAsset asset, PlayableDirector director)
|
||||
{
|
||||
ScriptPlayable<TimeNotificationBehaviour> notificationPlayable = ScriptPlayable<TimeNotificationBehaviour>.Null;
|
||||
DirectorWrapMode extrapolationMode = director != null ? director.extrapolationMode : DirectorWrapMode.None;
|
||||
bool didCalculateDuration = false;
|
||||
var duration = 0d;
|
||||
|
||||
foreach (IMarker e in markers)
|
||||
{
|
||||
var notification = e as INotification;
|
||||
if (notification == null)
|
||||
continue;
|
||||
|
||||
if (!didCalculateDuration)
|
||||
{
|
||||
duration = director != null ? director.playableAsset.duration : asset.duration;
|
||||
didCalculateDuration = true;
|
||||
}
|
||||
|
||||
if (notificationPlayable.Equals(ScriptPlayable<TimeNotificationBehaviour>.Null))
|
||||
{
|
||||
notificationPlayable = TimeNotificationBehaviour.Create(graph,
|
||||
duration, extrapolationMode);
|
||||
}
|
||||
|
||||
var time = (DiscreteTime)e.time;
|
||||
var tlDuration = (DiscreteTime)duration;
|
||||
if (time >= tlDuration && time <= tlDuration.OneTickAfter() && tlDuration != 0)
|
||||
time = tlDuration.OneTickBefore();
|
||||
|
||||
if (e is INotificationOptionProvider notificationOptionProvider)
|
||||
notificationPlayable.GetBehaviour().AddNotification((double)time, notification, notificationOptionProvider.flags);
|
||||
else
|
||||
notificationPlayable.GetBehaviour().AddNotification((double)time, notification);
|
||||
}
|
||||
|
||||
return notificationPlayable;
|
||||
}
|
||||
|
||||
public static bool TrackTypeSupportsNotifications(Type type)
|
||||
{
|
||||
var binding = (TrackBindingTypeAttribute)Attribute.GetCustomAttribute(type, typeof(TrackBindingTypeAttribute));
|
||||
return binding != null &&
|
||||
(typeof(Component).IsAssignableFrom(binding.type) ||
|
||||
typeof(GameObject).IsAssignableFrom(binding.type));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b90311a8f07b00f4bbeb2fff3b128d25
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,376 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
using UnityEngine.Playables;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// The standard frame rates supported when locking Timeline playback to frames.
|
||||
/// The frame rate is expressed in frames per second (fps).
|
||||
/// </summary>
|
||||
public enum StandardFrameRates
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a frame rate of 24 fps. This is the common frame rate for film.
|
||||
/// </summary>
|
||||
Fps24,
|
||||
/// <summary>
|
||||
/// Represents a drop frame rate of 23.97 fps. This is the common frame rate for NTSC film broadcast.
|
||||
/// </summary>
|
||||
Fps23_97,
|
||||
/// <summary>
|
||||
/// Represents a frame rate of 25 fps. This is commonly used for non-interlaced PAL television broadcast.
|
||||
/// </summary>
|
||||
Fps25,
|
||||
/// <summary>
|
||||
/// Represents a frame rate of 30 fps. This is commonly used for HD footage.
|
||||
/// </summary>
|
||||
Fps30,
|
||||
/// <summary>
|
||||
/// Represents a drop frame rate of 29.97 fps. This is commonly used for NTSC television broadcast.
|
||||
/// </summary>
|
||||
Fps29_97,
|
||||
/// <summary>
|
||||
/// Represents a frame rate of 50 fps. This is commonly used for interlaced PAL television broadcast.
|
||||
/// </summary>
|
||||
Fps50,
|
||||
/// <summary>
|
||||
/// Represents a frame rate of 60 fps. This is commonly used for games.
|
||||
/// </summary>
|
||||
Fps60,
|
||||
/// <summary>
|
||||
/// Represents a drop frame rate of 59.94 fps. This is commonly used for interlaced NTSC television broadcast.
|
||||
/// </summary>
|
||||
Fps59_94
|
||||
}
|
||||
|
||||
// Sequence specific utilities for time manipulation
|
||||
static class TimeUtility
|
||||
{
|
||||
// chosen because it will cause no rounding errors between time/frames for frames values up to at least 10 million
|
||||
public static readonly double kTimeEpsilon = 1e-14;
|
||||
public static readonly double kFrameRateEpsilon = 1e-6;
|
||||
public static readonly double k_MaxTimelineDurationInSeconds = 9e6; //104 days of running time
|
||||
public static readonly double kFrameRateRounding = 1e-2;
|
||||
|
||||
|
||||
static void ValidateFrameRate(double frameRate)
|
||||
{
|
||||
if (frameRate <= kTimeEpsilon)
|
||||
throw new ArgumentException("frame rate cannot be 0 or negative");
|
||||
}
|
||||
|
||||
public static int ToFrames(double time, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
time = Math.Min(Math.Max(time, -k_MaxTimelineDurationInSeconds), k_MaxTimelineDurationInSeconds);
|
||||
// this matches OnFrameBoundary
|
||||
double tolerance = GetEpsilon(time, frameRate);
|
||||
if (time < 0)
|
||||
{
|
||||
return (int)Math.Ceiling(time * frameRate - tolerance);
|
||||
}
|
||||
return (int)Math.Floor(time * frameRate + tolerance);
|
||||
}
|
||||
|
||||
public static double ToExactFrames(double time, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
return time * frameRate;
|
||||
}
|
||||
|
||||
public static double FromFrames(int frames, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
return (frames / frameRate);
|
||||
}
|
||||
|
||||
public static double FromFrames(double frames, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
return frames / frameRate;
|
||||
}
|
||||
|
||||
public static bool OnFrameBoundary(double time, double frameRate)
|
||||
{
|
||||
return OnFrameBoundary(time, frameRate, GetEpsilon(time, frameRate));
|
||||
}
|
||||
|
||||
public static double GetEpsilon(double time, double frameRate)
|
||||
{
|
||||
return Math.Max(Math.Abs(time), 1) * frameRate * kTimeEpsilon;
|
||||
}
|
||||
|
||||
public static int PreviousFrame(double time, double frameRate)
|
||||
{
|
||||
return Math.Max(0, ToFrames(time, frameRate) - 1);
|
||||
}
|
||||
|
||||
public static int NextFrame(double time, double frameRate)
|
||||
{
|
||||
return ToFrames(time, frameRate) + 1;
|
||||
}
|
||||
|
||||
public static double PreviousFrameTime(double time, double frameRate)
|
||||
{
|
||||
return FromFrames(PreviousFrame(time, frameRate), frameRate);
|
||||
}
|
||||
|
||||
public static double NextFrameTime(double time, double frameRate)
|
||||
{
|
||||
return FromFrames(NextFrame(time, frameRate), frameRate);
|
||||
}
|
||||
|
||||
public static bool OnFrameBoundary(double time, double frameRate, double epsilon)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
|
||||
double exact = ToExactFrames(time, frameRate);
|
||||
double rounded = Math.Round(exact);
|
||||
|
||||
return Math.Abs(exact - rounded) < epsilon;
|
||||
}
|
||||
|
||||
public static double RoundToFrame(double time, double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
|
||||
var frameBefore = (int)Math.Floor(time * frameRate) / frameRate;
|
||||
var frameAfter = (int)Math.Ceiling(time * frameRate) / frameRate;
|
||||
|
||||
return Math.Abs(time - frameBefore) < Math.Abs(time - frameAfter) ? frameBefore : frameAfter;
|
||||
}
|
||||
|
||||
public static string TimeAsFrames(double timeValue, double frameRate, string format = "F2")
|
||||
{
|
||||
if (OnFrameBoundary(timeValue, frameRate)) // make integral values when on time borders
|
||||
return ToFrames(timeValue, frameRate).ToString();
|
||||
return ToExactFrames(timeValue, frameRate).ToString(format);
|
||||
}
|
||||
|
||||
public static string TimeAsTimeCode(double timeValue, double frameRate, string format = "F2")
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
|
||||
int intTime = (int)Math.Abs(timeValue);
|
||||
|
||||
int hours = intTime / 3600;
|
||||
int minutes = (intTime % 3600) / 60;
|
||||
int seconds = intTime % 60;
|
||||
|
||||
string result;
|
||||
string sign = timeValue < 0 ? "-" : string.Empty;
|
||||
if (hours > 0)
|
||||
result = hours + ":" + minutes.ToString("D2") + ":" + seconds.ToString("D2");
|
||||
else if (minutes > 0)
|
||||
result = minutes + ":" + seconds.ToString("D2");
|
||||
else
|
||||
result = seconds.ToString();
|
||||
|
||||
int frameDigits = (int)Math.Floor(Math.Log10(frameRate) + 1);
|
||||
|
||||
// Add partial digits on the frame if needed.
|
||||
// we are testing the original value (not the truncated), because the truncation can cause rounding errors leading
|
||||
// to invalid strings for items on frame boundaries
|
||||
string frames = (ToFrames(timeValue, frameRate) - ToFrames(intTime, frameRate)).ToString().PadLeft(frameDigits, '0');
|
||||
if (!OnFrameBoundary(timeValue, frameRate))
|
||||
{
|
||||
string decimals = ToExactFrames(timeValue, frameRate).ToString(format);
|
||||
int decPlace = decimals.IndexOf('.');
|
||||
if (decPlace >= 0)
|
||||
frames += " [" + decimals.Substring(decPlace) + "]";
|
||||
}
|
||||
|
||||
return sign + result + ":" + frames;
|
||||
}
|
||||
|
||||
// Given a time code string, return the time in seconds
|
||||
// 1.5 -> 1.5 seconds
|
||||
// 1:1.5 -> 1 minute, 1.5 seconds
|
||||
// 1:1[.5] -> 1 second, 1.5 frames
|
||||
// 2:3:4 -> 2 minutes, 3 seconds, 4 frames
|
||||
// 1[.6] -> 1.6 frames
|
||||
public static double ParseTimeCode(string timeCode, double frameRate, double defaultValue)
|
||||
{
|
||||
timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c));
|
||||
string[] sections = timeCode.Split(':');
|
||||
if (sections.Length == 0 || sections.Length > 4)
|
||||
return defaultValue;
|
||||
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
double seconds = 0;
|
||||
double frames = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// depending on the format of the last numbers
|
||||
// seconds format
|
||||
string lastSection = sections[sections.Length - 1];
|
||||
if (Regex.Match(lastSection, @"^\d+\.\d+$").Success)
|
||||
{
|
||||
seconds = double.Parse(lastSection);
|
||||
if (sections.Length > 3) return defaultValue;
|
||||
if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]);
|
||||
if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]);
|
||||
}
|
||||
// frame formats
|
||||
else
|
||||
{
|
||||
if (Regex.Match(lastSection, @"^\d+\[\.\d+\]$").Success)
|
||||
{
|
||||
string stripped = RemoveChar(lastSection, c => c == '[' || c == ']');
|
||||
frames = double.Parse(stripped);
|
||||
}
|
||||
else if (Regex.Match(lastSection, @"^\d*$").Success)
|
||||
{
|
||||
frames = int.Parse(lastSection);
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (sections.Length > 1) seconds = int.Parse(sections[sections.Length - 2]);
|
||||
if (sections.Length > 2) minutes = int.Parse(sections[sections.Length - 3]);
|
||||
if (sections.Length > 3) hours = int.Parse(sections[sections.Length - 4]);
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return frames / frameRate + seconds + minutes * 60 + hours * 3600;
|
||||
}
|
||||
|
||||
public static double ParseTimeSeconds(string timeCode, double frameRate, double defaultValue)
|
||||
{
|
||||
timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c));
|
||||
string[] sections = timeCode.Split(':');
|
||||
if (sections.Length == 0 || sections.Length > 4)
|
||||
return defaultValue;
|
||||
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
double seconds = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// depending on the format of the last numbers
|
||||
// seconds format
|
||||
string lastSection = sections[sections.Length - 1];
|
||||
{
|
||||
if (!double.TryParse(lastSection, NumberStyles.Integer, CultureInfo.InvariantCulture, out seconds))
|
||||
if (Regex.Match(lastSection, @"^\d+\.\d+$").Success)
|
||||
seconds = double.Parse(lastSection);
|
||||
else
|
||||
return defaultValue;
|
||||
|
||||
if (!double.TryParse(lastSection, NumberStyles.Float, CultureInfo.InvariantCulture, out seconds))
|
||||
return defaultValue;
|
||||
|
||||
if (sections.Length > 3) return defaultValue;
|
||||
if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]);
|
||||
if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]);
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return seconds + minutes * 60 + hours * 3600;
|
||||
}
|
||||
|
||||
// fixes rounding errors from using single precision for length
|
||||
public static double GetAnimationClipLength(AnimationClip clip)
|
||||
{
|
||||
if (clip == null || clip.empty)
|
||||
return 0;
|
||||
|
||||
double length = clip.length;
|
||||
if (clip.frameRate > 0)
|
||||
{
|
||||
double frames = Mathf.Round(clip.length * clip.frameRate);
|
||||
length = frames / clip.frameRate;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static string RemoveChar(string str, Func<char, bool> charToRemoveFunc)
|
||||
{
|
||||
var len = str.Length;
|
||||
var src = str.ToCharArray();
|
||||
var dstIdx = 0;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
if (!charToRemoveFunc(src[i]))
|
||||
src[dstIdx++] = src[i];
|
||||
}
|
||||
return new string(src, 0, dstIdx);
|
||||
}
|
||||
|
||||
public static FrameRate GetClosestFrameRate(double frameRate)
|
||||
{
|
||||
ValidateFrameRate(frameRate);
|
||||
var actualFrameRate = FrameRate.DoubleToFrameRate(frameRate);
|
||||
return Math.Abs(frameRate - actualFrameRate.rate) < kFrameRateRounding ? actualFrameRate : new FrameRate();
|
||||
}
|
||||
|
||||
public static FrameRate ToFrameRate(StandardFrameRates enumValue)
|
||||
{
|
||||
switch (enumValue)
|
||||
{
|
||||
case StandardFrameRates.Fps23_97:
|
||||
return FrameRate.k_23_976Fps;
|
||||
case StandardFrameRates.Fps24:
|
||||
return FrameRate.k_24Fps;
|
||||
case StandardFrameRates.Fps25:
|
||||
return FrameRate.k_25Fps;
|
||||
case StandardFrameRates.Fps29_97:
|
||||
return FrameRate.k_29_97Fps;
|
||||
case StandardFrameRates.Fps30:
|
||||
return FrameRate.k_30Fps;
|
||||
case StandardFrameRates.Fps50:
|
||||
return FrameRate.k_50Fps;
|
||||
case StandardFrameRates.Fps59_94:
|
||||
return FrameRate.k_59_94Fps;
|
||||
case StandardFrameRates.Fps60:
|
||||
return FrameRate.k_60Fps;
|
||||
default:
|
||||
return new FrameRate();
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
internal static bool ToStandardFrameRate(FrameRate rate, out StandardFrameRates standard)
|
||||
{
|
||||
if (rate == FrameRate.k_23_976Fps)
|
||||
standard = StandardFrameRates.Fps23_97;
|
||||
else if (rate == FrameRate.k_24Fps)
|
||||
standard = StandardFrameRates.Fps24;
|
||||
else if (rate == FrameRate.k_25Fps)
|
||||
standard = StandardFrameRates.Fps25;
|
||||
else if (rate == FrameRate.k_30Fps)
|
||||
standard = StandardFrameRates.Fps30;
|
||||
else if (rate == FrameRate.k_29_97Fps)
|
||||
standard = StandardFrameRates.Fps29_97;
|
||||
else if (rate == FrameRate.k_50Fps)
|
||||
standard = StandardFrameRates.Fps50;
|
||||
else if (rate == FrameRate.k_59_94Fps)
|
||||
standard = StandardFrameRates.Fps59_94;
|
||||
else if (rate == FrameRate.k_60Fps)
|
||||
standard = StandardFrameRates.Fps60;
|
||||
else
|
||||
{
|
||||
standard = (StandardFrameRates)Enum.GetValues(typeof(StandardFrameRates)).Length;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f779e779d62b5ca49b658236c337845d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,123 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for TimelineClip
|
||||
/// </summary>
|
||||
public static class TimelineClipExtensions
|
||||
{
|
||||
static readonly string k_UndoSetParentTrackText = "Move Clip";
|
||||
|
||||
/// <summary>
|
||||
/// Tries to move a TimelineClip to a different track. Validates that the destination track can accept the clip before performing the move.
|
||||
/// Fails if the clip's PlayableAsset is null, the current and destination tracks are the same or the destination track cannot accept the clip.
|
||||
/// </summary>
|
||||
/// <param name="clip">Clip that is being moved</param>
|
||||
/// <param name="destinationTrack">Desired destination track</param>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="clip"/> or <paramref name="destinationTrack"/> are null</exception>
|
||||
/// <exception cref="System.InvalidOperationException">Thrown if the PlayableAsset in the TimelineClip is null</exception>
|
||||
/// <exception cref="System.InvalidOperationException">Thrown if the current parent track and destination track are the same</exception>
|
||||
/// <exception cref="System.InvalidOperationException">Thrown if the destination track cannot hold tracks of the given type</exception>
|
||||
public static void MoveToTrack(this TimelineClip clip, TrackAsset destinationTrack)
|
||||
{
|
||||
if (clip == null)
|
||||
{
|
||||
throw new ArgumentNullException($"'this' argument for {nameof(MoveToTrack)} cannot be null.");
|
||||
}
|
||||
|
||||
if (destinationTrack == null)
|
||||
{
|
||||
throw new ArgumentNullException("Cannot move TimelineClip to a null track.");
|
||||
}
|
||||
|
||||
TrackAsset parentTrack = clip.GetParentTrack();
|
||||
Object asset = clip.asset;
|
||||
|
||||
// If the asset is null we cannot validate its type against the destination track
|
||||
if (asset == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot move a TimelineClip to a different track if the TimelineClip's PlayableAsset is null.");
|
||||
}
|
||||
|
||||
if (parentTrack == destinationTrack)
|
||||
{
|
||||
throw new InvalidOperationException($"TimelineClip is already on {destinationTrack.name}.");
|
||||
}
|
||||
|
||||
if (!destinationTrack.ValidateClipType(asset.GetType()))
|
||||
{
|
||||
throw new InvalidOperationException($"Track {destinationTrack.name} cannot contain clips of type {clip.GetType().Name}.");
|
||||
}
|
||||
|
||||
MoveToTrack_Impl(clip, destinationTrack, asset, parentTrack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to move a TimelineClip to a different track. Validates that the destination track can accept the clip before performing the move.
|
||||
/// Fails if the clip's PlayableAsset is null, the current and destination tracks are the same or the destination track cannot accept the clip.
|
||||
/// </summary>
|
||||
/// <param name="clip">Clip that is being moved</param>
|
||||
/// <param name="destinationTrack">Desired destination track</param>
|
||||
/// <returns>Returns true if the clip was successfully moved to the destination track, false otherwise. Also returns false if either argument is null</returns>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="clip"/> or <paramref name="destinationTrack"/> are null</exception>
|
||||
public static bool TryMoveToTrack(this TimelineClip clip, TrackAsset destinationTrack)
|
||||
{
|
||||
if (clip == null)
|
||||
{
|
||||
throw new ArgumentNullException($"'this' argument for {nameof(TryMoveToTrack)} cannot be null.");
|
||||
}
|
||||
|
||||
if (destinationTrack == null)
|
||||
{
|
||||
throw new ArgumentNullException("Cannot move TimelineClip to a null parent.");
|
||||
}
|
||||
|
||||
TrackAsset parentTrack = clip.GetParentTrack();
|
||||
Object asset = clip.asset;
|
||||
|
||||
// If the asset is null we cannot validate its type against the destination track
|
||||
if (asset == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentTrack != destinationTrack)
|
||||
{
|
||||
if (!destinationTrack.ValidateClipType(asset.GetType()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MoveToTrack_Impl(clip, destinationTrack, asset, parentTrack);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void MoveToTrack_Impl(TimelineClip clip, TrackAsset destinationTrack, Object asset, TrackAsset parentTrack)
|
||||
{
|
||||
TimelineUndo.PushUndo(asset, k_UndoSetParentTrackText);
|
||||
if (parentTrack != null)
|
||||
{
|
||||
TimelineUndo.PushUndo(parentTrack, k_UndoSetParentTrackText);
|
||||
}
|
||||
|
||||
TimelineUndo.PushUndo(destinationTrack, k_UndoSetParentTrackText);
|
||||
|
||||
clip.SetParentTrack_Internal(destinationTrack);
|
||||
|
||||
if (parentTrack == null)
|
||||
{
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(asset, destinationTrack);
|
||||
}
|
||||
else if (parentTrack.timelineAsset != destinationTrack.timelineAsset)
|
||||
{
|
||||
TimelineCreateUtilities.RemoveAssetFromObject(asset, parentTrack);
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(asset, destinationTrack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a02f7efcd3e4f3baa0079e620bddbd7
|
||||
timeCreated: 1602852593
|
@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class TimelineCreateUtilities
|
||||
{
|
||||
// based off of ObjectNames.GetUniqueName, but can exist in runtime
|
||||
public static string GenerateUniqueActorName(List<ScriptableObject> tracks, string name)
|
||||
{
|
||||
if (!tracks.Exists(x => ((object)x) != null && x.name == name))
|
||||
return name;
|
||||
|
||||
int numberInParentheses = 0;
|
||||
string baseName = name;
|
||||
|
||||
if (!string.IsNullOrEmpty(name) && name[name.Length - 1] == ')')
|
||||
{
|
||||
int index = name.LastIndexOf('(');
|
||||
if (index > 0)
|
||||
{
|
||||
string numberString = name.Substring(index + 1, name.Length - index - 2);
|
||||
if (int.TryParse(numberString, out numberInParentheses))
|
||||
{
|
||||
numberInParentheses++;
|
||||
baseName = name.Substring(0, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseName = baseName.TrimEnd();
|
||||
|
||||
for (int i = numberInParentheses; i < numberInParentheses + 5000; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
string result = string.Format("{0} ({1})", baseName, i);
|
||||
if (!tracks.Exists(x => ((object)x) != null && x.name == result))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return name;
|
||||
}
|
||||
|
||||
public static void SaveAssetIntoObject(Object childAsset, Object masterAsset)
|
||||
{
|
||||
if (childAsset == null || masterAsset == null)
|
||||
return;
|
||||
|
||||
if ((masterAsset.hideFlags & HideFlags.DontSave) != 0)
|
||||
{
|
||||
childAsset.hideFlags |= HideFlags.DontSave;
|
||||
}
|
||||
else
|
||||
{
|
||||
childAsset.hideFlags |= HideFlags.HideInHierarchy;
|
||||
#if UNITY_EDITOR
|
||||
if (!AssetDatabase.Contains(childAsset) && AssetDatabase.Contains(masterAsset))
|
||||
AssetDatabase.AddObjectToAsset(childAsset, masterAsset);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveAssetFromObject(Object childAsset, Object masterAsset)
|
||||
{
|
||||
if (childAsset == null || masterAsset == null)
|
||||
return;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (AssetDatabase.Contains(childAsset) && AssetDatabase.Contains(masterAsset))
|
||||
AssetDatabase.RemoveObjectFromAsset(childAsset);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static AnimationClip CreateAnimationClipForTrack(string name, TrackAsset track, bool isLegacy)
|
||||
{
|
||||
var timelineAsset = track != null ? track.timelineAsset : null;
|
||||
var trackFlags = track != null ? track.hideFlags : HideFlags.None;
|
||||
|
||||
var curves = new AnimationClip
|
||||
{
|
||||
legacy = isLegacy,
|
||||
|
||||
name = name,
|
||||
|
||||
frameRate = timelineAsset == null
|
||||
? (float)TimelineAsset.EditorSettings.kDefaultFrameRate
|
||||
: (float)timelineAsset.editorSettings.frameRate
|
||||
};
|
||||
|
||||
SaveAssetIntoObject(curves, timelineAsset);
|
||||
curves.hideFlags = trackFlags & ~HideFlags.HideInHierarchy; // Never hide in hierarchy
|
||||
|
||||
TimelineUndo.RegisterCreatedObjectUndo(curves, "Create Curves");
|
||||
|
||||
return curves;
|
||||
}
|
||||
|
||||
public static bool ValidateParentTrack(TrackAsset parent, Type childType)
|
||||
{
|
||||
if (childType == null || !typeof(TrackAsset).IsAssignableFrom(childType))
|
||||
return false;
|
||||
|
||||
// no parent is valid for any type
|
||||
if (parent == null)
|
||||
return true;
|
||||
|
||||
// A track supports layers if it implements ILayerable. Only supported for parents that are
|
||||
// the same exact type as the child class, and 1 level of nesting only
|
||||
if (parent is ILayerable && !parent.isSubTrack && parent.GetType() == childType)
|
||||
return true;
|
||||
|
||||
var attr = Attribute.GetCustomAttribute(parent.GetType(), typeof(SupportsChildTracksAttribute)) as SupportsChildTracksAttribute;
|
||||
if (attr == null)
|
||||
return false;
|
||||
|
||||
// group track case, accepts all
|
||||
if (attr.childType == null)
|
||||
return true;
|
||||
|
||||
// specific case. Specifies nesting level
|
||||
if (childType == attr.childType)
|
||||
{
|
||||
int nestCount = 0;
|
||||
var p = parent;
|
||||
while (p != null && p.isSubTrack)
|
||||
{
|
||||
nestCount++;
|
||||
p = p.parent as TrackAsset;
|
||||
}
|
||||
|
||||
return nestCount < attr.levels;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40cb137d0e9816e48a4141ed13afedad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class TimelineUndo
|
||||
{
|
||||
internal static bool undoEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
return DisableUndoGuard.enableUndo && DisableUndoScope.enableUndo;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static void PushDestroyUndo(TimelineAsset timeline, Object thingToDirty, Object objectToDestroy)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (objectToDestroy == null || !undoEnabled)
|
||||
return;
|
||||
|
||||
if (thingToDirty != null)
|
||||
EditorUtility.SetDirty(thingToDirty);
|
||||
|
||||
if (timeline != null)
|
||||
EditorUtility.SetDirty(timeline);
|
||||
|
||||
Undo.DestroyObjectImmediate(objectToDestroy);
|
||||
#else
|
||||
if (objectToDestroy != null)
|
||||
Object.Destroy(objectToDestroy);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void PushUndo(Object[] thingsToDirty, string operation)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (thingsToDirty == null || !undoEnabled)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < thingsToDirty.Length; i++)
|
||||
{
|
||||
if (thingsToDirty[i] is TrackAsset track)
|
||||
track.MarkDirtyTrackAndClips();
|
||||
EditorUtility.SetDirty(thingsToDirty[i]);
|
||||
}
|
||||
Undo.RegisterCompleteObjectUndo(thingsToDirty, UndoName(operation));
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void PushUndo(Object thingToDirty, string operation)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (thingToDirty != null && undoEnabled)
|
||||
{
|
||||
var track = thingToDirty as TrackAsset;
|
||||
if (track != null)
|
||||
track.MarkDirtyTrackAndClips();
|
||||
|
||||
EditorUtility.SetDirty(thingToDirty);
|
||||
Undo.RegisterCompleteObjectUndo(thingToDirty, UndoName(operation));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void RegisterCreatedObjectUndo(Object thingCreated, string operation)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (undoEnabled)
|
||||
{
|
||||
Undo.RegisterCreatedObjectUndo(thingCreated, UndoName(operation));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static string UndoName(string name) => "Timeline " + name;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Provides stack management of the undo state.
|
||||
/// </summary>
|
||||
internal struct DisableUndoGuard : IDisposable
|
||||
{
|
||||
internal static bool enableUndo = true;
|
||||
static readonly Stack<bool> m_UndoStateStack = new Stack<bool>();
|
||||
bool m_MustDispose;
|
||||
public DisableUndoGuard(bool disable)
|
||||
{
|
||||
m_MustDispose = true;
|
||||
m_UndoStateStack.Push(enableUndo);
|
||||
enableUndo = !disable;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_MustDispose)
|
||||
{
|
||||
if (m_UndoStateStack.Count == 0)
|
||||
{
|
||||
Debug.LogError("UnMatched DisableUndoGuard calls");
|
||||
enableUndo = true;
|
||||
return;
|
||||
}
|
||||
enableUndo = m_UndoStateStack.Pop();
|
||||
m_MustDispose = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides an undo state switch.
|
||||
/// </summary>
|
||||
internal class DisableUndoScope : IDisposable
|
||||
{
|
||||
internal static bool enableUndo => m_Depth == 0;
|
||||
static int m_Depth;
|
||||
bool m_MustDispose;
|
||||
public DisableUndoScope()
|
||||
{
|
||||
m_MustDispose = true;
|
||||
m_Depth++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_MustDispose)
|
||||
{
|
||||
if (m_Depth == 0)
|
||||
{
|
||||
Debug.LogError("UnMatched DisableUndoScope calls");
|
||||
return;
|
||||
}
|
||||
m_Depth--;
|
||||
m_MustDispose = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f2a7e0d1b6bbba408a41e206945c23c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,30 @@
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
static class WeightUtility
|
||||
{
|
||||
// Given a mixer, normalizes the mixer if required
|
||||
// returns the output weight that should be applied to the mixer as input
|
||||
public static float NormalizeMixer(Playable mixer)
|
||||
{
|
||||
if (!mixer.IsValid())
|
||||
return 0;
|
||||
int count = mixer.GetInputCount();
|
||||
float weight = 0.0f;
|
||||
for (int c = 0; c < count; c++)
|
||||
{
|
||||
weight += mixer.GetInputWeight(c);
|
||||
}
|
||||
|
||||
if (weight > Mathf.Epsilon && weight < 1)
|
||||
{
|
||||
for (int c = 0; c < count; c++)
|
||||
{
|
||||
mixer.SetInputWeight(c, mixer.GetInputWeight(c) / weight);
|
||||
}
|
||||
}
|
||||
return Mathf.Clamp01(weight);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7a505b341283e14696e86433a5b1ae9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user