using System;
using System.Collections.Generic;
using UnityEngine.Playables;
namespace UnityEngine.Timeline
{
///
/// Playable Asset that generates playables for controlling time-related elements on a GameObject.
///
[Serializable]
[NotKeyable]
public class ControlPlayableAsset : PlayableAsset, IPropertyPreview, ITimelineClipAsset
{
const int k_MaxRandInt = 10000;
static readonly List k_EmptyDirectorsList = new List(0);
static readonly List k_EmptyParticlesList = new List(0);
///
/// GameObject in the scene to control, or the parent of the instantiated prefab.
///
[SerializeField] public ExposedReference sourceGameObject;
///
/// Prefab object that will be instantiated.
///
[SerializeField] public GameObject prefabGameObject;
///
/// Indicates whether Particle Systems will be controlled.
///
[SerializeField] public bool updateParticle = true;
///
/// Random seed to supply particle systems that are set to use autoRandomSeed
///
///
/// This is used to maintain determinism when playing back in timeline. Sub emitters will be assigned incrementing random seeds to maintain determinism and distinction.
///
[SerializeField] public uint particleRandomSeed;
///
/// Indicates whether playableDirectors are controlled.
///
[SerializeField] public bool updateDirector = true;
///
/// Indicates whether Monobehaviours implementing ITimeControl will be controlled.
///
[SerializeField] public bool updateITimeControl = true;
///
/// Indicates whether to search the entire hierarchy for controllable components.
///
[SerializeField] public bool searchHierarchy = true;
///
/// Indicate whether GameObject activation is controlled
///
[SerializeField] public bool active = true;
///
/// Indicates the active state of the GameObject when Timeline is stopped.
///
[SerializeField] public ActivationControlPlayable.PostPlaybackState postPlayback = ActivationControlPlayable.PostPlaybackState.Revert;
PlayableAsset m_ControlDirectorAsset;
double m_Duration = PlayableBinding.DefaultDuration;
bool m_SupportLoop;
private static HashSet s_ProcessedDirectors = new HashSet();
private static HashSet s_CreatedPrefabs = new HashSet();
// does the last instance created control directors and/or particles
internal bool controllingDirectors { get; private set; }
internal bool controllingParticles { get; private set; }
///
/// This function is called when the object is loaded.
///
public void OnEnable()
{
// can't be set in a constructor
if (particleRandomSeed == 0)
particleRandomSeed = (uint)Random.Range(1, k_MaxRandInt);
}
///
/// Returns the duration in seconds needed to play the underlying director or particle system exactly once.
///
public override double duration { get { return m_Duration; } }
///
/// Returns the capabilities of TimelineClips that contain a ControlPlayableAsset
///
public ClipCaps clipCaps
{
get { return ClipCaps.ClipIn | ClipCaps.SpeedMultiplier | (m_SupportLoop ? ClipCaps.Looping : ClipCaps.None); }
}
///
/// Creates the root of a Playable subgraph to control the contents of the game object.
///
/// PlayableGraph that will own the playable
/// The GameObject that triggered the graph build
/// The root playable of the subgraph
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();
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(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();
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 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 outplayables)
{
var activation = ActivationControlPlayable.Create(graph, root, postPlayback);
if (activation.IsValid())
outplayables.Add(activation);
}
void SearchHiearchyAndConnectParticleSystem(IEnumerable particleSystems, PlayableGraph graph,
List outplayables)
{
foreach (var particleSystem in particleSystems)
{
if (particleSystem != null)
{
controllingParticles = true;
outplayables.Add(ParticleControlPlayable.Create(graph, particleSystem, particleRandomSeed));
}
}
}
void SearchHierarchyAndConnectDirector(IEnumerable directors, PlayableGraph graph,
List 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 controlableScripts, PlayableGraph graph, List 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 GetComponent(GameObject gameObject)
{
var components = new List();
if (gameObject != null)
{
if (searchHierarchy)
{
gameObject.GetComponentsInChildren(true, components);
}
else
{
gameObject.GetComponents(components);
}
}
return components;
}
static IEnumerable GetControlableScripts(GameObject root)
{
if (root == null)
yield break;
foreach (var script in root.GetComponentsInChildren())
{
if (script is ITimeControl)
yield return script;
}
}
internal void UpdateDurationAndLoopFlag(IList directors, IList 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 GetParticleSystemRoots(GameObject go)
{
if (searchHierarchy)
{
// We only want the parent systems as they will handle all the child systems.
var roots = new List();
GetParticleSystemRoots(go.transform, roots);
return roots;
}
return GetComponent(go);
}
static void GetParticleSystemRoots(Transform t, ICollection roots)
{
var ps = t.GetComponent();
if (ps != null)
{
// its a root
roots.Add(ps);
return;
}
for (int i = 0; i < t.childCount; ++i)
{
GetParticleSystemRoots(t.GetChild(i), roots);
}
}
///
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(true))
{
driver.AddFromName(ps.gameObject, "randomSeed");
driver.AddFromName(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(gameObject))
{
if (childDirector == null)
continue;
var timeline = childDirector.playableAsset as TimelineAsset;
if (timeline == null)
continue;
timeline.GatherProperties(childDirector, driver);
}
}
}
s_ProcessedDirectors.Remove(director);
}
}
}