Add **Animation Events**
This commit is contained in:
@@ -52,7 +52,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// </summary>
|
||||
/// <param name="undo">The undo/redo to use for the history actions recording. Optional, can be null to disable undo support.</param>
|
||||
public AnimationTimeline(FlaxEditor.Undo undo)
|
||||
: base(PlaybackButtons.Play | PlaybackButtons.Stop, undo, false, false)
|
||||
: base(PlaybackButtons.Play | PlaybackButtons.Stop, undo, false, true)
|
||||
{
|
||||
PlaybackState = PlaybackStates.Seeking;
|
||||
ShowPreviewValues = false;
|
||||
@@ -61,6 +61,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
// Setup track types
|
||||
TrackArchetypes.Add(AnimationChannelTrack.GetArchetype());
|
||||
TrackArchetypes.Add(AnimationChannelDataTrack.GetArchetype());
|
||||
TrackArchetypes.Add(AnimationEventTrack.GetArchetype());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
283
Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
Normal file
283
Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
{
|
||||
/// <summary>
|
||||
/// The timeline media for <see cref="AnimEvent"/> and <see cref="AnimContinuousEvent"/>.
|
||||
/// </summary>
|
||||
public sealed class AnimationEventMedia : Media
|
||||
{
|
||||
private sealed class ProxyEditor : SyncPointEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<object> UndoObjects => Values;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
var instance = (AnimEvent)Values[0];
|
||||
var scriptType = TypeUtils.GetObjectType(instance);
|
||||
var editor = CustomEditorsUtil.CreateEditor(scriptType, false);
|
||||
layout.Object(Values, editor);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Proxy : ProxyBase<AnimationEventTrack, AnimationEventMedia>
|
||||
{
|
||||
[EditorDisplay("General", EditorDisplayAttribute.InlineStyle), CustomEditor(typeof(ProxyEditor))]
|
||||
public AnimEvent Event
|
||||
{
|
||||
get => Media.Instance;
|
||||
set => Media.Instance = value;
|
||||
}
|
||||
|
||||
public Proxy(AnimationEventTrack track, AnimationEventMedia media)
|
||||
: base(track, media)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isRegisteredForScriptsReload;
|
||||
private string _instanceTypeName;
|
||||
private byte[] _instanceData;
|
||||
|
||||
/// <summary>
|
||||
/// The event type.
|
||||
/// </summary>
|
||||
public ScriptType Type;
|
||||
|
||||
/// <summary>
|
||||
/// The event instance.
|
||||
/// </summary>
|
||||
public AnimEvent Instance;
|
||||
|
||||
/// <summary>
|
||||
/// True if event is continuous (with duration), not a single frame.
|
||||
/// </summary>
|
||||
public bool IsContinuous;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AnimationEventMedia"/> class.
|
||||
/// </summary>
|
||||
public AnimationEventMedia()
|
||||
{
|
||||
PropertiesEditObject = new Proxy(null, this);
|
||||
}
|
||||
|
||||
private void OnScriptsReloadBegin()
|
||||
{
|
||||
if (Instance)
|
||||
{
|
||||
_instanceTypeName = Type.TypeName;
|
||||
Type = ScriptType.Null;
|
||||
_instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(Instance);
|
||||
Object.Destroy(ref Instance);
|
||||
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScriptsReloadEnd()
|
||||
{
|
||||
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
|
||||
Type = TypeUtils.GetType(_instanceTypeName);
|
||||
if (Type == ScriptType.Null)
|
||||
{
|
||||
Editor.LogError("Missing anim event type " + _instanceTypeName);
|
||||
return;
|
||||
}
|
||||
Instance = (AnimEvent)Type.CreateInstance();
|
||||
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, _instanceData, Globals.EngineBuildNumber);
|
||||
_instanceData = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes track for the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
public void Init(ScriptType type)
|
||||
{
|
||||
Type = type;
|
||||
IsContinuous = new ScriptType(typeof(AnimContinuousEvent)).IsAssignableFrom(type);
|
||||
CanDelete = true;
|
||||
CanSplit = IsContinuous;
|
||||
CanResize = IsContinuous;
|
||||
TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(type);
|
||||
Instance = (AnimEvent)type.CreateInstance();
|
||||
BackgroundColor = Instance.Color;
|
||||
if (!_isRegisteredForScriptsReload)
|
||||
{
|
||||
_isRegisteredForScriptsReload = true;
|
||||
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDurationFramesChanged()
|
||||
{
|
||||
if (Type != ScriptType.Null)
|
||||
DurationFrames = IsContinuous ? Mathf.Max(DurationFrames, 2) : 1;
|
||||
|
||||
base.OnDurationFramesChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Type = ScriptType.Null;
|
||||
Object.Destroy(ref Instance);
|
||||
if (_isRegisteredForScriptsReload)
|
||||
{
|
||||
_isRegisteredForScriptsReload = false;
|
||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
}
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The timeline track for <see cref="AnimEvent"/> and <see cref="AnimContinuousEvent"/>.
|
||||
/// </summary>
|
||||
public sealed class AnimationEventTrack : Track
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the archetype.
|
||||
/// </summary>
|
||||
/// <returns>The archetype.</returns>
|
||||
public static TrackArchetype GetArchetype()
|
||||
{
|
||||
return new TrackArchetype
|
||||
{
|
||||
TypeId = 19,
|
||||
Name = "Animation Event",
|
||||
Create = options => new AnimationEventTrack(ref options),
|
||||
Load = LoadTrack,
|
||||
Save = SaveTrack,
|
||||
};
|
||||
}
|
||||
|
||||
private static void LoadTrack(int version, Track track, BinaryReader stream)
|
||||
{
|
||||
var e = (AnimationEventTrack)track;
|
||||
var count = stream.ReadInt32();
|
||||
while (e.Media.Count > count)
|
||||
e.RemoveMedia(e.Media.Last());
|
||||
while (e.Media.Count < count)
|
||||
e.AddMedia(new AnimationEventMedia());
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var m = (AnimationEventMedia)e.Media[i];
|
||||
m.StartFrame = (int)stream.ReadSingle();
|
||||
m.DurationFrames = (int)stream.ReadSingle();
|
||||
var typeName = stream.ReadStrAnsi(13);
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
if (type == ScriptType.Null)
|
||||
throw new Exception($"Unknown type {typeName}.");
|
||||
m.Init(type);
|
||||
stream.ReadJson(m.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveTrack(Track track, BinaryWriter stream)
|
||||
{
|
||||
var e = (AnimationEventTrack)track;
|
||||
var count = e.Media.Count;
|
||||
stream.Write(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var m = (AnimationEventMedia)e.Media[i];
|
||||
stream.Write((float)m.StartFrame);
|
||||
stream.Write((float)m.DurationFrames);
|
||||
stream.WriteStrAnsi(m.Type.TypeName, 13);
|
||||
stream.WriteJson(m.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnimationEventTrack(ref TrackCreateOptions options)
|
||||
: base(ref options)
|
||||
{
|
||||
// Add button
|
||||
const float buttonSize = 14;
|
||||
var addButton = new Button
|
||||
{
|
||||
Text = "+",
|
||||
TooltipText = "Add events",
|
||||
AutoFocus = true,
|
||||
AnchorPreset = AnchorPresets.MiddleRight,
|
||||
IsScrollable = false,
|
||||
Offsets = new Margin(-buttonSize - 2 + _muteCheckbox.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize),
|
||||
Parent = this,
|
||||
};
|
||||
addButton.ButtonClicked += OnAddButtonClicked;
|
||||
}
|
||||
|
||||
private void OnAddButtonClicked(Button button)
|
||||
{
|
||||
var cm = new ContextMenu.ContextMenu();
|
||||
OnContextMenu(cm);
|
||||
cm.Show(button.Parent, button.BottomLeft);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time)
|
||||
{
|
||||
base.OnTimelineContextMenu(menu, time);
|
||||
|
||||
AddContextMenu(menu, time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnContextMenu(ContextMenu.ContextMenu menu)
|
||||
{
|
||||
base.OnContextMenu(menu);
|
||||
|
||||
menu.AddSeparator();
|
||||
AddContextMenu(menu, 0.0f);
|
||||
}
|
||||
|
||||
private void AddContextMenu(ContextMenu.ContextMenu menu, float time)
|
||||
{
|
||||
var addEvent = menu.AddChildMenu("Add Anim Event");
|
||||
var addContinuousEvent = menu.AddChildMenu("Add Anim Continuous Event");
|
||||
var animEventTypes = Editor.Instance.CodeEditing.All.Get().Where(x => new ScriptType(typeof(AnimEvent)).IsAssignableFrom(x));
|
||||
foreach (var type in animEventTypes)
|
||||
{
|
||||
if (type.IsAbstract || !type.CanCreateInstance)
|
||||
continue;
|
||||
var add = new ScriptType(typeof(AnimContinuousEvent)).IsAssignableFrom(type) ? addContinuousEvent : addEvent;
|
||||
var b = add.ContextMenu.AddButton(type.Name);
|
||||
b.TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(type);
|
||||
b.Tag = type;
|
||||
b.Parent.Tag = time;
|
||||
b.ButtonClicked += OnAddAnimEvent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnAddAnimEvent(ContextMenuButton button)
|
||||
{
|
||||
var type = (ScriptType)button.Tag;
|
||||
var time = (float)button.Parent.Tag;
|
||||
var media = new AnimationEventMedia();
|
||||
media.Init(type);
|
||||
media.StartFrame = (int)(time * Timeline.FramesPerSecond);
|
||||
media.DurationFrames = media.IsContinuous ? Mathf.Max(Timeline.DurationFrames / 10, 10) : 1;
|
||||
Timeline.AddMedia(this, media);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user