Transposer/Entities/BonusText.cs
namespace Sandbox.Transposer;
/// <summary>
/// Floating bonus text ("+64", "+32", etc.) that drifts upward and fades out.
/// Spawned when the player collects a coin.
/// </summary>
public class BonusText : TextDisplay
{
private float _moveSpeed = 40f;
private Vector2 _direction;
private Queue<PixelPoint> _lastPositions = new();
private const int NUM_LAST_POSITIONS = 3;
private float _elapsedTime;
private const float LIFETIME = 2.5f;
public BonusText( string text, TransposerScene scene, int x, int y, string fontName, int scale = 1 )
: base( text, scene, x, y, fontName, Color.Black, scale )
{
_direction = new Vector2( 0, 1 );
}
public override void UpdateEntity( float deltaTime )
{
base.UpdateEntity( deltaTime );
Move( _direction, deltaTime );
_elapsedTime += deltaTime;
if ( _elapsedTime >= LIFETIME )
_scene.RemoveEntity( this );
}
private void Move( Vector2 moveVector, float deltaTime )
{
Vector2 newExactPos = ExactPos + moveVector * _moveSpeed * deltaTime;
PixelPoint newPixelPos = new( (int)MathF.Round( newExactPos.x ), (int)MathF.Round( newExactPos.y ) );
if ( newPixelPos.Y + _letterHeight > Screen.PixelHeight - 4 )
{
_direction = new Vector2( 0, -1 );
}
else
{
if ( newPixelPos != PixelPosition )
AddLastPosition( PixelPosition );
ExactPos = newExactPos;
}
_moveSpeed *= 0.98f; // Decelerate each frame — text slows to a drift then stops.
}
public override void Draw()
{
// Draw ghost trail in black.
float opacity = PixelUtils.Map( _elapsedTime, 0f, LIFETIME, 1f, 0f, EaseType.SineOut );
foreach ( PixelPoint pos in _lastPositions )
{
SetOverriddenColor( new Color( 0, 0, 0, opacity ) );
int currentX = pos.X;
int currentY = pos.Y;
for ( int i = 0; i < _text.Length; i++ )
{
char c = _text[i];
if ( c == '\n' ) { currentY -= _letterHeight * _scale; currentX = PixelX; continue; }
if ( c == ' ' ) { currentX += (_letterWidth + _spacing) * _scale; continue; }
List<PixelData> pxList = GetPixelDataList( _fontName, c.ToString() );
if ( pxList is not null ) DrawPixels( pxList, currentX, currentY, _scale );
currentX += (_letterWidth + _spacing) * _scale;
}
}
// Draw main text in white, fading.
opacity = PixelUtils.Map( _elapsedTime, 0f, LIFETIME, 1f, 0f, EaseType.CubicOut );
SetOverriddenColor( new Color( 1f, 1f, 1f, opacity ) );
int currX = PixelX;
int currY = PixelY;
for ( int i = 0; i < _text.Length; i++ )
{
char c = _text[i];
if ( c == '\n' ) { currY -= _letterHeight * _scale; currX = PixelX; continue; }
if ( c == ' ' ) { currX += (_letterWidth + _spacing) * _scale; continue; }
List<PixelData> pxList = GetPixelDataList( _fontName, c.ToString() );
if ( pxList is not null ) DrawPixels( pxList, currX, currY, _scale );
currX += (_letterWidth + _spacing) * _scale;
}
}
private void AddLastPosition( PixelPoint pos )
{
if ( _lastPositions.Count >= NUM_LAST_POSITIONS )
_lastPositions.Dequeue();
_lastPositions.Enqueue( pos );
}
}