Add **Animation Events**
This commit is contained in:
@@ -52,7 +52,7 @@ namespace FlaxEditor.GUI.Timeline
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="undo">The undo/redo to use for the history actions recording. Optional, can be null to disable undo support.</param>
|
/// <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)
|
public AnimationTimeline(FlaxEditor.Undo undo)
|
||||||
: base(PlaybackButtons.Play | PlaybackButtons.Stop, undo, false, false)
|
: base(PlaybackButtons.Play | PlaybackButtons.Stop, undo, false, true)
|
||||||
{
|
{
|
||||||
PlaybackState = PlaybackStates.Seeking;
|
PlaybackState = PlaybackStates.Seeking;
|
||||||
ShowPreviewValues = false;
|
ShowPreviewValues = false;
|
||||||
@@ -61,6 +61,7 @@ namespace FlaxEditor.GUI.Timeline
|
|||||||
// Setup track types
|
// Setup track types
|
||||||
TrackArchetypes.Add(AnimationChannelTrack.GetArchetype());
|
TrackArchetypes.Add(AnimationChannelTrack.GetArchetype());
|
||||||
TrackArchetypes.Add(AnimationChannelDataTrack.GetArchetype());
|
TrackArchetypes.Add(AnimationChannelDataTrack.GetArchetype());
|
||||||
|
TrackArchetypes.Add(AnimationEventTrack.GetArchetype());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -163,6 +163,7 @@ namespace FlaxEditor.Viewport.Previews
|
|||||||
: base(useWidgets)
|
: base(useWidgets)
|
||||||
{
|
{
|
||||||
Task.Begin += OnBegin;
|
Task.Begin += OnBegin;
|
||||||
|
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
||||||
|
|
||||||
// Setup preview scene
|
// Setup preview scene
|
||||||
_previewModel = new AnimatedModel
|
_previewModel = new AnimatedModel
|
||||||
@@ -275,6 +276,12 @@ namespace FlaxEditor.Viewport.Previews
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnScriptsReloadBegin()
|
||||||
|
{
|
||||||
|
// Prevent any crashes due to dangling references to anim events
|
||||||
|
_previewModel.ResetAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
private int ComputeLODIndex(SkinnedModel model)
|
private int ComputeLODIndex(SkinnedModel model)
|
||||||
{
|
{
|
||||||
if (PreviewActor.ForcedLOD != -1)
|
if (PreviewActor.ForcedLOD != -1)
|
||||||
@@ -428,6 +435,7 @@ namespace FlaxEditor.Viewport.Previews
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnDestroy()
|
public override void OnDestroy()
|
||||||
{
|
{
|
||||||
|
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||||
Object.Destroy(ref _floorModel);
|
Object.Destroy(ref _floorModel);
|
||||||
Object.Destroy(ref _previewModel);
|
Object.Destroy(ref _previewModel);
|
||||||
NodesMask = null;
|
NodesMask = null;
|
||||||
|
|||||||
70
Source/Engine/Animations/AnimEvent.cpp
Normal file
70
Source/Engine/Animations/AnimEvent.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#include "AnimEvent.h"
|
||||||
|
#include "Engine/Scripting/BinaryModule.h"
|
||||||
|
#include "Engine/Scripting/ManagedSerialization.h"
|
||||||
|
#include "Engine/Serialization/SerializationFwd.h"
|
||||||
|
#include "Engine/Serialization/Serialization.h"
|
||||||
|
|
||||||
|
AnimEvent::AnimEvent(const SpawnParams& params)
|
||||||
|
: ScriptingObject(params)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimEvent::Serialize(SerializeStream& stream, const void* otherObj)
|
||||||
|
{
|
||||||
|
SERIALIZE_GET_OTHER_OBJ(AnimEvent);
|
||||||
|
|
||||||
|
#if !COMPILE_WITHOUT_CSHARP
|
||||||
|
// Handle C# objects data serialization
|
||||||
|
if (Flags & ObjectFlags::IsManagedType)
|
||||||
|
{
|
||||||
|
stream.JKEY("V");
|
||||||
|
if (other)
|
||||||
|
{
|
||||||
|
ManagedSerialization::SerializeDiff(stream, GetOrCreateManagedInstance(), other->GetOrCreateManagedInstance());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ManagedSerialization::Serialize(stream, GetOrCreateManagedInstance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Handle custom scripting objects data serialization
|
||||||
|
if (Flags & ObjectFlags::IsCustomScriptingType)
|
||||||
|
{
|
||||||
|
stream.JKEY("D");
|
||||||
|
_type.Module->SerializeObject(stream, this, other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimEvent::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||||
|
{
|
||||||
|
#if !COMPILE_WITHOUT_CSHARP
|
||||||
|
// Handle C# objects data serialization
|
||||||
|
if (Flags & ObjectFlags::IsManagedType)
|
||||||
|
{
|
||||||
|
auto* const v = SERIALIZE_FIND_MEMBER(stream, "V");
|
||||||
|
if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0)
|
||||||
|
{
|
||||||
|
ManagedSerialization::Deserialize(v->value, GetOrCreateManagedInstance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Handle custom scripting objects data serialization
|
||||||
|
if (Flags & ObjectFlags::IsCustomScriptingType)
|
||||||
|
{
|
||||||
|
auto* const v = SERIALIZE_FIND_MEMBER(stream, "D");
|
||||||
|
if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0)
|
||||||
|
{
|
||||||
|
_type.Module->DeserializeObject(v->value, this, modifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimContinuousEvent::AnimContinuousEvent(const SpawnParams& params)
|
||||||
|
: AnimEvent(params)
|
||||||
|
{
|
||||||
|
}
|
||||||
72
Source/Engine/Animations/AnimEvent.h
Normal file
72
Source/Engine/Animations/AnimEvent.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Engine/Scripting/ScriptingObject.h"
|
||||||
|
#include "Engine/Core/ISerializable.h"
|
||||||
|
#if USE_EDITOR
|
||||||
|
#include "Engine/Core/Math/Color.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class AnimatedModel;
|
||||||
|
class Animation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The animation notification event triggered during animation playback.
|
||||||
|
/// </summary>
|
||||||
|
API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public ScriptingObject, public ISerializable
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE(AnimEvent);
|
||||||
|
|
||||||
|
#if USE_EDITOR
|
||||||
|
/// <summary>
|
||||||
|
/// Event display color in the Editor.
|
||||||
|
/// </summary>
|
||||||
|
API_FIELD(Attributes="HideInEditor, NoSerialize") Color Color = Color::White;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Animation event notification.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor">The animated model actor instance.</param>
|
||||||
|
/// <param name="anim">The source animation.</param>
|
||||||
|
/// <param name="time">The current animation time (in seconds).</param>
|
||||||
|
/// <param name="deltaTime">The current animation tick delta time (in seconds).</param>
|
||||||
|
API_FUNCTION() virtual void OnEvent(AnimatedModel* actor, Animation* anim, float time, float deltaTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ISerializable]
|
||||||
|
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||||
|
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The animation notification event (with duration) triggered during animation playback that contains begin and end (event notification is received as a tick).
|
||||||
|
/// </summary>
|
||||||
|
API_CLASS(Abstract) class FLAXENGINE_API AnimContinuousEvent : public AnimEvent
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE(AnimContinuousEvent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Animation notification called before the first event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor">The animated model actor instance.</param>
|
||||||
|
/// <param name="anim">The source animation.</param>
|
||||||
|
/// <param name="time">The current animation time (in seconds).</param>
|
||||||
|
/// <param name="deltaTime">The current animation tick delta time (in seconds).</param>
|
||||||
|
API_FUNCTION() virtual void OnBegin(AnimatedModel* actor, Animation* anim, float time, float deltaTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Animation notification called after the last event (guaranteed to be always called).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actor">The animated model actor instance.</param>
|
||||||
|
/// <param name="anim">The source animation.</param>
|
||||||
|
/// <param name="time">The current animation time (in seconds).</param>
|
||||||
|
/// <param name="deltaTime">The current animation tick delta time (in seconds).</param>
|
||||||
|
API_FUNCTION() virtual void OnEnd(AnimatedModel* actor, Animation* anim, float time, float deltaTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "AnimGraph.h"
|
#include "AnimGraph.h"
|
||||||
#include "Engine/Animations/Animations.h"
|
#include "Engine/Animations/Animations.h"
|
||||||
|
#include "Engine/Animations/AnimEvent.h"
|
||||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||||
#include "Engine/Graphics/Models/SkeletonData.h"
|
#include "Engine/Graphics/Models/SkeletonData.h"
|
||||||
#include "Engine/Scripting/Scripting.h"
|
#include "Engine/Scripting/Scripting.h"
|
||||||
@@ -91,6 +92,9 @@ void AnimGraphInstanceData::Clear()
|
|||||||
State.Resize(0);
|
State.Resize(0);
|
||||||
NodesPose.Resize(0);
|
NodesPose.Resize(0);
|
||||||
Slots.Resize(0);
|
Slots.Resize(0);
|
||||||
|
for (const auto& e : Events)
|
||||||
|
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
|
||||||
|
Events.Resize(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphInstanceData::ClearState()
|
void AnimGraphInstanceData::ClearState()
|
||||||
@@ -103,6 +107,9 @@ void AnimGraphInstanceData::ClearState()
|
|||||||
State.Resize(0);
|
State.Resize(0);
|
||||||
NodesPose.Resize(0);
|
NodesPose.Resize(0);
|
||||||
Slots.Clear();
|
Slots.Clear();
|
||||||
|
for (const auto& e : Events)
|
||||||
|
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
|
||||||
|
Events.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphInstanceData::Invalidate()
|
void AnimGraphInstanceData::Invalidate()
|
||||||
@@ -246,6 +253,8 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
|||||||
// Initialize buckets
|
// Initialize buckets
|
||||||
ResetBuckets(context, &_graph);
|
ResetBuckets(context, &_graph);
|
||||||
}
|
}
|
||||||
|
for (auto& e : data.Events)
|
||||||
|
e.Hit = false;
|
||||||
|
|
||||||
// Init empty nodes data
|
// Init empty nodes data
|
||||||
context.EmptyNodes.RootMotion = RootMotionData::Identity;
|
context.EmptyNodes.RootMotion = RootMotionData::Identity;
|
||||||
@@ -279,6 +288,19 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
|||||||
if (animResult == nullptr)
|
if (animResult == nullptr)
|
||||||
animResult = GetEmptyNodes();
|
animResult = GetEmptyNodes();
|
||||||
}
|
}
|
||||||
|
if (data.Events.Count() != 0)
|
||||||
|
{
|
||||||
|
ANIM_GRAPH_PROFILE_EVENT("Events");
|
||||||
|
for (int32 i = data.Events.Count() - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
const auto& e = data.Events[i];
|
||||||
|
if (!e.Hit)
|
||||||
|
{
|
||||||
|
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)context.Data->Object, e.Anim, 0.0f, 0.0f);
|
||||||
|
data.Events.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Allow for external override of the local pose (eg. by the ragdoll)
|
// Allow for external override of the local pose (eg. by the ragdoll)
|
||||||
data.LocalPoseOverride(animResult);
|
data.LocalPoseOverride(animResult);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#define ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS 32
|
#define ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS 32
|
||||||
#define ANIM_GRAPH_MAX_STATE_TRANSITIONS 64
|
#define ANIM_GRAPH_MAX_STATE_TRANSITIONS 64
|
||||||
#define ANIM_GRAPH_MAX_CALL_STACK 100
|
#define ANIM_GRAPH_MAX_CALL_STACK 100
|
||||||
|
#define ANIM_GRAPH_MAX_EVENTS 64
|
||||||
|
|
||||||
class AnimGraph;
|
class AnimGraph;
|
||||||
class AnimSubGraph;
|
class AnimSubGraph;
|
||||||
@@ -267,6 +268,7 @@ struct FLAXENGINE_API AnimGraphSlot
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class FLAXENGINE_API AnimGraphInstanceData
|
class FLAXENGINE_API AnimGraphInstanceData
|
||||||
{
|
{
|
||||||
|
friend AnimGraphExecutor;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// ---- Quick documentation ----
|
// ---- Quick documentation ----
|
||||||
@@ -402,6 +404,18 @@ public:
|
|||||||
/// Invalidates the update timer.
|
/// Invalidates the update timer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Invalidate();
|
void Invalidate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct Event
|
||||||
|
{
|
||||||
|
AnimEvent* Instance;
|
||||||
|
Animation* Anim;
|
||||||
|
AnimGraphNode* Node;
|
||||||
|
bool Hit;
|
||||||
|
};
|
||||||
|
|
||||||
|
Array<Event, InlinedAllocation<8>> Events;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
#include "Engine/Content/Assets/SkeletonMask.h"
|
#include "Engine/Content/Assets/SkeletonMask.h"
|
||||||
#include "Engine/Content/Assets/AnimationGraphFunction.h"
|
#include "Engine/Content/Assets/AnimationGraphFunction.h"
|
||||||
#include "Engine/Animations/AlphaBlend.h"
|
#include "Engine/Animations/AlphaBlend.h"
|
||||||
|
#include "Engine/Animations/AnimEvent.h"
|
||||||
#include "Engine/Animations/InverseKinematics.h"
|
#include "Engine/Animations/InverseKinematics.h"
|
||||||
|
#include "Engine/Level/Actors/AnimatedModel.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -146,6 +148,7 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
|
|||||||
if (anim == nullptr || !anim->IsLoaded())
|
if (anim == nullptr || !anim->IsLoaded())
|
||||||
return Value::Null;
|
return Value::Null;
|
||||||
PROFILE_CPU_ASSET(anim);
|
PROFILE_CPU_ASSET(anim);
|
||||||
|
const float oldTimePos = prevTimePos;
|
||||||
|
|
||||||
// Calculate actual time position within the animation node (defined by length and loop mode)
|
// Calculate actual time position within the animation node (defined by length and loop mode)
|
||||||
const float pos = GetAnimPos(newTimePos, startTimePos, loop, length);
|
const float pos = GetAnimPos(newTimePos, startTimePos, loop, length);
|
||||||
@@ -180,6 +183,81 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
|
|||||||
ExtractRootMotion(mapping, rootNodeIndex, anim, animPos, animPrevPos, nodes->Nodes[rootNodeIndex], nodes->RootMotion);
|
ExtractRootMotion(mapping, rootNodeIndex, anim, animPos, animPrevPos, nodes->Nodes[rootNodeIndex], nodes->RootMotion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect events
|
||||||
|
if (anim->Events.Count() != 0)
|
||||||
|
{
|
||||||
|
ANIM_GRAPH_PROFILE_EVENT("Events");
|
||||||
|
auto& context = Context.Get();
|
||||||
|
float eventTimeMin = animPrevPos;
|
||||||
|
float eventTimeMax = animPos;
|
||||||
|
if (loop)
|
||||||
|
{
|
||||||
|
// Check if animation looped
|
||||||
|
const float posNotLooped = startTimePos + oldTimePos;
|
||||||
|
if (posNotLooped < 0.0f || posNotLooped > length)
|
||||||
|
{
|
||||||
|
if (context.DeltaTime * speed < 0)
|
||||||
|
{
|
||||||
|
// Playback backwards
|
||||||
|
Swap(eventTimeMin, eventTimeMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const float eventTime = animPos / static_cast<float>(anim->Data.FramesPerSecond);
|
||||||
|
const float eventDeltaTime = (animPos - animPrevPos) / static_cast<float>(anim->Data.FramesPerSecond);
|
||||||
|
for (const auto& track : anim->Events)
|
||||||
|
{
|
||||||
|
for (const auto& k : track.Second.GetKeyframes())
|
||||||
|
{
|
||||||
|
if (!k.Value.Instance)
|
||||||
|
continue;
|
||||||
|
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
|
||||||
|
if (k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration)
|
||||||
|
{
|
||||||
|
int32 stateIndex = -1;
|
||||||
|
if (duration > 1)
|
||||||
|
{
|
||||||
|
// Begin for continuous event
|
||||||
|
for (stateIndex = 0; stateIndex < context.Data->Events.Count(); stateIndex++)
|
||||||
|
{
|
||||||
|
const auto& e = context.Data->Events[stateIndex];
|
||||||
|
if (e.Instance == k.Value.Instance && e.Node == node)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (stateIndex == context.Data->Events.Count())
|
||||||
|
{
|
||||||
|
auto& e = context.Data->Events.AddOne();
|
||||||
|
e.Instance = k.Value.Instance;
|
||||||
|
e.Anim = anim;
|
||||||
|
e.Node = node;
|
||||||
|
ASSERT(k.Value.Instance->Is<AnimContinuousEvent>());
|
||||||
|
((AnimContinuousEvent*)k.Value.Instance)->OnBegin((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event
|
||||||
|
k.Value.Instance->OnEvent((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
|
||||||
|
if (stateIndex != -1)
|
||||||
|
context.Data->Events[stateIndex].Hit = true;
|
||||||
|
}
|
||||||
|
else if (duration > 1)
|
||||||
|
{
|
||||||
|
// End for continuous event
|
||||||
|
for (int32 i = 0; i < context.Data->Events.Count(); i++)
|
||||||
|
{
|
||||||
|
const auto& e = context.Data->Events[i];
|
||||||
|
if (e.Instance == k.Value.Instance && e.Node == node)
|
||||||
|
{
|
||||||
|
((AnimContinuousEvent*)k.Value.Instance)->OnEnd((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
|
||||||
|
context.Data->Events.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public:
|
|||||||
CameraCut = 16,
|
CameraCut = 16,
|
||||||
//AnimationChannel = 17,
|
//AnimationChannel = 17,
|
||||||
//AnimationChannelData = 18,
|
//AnimationChannelData = 18,
|
||||||
|
//AnimationEvent = 19,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Flags
|
enum class Flags
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
#include "Engine/Profiler/ProfilerCPU.h"
|
||||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||||
#include "Engine/Animations/CurveSerialization.h"
|
#include "Engine/Animations/CurveSerialization.h"
|
||||||
|
#include "Engine/Animations/AnimEvent.h"
|
||||||
|
#include "Engine/Scripting/Scripting.h"
|
||||||
#include "Engine/Threading/Threading.h"
|
#include "Engine/Threading/Threading.h"
|
||||||
#include "Engine/Serialization/MemoryReadStream.h"
|
#include "Engine/Serialization/MemoryReadStream.h"
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||||
|
#include "Engine/Level/Level.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false);
|
REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false);
|
||||||
@@ -19,6 +22,21 @@ Animation::Animation(const SpawnParams& params, const AssetInfo* info)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if USE_EDITOR
|
||||||
|
|
||||||
|
void Animation::OnScriptsReloadStart()
|
||||||
|
{
|
||||||
|
for (auto& e : Events)
|
||||||
|
{
|
||||||
|
for (auto& k : e.Second.GetKeyframes())
|
||||||
|
{
|
||||||
|
Level::ScriptsReloadRegisterObject((ScriptingObject*&)k.Value.Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
Animation::InfoData Animation::GetInfo() const
|
Animation::InfoData Animation::GetInfo() const
|
||||||
{
|
{
|
||||||
ScopeLock lock(Locker);
|
ScopeLock lock(Locker);
|
||||||
@@ -127,7 +145,7 @@ void Animation::LoadTimeline(BytesContainer& result) const
|
|||||||
const float fpsInv = 1.0f / fps;
|
const float fpsInv = 1.0f / fps;
|
||||||
stream.WriteFloat(fps);
|
stream.WriteFloat(fps);
|
||||||
stream.WriteInt32((int32)Data.Duration);
|
stream.WriteInt32((int32)Data.Duration);
|
||||||
int32 tracksCount = Data.Channels.Count();
|
int32 tracksCount = Data.Channels.Count() + Events.Count();
|
||||||
for (auto& channel : Data.Channels)
|
for (auto& channel : Data.Channels)
|
||||||
tracksCount +=
|
tracksCount +=
|
||||||
(channel.Position.GetKeyframes().HasItems() ? 1 : 0) +
|
(channel.Position.GetKeyframes().HasItems() ? 1 : 0) +
|
||||||
@@ -214,6 +232,24 @@ void Animation::LoadTimeline(BytesContainer& result) const
|
|||||||
trackIndex++;
|
trackIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (auto& e : Events)
|
||||||
|
{
|
||||||
|
// Animation Event track
|
||||||
|
stream.WriteByte(19); // Track Type
|
||||||
|
stream.WriteByte(0); // Track Flags
|
||||||
|
stream.WriteInt32(-1); // Parent Index
|
||||||
|
stream.WriteInt32(0); // Children Count
|
||||||
|
stream.WriteString(e.First, -13); // Name
|
||||||
|
stream.Write(&Color32::White); // Color
|
||||||
|
stream.WriteInt32(e.Second.GetKeyframes().Count()); // Events Count
|
||||||
|
for (const auto& k : e.Second.GetKeyframes())
|
||||||
|
{
|
||||||
|
stream.WriteFloat(k.Time);
|
||||||
|
stream.WriteFloat(k.Value.Duration);
|
||||||
|
stream.WriteStringAnsi(k.Value.TypeName, 13);
|
||||||
|
stream.WriteJson(k.Value.Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.Copy(stream.GetHandle(), stream.GetPosition());
|
result.Copy(stream.GetHandle(), stream.GetPosition());
|
||||||
}
|
}
|
||||||
@@ -253,6 +289,7 @@ bool Animation::SaveTimeline(BytesContainer& data)
|
|||||||
|
|
||||||
// Tracks
|
// Tracks
|
||||||
Data.Channels.Clear();
|
Data.Channels.Clear();
|
||||||
|
Events.Clear();
|
||||||
Dictionary<int32, int32> animationChannelTrackIndexToChannelIndex;
|
Dictionary<int32, int32> animationChannelTrackIndexToChannelIndex;
|
||||||
animationChannelTrackIndexToChannelIndex.EnsureCapacity(tracksCount * 3);
|
animationChannelTrackIndexToChannelIndex.EnsureCapacity(tracksCount * 3);
|
||||||
for (int32 trackIndex = 0; trackIndex < tracksCount; trackIndex++)
|
for (int32 trackIndex = 0; trackIndex < tracksCount; trackIndex++)
|
||||||
@@ -325,6 +362,36 @@ bool Animation::SaveTimeline(BytesContainer& data)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 19:
|
||||||
|
{
|
||||||
|
// Animation Event
|
||||||
|
int32 count;
|
||||||
|
stream.ReadInt32(&count);
|
||||||
|
auto& eventTrack = Events.AddOne();
|
||||||
|
eventTrack.First = name;
|
||||||
|
eventTrack.Second.Resize(count);
|
||||||
|
for (int32 i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
auto& k = eventTrack.Second.GetKeyframes()[i];
|
||||||
|
stream.ReadFloat(&k.Time);
|
||||||
|
stream.ReadFloat(&k.Value.Duration);
|
||||||
|
stream.ReadStringAnsi(&k.Value.TypeName, 13);
|
||||||
|
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(k.Value.TypeName);
|
||||||
|
k.Value.Instance = NewObject<AnimEvent>(typeHandle);
|
||||||
|
stream.ReadJson(k.Value.Instance);
|
||||||
|
if (!k.Value.Instance)
|
||||||
|
{
|
||||||
|
LOG(Error, "Failed to spawn object of type {0}.", String(k.Value.TypeName));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!_registeredForScriptingReload)
|
||||||
|
{
|
||||||
|
_registeredForScriptingReload = true;
|
||||||
|
Level::ScriptsReloadStart.Bind<Animation, &Animation::OnScriptsReloadStart>(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
LOG(Error, "Unsupported track type {0} for animation.", trackType);
|
LOG(Error, "Unsupported track type {0} for animation.", trackType);
|
||||||
return true;
|
return true;
|
||||||
@@ -364,7 +431,7 @@ bool Animation::Save(const StringView& path)
|
|||||||
MemoryWriteStream stream(4096);
|
MemoryWriteStream stream(4096);
|
||||||
|
|
||||||
// Info
|
// Info
|
||||||
stream.WriteInt32(100);
|
stream.WriteInt32(101);
|
||||||
stream.WriteDouble(Data.Duration);
|
stream.WriteDouble(Data.Duration);
|
||||||
stream.WriteDouble(Data.FramesPerSecond);
|
stream.WriteDouble(Data.FramesPerSecond);
|
||||||
stream.WriteBool(Data.EnableRootMotion);
|
stream.WriteBool(Data.EnableRootMotion);
|
||||||
@@ -381,6 +448,22 @@ bool Animation::Save(const StringView& path)
|
|||||||
Serialization::Serialize(stream, anim.Scale);
|
Serialization::Serialize(stream, anim.Scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animation events
|
||||||
|
stream.WriteInt32(Events.Count());
|
||||||
|
for (int32 i = 0; i < Events.Count(); i++)
|
||||||
|
{
|
||||||
|
auto& e = Events[i];
|
||||||
|
stream.WriteString(e.First, 172);
|
||||||
|
stream.WriteInt32(e.Second.GetKeyframes().Count());
|
||||||
|
for (const auto& k : e.Second.GetKeyframes())
|
||||||
|
{
|
||||||
|
stream.WriteFloat(k.Time);
|
||||||
|
stream.WriteFloat(k.Value.Duration);
|
||||||
|
stream.WriteStringAnsi(k.Value.TypeName, 17);
|
||||||
|
stream.WriteJson(k.Value.Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set data to the chunk asset
|
// Set data to the chunk asset
|
||||||
auto chunk0 = GetOrCreateChunk(0);
|
auto chunk0 = GetOrCreateChunk(0);
|
||||||
ASSERT(chunk0 != nullptr);
|
ASSERT(chunk0 != nullptr);
|
||||||
@@ -432,6 +515,7 @@ Asset::LoadResult Animation::load()
|
|||||||
switch (headerVersion)
|
switch (headerVersion)
|
||||||
{
|
{
|
||||||
case 100:
|
case 100:
|
||||||
|
case 101:
|
||||||
{
|
{
|
||||||
stream.ReadInt32(&headerVersion);
|
stream.ReadInt32(&headerVersion);
|
||||||
stream.ReadDouble(&Data.Duration);
|
stream.ReadDouble(&Data.Duration);
|
||||||
@@ -471,13 +555,72 @@ Asset::LoadResult Animation::load()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animation events
|
||||||
|
if (headerVersion >= 101)
|
||||||
|
{
|
||||||
|
int32 eventTracksCount;
|
||||||
|
stream.ReadInt32(&eventTracksCount);
|
||||||
|
Events.Resize(eventTracksCount, false);
|
||||||
|
#if !USE_EDITOR
|
||||||
|
StringAnsi typeName;
|
||||||
|
#endif
|
||||||
|
for (int32 i = 0; i < eventTracksCount; i++)
|
||||||
|
{
|
||||||
|
auto& e = Events[i];
|
||||||
|
stream.ReadString(&e.First, 172);
|
||||||
|
int32 eventsCount;
|
||||||
|
stream.ReadInt32(&eventsCount);
|
||||||
|
e.Second.GetKeyframes().Resize(eventsCount);
|
||||||
|
for (auto& k : e.Second.GetKeyframes())
|
||||||
|
{
|
||||||
|
stream.ReadFloat(&k.Time);
|
||||||
|
stream.ReadFloat(&k.Value.Duration);
|
||||||
|
#if USE_EDITOR
|
||||||
|
StringAnsi& typeName = k.Value.TypeName;
|
||||||
|
#endif
|
||||||
|
stream.ReadStringAnsi(&typeName, 17);
|
||||||
|
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName);
|
||||||
|
k.Value.Instance = NewObject<AnimEvent>(typeHandle);
|
||||||
|
stream.ReadJson(k.Value.Instance);
|
||||||
|
if (!k.Value.Instance)
|
||||||
|
{
|
||||||
|
LOG(Error, "Failed to spawn object of type {0}.", String(typeName));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#if USE_EDITOR
|
||||||
|
if (!_registeredForScriptingReload)
|
||||||
|
{
|
||||||
|
_registeredForScriptingReload = true;
|
||||||
|
Level::ScriptsReloadStart.Bind<Animation, &Animation::OnScriptsReloadStart>(this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return LoadResult::Ok;
|
return LoadResult::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::unload(bool isReloading)
|
void Animation::unload(bool isReloading)
|
||||||
{
|
{
|
||||||
|
#if USE_EDITOR
|
||||||
|
if (_registeredForScriptingReload)
|
||||||
|
{
|
||||||
|
_registeredForScriptingReload = false;
|
||||||
|
Level::ScriptsReloadStart.Unbind<Animation, &Animation::OnScriptsReloadStart>(this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
ClearCache();
|
ClearCache();
|
||||||
Data.Dispose();
|
Data.Dispose();
|
||||||
|
for (const auto& e : Events)
|
||||||
|
{
|
||||||
|
for (const auto& k : e.Second.GetKeyframes())
|
||||||
|
{
|
||||||
|
if (k.Value.Instance)
|
||||||
|
Delete(k.Value.Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Events.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetChunksFlag Animation::getChunksToPreload() const
|
AssetChunksFlag Animation::getChunksToPreload() const
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Engine/Animations/AnimationData.h"
|
#include "Engine/Animations/AnimationData.h"
|
||||||
|
|
||||||
class SkinnedModel;
|
class SkinnedModel;
|
||||||
|
class AnimEvent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asset that contains an animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
|
/// Asset that contains an animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
|
||||||
@@ -48,6 +49,25 @@ DECLARE_BINARY_ASSET_HEADER(Animation, 1);
|
|||||||
API_FIELD() int32 MemoryUsage;
|
API_FIELD() int32 MemoryUsage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains <see cref="AnimEvent"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
struct FLAXENGINE_API AnimEventData
|
||||||
|
{
|
||||||
|
float Duration = 0.0f;
|
||||||
|
AnimEvent* Instance = nullptr;
|
||||||
|
#if USE_EDITOR
|
||||||
|
StringAnsi TypeName;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
#if USE_EDITOR
|
||||||
|
bool _registeredForScriptingReload = false;
|
||||||
|
void OnScriptsReloadStart();
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -55,6 +75,11 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
AnimationData Data;
|
AnimationData Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The animation events (keyframes per named track).
|
||||||
|
/// </summary>
|
||||||
|
Array<Pair<String, StepCurve<AnimEventData>>> Events;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the mapping for every skeleton node to the animation data channels.
|
/// Contains the mapping for every skeleton node to the animation data channels.
|
||||||
/// Can be used for a simple lookup or to check if a given node is animated (unused nodes are using -1 index).
|
/// Can be used for a simple lookup or to check if a given node is animated (unused nodes are using -1 index).
|
||||||
|
|||||||
Reference in New Issue
Block a user