// Copyright (c) 2012-2023 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; /// /// Locally cached actor for prefab creation. /// private Actor _prefabCreationActor; 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. public void CreatePrefab(List selection) { if (selection == null) selection = Editor.SceneEditing.Selection; if (selection.Count == 1 && selection[0] is ActorNode actorNode && actorNode.CanCreatePrefab) { CreatePrefab(actorNode.Actor); } } /// /// 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 public void CreatePrefab(Actor actor, bool rename) { // 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(); _prefabCreationActor = actor; Editor.Windows.ContentWin.NewItem(proxy, actor, OnPrefabCreated, actor.Name, rename); } /// /// Open a prefab in a Prefab Editor /// /// Whether the prefab was successfully opened in a Prefab Editor public bool OpenPrefab() { var selection = Editor.SceneEditing.Selection.Where(x => x is ActorNode actorNode && actorNode.HasPrefabLink).ToList().BuildNodesParents(); if (selection.Count == 0 || !((ActorNode)selection[0]).Actor.HasPrefabLink) return false; var prefabId = ((ActorNode)selection[0]).Actor.PrefabID; var item = Editor.Instance.ContentDatabase.Find(prefabId); if(item != null) { Editor.Instance.ContentEditing.Open(item); return true; } return false; } private void OnPrefabCreated(ContentItem contentItem) { if (contentItem is PrefabItem prefabItem) { PrefabCreated?.Invoke(prefabItem); } // Skip in invalid states if (!Editor.StateMachine.CurrentState.CanEditScene) return; // Record undo for prefab creating (backend links the target instance with the prefab) if (Editor.Undo.Enabled) { if (!_prefabCreationActor) return; var actorsList = new List(); Utilities.Utils.GetActorsTree(actorsList, _prefabCreationActor); var actions = new IUndoAction[actorsList.Count]; for (int i = 0; i < actorsList.Count; i++) { var action = BreakPrefabLinkAction.Linked(actorsList[i]); actions[i] = action; } Undo.AddAction(new MultiUndoAction(actions)); _prefabCreationActor = null; } Editor.Instance.Windows.PropertiesWin.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); // Call backend if (PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance))) throw new Exception("Failed to apply the prefab. See log to learn more."); PrefabApplied?.Invoke(prefab, instance); } } }