Files
sbox-public/engine/Sandbox.Engine/Scene/Components/Particles/Renderers/ParticleSpriteRenderer.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

279 lines
7.8 KiB
C#

using Sandbox.Rendering;
namespace Sandbox;
/// <summary>
/// Renders particles as 2D sprites - can be static or animated
/// </summary>
[Expose]
[Title( "Particle Sprite Renderer" )]
[Category( "Particles" )]
[Icon( "favorite" )]
public sealed partial class ParticleSpriteRenderer : ParticleRenderer, Component.ExecuteInEditor, IBatchedParticleSpriteRenderer
{
/// <summary>
/// The sprite resource to render. This can be completely static or contain animation(s).
/// </summary>
[Property]
public Sprite Sprite
{
get => _sprite;
set
{
if ( _sprite == value ) return;
_sprite = value;
}
}
/// <summary>
/// The animation that this sprite should start playing when the scene starts.
/// </summary>
[Property, Title( "Animation" ), Category( "Animation" ), Order( -400 ), Editor( "sprite_animation_name" )]
[ShowIf( nameof( IsAnimated ), true )]
public string StartingAnimationName
{
get => CurrentAnimation?.Name ?? (_sprite?.Animations?.FirstOrDefault()?.Name ?? "");
set
{
if ( _sprite == null ) return;
SetAnimation( value );
}
}
[Property, Title( "Animation" ), Category( "Animation" ), Order( -399 )]
[ShowIf( nameof( IsAnimated ), true )]
public float PlaybackSpeed
{
get => _animationState.PlaybackSpeed;
set => _animationState.PlaybackSpeed = value;
}
/// <summary>
/// The scale of the sprite when rendered.
/// </summary>
[Group( "Rendering" )]
[Property, Range( 0, 2 )] public float Scale { get; set; } = 1.0f;
/// <summary>
/// Whether or not the sprite should be rendered additively.
/// </summary>
[Group( "Rendering" )]
[Property] public bool Additive { get; set; }
/// <summary>
/// Whether or not the sprite should cast shadows in the scene.
/// </summary>
[Group( "Rendering" ), Title( "Cast Shadows" )]
[Property] public bool Shadows { get; set; }
/// <summary>
/// Whether or not the sprite should be lit by the scene lighting.
/// </summary>
[Group( "Rendering" )]
[Property] public bool Lighting { get; set; }
/// <summary>
/// Indicates whether the sprite is opaque, optimizing rendering by skipping sorting.
/// </summary>
[Group( "Rendering" )]
[Property] public bool Opaque { get; set; }
/// <summary>
/// The texture filtering mode used when rendering the sprite. For pixelated sprites use <see cref="FilterMode.Point"/>.
/// </summary>
[Group( "Rendering" )]
[Property] public FilterMode TextureFilter { get; set; } = FilterMode.Bilinear;
/// <summary>
/// Alignment mode for the sprite's billboard behavior.
/// </summary>
[Group( "Rendering" )]
[Property] public BillboardAlignment Alignment { get; set; } = BillboardAlignment.LookAtCamera;
/// <summary>
/// Sorting mode used for rendering particles.
/// </summary>
[Group( "Rendering" )]
[Property] public ParticleSortMode SortMode { get; set; }
/// <summary>
/// Amount of feathering applied to the depth, softening its intersection with geometry.
/// </summary>
[Group( "Rendering" )]
[Property, Range( 0, 50 )] public float DepthFeather { get; set; } = 0.0f;
/// <summary>
/// The strength of the fog effect applied to the sprite. This determines how much the sprite blends with any fog in the scene.
/// </summary>
[Group( "Rendering" )]
[Property, Range( 0, 1 )] public float FogStrength { get; set; } = 1.0f;
/// <summary>
/// Aligns the sprite to face its velocity direction.
/// </summary>
[Property, ToggleGroup( "FaceVelocity" ), Order( 2 )]
public bool FaceVelocity { get; set; }
/// <summary>
/// Offset applied to the rotation when facing velocity.
/// </summary>
[Property, ToggleGroup( "FaceVelocity" )]
[Range( 0, 360 )] public float RotationOffset { get; set; }
/// <summary>
/// Enables motion blur effects for the sprite.
/// </summary>
[Property, ToggleGroup( "MotionBlur" ), Order( 3 )]
public bool MotionBlur { get; set; }
/// <summary>
/// Determines whether the motion blur effect includes a leading trail.
/// </summary>
[Property, ToggleGroup( "MotionBlur" )]
[InfoBox( "Creates a blur of sprites along the velocity of the particle, giving the impression of motion blur" )]
public bool LeadingTrail { get; set; } = true;
/// <summary>
/// Amount of blur applied to the sprite during motion blur.
/// </summary>
[Property, ToggleGroup( "MotionBlur" ), Range( 0, 1 )]
public float BlurAmount { get; set; } = 0.5f;
/// <summary>
/// Spacing between blur samples in the motion blur effect.
/// </summary>
[Property, ToggleGroup( "MotionBlur" ), Range( 0, 1 )]
public float BlurSpacing { get; set; } = 0.5f;
/// <summary>
/// Opacity of the blur effect applied to the sprite.
/// </summary>
[Property, ToggleGroup( "MotionBlur" ), Range( 0, 1 )]
public float BlurOpacity { get; set; } = 0.5f;
/// <summary>
/// The animation that is currently being played. Returns null if no sprite is set or the sprite has no animations.
/// </summary>
public Sprite.Animation CurrentAnimation => _sprite?.GetAnimation( _currentAnimationIndex );
/// <summary>
/// Whether or not the sprite is animated. This is true if the sprite has more than one animation or if the current animation has more than one frame.
/// </summary>
public bool IsAnimated => (_sprite?.Animations?.Count ?? 0) > 1;
/// <summary>
/// Interface property to determine if particles should be sorted
/// </summary>
public bool IsSorted => SortMode != ParticleSortMode.Unsorted;
/// <summary>
/// The pivot point of the sprite, used for rotation and scaling. This is in normalized coordinates (0 to 1).
/// </summary>
public Vector2 Pivot => CurrentAnimation?.Origin ?? new Vector2( 0.5f, 0.5f );
/// <summary>
/// The texture being displayed from the sprite given the current frame/animation.
/// </summary>
public Texture Texture
{
get => RenderTexture;
[Obsolete]
set { }
}
/// <summary>
/// Provides texture for rendering - implementation for IBatchedParticleSpriteRenderer
/// </summary>
public Texture RenderTexture
{
get
{
var _anim = CurrentAnimation;
if ( _anim is null )
return Texture.Transparent;
var currentFrameIndex = _animationState.CurrentFrameIndex;
if ( currentFrameIndex < 0 || currentFrameIndex >= _anim.Frames.Count )
return Texture.Transparent;
return _anim.Frames[currentFrameIndex]?.Texture;
}
}
Sprite.AnimationState _animationState = new();
int _currentAnimationIndex = 0;
Sprite _sprite;
public enum ParticleSortMode
{
Unsorted,
ByDistance
}
public enum BillboardAlignment
{
/// <summary>
/// Look directly at the camera, apply roll
/// </summary>
LookAtCamera,
/// <summary>
/// Look at the camera but don't pitch up and down, up is always up, can roll
/// </summary>
RotateToCamera,
/// <summary>
/// Use rotation provided by the particle, pitch yaw and roll
/// </summary>
Particle,
/// <summary>
/// Align to game object rotation, apply pitch yaw and roll
/// </summary>
Object,
}
protected override void OnAwake()
{
Tags.Add( "particles" );
base.OnAwake();
}
/// <summary>
/// Set the animation by index (the first animation is index 0).
/// </summary>
public void SetAnimation( int index )
{
if ( _sprite is null ) return;
if ( index < 0 || index >= (_sprite.Animations?.Count ?? 0) )
{
Log.Warning( $"Sprite '{_sprite.ResourceName}' does not have an animation at index {index}." );
return;
}
_currentAnimationIndex = index;
_animationState.CurrentFrameIndex = 0;
}
/// <summary>
/// Set the animation by name.
/// </summary>
public void SetAnimation( string name )
{
if ( _sprite is null ) return;
int index = _sprite.GetAnimationIndex( name );
if ( index < 0 )
{
Log.Warning( $"Sprite '{_sprite.ResourceName}' does not have an animation named '{name}'." );
return;
}
SetAnimation( index );
}
internal void AdvanceFrame()
{
_animationState.TryAdvanceFrame( CurrentAnimation, Game.IsPlaying ? Time.Delta : RealTime.Delta );
}
}