// 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.ClearItemsSearch();
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.HasScene)
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);
}
}
}