diff --git a/Source/Editor/GUI/Timeline/TrackArchetype.cs b/Source/Editor/GUI/Timeline/TrackArchetype.cs index 8833f2c1f..798a2732c 100644 --- a/Source/Editor/GUI/Timeline/TrackArchetype.cs +++ b/Source/Editor/GUI/Timeline/TrackArchetype.cs @@ -26,6 +26,11 @@ namespace FlaxEditor.GUI.Timeline /// The loop flag. Looped tracks are doing a playback of its data in a loop. /// Loop = 2, + + /// + /// The prefab object reference flag for tracks used to animate objects in prefabs (for reusable instanced animations). + /// + PrefabObject = 4, } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs index 9ab1a0d07..42450723f 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs @@ -62,8 +62,57 @@ namespace FlaxEditor.GUI.Timeline.Tracks /// public Actor Actor { - get => FlaxEngine.Object.TryFind(ref ActorID); - set => ActorID = value?.ID ?? Guid.Empty; + get + { + if (Flags.HasFlag(TrackFlags.PrefabObject)) + { + // TODO: reuse cached actor to improve perf + foreach (var window in Editor.Instance.Windows.Windows) + { + if (window is Windows.Assets.PrefabWindow prefabWindow && prefabWindow.Graph.MainActor) + { + var actor = FindActorWithPrefabObjectID(prefabWindow.Graph.MainActor, ref ActorID); + if (actor != null) + return actor; + } + } + return null; + } + return FlaxEngine.Object.TryFind(ref ActorID); + } + set + { + if (value != null) + { + if (value.HasPrefabLink && value.Scene == null) + { + // Track with prefab object reference assigned in Editor + ActorID = value.PrefabObjectID; + Flags |= TrackFlags.PrefabObject; + } + else + { + ActorID = value.ID; + } + } + else + { + ActorID = Guid.Empty; + } + } + } + + private static Actor FindActorWithPrefabObjectID(Actor a, ref Guid id) + { + if (a.PrefabObjectID == id) + return a; + for (int i = 0; i < a.ChildrenCount; i++) + { + var e = FindActorWithPrefabObjectID(a.GetChild(i), ref id); + if (e != null) + return e; + } + return null; } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs index 53a78ea64..01c1b52f7 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs @@ -80,7 +80,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks var obj = Object; var hasObject = obj != null; - TitleTintColor = hasObject ? Color.White : Color.Red; + TitleTintColor = hasObject ? (Flags.HasFlag(TrackFlags.PrefabObject) ? Style.Current.ProgressNormal : Color.White) : Color.Red; if (hasObject != _hasObject) OnObjectExistenceChanged(obj); _hasObject = hasObject; diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h index 538e35cf3..83dd85550 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h @@ -49,6 +49,7 @@ public: None = 0, Mute = 1, Loop = 2, + PrefabObject = 4, }; /// @@ -473,3 +474,5 @@ protected: void unload(bool isReloading) override; AssetChunksFlag getChunksToPreload() const override; }; + +DECLARE_ENUM_OPERATORS(SceneAnimation::Track::Flags); diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp index 7f9a4eead..4139041c5 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp @@ -1124,6 +1124,7 @@ void SceneAnimationPlayer::Serialize(SerializeStream& stream, const void* otherO SERIALIZE(RandomStartTime); SERIALIZE(RestoreStateOnStop); SERIALIZE(UpdateMode); + SERIALIZE(UsePrefabObjects); } void SceneAnimationPlayer::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -1140,6 +1141,30 @@ void SceneAnimationPlayer::Deserialize(DeserializeStream& stream, ISerializeModi DESERIALIZE(RandomStartTime); DESERIALIZE(RestoreStateOnStop); DESERIALIZE(UpdateMode); + DESERIALIZE(UsePrefabObjects); + + if (UsePrefabObjects && Animation && !Animation->WaitForLoaded()) + { + // When loading from prefab automatically map objects from prefab instance into animation tracks with object references + for (auto& track : Animation->Tracks) + { + if (track.Disabled || !(track.Flag & SceneAnimation::Track::Flags::PrefabObject)) + continue; + switch (track.Type) + { + case SceneAnimation::Track::Types::Actor: + case SceneAnimation::Track::Types::Script: + case SceneAnimation::Track::Types::CameraCut: + { + const auto trackData = track.GetData(); + Guid id; + if (modifier->IdsMapping.TryGet(trackData->ID, id)) + _objectsMapping[trackData->ID] = id; + break; + } + } + } + } } void SceneAnimationPlayer::Collect(RenderContext& renderContext) diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h index 6d38e743f..aeb1a33cd 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h @@ -128,6 +128,12 @@ public: API_FIELD(Attributes="EditorDisplay(\"Scene Animation\"), EditorOrder(80), DefaultValue(UpdateModes.EveryUpdate)") UpdateModes UpdateMode = UpdateModes::EveryUpdate; + /// + /// Determines whether the scene animation should automatically map prefab objects from scene animation into prefab instances. Useful for reusable animations to automatically link prefab objects. + /// + API_FIELD(Attributes="EditorDisplay(\"Scene Animation\"), EditorOrder(100)") + bool UsePrefabObjects = false; + public: ///