Mutators & UnrealScript

Night Vision Goggles in UnrealScript: a Classic Tutorial and How It Maps to Killing Floor

The OldUnreal wiki keeps a classic UnrealScript tutorial for night vision goggles in Unreal Tournament (1999). Killing Floor runs on a much later engine, so the code will not compile as is, but the design shows how to build any client-side vision effect: what the player sees, what everybody else sees, and what each part costs in performance. This page explains the original and lists what to change for KF.

A specimen closing in through rain and darkness in Killing Floor Rain, fog and a zed a few metres away: the kind of scene night vision is meant for.

The design

The goggles do two things:

  1. Brighten and tint the player's own view, by shifting the view "glow" (a screen-wide colour and brightness offset).
  2. Make other pawns easier to see, by attaching a translucent, slightly inflated copy of their mesh, so they look like they have a light aura.

On top of that comes the "flashy" part: a 256×256 animated noise texture drawn over the whole screen. All of it is client-side: nobody else sees your overlay or the auras.

The code has three classes:

ClassRole
NVGMutatorA HUD mutator: hooks the HUD's PostRender() and draws the overlay when the owner's goggles are on
NVGThe inventory item: toggle command, glow change, spawns the auras
NVGIllumThe aura effect: follows its owner's mesh and animation, drawn translucent and unlit

NVGMutator: drawing over the screen

UT99 HUD mutators register themselves in a chain on the player's HUD and get a PostRender(Canvas) call every frame. The mutator checks the player's inventory for the goggles, but only every 0.3 seconds to save time, and draws the overlay scaled to the screen:

// Draw overlay if NVG activated
if ( aNVG != None && aNVG.bEnabled )
{
    C.SetPos(0, 0);
    C.Style = ERenderStyle.STY_Translucent;
    C.DrawIcon(Texture'Static_a00', FMax(C.ClipX, C.ClipY)/256.0); // assumes a 256x256 texture
}
if ( NextHUDMutator != None )
    NextHUDMutator.PostRender( C );

It always passes PostRender on to the next HUD mutator. Breaking the chain would break every other HUD mod on the server.

NVG: toggle and view glow

replication
{
    reliable if (bNetOwner && ROLE == ROLE_Authority)
        bEnabled;
    reliable if (Role < ROLE_Authority)
        NVGToggle;
}

exec function NVGToggle()
{
    bEnabled = !bEnabled;
    Owner.PlaySound(Sound'UnrealShare.Pickups.FSHLITE1',, 1.5 * Pawn(Owner).SoundDampening);
    AdjustGlow();
}

simulated function AdjustGlow()
{
    local PlayerPawn PP;
    PP = PlayerPawn(Owner);
    if (PP == None)
        return;
    if (bEnabled)
        PP.ClientAdjustGlow(-0.15, vect(139.0, 218.0, 72.0));   // brighten and tint green
    else
        PP.ClientAdjustGlow(+0.15, vect(-139.0, -218.0, -72.0)); // undo exactly
}

Two details matter:

  • The glow change is relative, so turning the goggles off must undo exactly what turning them on did. The item also calls AdjustGlow() from Destroyed() and DropFrom(), otherwise a player who dies or drops the goggles keeps a green screen.
  • NVGToggle is replicated to the server, and bEnabled comes back to the owner only (bNetOwner).

NVGIllum: the aura

Every few ticks the goggles look for pawns within Range (900 units) with VisibleCollidingActors, and give each one an NVGIllum from a reusable pool. The effect copies the owner's mesh, follows it with PHYS_Trailer and bAnimByOwner, and is drawn STY_Translucent, bUnlit, with Fatness slightly above the owner's. The texture is dark grey: UT draws translucency additively, so dark grey reads as a soft light shell. bOwnerNoSee hides your own aura from you.

The performance warning

The author measured a loss of about 15 FPS and named the cause: foreach AllActors and VisibleCollidingActors in Tick(), even when it only runs every third tick. The overlay and glow cost almost nothing. Keep that in mind for any port.

Porting to Killing Floor

KF uses the Unreal Engine 2.5 code base inherited from UT2004, so the building blocks have different names. Treat the table as a map for your own research, and check every name against the KF script source before relying on it.

UT99 pieceKilling Floor / UE2 equivalent
HUD mutator with PostRender(Canvas)A client-side Interaction with bVisible=true and PostRender(Canvas), or a HUD overlay if the KF HUD exposes it; ServerPerks setups usually already have a custom HUD class to hook into
PlayerPawnSplit into PlayerController (view, input, HUD) and Pawn (body)
ClientAdjustGlow(scale, fog)View flash/fog on PlayerController (ClientFlash, constant glow scale and fog). Remember that zeds in KF already use screen flashes, so restore the previous values instead of assuming defaults
TournamentPickup inventory itemA KFWeapon or Inventory subclass, or simpler: a key binding handled by your mutator's interaction
exec function NVGToggleBind a key to a console command your interaction handles, e.g. set input N mutate nvg on the server side
NVGIllum shell around pawnsPossible, but costly in KF where 30-60 zeds can be alive at once. An overlay plus brightness alone gives most of the effect

Gameplay notes for KF:

  • Balance. Many KF maps rely on darkness, and the Commando already sees Stalkers and zed health. A free night-vision mode changes the difficulty a lot. Consider tying it to a perk, limiting its duration or giving it a battery.
  • Readability. A full-screen noise overlay in a game about crowds is tiring. A light vignette and tint work better than heavy static.
  • Whitelist. A client-side visual mutator still has to be accepted by the server; see whitelisted mutators.