diff options
Diffstat (limited to 'Library/PackageCache/com.unity.timeline@1.2.13/Runtime/Control/ControlPlayableAsset.cs')
| -rw-r--r-- | Library/PackageCache/com.unity.timeline@1.2.13/Runtime/Control/ControlPlayableAsset.cs | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/Control/ControlPlayableAsset.cs b/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/Control/ControlPlayableAsset.cs new file mode 100644 index 0000000..fd806f6 --- /dev/null +++ b/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/Control/ControlPlayableAsset.cs @@ -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); + } + } +} |
