A small, engine-independent fire control model for weapons. It tracks fire mode (Semi, Burst, Auto), RPM, burst count and returns whether a shot should fire given trigger state each tick, with cooldown and burst queuing logic.
using System;
namespace Goo.FpsUI;
// The three trigger disciplines. Semi: one round per pull. Burst: a fixed count per pull. Auto: held spray.
public enum FireMode { Semi, Burst, Auto }
// Fire-mode gating, engine-free. Feed it the trigger-held state each tick, it returns true only on the
// ticks a round should actually leave the barrel, capped at the RPM cadence. Unit-testable.
public sealed class FireControlModel
{
public FireMode Mode = FireMode.Auto; // active fire mode
public int BurstCount = 3; // rounds per pull in Burst mode
public float Rpm = 600f; // cadence cap (rounds per minute)
float _cooldown; // seconds until the next round is allowed
int _burstLeft; // rounds still owed in the current burst
bool _prevDown; // trigger state last tick (for rising-edge detection)
public void Reset() { _cooldown = 0f; _burstLeft = 0; _prevDown = false; }
public void Cycle() => Mode = Mode switch // Semi -> Burst -> Auto -> Semi
{
FireMode.Semi => FireMode.Burst,
FireMode.Burst => FireMode.Auto,
_ => FireMode.Semi,
};
// True on ticks a round should fire. triggerDown is the held state of the fire button this tick.
public bool Tick( bool triggerDown, float dt )
{
bool pressed = triggerDown && !_prevDown; // rising edge this tick
_prevDown = triggerDown;
if ( _cooldown > 0f ) _cooldown -= dt;
float interval = Rpm > 0f ? 60f / Rpm : 0f;
bool fire = false;
switch ( Mode )
{
case FireMode.Auto:
if ( triggerDown && _cooldown <= 0f ) fire = true;
break;
case FireMode.Semi:
if ( pressed && _cooldown <= 0f ) fire = true;
break;
case FireMode.Burst:
if ( pressed && _burstLeft <= 0 ) _burstLeft = Math.Max( 1, BurstCount ); // queue a burst on press
if ( _burstLeft > 0 && _cooldown <= 0f ) { fire = true; _burstLeft--; } // pay it out at cadence
break;
}
if ( fire ) _cooldown = interval;
return fire;
}
}