Merge branch 'new-content-creations' of https://github.com/Menotdan/FlaxEngine into Menotdan-new-content-creations

This commit is contained in:
Wojtek Figat
2024-09-10 20:00:18 +02:00
6 changed files with 334 additions and 24 deletions

View File

@@ -0,0 +1,181 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Content.Create
{
/// <summary>
/// Prefab asset creating handler. Allows to specify base actor to use as the root.
/// </summary>
/// <seealso cref="FlaxEditor.Content.Create.CreateFileEntry" />
public class PrefabCreateEntry : CreateFileEntry
{
/// <summary>
/// The create options.
/// </summary>
public class Options
{
/// <summary>
/// The root actor.
/// </summary>
[TypeReference(typeof(FlaxEngine.Actor), nameof(IsValid))]
[Tooltip("The actor type of the root of the new Prefab.")]
public Type RootActorType = typeof(EmptyActor);
private static bool IsValid(Type type)
{
return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType;
}
}
private readonly Options _options = new Options();
/// <inheritdoc />
public override object Settings => _options;
/// <summary>
/// Initializes a new instance of the <see cref="PrefabCreateEntry"/> class.
/// </summary>
/// <param name="resultUrl">The result file url.</param>
public PrefabCreateEntry(string resultUrl)
: base("Settings", resultUrl)
{
}
/// <inheritdoc />
public override bool Create()
{
if (_options.RootActorType == null)
_options.RootActorType = typeof(EmptyActor);
ScriptType actorType = new ScriptType(_options.RootActorType);
Actor actor = null;
try
{
actor = actorType.CreateInstance() as Actor;
Object.Destroy(actor, 20.0f);
}
catch (Exception ex)
{
Editor.LogError("Failed to create prefab with root actor type: " + actorType.Name);
Editor.LogWarning(ex);
return true;
}
return PrefabManager.CreatePrefab(actor, ResultUrl, true);
}
}
/// <summary>
/// Widget asset creating handler. Allows to specify base UIControl to use as the root.
/// </summary>
/// <seealso cref="FlaxEditor.Content.Create.CreateFileEntry" />
public class WidgetCreateEntry : CreateFileEntry
{
/// <summary>
/// The create options.
/// </summary>
public class Options
{
/// <summary>
/// Which mode is used to initialize this widget.
/// </summary>
public enum WidgetMode
{
/// <summary>
/// Initialize the widget with a UICanvas.
/// </summary>
Canvas,
/// <summary>
/// Initialize the widget with a UIControl.
/// </summary>
Control
}
/// <summary>
/// The mode used to initialize the widget.
/// </summary>
[Tooltip("Whether to initialize the widget with a canvas or a control.")]
public WidgetMode WidgetInitializationMode = WidgetMode.Canvas;
bool ShowRoot => WidgetInitializationMode == WidgetMode.Control;
/// <summary>
/// The root control.
/// </summary>
[TypeReference(typeof(Control), nameof(IsValid))]
[Tooltip("The control type of the root of the new Widget's root control."), VisibleIf(nameof(ShowRoot))]
public Type RootControlType = typeof(Panel);
private static bool IsValid(Type type)
{
return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType;
}
}
private readonly Options _options = new Options();
/// <inheritdoc />
public override object Settings => _options;
/// <summary>
/// Initializes a new instance of the <see cref="WidgetCreateEntry"/> class.
/// </summary>
/// <param name="resultUrl">The result file url.</param>
public WidgetCreateEntry(string resultUrl)
: base("Settings", resultUrl)
{
}
/// <inheritdoc />
public override bool Create()
{
Actor actor = null;
if (_options.WidgetInitializationMode == Options.WidgetMode.Control)
{
if (_options.RootControlType == null)
_options.RootControlType = typeof(Control);
ScriptType controlType = new ScriptType(_options.RootControlType);
Control control = null;
try
{
control = controlType.CreateInstance() as Control;
}
catch (Exception ex)
{
Editor.LogError("Failed to create widget with root control type: " + controlType.Name);
Editor.LogWarning(ex);
return true;
}
UIControl newControl = new UIControl();
newControl.Control = control;
actor = newControl;
}
else if (_options.WidgetInitializationMode == Options.WidgetMode.Canvas)
{
actor = new UICanvas();
}
if (actor == null)
{
Editor.LogError("Failed to create widget. Final actor was null.");
return true;
}
Object.Destroy(actor, 20.0f);
return PrefabManager.CreatePrefab(actor, ResultUrl, true);
}
}
}

View File

@@ -42,6 +42,32 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override SpriteHandle DefaultThumbnail => SpriteHandle.Invalid;
private string _cachedTypeDescription = null;
/// <inheritdoc />
public override string TypeDescription
{
get
{
if (_cachedTypeDescription != null)
return _cachedTypeDescription;
Prefab prefab = FlaxEngine.Content.LoadAsync<Prefab>(ID);
if (prefab.WaitForLoaded(5000))
{
_cachedTypeDescription = "Prefab";
}
Actor root = prefab.GetDefaultInstance();
if (root is UIControl or UICanvas)
_cachedTypeDescription = "Widget";
else
_cachedTypeDescription = "Prefab";
return _cachedTypeDescription;
}
}
/// <inheritdoc />
public override bool IsOfType(Type type)
{

View File

@@ -3,6 +3,9 @@
using System;
using FlaxEditor.Content.Create;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Timeline;
using FlaxEditor.GUI.Timeline.Tracks;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
@@ -48,6 +51,63 @@ namespace FlaxEditor.Content
Editor.Instance.ContentImporting.Create(new ParticleEmitterCreateEntry(outputPath));
}
/// <inheritdoc />
public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
{
base.OnContentWindowContextMenu(menu, item);
if (item is BinaryAssetItem binaryAssetItem)
{
var button = menu.AddButton("Create Particle System", CreateParticleSystemClicked);
button.Tag = binaryAssetItem;
}
}
private void CreateParticleSystemClicked(ContextMenuButton obj)
{
var binaryAssetItem = (BinaryAssetItem)obj.Tag;
CreateParticleSystem(binaryAssetItem);
}
/// <summary>
/// Creates the particle system from the given particle emitter.
/// </summary>
/// <param name="emitterItem">The particle emitter item to use as a base for the particle system.</param>
public static void CreateParticleSystem(BinaryAssetItem emitterItem)
{
var particleSystemName = emitterItem.ShortName + " Particle System";
var particleSystemProxy = Editor.Instance.ContentDatabase.GetProxy<ParticleSystem>();
Editor.Instance.Windows.ContentWin.NewItem(particleSystemProxy, null, item => OnParticleSystemCreated(item, emitterItem), particleSystemName);
}
private static void OnParticleSystemCreated(ContentItem item, BinaryAssetItem particleItem)
{
var assetItem = (AssetItem)item;
var particleSystem = FlaxEngine.Content.LoadAsync<ParticleSystem>(assetItem.ID);
if (particleSystem == null || particleSystem.WaitForLoaded())
{
Editor.LogError("Failed to load created particle system.");
return;
}
ParticleEmitter emitter = FlaxEngine.Content.LoadAsync<ParticleEmitter>(particleItem.ID);
if (emitter == null || emitter.WaitForLoaded())
{
Editor.LogError("Failed to load base particle emitter.");
}
ParticleSystemPreview tempPreview = new ParticleSystemPreview(false);
ParticleSystemTimeline timeline = new ParticleSystemTimeline(tempPreview);
timeline.Load(particleSystem);
var track = (ParticleEmitterTrack)timeline.NewTrack(ParticleEmitterTrack.GetArchetype());
track.Asset = emitter;
track.TrackMedia.DurationFrames = timeline.DurationFrames;
track.Rename(particleItem.ShortName);
timeline.AddTrack(track);
timeline.Save(particleSystem);
}
/// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{

View File

@@ -2,6 +2,7 @@
using System;
using System.IO;
using FlaxEditor.Content.Create;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
@@ -86,30 +87,21 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{
bool resetTransform = false;
var transform = Transform.Identity;
if (!(arg is Actor actor))
{
// Create default prefab root object
actor = new EmptyActor
{
Name = "Root"
};
// Cleanup it after usage
Object.Destroy(actor, 20.0f);
Editor.Instance.ContentImporting.Create(new PrefabCreateEntry(outputPath));
return;
}
else if (actor.HasScene)
{
// Create prefab with identity transform so the actor instance on a level will have it customized
resetTransform = true;
transform = actor.LocalTransform;
actor.LocalTransform = Transform.Identity;
}
PrefabManager.CreatePrefab(actor, outputPath, true);
if (resetTransform)
actor.LocalTransform = transform;
actor.LocalTransform = transform;
}
/// <inheritdoc />
@@ -251,18 +243,8 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{
// Create prefab with UI Control
var actor = new UIControl
{
Name = Path.GetFileNameWithoutExtension(outputPath),
StaticFlags = StaticFlags.None,
};
actor.Control = new Button
{
Text = "Button",
};
PrefabManager.CreatePrefab(actor, outputPath, false);
Object.Destroy(actor, 20.0f);
Editor.Instance.ContentImporting.Create(new WidgetCreateEntry(outputPath));
return;
}
}
}

View File

@@ -2,6 +2,7 @@
using System;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
@@ -39,6 +40,57 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override Type AssetType => typeof(SkinnedModel);
/// <inheritdoc />
public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
{
base.OnContentWindowContextMenu(menu, item);
if (item is BinaryAssetItem binaryAssetItem)
{
var button = menu.AddButton("Create Animation Graph", CreateAnimationGraphClicked);
button.Tag = binaryAssetItem;
}
}
private void CreateAnimationGraphClicked(ContextMenuButton obj)
{
var binaryAssetItem = (BinaryAssetItem)obj.Tag;
CreateAnimationGraph(binaryAssetItem);
}
/// <summary>
/// Creates the animation graph from the given particle emitter.
/// </summary>
/// <param name="skinnedModelItem">The skinned model item to use as the base model for the animation graph.</param>
public static void CreateAnimationGraph(BinaryAssetItem skinnedModelItem)
{
var animationGraphName = skinnedModelItem.ShortName + " Graph";
var animationGraphProxy = Editor.Instance.ContentDatabase.GetProxy<AnimationGraph>();
Editor.Instance.Windows.ContentWin.NewItem(animationGraphProxy, null, item => OnAnimationGraphCreated(item, skinnedModelItem), animationGraphName);
}
private static void OnAnimationGraphCreated(ContentItem item, BinaryAssetItem skinnedModelItem)
{
var skinnedModel = FlaxEngine.Content.LoadAsync<SkinnedModel>(skinnedModelItem.ID);
if (skinnedModel == null || skinnedModel.WaitForLoaded())
{
Editor.LogError("Failed to load base skinned model.");
}
// Hack the animation graph window to modify the base model of the animation graph.
AnimationGraphWindow win = new AnimationGraphWindow(Editor.Instance, item as AssetItem);
win.Show();
// Ensure the window knows the asset is loaded so we can save it later.
win.Asset.WaitForLoaded();
win.Update(0); // Call Update() to refresh the loaded flag.
win.SetBaseModel(skinnedModel);
win.Surface.MarkAsEdited();
win.Save();
win.Close();
}
/// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{

View File

@@ -295,6 +295,15 @@ namespace FlaxEditor.Windows.Assets
base.SetParameter(index, value);
}
/// <summary>
/// Sets the base model of the animation graph this window is editing.
/// </summary>
/// <param name="baseModel">The new base model.</param>
public void SetBaseModel(SkinnedModel baseModel)
{
_properties.BaseModel = baseModel;
}
/// <inheritdoc />
protected override void UnlinkItem()
{