Weapons
Overview
The weapon system in the FPS template is built around two classes: BaseWeapon and WeaponInventory. BaseWeapon is an abstract class you inherit from to create individual weapons. WeaponInventory manages a grid of weapon slots and handles selection, scrolling, and the HUD display. The player controller owns a WeaponInventory and calls into it every frame.
Weapons are also not tied to the player. Any entity can hold and fire a BaseWeapon, including NPCs.
Creating a Weapon
Create a new file and inherit from BaseWeapon. You’ll implement a set of abstract properties and one or two fire methods. Here’s a minimal submachine gun:
public class WeaponSMG : BaseWeapon
{
public override int MaxAmmo => 30;
public override int MaxReserveAmmo => 240;
public override int PrimaryFireAmmoUse => 1;
public override int SecondaryFireAmmoUse => 0;
public override float ViewPunch => 0.025f * (Random.Shared.NextSingle() * 0.1f + 0.9f);
public override float ViewPunchOffset => (Random.Shared.NextSingle() - 0.5f);
public override bool UseMuzzleFlash => true;
public override bool UseDefaultFireBehavior => true;
public override string WeaponName => “smg”;
protected override void PrimaryFire(Vector3 forward, Vector3 right, Vector3 up, Vector3 position, Vector3? tracerVisualOrigin = null)
{
GameEngine.ShootBullet(Owner, position, tracerVisualOrigin ?? position + right * 0.04f,
GetSpreadDirection(forward, right, up, 0.01f), 4);
currentRefire = 0.1f;
SoundDevice.Device.PlaySound($“{GameEngine.Instance.Content.RootDirectory}/Audio/Weapons/smgfire.ogg”,
position, pitch: Random.Shared.NextSingle() * 0.1f + 0.95f, disable3D: Owner is Player);
}
protected override void SecondaryFire(Vector3 forward, Vector3 right, Vector3 up, Vector3 position, Vector3? tracerVisualOrigin = null)
{
// No secondary fire on this weapon
}
}
The Abstract Properties
MaxAmmo How many rounds fit in the clip.
MaxReserveAmmo Total reserve ammo the player can carry.
PrimaryFireAmmoUse Ammo consumed per primary fire. Set to 0 for infinite ammo.
SecondaryFireAmmoUse Ammo consumed per secondary fire. Set to 0 if unused.
ViewPunch How much the camera kicks when firing. Randomizing this slightly gives each shot natural variation.
ViewPunchOffset The sine offset of the kick, which controls the kick direction. Randomizing this makes each shot feel slightly different.
UseMuzzleFlash Whether to spawn a brief point light at the muzzle when firing.
UseDefaultFireBehavior Controls how the player controller invokes fire. See below.
WeaponName The internal name. Used to look up the HUD icon texture from Textures/ui/weapons/[WeaponName].png.
UseDefaultFireBehavior
When true, the player calls TryFirePrimary() and TryFireSecondary() every frame while the fire buttons are held. The base class handles checking ammo, applying the refire timer, and consuming ammo before calling your PrimaryFire() method.
When false, the player instead calls press and release handlers you override:
public virtual void HandleFirePressed(Vector3 forward, Vector3 right, Vector3 up, Vector3 position) { }
public virtual void HandleFireReleased(Vector3 forward, Vector3 right, Vector3 up, Vector3 position) { }
public virtual void HandleSecondaryFirePressed(Vector3 forward, Vector3 right, Vector3 up, Vector3 position) { }
public virtual void HandleSecondaryFireReleased(Vector3 forward, Vector3 right, Vector3 up, Vector3 position) { }
This is useful for charge weapons that fire on release, or weapons that behave differently based on hold duration. In this mode you’re responsible for managing ammo yourself.
The Refire Timer
currentRefire is a countdown that prevents the weapon from firing again until it reaches zero. Set it at the end of PrimaryFire() to control fire rate:
The base class decrements this automatically in Update(). TryFirePrimary() won’t call through to your fire method until it reaches zero. You don’t need to manage it yourself beyond setting it.
Firing Bullets
The FPS template’s GameEngine.ShootBullet() handles raycasting, entity hit detection, decals, impact particles, and tracer effects in one call:
GameEngine.ShootBullet(
Owner, // the entity firing the shot
position, // origin of the ray
position + right * 0.04f + forward, // visual tracer origin
GetSpreadDirection(forward, right, up, 0.01f), // direction with spread
4 // damage
);
The tracer visual origin is offset slightly so the tracer appears to come from the weapon model rather than the center of the screen. GetSpreadDirection() is provided by BaseWeapon and randomly rotates the forward vector within a cone defined by the spread value in radians. Pass 0 for a perfectly accurate shot.
Optional Overrides
Reload() Called when the player presses reload. The default moves ammo from reserve into the clip correctly. Override for custom reload behavior.
UpdateSpecial() Called every frame regardless of firing state. Useful for charge mechanics or continuous effects.
OnEquipped() Called when this weapon becomes the active selection.
OnHolstered() Called when the player switches away from this weapon.
Adding Weapons to the Player
The FPS template uses a WeaponRegistry to register weapons at startup and a WeaponInventory to manage active slots. Register your weapons in GameEngine.cs:
WeaponRegistry.RegisterWeapon(“w_smg”, typeof(WeaponSMG), 1, 0);
WeaponRegistry.RegisterWeapon(“w_shotgun”, typeof(WeaponShotgun), 2, 0);
The arguments are: internal name, weapon type, slot number (1-9 maps to number keys), and row within that slot. Multiple weapons can share a slot at different rows. Pressing the slot key while the HUD is open cycles between them.
Players then pick up weapons from your item entities. The weapon name string from the registry matches the pickup entity to the weapon class, so an item entity with name w_smg gives the player the WeaponSMG.
Each weapon needs a HUD icon texture at Textures/ui/weapons/[WeaponName].png where WeaponName matches the string returned by the weapon’s WeaponName property. Without this, the slot will show no icon but still function.
Giving Weapons to NPCs
BaseWeapon is not tied to the player. The Owner field is just a WorldEntity, so any entity can hold and fire one:
BaseWeapon gun = new WeaponSMG();
gun.Owner = entity;
// In OnUpdate, call this every frame to tick the refire timer:
gun.Update();
// When ready to shoot:
gun.TryFirePrimary(aimDirection, right, up, entity.position);
The BaseNPCController in the FPS template handles weapon management for NPCs automatically if you assign one. See NPC System.