Night Vision Goggles

From Oldunreal-Wiki
Jump to navigation Jump to search

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.