terminalCode Example
Sandbox: BaseBulletWeapon — hitscan shooting with BulletConfiguration, recoil, and IronSightsWeapon
BaseBulletWeapon — Hitscan Shooting with Recoil and Camera Noise
BaseBulletWeapon extends BaseWeapon and implements hitscan bullet firing with configurable aim cone, recoil, and camera noise. IronSightsWeapon extends it further with ADS (aim down sights) support.BulletConfiguration
CSHARP
public struct BulletConfiguration
{
public float Damage { get; set; }
public float BulletRadius { get; set; } // Sphere trace radius
public Vector2 AimConeBase { get; set; } // (horizontal, vertical) degrees when hip-firing
public Vector2 AimConeSpread { get; set; } // Additional spread per shot (resets over time)
public Vector2 RecoilPitch { get; set; } // Random pitch recoil range
public Vector2 RecoilYaw { get; set; } // Random yaw recoil range
public float CameraRecoilStrength { get; set; }
public float CameraRecoilFrequency { get; set; }
public float Range { get; set; } // Max trace distance
}ShootBullet
CSHARP
protected void ShootBullet( float fireRate, in BulletConfiguration config )
{
if ( HasOwner && ( !HasAmmo() || IsReloading() ) )
{
TryAutoReload();
return;
}
if ( TimeUntilNextShotAllowed > 0 ) return;
AddShootDelay( fireRate );
ConsumeAmmo();
// Build aim direction with cone spread
var dir = AimRay.Forward.WithAimCone( config.AimConeBase.x );
// Sphere trace from eye
var tr = Scene.Trace.Ray( AimRay.Position, AimRay.Position + dir * config.Range )
.IgnoreGameObjectHierarchy( AimIgnoreRoot )
.WithoutTags( "playercontroller" )
.Radius( config.BulletRadius )
.UseHitboxes()
.Run();
ShootEffects( tr.EndPosition, tr.Hit, tr.Normal, tr.GameObject, tr.Surface );
TraceAttack( TraceAttackInfo.From( tr, config.Damage ) );
TimeSinceShoot = 0;
// Recoil — only when held by a player
if ( !HasOwner )
{
// Standalone: apply physical force to the weapon's rigidbody
if ( ShootForce > 0f && GetComponent<Rigidbody>( true ) is var rb )
{
var muzzle = WeaponModel?.MuzzleTransform?.WorldTransform ?? WorldTransform;
rb.ApplyForce( muzzle.Rotation.Up * ShootForce );
}
return;
}
// Player recoil: nudge eye angles
Owner.Controller.EyeAngles += new Angles(
Random.Shared.Float( config.RecoilPitch.x, config.RecoilPitch.y ),
Random.Shared.Float( config.RecoilYaw.x, config.RecoilYaw.y ),
0
);
// Camera noise (first-person only)
if ( !Owner.Controller.ThirdPerson && Owner.IsLocalPlayer )
{
_ = new Sandbox.CameraNoise.Recoil( config.CameraRecoilStrength, config.CameraRecoilFrequency );
}
}IronSightsWeapon — ADS
CSHARP
public abstract class IronSightsWeapon : BaseBulletWeapon
{
[Property] public float IronSightsFireScale { get; set; } = 0.2f; // Reduces aim cone when ADS
private bool _isAiming;
public bool IsAiming => _isAiming;
public override bool CanSecondaryAttack() => false; // Secondary = ADS, not attack
public override void OnControl( Player player )
{
base.OnControl( player );
var wantsAim = Input.Down( "attack2" );
if ( wantsAim == _isAiming ) return;
_isAiming = wantsAim;
ViewModel?.RunEvent<ViewModel>( x =>
{
x.Renderer?.Set( "ironsights", _isAiming ? 1 : 0 );
x.Renderer?.Set( "ironsights_fire_scale", _isAiming ? IronSightsFireScale : 1f );
} );
}
protected BulletConfiguration GetBullet()
{
if ( !_isAiming ) return Bullet;
var config = Bullet;
config.AimConeBase *= IronSightsFireScale;
config.AimConeSpread *= IronSightsFireScale;
return config;
}
}Key points
- WithAimCone(degrees) is an extension method on Vector3 that applies random angular deviation
- UseHitboxes() on the trace enables per-bone hitbox detection
- Standalone weapons (no owner) apply physical recoil force via Rigidbody.ApplyForce instead of nudging eye angles
- CameraNoiseSystem.Recoil creates a RollShake — a sinusoidal roll oscillation that fades over 0.3s
- IronSightsFireScale = 0.2f means ADS reduces aim cone to 20% of hip-fire spread
Was this helpful?