Property animations track (#867)

* WiP property animations system

* Use better lerp for Angle property animations.

* Fix handling of offset in sprite component.

* Allow animating some sprite layer properties.

* Allow animating some Transform properties.

Obviously not advisable for server entities, but great for client side entities!

* Improve animation property interpolation handling.

Added a "previous" mode.
Made values that cannot be sanely interpolated fall back to this mode.

* Improve some animation docs.
This commit is contained in:
Pieter-Jan Briers
2019-09-17 22:57:12 +02:00
committed by GitHub
parent 65a8d33d0c
commit 5d5b897a9b
20 changed files with 561 additions and 176 deletions

View File

@@ -1,12 +1,6 @@
using System;
using System.Collections.Generic;
using Robust.Client.GameObjects.Components.Animations;
using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
namespace Robust.Client.Animations
{
@@ -23,162 +17,4 @@ namespace Robust.Client.Animations
public TimeSpan Length { get; set; }
}
/// <summary>
/// A single track of an <see cref="Animation"/>.
/// </summary>
public abstract class AnimationTrack
{
/// <summary>
/// Return the values necessary to initialize a playback.
/// </summary>
/// <returns>
/// A tuple containing the new key frame the animation track is on and the new time left in said key frame.
/// </returns>
public abstract (int KeyFrameIndex, float FramePlayingTime) InitPlayback();
/// <summary>
/// Advance this animation track's playback.
/// </summary>
/// <param name="context">The object this animation track is being played on, e.g. an entity.</param>
/// <param name="prevKeyFrameIndex">The key frame this animation track is on.</param>
/// <param name="prevPlayingTime">The amount of time this keyframe has been running.</param>
/// <param name="frameTime">The amount of time to increase.</param>
/// <returns>
/// A tuple containing the new key frame the animation track is on and the current time on said key frame.
/// </returns>
public abstract (int KeyFrameIndex, float FramePlayingTime)
AdvancePlayback(object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime);
}
/// <summary>
/// An animation track that plays RSI state animations manually, so they can be precisely controlled etc.
/// </summary>
public sealed class AnimationTrackSpriteFlick : AnimationTrack
{
/// <summary>
/// A list of key frames for when to fire flicks.
/// </summary>
public readonly List<KeyFrame> KeyFrames = new List<KeyFrame>();
// TODO: Should this layer key be per keyframe maybe?
/// <summary>
/// The layer key of the layer to flick on.
/// </summary>
public object LayerKey { get; set; }
public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
{
return (-1, 0);
}
public override (int KeyFrameIndex, float FramePlayingTime)
AdvancePlayback(object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
{
var entity = (IEntity) context;
var sprite = entity.GetComponent<ISpriteComponent>();
var playingTime = prevPlayingTime + frameTime;
var keyFrameIndex = prevKeyFrameIndex;
// Advance to the correct key frame.
while (keyFrameIndex != KeyFrames.Count - 1 && KeyFrames[keyFrameIndex + 1].KeyTime < playingTime)
{
playingTime -= KeyFrames[keyFrameIndex + 1].KeyTime;
keyFrameIndex += 1;
}
if (keyFrameIndex >= 0)
{
var keyFrame = KeyFrames[keyFrameIndex];
// Advance animation on current key frame.
var rsi = sprite.LayerGetActualRSI(LayerKey);
var state = rsi[keyFrame.State];
DebugTools.Assert(state.AnimationLength != null, "state.AnimationLength != null");
var animationTime = Math.Min(state.AnimationLength.Value - 0.01f, playingTime);
sprite.LayerSetAutoAnimated(LayerKey, false);
// TODO: Doesn't setting the state explicitly reset the animation
// so it's slightly more inefficient?
sprite.LayerSetState(LayerKey, keyFrame.State);
sprite.LayerSetAnimationTime(LayerKey, animationTime);
}
return (keyFrameIndex, playingTime);
}
public struct KeyFrame
{
/// <summary>
/// The RSI state to play when this keyframe gets triggered.
/// </summary>
public readonly RSI.StateId State;
/// <summary>
/// The time between this keyframe and the last.
/// </summary>
public readonly float KeyTime;
public KeyFrame(RSI.StateId state, float keyTime)
{
State = state;
KeyTime = keyTime;
}
}
}
/// <summary>
/// An animation track that plays RSI state animations manually, so they can be precisely controlled etc.
/// </summary>
public sealed class AnimationTrackPlaySound : AnimationTrack
{
/// <summary>
/// A list of key frames for when to fire flicks.
/// </summary>
public readonly List<KeyFrame> KeyFrames = new List<KeyFrame>();
public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
{
return (-1, 0);
}
public override (int KeyFrameIndex, float FramePlayingTime)
AdvancePlayback(object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
{
var entity = (IEntity) context;
var playingTime = prevPlayingTime + frameTime;
var keyFrameIndex = prevKeyFrameIndex;
// Advance to the correct key frame.
while (keyFrameIndex != KeyFrames.Count - 1 && KeyFrames[keyFrameIndex + 1].KeyTime < playingTime)
{
playingTime -= KeyFrames[keyFrameIndex + 1].KeyTime;
keyFrameIndex += 1;
var keyFrame = KeyFrames[keyFrameIndex];
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>()
.Play(keyFrame.Resource, entity);
}
return (keyFrameIndex, playingTime);
}
public struct KeyFrame
{
/// <summary>
/// The RSI state to play when this keyframe gets triggered.
/// </summary>
public readonly string Resource;
/// <summary>
/// The time between this keyframe and the last.
/// </summary>
public readonly float KeyTime;
public KeyFrame(string resource, float keyTime)
{
Resource = resource;
KeyTime = keyTime;
}
}
}
}