mirror of
https://github.com/Project-Redacted/Highscores-Server.git
synced 2025-05-23 03:44:52 +00:00
Add example Unity Project
This commit is contained in:
parent
fda7ff28dd
commit
e3acdb9d6b
7122 changed files with 505543 additions and 2 deletions
|
@ -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,93 @@
|
|||
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]
|
||||
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,326 @@
|
|||
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.All;
|
||||
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,54 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Experimental.Animations;
|
||||
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,971 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Experimental.Animations;
|
||||
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]
|
||||
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;
|
||||
|
||||
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, TrackAsset 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);
|
||||
}
|
||||
}
|
||||
|
||||
return ApplyTrackOffset(graph, mixer, go, mode);
|
||||
}
|
||||
|
||||
Playable ILayerable.CreateLayerMixer(PlayableGraph graph, GameObject go, int inputCount)
|
||||
{
|
||||
return Playable.Null;
|
||||
}
|
||||
|
||||
internal override Playable OnCreateClipPlayableGraph(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);
|
||||
|
||||
|
||||
bool animatesRootTransform = AnimatesRootTransform();
|
||||
foreach (var subTrack in GetChildTracks())
|
||||
{
|
||||
var child = subTrack as AnimationTrack;
|
||||
if (child != null && child.CanCompileClips())
|
||||
{
|
||||
animatesRootTransform |= child.AnimatesRootTransform();
|
||||
flattenTracks.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
// figure out which mode to apply
|
||||
AppliedOffsetMode mode = GetOffsetMode(go, animatesRootTransform);
|
||||
var layerMixer = CreateGroupMixer(graph, go, flattenTracks.Count);
|
||||
for (int c = 0; c < flattenTracks.Count; c++)
|
||||
{
|
||||
var compiledTrackPlayable = flattenTracks[c].inClipMode ?
|
||||
CompileTrackPlayable(graph, flattenTracks[c], go, tree, mode) :
|
||||
flattenTracks[c].CreateInfiniteTrackPlayable(graph, go, tree, mode);
|
||||
graph.Connect(compiledTrackPlayable, 0, layerMixer, c);
|
||||
layerMixer.SetInputWeight(c, flattenTracks[c].inClipMode ? 0 : 1);
|
||||
if (flattenTracks[c].applyAvatarMask && flattenTracks[c].avatarMask != null)
|
||||
{
|
||||
layerMixer.SetLayerMaskFromAvatarMask((uint)c, flattenTracks[c].avatarMask);
|
||||
}
|
||||
}
|
||||
|
||||
bool requiresMotionXPlayable = RequiresMotionXPlayable(mode, go);
|
||||
|
||||
Playable mixer = layerMixer;
|
||||
mixer = CreateDefaultBlend(graph, go, mixer, requiresMotionXPlayable);
|
||||
|
||||
// motionX playable not required in scene offset mode, or root transform mode
|
||||
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;
|
||||
}
|
||||
|
||||
// Creates a layer mixer containing default blends
|
||||
// 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 Playable CreateDefaultBlend(PlayableGraph graph, GameObject go, Playable mixer, bool requireOffset)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
return mixer;
|
||||
|
||||
int inputs = 1 + ((m_CachedPropertiesClip != null) ? 1 : 0) + ((m_DefaultPoseClip != null) ? 1 : 0);
|
||||
if (inputs == 1)
|
||||
return mixer;
|
||||
|
||||
var defaultPoseMixer = AnimationLayerMixerPlayable.Create(graph, inputs);
|
||||
|
||||
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, defaultPoseMixer, mixerInput);
|
||||
defaultPoseMixer.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, defaultPoseMixer, mixerInput);
|
||||
defaultPoseMixer.SetInputWeight(mixerInput, 1.0f);
|
||||
mixerInput++;
|
||||
}
|
||||
|
||||
|
||||
graph.Connect(mixer, 0, defaultPoseMixer, mixerInput);
|
||||
defaultPoseMixer.SetInputWeight(mixerInput, 1.0f);
|
||||
|
||||
return defaultPoseMixer;
|
||||
#else
|
||||
return mixer;
|
||||
#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)
|
||||
{
|
||||
return AnimationLayerMixerPlayable.Create(graph, inputCount);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return 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 ||
|
||||
!AnimatesRootTransform()
|
||||
)
|
||||
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);
|
||||
|
||||
m_SceneOffsetPosition = animator.transform.localPosition;
|
||||
m_SceneOffsetRotation = animator.transform.localEulerAngles;
|
||||
|
||||
// Create default pose clip from collected properties
|
||||
if (hasHumanMotion)
|
||||
animClips.Add(GetDefaultHumanoidClip());
|
||||
|
||||
var bindings = AnimationPreviewUtilities.GetBindings(animator.gameObject, animClips);
|
||||
|
||||
m_CachedPropertiesClip = AnimationPreviewUtilities.CreateDefaultClip(animator.gameObject, bindings);
|
||||
AnimationPreviewUtilities.PreviewFromCurves(animator.gameObject, bindings); // faster to preview from curves then an animation clip
|
||||
m_DefaultPoseClip = hasHumanMotion ? GetDefaultHumanoidClip() : null;
|
||||
#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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,47 @@
|
|||
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
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
m_Version = k_LatestVersion;
|
||||
}
|
||||
|
||||
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,102 @@
|
|||
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
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("openClipOffsetPosition has been deprecated. Use infiniteClipOffsetPosition instead. (UnityUpgradable) -> infiniteClipOffsetPosition", true)]
|
||||
public Vector3 openClipOffsetPosition
|
||||
{
|
||||
get { return infiniteClipOffsetPosition; }
|
||||
set { infiniteClipOffsetPosition = value; }
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("openClipOffsetRotation has been deprecated. Use infiniteClipOffsetRotation instead. (UnityUpgradable) -> infiniteClipOffsetRotation", true)]
|
||||
public Quaternion openClipOffsetRotation
|
||||
{
|
||||
get { return infiniteClipOffsetRotation; }
|
||||
set { infiniteClipOffsetRotation = value; }
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("openClipOffsetEulerAngles has been deprecated. Use infiniteClipOffsetEulerAngles instead. (UnityUpgradable) -> infiniteClipOffsetEulerAngles", true)]
|
||||
public Vector3 openClipOffsetEulerAngles
|
||||
{
|
||||
get { return infiniteClipOffsetEulerAngles; }
|
||||
set { infiniteClipOffsetEulerAngles = value; }
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("openClipPreExtrapolation has been deprecated. Use infiniteClipPreExtrapolation instead. (UnityUpgradable) -> infiniteClipPreExtrapolation", true)]
|
||||
public TimelineClip.ClipExtrapolation openClipPreExtrapolation
|
||||
{
|
||||
get { return infiniteClipPreExtrapolation; }
|
||||
set { infiniteClipPreExtrapolation = value; }
|
||||
}
|
||||
|
||||
[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,76 @@
|
|||
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;
|
||||
|
||||
protected virtual void OnBeforeTrackSerialize() {}
|
||||
protected virtual void OnAfterTrackDeserialize() {}
|
||||
|
||||
internal virtual void OnUpgradeFromVersion(int oldVersion) {}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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,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,129 @@
|
|||
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]
|
||||
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,85 @@
|
|||
using System;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
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>
|
||||
/// 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,406 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
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);
|
||||
|
||||
/// <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 = false;
|
||||
|
||||
/// <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 ? GetParticleSystemRoots(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)
|
||||
SearchHiearchyAndConnectParticleSystem(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 SearchHiearchyAndConnectParticleSystem(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;
|
||||
}
|
||||
|
||||
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> GetParticleSystemRoots(GameObject go)
|
||||
{
|
||||
if (searchHierarchy)
|
||||
{
|
||||
// We only want the parent systems as they will handle all the child systems.
|
||||
var roots = new List<ParticleSystem>();
|
||||
GetParticleSystemRoots(go.transform, roots);
|
||||
return roots;
|
||||
}
|
||||
return GetComponent<ParticleSystem>(go);
|
||||
}
|
||||
|
||||
static void GetParticleSystemRoots(Transform t, ICollection<ParticleSystem> roots)
|
||||
{
|
||||
var ps = t.GetComponent<ParticleSystem>();
|
||||
if (ps != null)
|
||||
{
|
||||
// its a root
|
||||
roots.Add(ps);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < t.childCount; ++i)
|
||||
{
|
||||
GetParticleSystemRoots(t.GetChild(i), roots);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
|
||||
{
|
||||
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.
|
||||
foreach (var ps in gameObject.GetComponentsInChildren<ParticleSystem>(true))
|
||||
{
|
||||
driver.AddFromName<ParticleSystem>(ps.gameObject, "randomSeed");
|
||||
driver.AddFromName<ParticleSystem>(ps.gameObject, "autoRandomSeed");
|
||||
}
|
||||
}
|
||||
|
||||
if (active)
|
||||
{
|
||||
driver.AddFromName(gameObject, "m_IsActive");
|
||||
}
|
||||
|
||||
if (updateITimeControl)
|
||||
{
|
||||
foreach (var script in GetControlableScripts(gameObject))
|
||||
{
|
||||
var propertyPreview = script as IPropertyPreview;
|
||||
if (propertyPreview != null)
|
||||
propertyPreview.GatherProperties(director, driver);
|
||||
else
|
||||
driver.AddFromComponent(script.gameObject, script);
|
||||
}
|
||||
}
|
||||
|
||||
if (updateDirector)
|
||||
{
|
||||
foreach (var childDirector in GetComponent<PlayableDirector>(gameObject))
|
||||
{
|
||||
if (childDirector == null)
|
||||
continue;
|
||||
|
||||
var timeline = childDirector.playableAsset as TimelineAsset;
|
||||
if (timeline == null)
|
||||
continue;
|
||||
|
||||
timeline.GatherProperties(childDirector, driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
s_ProcessedDirectors.Remove(director);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 48853ae485fa386428341ac1ea122570
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,14 @@
|
|||
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]
|
||||
public class ControlTrack : TrackAsset
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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,46 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,110 @@
|
|||
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;
|
||||
|
||||
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 Reset()
|
||||
{
|
||||
SetTime(m_Clip.clipIn);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,17 @@
|
|||
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 virtual void Reset() {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 76b6bf32a6fcf934aab8c529bddccc81
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,111 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,53 @@
|
|||
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); }
|
||||
}
|
||||
|
||||
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>
|
||||
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, "Delete Marker");
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Use this track to add Markers bound to a GameObject.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[TrackBindingType(typeof(GameObject))]
|
||||
[HideInMenu]
|
||||
[ExcludeFromPreset]
|
||||
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,19 @@
|
|||
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]
|
||||
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,22 @@
|
|||
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")]
|
||||
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,72 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <inheritdoc cref="UnityEngine.Timeline.IMarker" />
|
||||
/// <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"/>
|
||||
[Serializable]
|
||||
[CustomStyle("SignalEmitter")]
|
||||
[ExcludeFromPreset]
|
||||
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,248 @@
|
|||
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"/>
|
||||
public class SignalReceiver : MonoBehaviour, INotificationReceiver
|
||||
{
|
||||
[SerializeField]
|
||||
EventKeyValue m_Events = new EventKeyValue();
|
||||
|
||||
/// <summary>
|
||||
/// Called when a notification is sent.
|
||||
/// </summary>
|
||||
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,26 @@
|
|||
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]
|
||||
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,140 @@
|
|||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Playable that controls the active state of a GameObject.
|
||||
/// </summary>
|
||||
public class ActivationControlPlayable : PlayableBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The state of a GameObject's activeness when a PlayableGraph stops.
|
||||
/// </summary>
|
||||
public enum PostPlaybackState
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the GameObject to active when the PlayableGraph stops.
|
||||
/// </summary>
|
||||
Active,
|
||||
|
||||
/// <summary>
|
||||
/// Set the GameObject to inactive when the [[PlayableGraph]] stops.
|
||||
/// </summary>
|
||||
Inactive,
|
||||
|
||||
/// <summary>
|
||||
/// Revert the GameObject to the active state it was before the [[PlayableGraph]] started.
|
||||
/// </summary>
|
||||
Revert
|
||||
}
|
||||
|
||||
enum InitialState
|
||||
{
|
||||
Unset,
|
||||
Active,
|
||||
Inactive
|
||||
}
|
||||
|
||||
public GameObject gameObject = null;
|
||||
public PostPlaybackState postPlayback = PostPlaybackState.Revert;
|
||||
InitialState m_InitialState;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ScriptPlayable with an ActivationControlPlayable behaviour attached
|
||||
/// </summary>
|
||||
/// <param name="graph">PlayableGraph that will own the playable</param>
|
||||
/// <param name="gameObject">The GameObject that triggered the graph build</param>
|
||||
/// <param name="postPlaybackState">The state to leave the gameObject after the graph is stopped</param>
|
||||
/// <returns>Returns a playable that controls activation of a game object</returns>
|
||||
public static ScriptPlayable<ActivationControlPlayable> Create(PlayableGraph graph, GameObject gameObject, ActivationControlPlayable.PostPlaybackState postPlaybackState)
|
||||
{
|
||||
if (gameObject == null)
|
||||
return ScriptPlayable<ActivationControlPlayable>.Null;
|
||||
|
||||
var handle = ScriptPlayable<ActivationControlPlayable>.Create(graph);
|
||||
var playable = handle.GetBehaviour();
|
||||
playable.gameObject = gameObject;
|
||||
playable.postPlayback = postPlaybackState;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is called when the Playable play state is changed to Playables.PlayState.Playing.
|
||||
/// </summary>
|
||||
/// <param name="playable">The playable this behaviour is attached to.</param>
|
||||
/// <param name="info">The information about this frame</param>
|
||||
public override void OnBehaviourPlay(Playable playable, FrameData info)
|
||||
{
|
||||
if (gameObject == null)
|
||||
return;
|
||||
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is called when the Playable play state is changed to PlayState.Paused.
|
||||
/// </summary>
|
||||
/// <param name="playable">The playable this behaviour is attached to.</param>
|
||||
/// <param name="info">The information about this frame</param>
|
||||
public override void OnBehaviourPause(Playable playable, FrameData info)
|
||||
{
|
||||
// OnBehaviourPause can be called if the graph is stopped for a variety of reasons
|
||||
// the effectivePlayState will test if the pause is due to the clip being out of bounds
|
||||
if (gameObject != null && info.effectivePlayState == PlayState.Paused)
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is called during the ProcessFrame phase of the PlayableGraph.
|
||||
/// </summary>
|
||||
/// <param name="playable">The playable this behaviour is attached to.</param>
|
||||
/// <param name="info">A FrameData structure that contains information about the current frame context.</param>
|
||||
/// <param name="userData">unused</param>
|
||||
public override void ProcessFrame(Playable playable, FrameData info, object userData)
|
||||
{
|
||||
if (gameObject != null)// && !gameObject.activeSelf)
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is called when the PlayableGraph that owns this PlayableBehaviour starts.
|
||||
/// </summary>
|
||||
/// <param name="playable">The playable this behaviour is attached to.</param>
|
||||
public override void OnGraphStart(Playable playable)
|
||||
{
|
||||
if (gameObject != null)
|
||||
{
|
||||
if (m_InitialState == InitialState.Unset)
|
||||
m_InitialState = gameObject.activeSelf ? InitialState.Active : InitialState.Inactive;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is called when the Playable that owns the PlayableBehaviour is destroyed.
|
||||
/// </summary>
|
||||
/// <param name="playable">The playable this behaviour is attached to.</param>
|
||||
public override void OnPlayableDestroy(Playable playable)
|
||||
{
|
||||
if (gameObject == null || m_InitialState == InitialState.Unset)
|
||||
return;
|
||||
|
||||
switch (postPlayback)
|
||||
{
|
||||
case PostPlaybackState.Active:
|
||||
gameObject.SetActive(true);
|
||||
break;
|
||||
|
||||
case PostPlaybackState.Inactive:
|
||||
gameObject.SetActive(false);
|
||||
break;
|
||||
|
||||
case PostPlaybackState.Revert:
|
||||
gameObject.SetActive(m_InitialState == InitialState.Active);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d20e4e177b86a2843805dd3894f41b42
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,91 @@
|
|||
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
|
||||
{
|
||||
public BasicPlayableBehaviour() {}
|
||||
|
||||
/// <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,206 @@
|
|||
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;
|
||||
|
||||
private bool m_SyncTime = false;
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
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);
|
||||
SyncPlayState(playable.GetGraph(), 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)
|
||||
{
|
||||
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);
|
||||
director.Evaluate();
|
||||
}
|
||||
|
||||
m_SyncTime = false;
|
||||
}
|
||||
|
||||
#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 SyncPlayState(PlayableGraph graph, double playableTime)
|
||||
{
|
||||
bool expectedFinished = (playableTime >= m_AssetDuration) && director.extrapolationMode == DirectorWrapMode.None;
|
||||
if (graph.IsPlaying() && !expectedFinished)
|
||||
director.Play();
|
||||
else
|
||||
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)
|
||||
{
|
||||
if (director.extrapolationMode == DirectorWrapMode.None)
|
||||
return false;
|
||||
else if (director.extrapolationMode == DirectorWrapMode.Hold)
|
||||
expectedTime = m_AssetDuration;
|
||||
else if (m_AssetDuration > float.Epsilon) // loop
|
||||
expectedTime = expectedTime % m_AssetDuration;
|
||||
}
|
||||
|
||||
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 = 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();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue