A small UI model that tracks recent XP grants for an FPS HUD. Stores a capped list of entries with Id, Amount, Action, Age and a spring-based Pop for animation, updates total XP, adds new entries, ages and evicts expired entries via Tick.
using System.Collections.Generic;
using Goo.Animation;
namespace Goo.FpsUI;
// XP feed logic: a short-lived stack of "+amount XP (action)" grants that pop in and fade out.
// Each entry carries a stable Id (use as the Goo child Key) and a pop spring. Engine-free.
public sealed class XpModel
{
public float Lifetime = 1.8f; // seconds a grant stays on screen
public int MaxEntries = 6; // cap before the oldest is dropped
public sealed class Entry
{
public int Id; // stable identity for keying
public int Amount; // XP granted
public string Action = ""; // optional reason ("Headshot", "Assist", ...)
public float Age; // seconds since added
public SpringFloat Pop; // 0 -> 1 pop-in progress
}
readonly List<Entry> _entries = new();
int _nextId;
public IReadOnlyList<Entry> Entries => _entries;
public int Total { get; private set; } // running XP total (drive a level bar from this later)
// Award XP. action is an optional label shown to the right of the amount.
public void Add( int amount, string action = "" )
{
Total += amount;
_entries.Insert( 0, new Entry
{
Id = _nextId++, Amount = amount, Action = action ?? "",
Pop = new SpringFloat( 0f, 18f, 0.38f ) { Target = 1f }, // underdamped: overshoots past 1 for a springy pop
} );
while ( _entries.Count > MaxEntries ) _entries.RemoveAt( _entries.Count - 1 );
}
public bool Tick( float dt ) // age and pop entries, evict expired, true while anything changes
{
bool moving = false;
for ( int i = _entries.Count - 1; i >= 0; i-- )
{
var e = _entries[i];
e.Age += dt;
moving |= e.Pop.Tick( dt );
if ( e.Age >= Lifetime ) { _entries.RemoveAt( i ); moving = true; }
}
return moving;
}
}