// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. using System; 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() { // Check selection var 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) { // 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, OnPrefabCreated); } 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) { var selection = Editor.SceneEditing.Selection.Where(x => x is ActorNode).ToList().BuildNodesParents(); if (selection.Count == 0) return; if (selection.Count == 1) { var action = BreakPrefabLinkAction.Linked(((ActorNode)selection[0]).Actor); Undo.AddAction(action); } else { var actions = new IUndoAction[selection.Count]; for (int i = 0; i < selection.Count; i++) { var action = BreakPrefabLinkAction.Linked(((ActorNode)selection[i]).Actor); actions[i] = action; } Undo.AddAction(new MultiUndoAction(actions)); } } 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 FlaxException("Failed to apply the prefab. See log to learn more."); PrefabApplied?.Invoke(prefab, instance); } } }