news | about | forums | links | mods | tools | tutorials | contact

Night Vision Goggles - author: [mr.self-destruct]
This tutorial outlines one means of achieving a night vision goggles effect, the code is complete, but you must create the necessary textures yourself. Night vision goggles can be broken down into two parts, the first is providing better illumination and the second is flashy graphics. The essential part is devising a means of illuminating objects & scenery for one player's view only (thus most likely a client side only effect). The approach I've taken is to use two techniques for this. The first is to adjust the player's view glow to brighten their view (and colour it as part of the flashy effects). The second is to create an effect that encompasses other players making them more visible.
To provide the nice graphic effect all we use is a 256x256 animated texture which we overlay onto the player's view.

To be able to draw onto the player's view, I've used a HUD mutator, which allows us to hook into PostRender() and draw our animated texture. This approach is used by the UT relics, so the relic code can be used as a basis to work from.

// HUD Mutator to handle drawing NVG overlay class NVGMutator extends Mutator; var PlayerPawn PlayerOwner; var float LastChecked; var NVG aNVG;

// If the mutator hasn't been registered, register it
simulated function Tick(float DeltaTime)
{
if (Level.NetMode == NM_DedicatedServer || bHUDMutator)
Disable('Tick');
else
RegisterHUDMutator();
}

// RegisterHUDMutator that keeps track of the HUD's owner
simulated function RegisterHUDMutator()
{
local PlayerPawn P;

ForEach AllActors(class'PlayerPawn', P)
if ( P.IsA('PlayerPawn') && (P.myHUD != None) )
{
NextHUDMutator = P.myHud.HUDMutator;
P.myHUD.HUDMutator = Self;
bHUDMutator = True;
PlayerOwner = P;
}
}

// If the player has NVG, draw overlay
simulated function PostRender(canvas C)
{
local int i;
local Inventory inv;

if ( PlayerOwner == None )
{
Destroy();
if ( NextHUDMutator != None )
NextHUDMutator.PostRender( C );
return;
}
if ( PlayerOwner == ChallengeHUD(PlayerOwner.MyHUD).PawnOwner )
{
// Delay checking of inventory
if ( Level.TimeSeconds - LastChecked > 0.3 )
{
LastChecked = Level.TimeSeconds;
inv = PlayerOwner.Inventory;
while (inv != None )
{
if ( inv.IsA('NVG') )
{
aNVG = NVG(inv);
break;
}
inv = inv.inventory;
i++;
if ( i > 200 )
inv = none;
}
}
// 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 256x256 texture
}
}

if ( NextHUDMutator != None )
NextHUDMutator.PostRender( C );
}

defaultproperties
{
aNVG=None
bAlwaysRelevant=True
bNetTemporary=True
RemoteRole=Role_SimulatedProxy
}

The above HUD mutator periodically checks the player's inventory to see if they're carrying a an NVG object, if they are and it's activated then it overlays their display with an animated texture (a nice static effect like the guided redeemer has is ideal). It avoids checking the player's inventory too frequently to help reduce the performance hit when using the night vision goggles (more on this later). Note that as it's a HUD mutator most of the work goes on client side.

Next comes the actual NVG inventory object, this ties everything together. It's job is to ensure that the player using it has the necessary HUD mutator in play and to implement the remaining functionality for the night vision goggles. It also provides an exec function for turning the night vision goggles on or off.

// Night vision goggles class NVG extends TournamentPickup; var float Range; // Illumination range of NVG var bool bEnabled; // NVG in use? var int tickCount;

replication
{
// Variables replicated from server to client
reliable if (bNetOwner && ROLE == ROLE_Authority)
bEnabled;
// Functions called on the server
reliable if (Role < ROLE_Authority)
NVGToggle;
}

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

// Ensure that NVG effects are disabled when NVG is destroyed or dropped

function Destroyed()
{
bEnabled = false;
AdjustGlow();

Super.Destroyed();
}

function DropFrom(vector StartLocation)
{
Super.DropFrom(StartLocation);

bEnabled = false;
AdjustGlow();
}

// Adjust view glow; brighter when NVG activated
simulated function AdjustGlow()
{
local PlayerPawn PP;

PP = PlayerPawn(Owner);
if (bEnabled)
{
// Brighten view
if (PP != None)
PP.ClientAdjustGlow(-0.15, vect(139.0,218.0,72.0));
}
else
{
// Darken view
if (PP != None)
PP.ClientAdjustGlow(+0.15, vect(-139.0,-218.0,-72.0));
}
}

// Ensure that the player has an NVGMutator
simulated function PostBeginPlay()
{
Super.PostBeginPlay();

if ( Level.NetMode == NM_DedicatedServer )
return;

CheckForHUDMutator();
}

// Check for an NVGMutator, spawn one if necessary
simulated function CheckForHUDMutator()
{
local Mutator M;
local NVGMutator HM;
local PlayerPawn P;
ForEach AllActors(class'PlayerPawn', P)
if ( P.myHUD != None )
{
// check if it already has a NVGMutator
M = P.myHud.HUDMutator;
while ( M != None )
{
if ( M.IsA('NVGMutator') )
return;
M = M.NextHUDMutator;
}
HM = Spawn(class'NVGMutator');
HM.RegisterHUDMutator();
if ( HM.bHUDMutator )
{
if ( Role == ROLE_SimulatedProxy )
SetTimer(0.0, false);
return;
}
else
HM.Destroy();
}

if ( Role == ROLE_SimulatedProxy )
SetTimer(1.0, true);
}

// Ensure that the player has an NVGMutator
simulated function Timer()
{
Super.Timer();
if ( Role == ROLE_SimulatedProxy )
CheckForHUDMutator();
}

// Spawn and locate NVGIllum objects simulated function Tick(float DeltaTime)
{
local pawn P;
local int illumCount;
local int numPawns;
local NVGIllum NVGi;

if (Owner == None)
SetOwner(Instigator);
// Bots have no use for NVGs
if (Owner.IsA('Bot'))
{
Destroy();
return;
}

// Client side only
if (Level.NetMode == NM_DedicatedServer)
return;

// Only update every third tick to reduce CPU usage
tickCount++;
if (tickCount <= 2)
return;
tickCount = 0;

// Count number of NVGIllum objects on client
illumCount = 0;
foreach AllActors(class'NVGIllum', NVGi)
{
illumCount++;
NVGi.bHidden = true; // we use this to set the symbol as 'available' for assignment
}
if (bEnabled)
{
// Iterate through pawns and assign them an NVGIllum object
numPawns = 0;
foreach VisibleCollidingActors(class'Pawn', P, Range, Owner.Location)
{
// spawn another NVGIllum if necessary
numPawns++;
if (numPawns > illumCount)
{
NVGi = Spawn(class'NVGIllum', P);
NVGi.bHidden = P.bHidden;
NVGi.SetOwner(P);
NVGi.Mesh = P.Mesh;
NVGi.DrawScale = P.DrawScale;
illumCount++;
}
// otherwise assign an NVGIllum to the Pawn
else
{
foreach AllActors (class 'NVGIllum', NVGi)
{
if (NVGi.bHidden == True)
{
NVGi.bHidden = P.bHidden;
NVGi.SetOwner(P);
NVGi.Mesh = P.Mesh;
NVGi.DrawScale = P.DrawScale;
break;
}
}
}
}
}
}

defaultproperties
{
tickCount=0
Range=900.0
bEnabled=False
PickupMessage="You got the NVG."
ItemName="NVG"
PickupViewMesh=LodMesh'Botpack.ThighPads'
RemoteRole=ROLE_SimulatedProxy
}

The brightening of the player's view is handled by the NVGToggle() and AdjustGlow() functions. Providing better illumination of other players is performed in Tick(). This part works pretty much like the team beacon HUD mutator (indeed the code is virtually the same) and is quite processor intensive, due to the repeated use of foreach iterators (not a good idea). To reduce to performance hit it only updates every other tick. In fact you could leave out Tick() and have a quite good night vision goggles effect but I think it makes the night vision goggles more useful if they provide a little better illumination of players.
The player illumination is a bit a of a cheat, we simply iterate through all the players we can see and assign them an effect very similar to the UT_ShieldBeltEffect, except that its texture is a dark grey. Due to UT using additive alpha blending when doing transluceny, this has the effect of creating the appearence of a lighter "aura" around the player, making them more visible. To reduce the performance hit of this, there's a limit on how far away a player is to qualify for the effect. This adds realism in a way too, as all night vision goggles do have limited visibility. Most of this is executed client side (so no other player sees the aura effect around the other players).

Finally we come to the aura effect:

// Illumination effect for NVG class NVGIllum extends Effects; var int FatnessOffset; simulated function Destroyed() {
if ( bHidden && (Owner != None) )
{
if ( Level.NetMode == NM_Client )
Owner.Texture = Owner.Default.Texture;
else
Owner.SetDefaultDisplayProperties();
}
Super.Destroyed();
}

simulated function Tick(float DeltaTime)
{
local int IdealFatness;

if ( bHidden || (Level.NetMode == NM_DedicatedServer) || (Owner == None) )
{
Disable('Tick');
return;
}

IdealFatness = Owner.Fatness; // Convert to int for safety.
IdealFatness += FatnessOffset;

if ( Fatness > IdealFatness )
Fatness = Max(IdealFatness, Fatness - 130 * DeltaTime);
else
Fatness = Min(IdealFatness, 255);
}

defaultproperties
{
FatnessOffset=24
bAnimByOwner=True
bOwnerNoSee=True
bNetTemporary=False
bTrailerSameRotation=True
Physics=PHYS_Trailer
RemoteRole=ROLE_SimulatedProxy
DrawType=DT_Mesh
Style=STY_Translucent
Texture=Texture'Illum'
ScaleGlow=0.500000
Fatness=157
bUnlit=True
}

Basically this simply mimics its Owner's location, rotation & fatness, creating a translucent shell around them. The colour of the texture should be chosen carefully (I used a dark grey) to create the right effect.

And now you should have working night vision goggles. The code could undoubtably be improved, mostly to reduce the performance hit (which is around 15fps on my PC); all those iterators in Tick() are not a good thing.

suggestions? questions? comments? please send them all to ray@beyondunreal.com

copyright 2001 ray davis. this site and all content belongs to me unless otherwise noted, with the exception of some tutorials. the authors retain the rights to their material, and are hosted here with their express permission. everything else is mine though, so don't steal it. bastards.
privacy