// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.SceneGraph; using FlaxEditor.Windows; using FlaxEngine; namespace FlaxEditor.Modules { /// /// Prefabs management module. /// /// public sealed class PrefabsModule : EditorModule { /// /// Occurs before prefab asset creating. Argument is a target actor. /// public event Action PrefabCreating; /// /// Occurs after prefab asset creating. Arguments is created prefab asset item. /// public event Action PrefabCreated; /// /// Occurs before applying changes to the prefab. Arguments are prefab and the target instance. /// public event Action PrefabApplying; /// /// Occurs after applying changes to the prefab. Arguments are prefab and the target instance. /// public event Action PrefabApplied; internal PrefabsModule(Editor editor) : base(editor) { } /// /// Starts the creating prefab for the selected actor by showing the new item creation dialog in . /// /// /// To create prefab manually (from code) use method. /// public void CreatePrefab() { CreatePrefab(Editor.SceneEditing.Selection); } /// /// Starts the creating prefab for the selected actor by showing the new item creation dialog in . /// /// /// To create prefab manually (from code) use method. /// /// The scene selection to use. /// The prefab window that creates it. public void CreatePrefab(List selection, Windows.Assets.PrefabWindow prefabWindow = null) { if (selection == null) selection = Editor.SceneEditing.Selection; if (selection.Count == 1 && selection[0] is ActorNode actorNode && actorNode.CanCreatePrefab) { CreatePrefab(actorNode.Actor, true, prefabWindow); } } /// /// Starts the creating prefab for the given actor by showing the new item creation dialog in . User can specify the new asset name. /// /// /// To create prefab manually (from code) use method. /// /// The root prefab actor. public void CreatePrefab(Actor actor) { CreatePrefab(actor, true); } /// /// Starts the creating prefab for the given actor by showing the new item creation dialog in . /// /// The root prefab actor. /// Allow renaming or not /// The prefab window that creates it. public void CreatePrefab(Actor actor, bool rename, Windows.Assets.PrefabWindow prefabWindow = null) { // Skip in invalid states if (!Editor.StateMachine.CurrentState.CanEditContent) return; // Skip if cannot create assets in the given location if (!Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets) return; PrefabCreating?.Invoke(actor); var proxy = Editor.ContentDatabase.GetProxy(); Editor.Windows.ContentWin.NewItem(proxy, actor, contentItem => OnPrefabCreated(contentItem, actor, prefabWindow), actor.Name, rename); } private void OnPrefabCreated(ContentItem contentItem, Actor actor, Windows.Assets.PrefabWindow prefabWindow) { if (contentItem is PrefabItem prefabItem) { PrefabCreated?.Invoke(prefabItem); } Undo undo = null; if (prefabWindow != null) { prefabWindow.MarkAsEdited(); undo = prefabWindow.Undo; } else { // Skip in invalid states if (!Editor.StateMachine.CurrentState.CanEditScene) return; undo = Editor.Undo; Editor.Scene.MarkSceneEdited(actor.Scene); } // Record undo for prefab creating (backend links the target instance with the prefab) if (undo.Enabled) { if (!actor) return; var actorsList = new List(); Utilities.Utils.GetActorsTree(actorsList, actor); var actions = new IUndoAction[actorsList.Count]; for (int i = 0; i < actorsList.Count; i++) actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]); undo.AddAction(new MultiUndoAction(actions)); } Editor.Windows.PropertiesWin.Presenter.BuildLayout(); if (prefabWindow != null) prefabWindow.Presenter.BuildLayout(); } /// /// Breaks any prefab links for the selected objects. Supports undo/redo. /// public void BreakLinks() { // Skip in invalid states if (!Editor.StateMachine.CurrentState.CanEditScene) return; // Get valid objects (the top ones, C++ backend will process the child objects) var selection = Editor.SceneEditing.Selection.Where(x => x is ActorNode actorNode && actorNode.HasPrefabLink).ToList().BuildNodesParents(); if (selection.Count == 0) return; // Perform action if (Editor.StateMachine.CurrentState.CanUseUndoRedo) { if (selection.Count == 1) { var action = BreakPrefabLinkAction.Break(((ActorNode)selection[0]).Actor); Undo.AddAction(action); action.Do(); } else { var actions = new IUndoAction[selection.Count]; for (int i = 0; i < selection.Count; i++) { var action = BreakPrefabLinkAction.Break(((ActorNode)selection[i]).Actor); actions[i] = action; action.Do(); } Undo.AddAction(new MultiUndoAction(actions)); } } else { for (int i = 0; i < selection.Count; i++) { ((ActorNode)selection[i]).Actor.BreakPrefabLink(); } } } /// /// Selects in Content Window the prefab asset used by the selected objects. /// public void SelectPrefab() { // Get valid objects (the top ones, C++ backend will process the child objects) var selection = Editor.SceneEditing.Selection.Where(x => x is ActorNode actorNode && actorNode.HasPrefabLink).ToList().BuildNodesParents(); if (selection.Count == 0) return; var prefabId = ((ActorNode)selection[0]).Actor.PrefabID; var prefab = FlaxEngine.Content.LoadAsync(prefabId); Editor.Windows.ContentWin.Select(prefab); } /// /// Applies the difference from the prefab object instance, saves the changes and synchronizes them with the active instances of the prefab asset. /// /// /// Applies all the changes from not only the given actor instance but all actors created within that prefab instance. /// /// The modified instance. public void ApplyAll(Actor instance) { // Validate input if (!instance) throw new ArgumentNullException(nameof(instance)); if (!instance.HasPrefabLink || instance.PrefabID == Guid.Empty) throw new ArgumentException("The modified actor instance has missing prefab link."); var prefab = FlaxEngine.Content.LoadAsync(instance.PrefabID); if (prefab == null) throw new ArgumentException("Missing prefab to apply."); PrefabApplying?.Invoke(prefab, instance); // When applying changes to prefab from actor in level ignore it's root transformation (see ActorEditor.ProcessDiff) var originalTransform = instance.LocalTransform; if (instance.IsPrefabRoot && instance.Scene != null) instance.LocalTransform = prefab.GetDefaultInstance().Transform; // Call backend var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance)); instance.LocalTransform = originalTransform; if (failed) throw new Exception("Failed to apply the prefab. See log to learn more."); PrefabApplied?.Invoke(prefab, instance); } } }