mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-03 01:09:29 -04:00
* Added loop start/end points, with draggable start/end markers in the Sprite Editor * Loop markers don't show when LoopMode is None * Don't stop animation at loop region when LoopMode is None * LoopMode.None never plays reverse after a ping-pong https://files.facepunch.com/CarsonKompon/2026/June/17_08-58-FrigidWildcat.mp4 * Stop playback when Sprite Editor reaches the end with `LoopMode.None` * When pressing play on the last frame when LoopMode.None, restart from the first frame * simplify * Push undo/redo entries and ensure asset is marked dirty * Ensure base FrameButton OnMouseMove still works so frame buttons can be re-ordered * Fix EffectiveLoopStart/End clamp logic
159 lines
4.4 KiB
C#
159 lines
4.4 KiB
C#
using System.Text.Json.Serialization;
|
|
|
|
namespace Sandbox;
|
|
|
|
public partial class Sprite
|
|
{
|
|
/// <summary>
|
|
/// The different loop modes for sprite animation.
|
|
/// </summary>
|
|
public enum LoopMode
|
|
{
|
|
/// <summary>
|
|
/// The animation will play from start to finish and then stop.
|
|
/// </summary>
|
|
[Icon( "not_interested" )]
|
|
None,
|
|
|
|
/// <summary>
|
|
/// The animation will play from start to finish and then loop back to the start.
|
|
/// </summary>
|
|
[Icon( "loop" )]
|
|
Loop,
|
|
|
|
/// <summary>
|
|
/// The animation will play from start to finish and then backwards from finish to start before looping.
|
|
/// </summary>
|
|
[Icon( "sync_alt" )]
|
|
PingPong
|
|
}
|
|
|
|
public enum BroadcastEventType
|
|
{
|
|
/// <summary>
|
|
/// A custom string will be broadcast by invoking an Action on the SpriteRenderer
|
|
/// </summary>
|
|
[Icon( "message" )]
|
|
CustomMessage,
|
|
|
|
/// <summary>
|
|
/// Plays a sound from the sprite's position
|
|
/// </summary>
|
|
[Icon( "volume_up" )]
|
|
PlaySound,
|
|
|
|
/// <summary>
|
|
/// Spawns a prefab at the sprite's position
|
|
/// </summary>
|
|
[Icon( "library_add" )]
|
|
SpawnPrefab
|
|
}
|
|
|
|
/// <summary>
|
|
/// Describes a single animation frame
|
|
/// </summary>
|
|
public class Frame
|
|
{
|
|
[KeyProperty]
|
|
public Texture Texture { get; set; } = Texture.Transparent;
|
|
|
|
public List<BroadcastEvent> BroadcastMessages { get; set; } = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A message that is broadcast when a frame is displayed.
|
|
/// </summary>
|
|
public class BroadcastEvent
|
|
{
|
|
[KeyProperty, EnumDropdown]
|
|
public BroadcastEventType Type { get; set; } = BroadcastEventType.CustomMessage;
|
|
|
|
[KeyProperty, ShowIf( nameof( Type ), BroadcastEventType.CustomMessage )]
|
|
public string Message { get; set; }
|
|
|
|
[KeyProperty, ShowIf( nameof( Type ), BroadcastEventType.PlaySound )]
|
|
public SoundEvent Sound { get; set; }
|
|
|
|
[KeyProperty, ShowIf( nameof( Type ), BroadcastEventType.SpawnPrefab )]
|
|
public GameObject Prefab { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
switch ( Type )
|
|
{
|
|
case BroadcastEventType.PlaySound:
|
|
return Sound?.ResourceName ?? "no sound";
|
|
case BroadcastEventType.SpawnPrefab:
|
|
return Prefab?.Name ?? "no prefab";
|
|
default:
|
|
return Message;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains one or multiple frames that can be played in sequence.
|
|
/// </summary>
|
|
public class Animation
|
|
{
|
|
/// <summary>
|
|
/// The name of the animation. Allows you to play specific animations by name.
|
|
/// </summary>
|
|
public string Name { get; set; } = "Default";
|
|
|
|
/// <summary>
|
|
/// The speed of the animation in frames per second.
|
|
/// </summary>
|
|
[ShowIf( nameof( IsAnimated ), true )]
|
|
public float FrameRate { get; set; } = 15.0f;
|
|
|
|
/// <summary>
|
|
/// The point at which the rendered sprite is anchored from. This means scaling/rotating a sprite will do so around the origin.
|
|
/// </summary>
|
|
[Range( 0, 1 )]
|
|
public Vector2 Origin { get; set; } = new Vector2( 0.5f, 0.5f );
|
|
|
|
/// <summary>
|
|
/// The loop mode of the animation. This determines what should happen when the animation reaches the final frame in playback.
|
|
/// </summary>
|
|
[ShowIf( nameof( IsAnimated ), true )]
|
|
public LoopMode LoopMode { get; set; } = LoopMode.Loop;
|
|
|
|
/// <summary>
|
|
/// The frame index at which looping starts. A value of -1 means the first frame (0).
|
|
/// </summary>
|
|
[Hide]
|
|
public int LoopStart { get; set; } = -1;
|
|
|
|
/// <summary>
|
|
/// The frame index at which looping ends. A value of -1 means the last frame.
|
|
/// </summary>
|
|
[Hide]
|
|
public int LoopEnd { get; set; } = -1;
|
|
|
|
/// <summary>
|
|
/// Returns the effective loop start frame index, resolving -1 to 0.
|
|
/// </summary>
|
|
[Hide, JsonIgnore]
|
|
public int EffectiveLoopStart => LoopStart < 0 ? 0 : Math.Clamp( LoopStart, 0, Math.Max( 0, (Frames?.Count ?? 1) - 1 ) );
|
|
|
|
/// <summary>
|
|
/// Returns the effective loop end frame index, resolving -1 to the last frame.
|
|
/// </summary>
|
|
[Hide, JsonIgnore]
|
|
public int EffectiveLoopEnd => LoopEnd < 0 ? Math.Max( 0, (Frames?.Count ?? 1) - 1 ) : Math.Clamp( LoopEnd, 0, Math.Max( 0, (Frames?.Count ?? 1) - 1 ) );
|
|
|
|
/// <summary>
|
|
/// A list of frames that make up the animation. Each frame is a texture that will be displayed in sequence.
|
|
/// </summary>
|
|
[Group( "Frames", StartFolded = true ), WideMode( HasLabel = false )]
|
|
public List<Frame> Frames { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// True if we have more than one frame
|
|
/// </summary>
|
|
[Hide, JsonIgnore]
|
|
public bool IsAnimated => Frames?.Count > 1;
|
|
}
|
|
}
|