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:
///