| using System; |
| using System.Collections.Generic; |
| using System.Linq; |
| using UnityEngine.Playables; |
|
|
| namespace UnityEngine.Timeline |
| { |
| |
| |
| |
| [Serializable] |
| [NotKeyable] |
| public class ControlPlayableAsset : PlayableAsset, IPropertyPreview, ITimelineClipAsset |
| { |
| const int k_MaxRandInt = 10000; |
| static readonly List<PlayableDirector> k_EmptyDirectorsList = new List<PlayableDirector>(0); |
| static readonly List<ParticleSystem> k_EmptyParticlesList = new List<ParticleSystem>(0); |
| static readonly HashSet<ParticleSystem> s_SubEmitterCollector = new HashSet<ParticleSystem>(); |
|
|
| |
| |
| |
| [SerializeField] public ExposedReference<GameObject> sourceGameObject; |
|
|
| |
| |
| |
| [SerializeField] public GameObject prefabGameObject; |
|
|
| |
| |
| |
| [SerializeField] public bool updateParticle = true; |
|
|
| |
| |
| |
| |
| |
| |
| [SerializeField] public uint particleRandomSeed; |
|
|
| |
| |
| |
| [SerializeField] public bool updateDirector = true; |
|
|
| |
| |
| |
| [SerializeField] public bool updateITimeControl = true; |
|
|
| |
| |
| |
| [SerializeField] public bool searchHierarchy = false; |
|
|
| |
| |
| |
| [SerializeField] public bool active = true; |
|
|
| |
| |
| |
| [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>(); |
|
|
| |
| internal bool controllingDirectors { get; private set; } |
| internal bool controllingParticles { get; private set; } |
|
|
| |
| |
| |
| public void OnEnable() |
| { |
| |
| if (particleRandomSeed == 0) |
| particleRandomSeed = (uint)Random.Range(1, k_MaxRandInt); |
| } |
|
|
| |
| |
| |
| public override double duration { get { return m_Duration; } } |
|
|
| |
| |
| |
| public ClipCaps clipCaps |
| { |
| get { return ClipCaps.ClipIn | ClipCaps.SpeedMultiplier | (m_SupportLoop ? ClipCaps.Looping : ClipCaps.None); } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public override Playable CreatePlayable(PlayableGraph graph, GameObject go) |
| { |
| |
| if (prefabGameObject != null) |
| { |
| if (s_CreatedPrefabs.Contains(prefabGameObject)) |
| { |
| Debug.LogWarningFormat("Control Track Clip ({0}) is causing a prefab to instantiate itself recursively. Aborting further instances.", name); |
| return Playable.Create(graph); |
| } |
| s_CreatedPrefabs.Add(prefabGameObject); |
| } |
|
|
| Playable root = Playable.Null; |
| var playables = new List<Playable>(); |
|
|
| GameObject sourceObject = sourceGameObject.Resolve(graph.GetResolver()); |
| if (prefabGameObject != null) |
| { |
| Transform parenTransform = sourceObject != null ? sourceObject.transform : null; |
| var controlPlayable = PrefabControlPlayable.Create(graph, prefabGameObject, parenTransform); |
|
|
| sourceObject = controlPlayable.GetBehaviour().prefabInstance; |
| playables.Add(controlPlayable); |
| } |
|
|
| m_Duration = PlayableBinding.DefaultDuration; |
| m_SupportLoop = false; |
|
|
| controllingParticles = false; |
| controllingDirectors = false; |
|
|
| if (sourceObject != null) |
| { |
| var directors = updateDirector ? GetComponent<PlayableDirector>(sourceObject) : k_EmptyDirectorsList; |
| var particleSystems = updateParticle ? GetControllableParticleSystems(sourceObject) : k_EmptyParticlesList; |
|
|
| |
| |
| UpdateDurationAndLoopFlag(directors, particleSystems); |
|
|
| var director = go.GetComponent<PlayableDirector>(); |
| if (director != null) |
| m_ControlDirectorAsset = director.playableAsset; |
|
|
| if (go == sourceObject && prefabGameObject == null) |
| { |
| Debug.LogWarningFormat("Control Playable ({0}) is referencing the same PlayableDirector component than the one in which it is playing.", name); |
| active = false; |
| if (!searchHierarchy) |
| updateDirector = false; |
| } |
|
|
| if (active) |
| CreateActivationPlayable(sourceObject, graph, playables); |
|
|
| if (updateDirector) |
| SearchHierarchyAndConnectDirector(directors, graph, playables, prefabGameObject != null); |
|
|
| if (updateParticle) |
| SearchHierarchyAndConnectParticleSystem(particleSystems, graph, playables); |
|
|
| if (updateITimeControl) |
| SearchHierarchyAndConnectControlableScripts(GetControlableScripts(sourceObject), graph, playables); |
|
|
| |
| root = ConnectPlayablesToMixer(graph, playables); |
| } |
|
|
| if (prefabGameObject != null) |
| s_CreatedPrefabs.Remove(prefabGameObject); |
|
|
| if (!root.IsValid()) |
| root = Playable.Create(graph); |
|
|
| return root; |
| } |
|
|
| static Playable ConnectPlayablesToMixer(PlayableGraph graph, List<Playable> playables) |
| { |
| var mixer = Playable.Create(graph, playables.Count); |
|
|
| for (int i = 0; i != playables.Count; ++i) |
| { |
| ConnectMixerAndPlayable(graph, mixer, playables[i], i); |
| } |
|
|
| mixer.SetPropagateSetTime(true); |
|
|
| return mixer; |
| } |
|
|
| void CreateActivationPlayable(GameObject root, PlayableGraph graph, |
| List<Playable> outplayables) |
| { |
| var activation = ActivationControlPlayable.Create(graph, root, postPlayback); |
| if (activation.IsValid()) |
| outplayables.Add(activation); |
| } |
|
|
| void SearchHierarchyAndConnectParticleSystem(IEnumerable<ParticleSystem> particleSystems, PlayableGraph graph, |
| List<Playable> outplayables) |
| { |
| foreach (var particleSystem in particleSystems) |
| { |
| if (particleSystem != null) |
| { |
| controllingParticles = true; |
| outplayables.Add(ParticleControlPlayable.Create(graph, particleSystem, particleRandomSeed)); |
| } |
| } |
| } |
|
|
| void SearchHierarchyAndConnectDirector(IEnumerable<PlayableDirector> directors, PlayableGraph graph, |
| List<Playable> outplayables, bool disableSelfReferences) |
| { |
| foreach (var director in directors) |
| { |
| if (director != null) |
| { |
| if (director.playableAsset != m_ControlDirectorAsset) |
| { |
| outplayables.Add(DirectorControlPlayable.Create(graph, director)); |
| controllingDirectors = true; |
| } |
| |
| else if (disableSelfReferences) |
| { |
| director.enabled = false; |
| } |
| } |
| } |
| } |
|
|
| static void SearchHierarchyAndConnectControlableScripts(IEnumerable<MonoBehaviour> controlableScripts, PlayableGraph graph, List<Playable> outplayables) |
| { |
| foreach (var script in controlableScripts) |
| { |
| outplayables.Add(TimeControlPlayable.Create(graph, (ITimeControl)script)); |
| } |
| } |
|
|
| static void ConnectMixerAndPlayable(PlayableGraph graph, Playable mixer, Playable playable, |
| int portIndex) |
| { |
| graph.Connect(playable, 0, mixer, portIndex); |
| mixer.SetInputWeight(playable, 1.0f); |
| } |
|
|
| internal IList<T> GetComponent<T>(GameObject gameObject) |
| { |
| var components = new List<T>(); |
| if (gameObject != null) |
| { |
| if (searchHierarchy) |
| { |
| gameObject.GetComponentsInChildren<T>(true, components); |
| } |
| else |
| { |
| gameObject.GetComponents<T>(components); |
| } |
| } |
| return components; |
| } |
|
|
| internal static IEnumerable<MonoBehaviour> GetControlableScripts(GameObject root) |
| { |
| if (root == null) |
| yield break; |
|
|
| foreach (var script in root.GetComponentsInChildren<MonoBehaviour>()) |
| { |
| if (script is ITimeControl) |
| yield return script; |
| } |
| } |
|
|
| internal void UpdateDurationAndLoopFlag(IList<PlayableDirector> directors, IList<ParticleSystem> particleSystems) |
| { |
| if (directors.Count == 0 && particleSystems.Count == 0) |
| return; |
|
|
| const double invalidDuration = double.NegativeInfinity; |
|
|
| var maxDuration = invalidDuration; |
| var supportsLoop = false; |
|
|
| foreach (var director in directors) |
| { |
| if (director.playableAsset != null) |
| { |
| var assetDuration = director.playableAsset.duration; |
|
|
| if (director.playableAsset is TimelineAsset && assetDuration > 0.0) |
| |
| assetDuration = (double)((DiscreteTime)assetDuration).OneTickAfter(); |
|
|
| maxDuration = Math.Max(maxDuration, assetDuration); |
| supportsLoop = supportsLoop || director.extrapolationMode == DirectorWrapMode.Loop; |
| } |
| } |
|
|
| foreach (var particleSystem in particleSystems) |
| { |
| maxDuration = Math.Max(maxDuration, particleSystem.main.duration); |
| supportsLoop = supportsLoop || particleSystem.main.loop; |
| } |
|
|
| m_Duration = double.IsNegativeInfinity(maxDuration) ? PlayableBinding.DefaultDuration : maxDuration; |
| m_SupportLoop = supportsLoop; |
| } |
|
|
| IList<ParticleSystem> GetControllableParticleSystems(GameObject go) |
| { |
| var roots = new List<ParticleSystem>(); |
|
|
| |
| |
| |
|
|
| if (searchHierarchy || go.GetComponent<ParticleSystem>() != null) |
| { |
| GetControllableParticleSystems(go.transform, roots, s_SubEmitterCollector); |
| s_SubEmitterCollector.Clear(); |
| } |
|
|
| return roots; |
| } |
|
|
| static void GetControllableParticleSystems(Transform t, ICollection<ParticleSystem> roots, HashSet<ParticleSystem> subEmitters) |
| { |
| var ps = t.GetComponent<ParticleSystem>(); |
| if (ps != null) |
| { |
| if (!subEmitters.Contains(ps)) |
| { |
| roots.Add(ps); |
| CacheSubEmitters(ps, subEmitters); |
| } |
| } |
|
|
| for (int i = 0; i < t.childCount; ++i) |
| { |
| GetControllableParticleSystems(t.GetChild(i), roots, subEmitters); |
| } |
| } |
|
|
| static void CacheSubEmitters(ParticleSystem ps, HashSet<ParticleSystem> subEmitters) |
| { |
| if (ps == null) |
| return; |
|
|
| for (int i = 0; i < ps.subEmitters.subEmittersCount; i++) |
| { |
| subEmitters.Add(ps.subEmitters.GetSubEmitterSystem(i)); |
| |
| } |
| } |
|
|
| |
| public void GatherProperties(PlayableDirector director, IPropertyCollector driver) |
| { |
| |
| if (director == null) |
| return; |
|
|
| |
| if (s_ProcessedDirectors.Contains(director)) |
| return; |
| s_ProcessedDirectors.Add(director); |
|
|
| var gameObject = sourceGameObject.Resolve(director); |
| if (gameObject != null) |
| { |
| if (updateParticle) |
| PreviewParticles(driver, gameObject.GetComponentsInChildren<ParticleSystem>(true)); |
|
|
| if (active) |
| PreviewActivation(driver, new[] { gameObject }); |
|
|
| if (updateITimeControl) |
| PreviewTimeControl(driver, director, GetControlableScripts(gameObject)); |
|
|
| if (updateDirector) |
| PreviewDirectors(driver, GetComponent<PlayableDirector>(gameObject)); |
| } |
| s_ProcessedDirectors.Remove(director); |
| } |
|
|
| internal static void PreviewParticles(IPropertyCollector driver, IEnumerable<ParticleSystem> particles) |
| { |
| foreach (var ps in particles) |
| { |
| driver.AddFromName<ParticleSystem>(ps.gameObject, "randomSeed"); |
| driver.AddFromName<ParticleSystem>(ps.gameObject, "autoRandomSeed"); |
| } |
| } |
|
|
| internal static void PreviewActivation(IPropertyCollector driver, IEnumerable<GameObject> objects) |
| { |
| foreach (var gameObject in objects) |
| driver.AddFromName(gameObject, "m_IsActive"); |
| } |
|
|
| internal static void PreviewTimeControl(IPropertyCollector driver, PlayableDirector director, IEnumerable<MonoBehaviour> scripts) |
| { |
| foreach (var script in scripts) |
| { |
| var propertyPreview = script as IPropertyPreview; |
| if (propertyPreview != null) |
| propertyPreview.GatherProperties(director, driver); |
| else |
| driver.AddFromComponent(script.gameObject, script); |
| } |
| } |
|
|
| internal static void PreviewDirectors(IPropertyCollector driver, IEnumerable<PlayableDirector> directors) |
| { |
| foreach (var childDirector in directors) |
| { |
| if (childDirector == null) |
| continue; |
|
|
| var timeline = childDirector.playableAsset as TimelineAsset; |
| if (timeline == null) |
| continue; |
|
|
| timeline.GatherProperties(childDirector, driver); |
| } |
| } |
| } |
| } |
|
|