Creating weapons for your game using UnrealScript

Exclusive offer: get 50% off this eBook here
UnrealScript Game Programming Cookbook

UnrealScript Game Programming Cookbook — Save 50%

Discover how you can augment your game development with the power of UnrealScript book and ebook.

$29.99    $15.00
by Dave Voyles | April 2013 | Cookbooks Games

In this article by Dave Voyles, author of UnrealScript Game Programming Cookbook, we will learn how we can create a gun that fires homing missiles, a gun that heals pawns, and a weapon that can damage over time.

(For more resources related to this topic, see here.)

Creating a gun that fires homing missiles

UDK already has a homing rocket launcher packaged with the dev kit (UTWeap_ RocketLauncher). The problem however, is that it isn't documented well; it has a ton of excess code only necessary for multiplayer games played over a network, and can only lock on when you have loaded three rockets.

We're going to change all of that, and allow our homing weapon to lock onto a pawn and fire any projectile of our choice. We also need to change a few functions, so that our weapon fires from the correct location and uses the pawn's rotation and not the camera's.

Getting ready

As I mentioned earlier, our main weapon for this article will extend from the UTWeap_ ShockRifle, as that gun offers a ton of great base functionality which we can build from.

Let's start by opening your IDE and creating a new weapon called MyWeapon, and have it extend from UTWeap_ShockRifle as shown as follows:

class MyWeapon extends UTWeap_ShockRifle;

How to do it...

We need to start by adding all of the variables that we'll be needing for our lock on feature. There are quite a few here, but they're all commented in pretty great detail. Much of this code is straight from UDK's rocket launcher, that is why it looks familiar. In this recipe, we'll be creating a base weapon which extends from one of the Unreal Tournament's most commonly used weapons, the shock rifle, and base all of our weapons from that.

  1. I've gone ahead and removed an unnecessary information, added comments, and altered functionality so that we can lock onto pawns with any weapon, and fire only one missile while doing so.

    /********************************************************
    * Weapon lock on support
    ********************************************************/
    /** Class of the rocket to use when seeking */
    var class<UTProjectile> SeekingRocketClass;
    /** The frequency with which we will check for a lock */
    var(Locking) float LockCheckTime;
    /** How far out should we be considering actors for a lock */
    var float LockRange;
    /** How long does the player need to target an actor to lock on to
    it*/
    var(Locking) float LockAcquireTime;
    /** Once locked, how long can the player go without painting the
    object before they lose the lock */
    var(Locking) float LockTolerance;
    /** When true, this weapon is locked on target */
    var bool bLockedOnTarget;
    /** What "target" is this weapon locked on to */
    var Actor LockedTarget;
    var PlayerReplicationInfo LockedTargetPRI;
    /** What "target" is current pending to be locked on to */
    var Actor PendingLockedTarget;
    /** How long since the Lock Target has been valid */
    var float LastLockedOnTime;
    /** When did the pending Target become valid */
    var float PendingLockedTargetTime;
    /** When was the last time we had a valid target */
    var float LastValidTargetTime;
    /** angle for locking for lock targets */
    var float LockAim;
    /** angle for locking for lock targets when on Console */
    var float ConsoleLockAim;
    /** Sound Effects to play when Locking */
    var SoundCue LockAcquiredSound;
    var SoundCue LockLostSound;
    /** If true, weapon will try to lock onto targets */
    var bool bTargetLockingActive;
    /** Last time target lock was checked */
    var float LastTargetLockCheckTime;

  2. With our variables in place, we can now move onto the weapon's functionality. The InstantFireStartTrace() function is the same function we added in our weapon. It allows our weapon to start its trace from the correct location using the GetPhysicalFireStartLoc() function. function.

    As mentioned before, this simply grabs the rotation of the weapon's muzzle flash socket, and tells the weapon to fire projectiles from that location, using the socket's rotation. The same goes for GetEffectLocation(), which is where our muzzle flash will occur.

    The v in vector for the InstantFireStartTrace() function is not capitalized. The reason being that vector is actually of struct type, and not a function, and that is standard procedure in UDK.

    /********************************************************
    * Overriden to use GetPhysicalFireStartLoc() instead of
    * Instigator.GetWeaponStartTraceLocation()
    * @returns position of trace start for instantfire()
    ********************************************************/
    simulated function vector InstantFireStartTrace()
    {
    return GetPhysicalFireStartLoc();
    }
    /********************************************************
    * Location that projectiles will spawn from. Works for secondary
    fire on
    * third person mesh
    ********************************************************/
    simulated function vector GetPhysicalFireStartLoc(optional vector
    AimDir)
    {
    Local SkeletalMeshComponent AttachedMesh;
    local vector SocketLocation;
    Local TutorialPawn TutPawn;
    TutPawn = TutorialPawn(Owner);
    AttachedMesh = TutPawn.CurrentWeaponAttachment.Mesh;
    /** Check to prevent log spam, and the odd situation win
    which a cast to type TutPawn can fail */
    if (TutPawn != none)
    {
    AttachedMesh.GetSocketWorldLocationAndRotation
    (MuzzleFlashSocket, SocketLocation);
    }
    return SocketLocation;
    }
    /********************************************************
    * Overridden from UTWeapon.uc
    * @return the location + offset from which to spawn effects
    (primarily tracers)
    ********************************************************/
    simulated function vector GetEffectLocation()
    {
    Local SkeletalMeshComponent AttachedMesh;
    local vector SocketLocation;
    Local TutorialPawn TutPawn;
    TutPawn = TutorialPawn(Owner);
    AttachedMesh = TutPawn.CurrentWeaponAttachment.Mesh;
    if (TutPawn != none)
    {
    AttachedMesh.GetSocketWorldLocationAndRotation
    (MuzzleFlashSocket, SocketLocation);
    }
    MuzzleFlashSocket, SocketLocation);
    return SocketLocation;
    }

  3. Now we're ready to dive into the parts of code that are applicable to the actual homing of the weapon. Let's start by adding our debug info, which allows us to troubleshoot any issues we may have along the way.

    *********************************************************
    * Prints debug info for the weapon
    ********************************************************/
    simulated function GetWeaponDebug( out Array<String> DebugInfo )
    {
    Super.GetWeaponDebug(DebugInfo);
    DebugInfo[DebugInfo.Length] = "Locked:
    "@bLockedOnTarget@LockedTarget@LastLockedontime@
    (WorldInfo.TimeSeconds-LastLockedOnTime);
    DebugInfo[DebugInfo.Length] =
    "Pending:"@PendingLockedTarget@PendingLockedTargetTime
    @WorldInfo.TimeSeconds;
    }

    Here we are simply stating which target our weapon is currently locked onto, in addition to the pending target. It does this by grabbing the variables we've listed before, after they've returned from their functions, which we'll add in the next part.

  4. We need to have a default state for our weapon to begin with, so we mark it as inactive.

    /********************************************************
    * Default state. Go back to prev state, and don't use our
    * current tick
    ********************************************************/
    auto simulated state Inactive
    {
    ignores Tick;
    simulated function BeginState(name PreviousStateName)
    {
    Super.BeginState(PreviousStateName);
    // not looking to lock onto a target
    bTargetLockingActive = false;
    // Don't adjust our target lock
    AdjustLockTarget(None);
    }

    We ignore the tick which tells the weapon to stop updating any of its homing functions. Additionally, we tell it not to look for an active target or adjust its current target, if we did have one at the moment.

  5. While on the topic of states, if we finish our current one, then it's time to move onto the next:

    /********************************************************
    * Finish current state, & prepare for the next one
    ********************************************************/
    simulated function EndState(Name NextStateName)
    {
    Super.EndState(NextStateName);
    // If true, weapon will try to lock onto targets
    bTargetLockingActive = true;
    }
    }

  6. If our weapon is destroyed or we are destroyed, then we want to prevent the weapon from continuing to lock onto a target.

    /********************************************************
    * If the weapon is destroyed, cancel any target lock
    ********************************************************/
    simulated event Destroyed()
    {
    // Used to adjust the LockTarget.
    AdjustLockTarget(none);
    //Calls the previously defined Destroyed function
    super.Destroyed();
    }

  7. Our next chunk of code is pretty large, but don't let it intimidate you. Take your time and read it through to have a thorough understanding of what is occurring. When it all boils down, the CheckTargetLock() function verifies that we've actually locked onto our target.

    We start by checking that we have a pawn, a player controller, and that we are using a weapon which can lock onto a target. We then check if we can lock onto the target, and if it is possible, we do it. At the moment we only have the ability to lock onto pawns.

    /*****************************************************************
    * Have we locked onto our target?
    ****************************************************************/
    function CheckTargetLock()
    {
    local Actor BestTarget, HitActor, TA;
    local UDKBot BotController;
    local vector StartTrace, EndTrace, Aim, HitLocation,
    HitNormal;
    local rotator AimRot;
    local float BestAim, BestDist;
    if((Instigator == None)||(Instigator.Controller ==
    None)||(self != Instigator.Weapon) )
    {
    return;
    }
    if ( Instigator.bNoWeaponFiring)
    // TRUE indicates that weapon firing is disabled for this
    pawn
    {
    // Used to adjust the LockTarget.
    AdjustLockTarget(None);
    // "target" is current pending to be locked on to
    PendingLockedTarget = None;
    return;
    }
    // We don't have a target
    BestTarget = None;
    BotController = UDKBot(Instigator.Controller);
    // If there is BotController...
    if ( BotController != None )
    {
    // only try locking onto bot's target
    if((BotController.Focus != None) &&
    CanLockOnTo(BotController.Focus) )
    {
    // make sure bot can hit it
    BotController.GetPlayerViewPoint
    ( StartTrace, AimRot );
    Aim = vector(AimRot);
    if((Aim dot Normal(BotController.Focus.Location -
    StartTrace)) > LockAim )
    {
    HitActor = Trace(HitLocation, HitNormal,
    BotController.Focus.Location, StartTrace, true,,,
    TRACEFLAG_Bullet);
    if((HitActor == None)||
    (HitActor == BotController.Focus) )
    {
    // Actor being looked at
    BestTarget = BotController.Focus;
    }
    }
    }
    }

    Immediately after that, we do a trace to see if our missile can hit the target, and check for anything that may be in the way. If we determine that we can't hit our target then it's time to start looking for a new one.

    else
    {
    // Trace the shot to see if it hits anyone
    Instigator.Controller.GetPlayerViewPoint
    ( StartTrace, AimRot );
    Aim = vector(AimRot);
    // Where our trace stops
    EndTrace = StartTrace + Aim * LockRange;
    HitActor = Trace
    (HitLocation, HitNormal, EndTrace, StartTrace,
    true,,, TRACEFLAG_Bullet);
    // Check for a hit
    if((HitActor == None)||!CanLockOnTo(HitActor) )
    {
    /** We didn't hit a valid target? Controller
    attempts to pick a good target */
    BestAim = ((UDKPlayerController
    (Instigator.Controller)!=None)&&
    UDKPlayerController(Instigator.Controller).
    bConsolePlayer) ? ConsoleLockAim : LockAim;
    BestDist = 0.0;
    TA = Instigator.Controller.PickTarget
    (class'Pawn', BestAim, BestDist, Aim, StartTrace,
    LockRange);
    if ( TA != None && CanLockOnTo(TA) )
    {
    /** Best target is the target we've locked */
    BestTarget = TA;
    }
    }
    // We hit a valid target
    else
    {
    // Best Target is the one we've done a trace on
    BestTarget = HitActor;
    }
    }

  8. If we have a possible target, then we note its time mark for locking onto it. If we can lock onto it, then start the timer. The timer can be adjusted in the default properties and determines how long we need to track our target before we have a solid lock.

    // If we have a "possible" target, note its time mark
    if ( BestTarget != None )
    {
    LastValidTargetTime = WorldInfo.TimeSeconds;
    // If we're locked onto our best target
    if ( BestTarget == LockedTarget )
    {
    /** Set the LLOT to the time in seconds since
    level began play */
    LastLockedOnTime = WorldInfo.TimeSeconds;
    }

    Once we have a good target, it should turn into our current one, and start our lock on it. If we've been tracking it for enough time with our crosshair (PendingLockedTargetTime), then lock onto it.

    else
    {
    if ( LockedTarget != None&&(
    (WorldInfo.TimeSeconds - LastLockedOnTime >
    LockTolerance)||!CanLockOnTo(LockedTarget)) )
    {
    // Invalidate the current locked Target
    AdjustLockTarget(None);
    }
    /** We have our best target, see if they should
    become our current target Check for a new
    pending lock */
    if (PendingLockedTarget != BestTarget)
    {
    PendingLockedTarget = BestTarget;
    PendingLockedTargetTime =
    ((Vehicle(PendingLockedTarget) != None)
    &&(UDKPlayerController
    (Instigator.Controller)!=None)
    &&UDKPlayerController(Instigator.Controller).
    bConsolePlayer)
    ? WorldInfo.TimeSeconds + 0.5*LockAcquireTime
    : WorldInfo.TimeSeconds + LockAcquireTime;
    }
    /** Otherwise check to see if we have been
    tracking the pending lock long enough */
    else if (PendingLockedTarget == BestTarget
    && WorldInfo.TimeSeconds = PendingLockedTargetTime )
    {
    AdjustLockTarget(PendingLockedTarget);
    LastLockedOnTime = WorldInfo.TimeSeconds;
    PendingLockedTarget = None;
    PendingLockedTargetTime = 0.0;
    }
    }
    }

    Otherwise, if we can't lock onto our current or our pending target, then cancel our current target, along with our pending target.

    else
    {
    if ( LockedTarget != None&&((WorldInfo.TimeSeconds -
    LastLockedOnTime > LockTolerance)||
    !CanLockOnTo(LockedTarget)) )
    {
    // Invalidate the current locked Target
    AdjustLockTarget(None);
    }
    // Next attempt to invalidate the Pending Target
    if ( PendingLockedTarget != None&&
    ((WorldInfo.TimeSeconds - LastValidTargetTime >
    LockTolerance)||!CanLockOnTo(PendingLockedTarget)) )
    {
    // We are not pending another target to lock onto
    PendingLockedTarget = None;
    }
    }
    }

    That was quite a bit to digest. Don't worry, because the functions from here on out are pretty simple and straightforward.

  9. As with most other classes, we need a Tick() function to check for something in each frame. Here, we'll be checking whether or not we have a target locked in each frame, as well as setting our LastTargetLockCheckTime to the number of seconds passed during game-time.

    /********************************************************
    * Check target locking with each update
    ********************************************************/
    event Tick( Float DeltaTime )
    {
    if ( bTargetLockingActive && ( WorldInfo.TimeSeconds >
    LastTargetLockCheckTime + LockCheckTime ) )
    {
    LastTargetLockCheckTime = WorldInfo.TimeSeconds;
    // Time, in seconds, since level began play
    CheckTargetLock();
    // Checks to see if we are locked on a target
    }
    }

  10. As I mentioned earlier, we can only lock onto pawns. Therefore, we need a function to check whether or not our target is a pawn.

    /********************************************************
    * Given an potential target TA determine if we can lock on to it.
    By
    * default, we can only lock on to pawns.
    ********************************************************/
    simulated function bool CanLockOnTo(Actor TA)
    {
    if ( (TA == None) || !TA.bProjTarget || TA.bDeleteMe ||
    (Pawn(TA) == None) || (TA == Instigator) ||
    (Pawn(TA).Health <= 0) )
    {
    return false;
    }
    return ( (WorldInfo.Game == None) ||
    !WorldInfo.Game.bTeamGame || (WorldInfo.GRI == None) ||
    !WorldInfo.GRI.OnSameTeam(Instigator,TA) );
    }

  11. Once we have a locked target we need to trigger a sound, so that the player is aware of the lock. The whole first half of this function simply sets two variables to not have a target, and also plays a sound cue to notify the player that we've lost track of our target.

    /********************************************************
    * Used to adjust the LockTarget.
    ********************************************************/
    function AdjustLockTarget(actor NewLockTarget)
    {
    if ( LockedTarget == NewLockTarget )
    {
    // No need to update
    return;
    }
    if (NewLockTarget == None)
    {
    // Clear the lock
    if (bLockedOnTarget)
    {
    // No target
    LockedTarget = None;
    // Not locked onto a target
    bLockedOnTarget = false;
    if (LockLostSound != None && Instigator != None &&
    Instigator.IsHumanControlled() )
    {
    // Play the LockLostSound if we lost track of the
    target
    PlayerController(Instigator.Controller).
    ClientPlaySound(LockLostSound);
    }
    }
    }
    else
    {
    // Set the lock
    bLockedOnTarget = true;
    LockedTarget = NewLockTarget;
    LockedTargetPRI = (Pawn(NewLockTarget) != None) ?
    Pawn(NewLockTarget).PlayerReplicationInfo : None;
    if ( LockAcquiredSound != None && Instigator != None &&
    Instigator.IsHumanControlled() )
    {
    PlayerController(Instigator.Controller).
    ClientPlaySound(LockAcquiredSound);
    }
    }
    }

  12. Once it looks like everything has checked out we can fire our ammo! We're just setting everything back to 0 at this point, as our projectile is seeking our target, so it's time to start over and see whether we will use the same target or find another one.

    /********************************************************
    * Everything looks good, so fire our ammo!
    ********************************************************/
    simulated function FireAmmunition()
    {
    Super.FireAmmunition();
    AdjustLockTarget(None);
    LastValidTargetTime = 0;
    PendingLockedTarget = None;
    LastLockedOnTime = 0;
    PendingLockedTargetTime = 0;
    }

  13. With all of that out of the way, we can finally work on firing our projectile, or in our case, our missile. ProjectileFile() tells our missile to go after our currently locked target, by setting the SeekTarget variable to our currently locked target.

    /********************************************************
    * If locked on, we need to set the Seeking projectile's
    * LockedTarget.
    ********************************************************/
    simulated function Projectile ProjectileFire()
    {
    local Projectile SpawnedProjectile;
    SpawnedProjectile = super.ProjectileFire();
    if (bLockedOnTarget &&
    UTProj_SeekingRocket(SpawnedProjectile) != None)
    {
    /** Go after the target we are currently locked
    onto */
    UTProj_SeekingRocket(SpawnedProjectile).SeekTarget =
    LockedTarget;
    }
    return SpawnedProjectile;
    }

  14. Really though, our projectile could be anything at this point. We need to tell our weapon to actually use our missile (or rocket, they are used interchangeably) which we will define in our defaultproperties block.

    /********************************************************
    * We override GetProjectileClass to swap in a Seeking Rocket if we
    are
    * locked on.
    ********************************************************/
    function class<Projectile> GetProjectileClass()
    {
    // if we're locked on...
    if (bLockedOnTarget)
    {
    // use our homing rocket
    return SeekingRocketClass;
    }
    // Otherwise...
    else
    {
    // Use our default projectile
    return WeaponProjectiles[CurrentFireMode];
    }
    }

    If we don't have a SeekingRocketClass class defined, then we just use the currently defined projectile from our CurrentFireMode array.

  15. The last part of this class involves the defaultproperties block. This is the same thing we saw in our Camera class. We're setting our muzzle flash socket, which is used for not only firing effects, but also weapon traces, to actually use our muzzle flash socket.

    defaultproperties
    {
    // Forces the secondary fire projectile to fire from
    the weapon attachment */
    MuzzleFlashSocket=MuzzleFlashSocket
    }

    Our MyWeapon class is complete. We don't want to clog our defaultproperties block and we have some great base functionality, so from here on out our weapon classes will generally be only changes to the defaultproperties block. Simplicity!

  16. Create a new class called MyWeapon_HomingRocket. Have it extend from MyWeapon.

    class MyWeapon_HomingRocket extends MyWeapon;

  17. In our defaultproperties block, let's add our skeletal and static meshes. We're just going to keep using the shock rifle mesh. Although it's not necessary to do this, as we're already a child class of (that is, inheriting from) UTWeap_ShockRifle, I still want you to see where you would change the mesh if you ever wanted to.

    defaultproperties
    {
    // Weapon SkeletalMesh
    Begin Object class=AnimNodeSequence Name=MeshSequenceA
    End Object
    // Weapon SkeletalMesh
    Begin Object Name=FirstPersonMesh
    SkeletalMesh=
    SkeletalMesh'WP_ShockRifle.Mesh.SK_WP_ShockRifle_1P'
    AnimSets(0)=
    AnimSet'WP_ShockRifle.Anim.K_WP_ShockRifle_1P_Base'
    Animations=MeshSequenceA
    Rotation=(Yaw=-16384)
    FOV=60.0
    End Object
    // PickupMesh
    Begin Object Name=PickupMesh
    SkeletalMesh=
    SkeletalMesh'WP_ShockRifle.Mesh.SK_WP_ShockRifle_3P'
    End Object
    // Attachment class
    AttachmentClass=
    class'UTGameContent.UTAttachment_ShockRifle'

  18. Next, we want to declare the type of projectile, the type of damage it does, and the frequency at which it can be fired. Moreover, we want to declare that each shot fired will only deplete one round from our inventory. We can declare how much ammo the weapon starts with too.

    // Defines the type of fire for each mode
    WeaponFireTypes(0)=EWFT_InstantHit
    WeaponFireTypes(1)=EWFT_Projectile
    WeaponProjectiles(1)=class'UTProj_Rocket'
    // Damage types
    InstantHitDamage(0)=45
    FireInterval(0)=+1.0
    FireInterval(1)=+1.3
    InstantHitDamageTypes(0)=class'UTDmgType_ShockPrimary'
    InstantHitDamageTypes(1)=None
    // Not an instant hit weapon, so set to "None"
    // How much ammo will each shot use?
    ShotCost(0)=1
    ShotCost(1)=1
    // # of ammo gun should start with
    AmmoCount=20
    // Initial ammo count if weapon is locked
    LockerAmmoCount=20
    // Max ammo count
    MaxAmmoCount=40

  19. Our weapon will use a number of sounds that we didn't previously need, such as locking onto a pawn, as well as losing lock. So let's add those now.

    // Sound effects
    WeaponFireSnd[0] =
    SoundCue'A_Weapon_ShockRifle.Cue.A_Weapon_SR_FireCue'
    WeaponFireSnd[1]=SoundCue'A_Weapon_RocketLauncher.Cue.
    A_Weapon_RL_Fire_Cue'
    WeaponEquipSnd=
    SoundCue'A_Weapon_ShockRifle.Cue.A_Weapon_SR_RaiseCue'
    WeaponPutDownSnd=
    SoundCue'A_Weapon_ShockRifle.Cue.A_Weapon_SR_LowerCue'
    PickupSound=SoundCue'A_Pickups.Weapons.Cue.
    A_Pickup_Weapons_Shock_Cue'
    LockAcquiredSound=SoundCue'A_Weapon_RocketLauncher.Cue.
    A_Weapon_RL_SeekLock_Cue'
    LockLostSound=SoundCue'A_Weapon_RocketLauncher.Cue.
    A_Weapon_RL_SeekLost_Cue'

  20. We won't be the only one to use this weapon, as bots will be picking it up during Deathmatch style games as well. Therefore, we want to declare some logic for the bots, such as how strongly they will desire it, and whether or not they can use it for things like sniping.

    // AI logic
    MaxDesireability=0.65 // Max desireability for bots
    AIRating=0.65
    CurrentRating=0.65
    bInstantHit=false // Is it an instant hit weapon?
    bSplashJump=false
    // Can a bot use this for splash damage?
    bRecommendSplashDamage=true
    // Could a bot snipe with this?
    bSniping=false
    // Should it fire when the mouse is released?
    ShouldFireOnRelease(0)=0
    // Should it fire when the mouse is released?
    ShouldFireOnRelease(1)=0

  21. We need to create an offset for the camera too, otherwise the weapon wouldn't display correctly as we switch between first and third person cameras.

    // Holds an offset for spawning projectile effects
    FireOffset=(X=20,Y=5)
    // Offset from view center (first person)
    PlayerViewOffset=(X=17,Y=10.0,Z=-8.0)

  22. Our homing properties section is the bread and butter of our class. This is where you'll alter the default values for anything to do with locking onto pawns.

    // Homing properties
    /** angle for locking for lock
    targets when on Console */
    ConsoleLockAim=0.992
    /** How far out should we be before considering actors for
    a lock? */
    LockRange=9000
    // Angle for locking, for lockTarget
    LockAim=0.997
    // How often we check for lock
    LockChecktime=0.1
    // How long does player need to hover over actor to lock?
    LockAcquireTime=.3
    // How close does the trace need to be to the actual target
    LockTolerance=0.8
    SeekingRocketClass=class'UTProj_SeekingRocket'

  23. Animations are an essential part of realism, so we want the camera to shake when firing a weapon, in addition to an animation for the weapon itself.

    // camera anim to play when firing (for camera shakes)
    FireCameraAnim(1)=CameraAnim'Camera_FX.ShockRifle.
    C_WP_ShockRifle_Alt_Fire_Shake'
    // Animation to play when the weapon is fired
    WeaponFireAnim(1)=WeaponAltFire

  24. While we're on the topic of visuals, we may as well add the flashes at the muzzle, as well as the crosshairs for the weapon.

    // Muzzle flashes
    MuzzleFlashPSCTemplate=WP_ShockRifle.Particles.
    P_ShockRifle_MF_Alt
    MuzzleFlashAltPSCTemplate=WP_ShockRifle.Particles.
    P_ShockRifle_MF_Alt
    MuzzleFlashColor=(R=200,G=120,B=255,A=255)
    MuzzleFlashDuration=0.33
    MuzzleFlashLightClass=
    class'UTGame.UTShockMuzzleFlashLight'
    CrossHairCoordinates=(U=256,V=0,UL=64,VL=64)
    LockerRotation=(Pitch=32768,Roll=16384)
    // Crosshair
    IconCoordinates=(U=728,V=382,UL=162,VL=45)
    IconX=400
    IconY=129
    IconWidth=22
    IconHeight=48
    /** The Color used when drawing the Weapon's Name on the
    HUD */
    WeaponColor=(R=160,G=0,B=255,A=255)

  25. Since weapons are part of a pawn's inventory, we need to declare which slot this weapon will fall into (from one to nine).

    // Inventory
    InventoryGroup=4 // The weapon/inventory set, 0-9
    GroupWeight=0.5 // position within inventory group.
    (used by prevweapon and nextweapon)

  26. Our final piece of code has to do with rumble feedback with the Xbox gamepad. This is not only used on consoles, but also it is generally reserved for it.

    /** Manages the waveform data for a forcefeedback device,
    specifically for the xbox gamepads. */
    Begin Object Class=ForceFeedbackWaveform
    Name=ForceFeedbackWaveformShooting1
    Samples(0)=(LeftAmplitude=90,RightAmplitude=40,
    LeftFunction=WF_Constant,
    RightFunction=WF_LinearDecreasing,Duration=0.1200)
    End Object
    // controller rumble to play when firing
    WeaponFireWaveForm=ForceFeedbackWaveformShooting1
    }

  27. All that's left to do is to add the weapon to your pawn's default inventory. You can easily do this by adding the following line to your TutorialGame class's defaultproperties block:

    defaultproperties
    {
    DefaultInventory(0)=class'MyWeapon_HomingRocket'
    }

Load up your map with a few bots on it, hold your aiming reticule over it for a brief moment and when you hear the lock sound, fire away!

How it works...

To keep things simple we extend from UTWeap_ShockRifle. This gave us a great bit of base functionality to work from. We created a MyWeapon class which offers not only everything that the shock rifle does, but also the ability to lock onto targets.

When we aim our target reticule over an enemy bot, it checks for a number of things. First, it verifies that it is an enemy and also whether or not the target can be reached. It does this by drawing a trace and returns any actors which may fall in our weapon's path. If all of these things check out, then it begins to lock onto our target after we've held the reticule over the enemy for a set period of time. We then fire our projectile, which is either the weapon's firing mode, or in our case, a rocket.

We didn't want to clutter the defaultproperties block for MyWeapon; so we create a child class called MyWeapon_HomingRocket that makes use of all the functionality and only changes the defaultproperties block, which will influence the weapon's aesthetics, sound effects, and even some functionality with the target lock.

UnrealScript Game Programming Cookbook Discover how you can augment your game development with the power of UnrealScript book and ebook.
Published: February 2013
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

Creating a gun that heals pawns

UDK has built-in functionality for healing players through pickups such as health packs, but there is no way for one player to heal another.

In the following recipe, we'll create an instant hit weapon that heals a target for 10 points of health each time it is shot.

Getting ready

Start by creating a new class called MyWeapon_HealingInstantHit and have it extend from MyWeapon.

class MyWeapon_HealingInstantHit extends MyWeapon;

How to do it...

  1. The great thing about setting up our MyWeapon class is that adding additional functionality to it is a breeze. This class has only one function. Let's add the ProcessInstantHit() function now.

    First, we define the pawn that is being hit. Then, it takes the ProcessInstantHit() function and rather than have it apply damage to a pawn, it applies additional health by calling our pawn's HealDamage() function. The first parameter used by HealDamage() is an integer, which declares exactly how much health each shot will heal a pawn for. We've set it to the modest value of 10.

    We use a log for debugging again and have it output our pawn's health each time it is shot, through the call to P.Health.

    /********************************************************
    * Heals a pawn with an instant hit weapon
    * Doesn't allow pawn's health to exceed maximum (100)
    ********************************************************/
    simulated function ProcessInstantHit(byte FiringMode, ImpactInfo
    Impact, optional int NumHits)
    {
    local Pawn P;
    if (Impact.HitActor != None &&
    (Impact.HitActor).IsA('Pawn'))
    {
    // Defining the pawn
    P = Pawn(Impact.HitActor);
    // Increase health by 10
    P.HealDamage(10, Instigator.Controller,
    InstantHitDamageTypes[1]);
    // Log for debugging
    'Log("***Pawn Health:" @P.Health);
    }
    }

  2. We still need to make one alteration to our DefaultProperties block.

    DefaultProperties
    {
    // Do not perform any damage
    InstantHitDamageTypes(1)=None
    }

    We're telling the game that despite us using an instant hit weapon, we do not want it to perform any damage. Therefore, we set the damage type to None.

How it works...

By overriding UTWeapon's ProcessInstantHit(), we remove its default functionality of performing damage on a pawn and instead do just the opposite; heal it! Alternatively, you could set a function that causes damage to a pawn to a negative number, and that could also heal the pawn.

We simply heal the pawn, let it know which pawn is performing the heal, and finally assign a firing mode to the function. In our case, this is the secondary function for our new weapon. Don't forget, arrays in UDK start at 0, so 0 is really the first firing mode, and 1 is the second.

Also don't forget to change your pawn's default inventory, so that it uses your new weapon!

There's more...

See if you can make the weapon rapid fire by increasing its firing speed. This can be done in the default properties again. You may want to decrease how much health each shot is worth, however, otherwise you can increase a pawn's health to maximum capacity instantly.

Creating a weapon that can damage over time

Damage over Time (DoT) weapons have been a staple in gaming for decades. They can be anything from a pawn taking acid damage, falling into a pool of lava, drowning, or even being poisoned.

Our next recipe will have us creating a weapon that allows our pawn to take a set amount of damage over a brief period of time. This will require both a weapon, as well as a number of changes to our pawn.

Getting ready

Start by creating a new class called MyWeapon_PoisonDamage and have it extend from MyWeapon.

class MyWeapon_PoisonDamage extends MyWeapon;

How to do it...

We're only going to add one function to this class.

  1. Just as we did with our healing weapon, we need to override the UTWeapon class's ProcessInstantHit() function. We're not going to do any sort of healing again though.

    simulated function ProcessInstantHit(byte FiringMode, ImpactInfo
    Impact, optional int NumHits)
    {
    local TutorialPawn TP;
    if (Impact.HitActor != None &&
    (Impact.HitActor).IsA('Pawn'))
    {
    // Defining the pawn
    TP = TutorialPawn(Impact.HitActor);
    /** Calls the poison function in the TutorialPawn
    class */
    TP.PoisonPlayer();
    // Log for debugging
    'Log("***Pawn Health:" @TP.Health);
    }
    }

    First we perform a check to see whether our projectile has hit an actor. Immediately after that, we check to see if that actor is a pawn. We don't want to apply our poison damage to something non-living like a vehicle or a barrel. Afterwards, we check to see if the pawn is of our TutorialPawn class. From there, we call the PoisonPlayer() function from our TutorialPawn class. This is what actually poisons the pawn. Finally, we add our standard log which tracks our tutorial pawn's health, to verify that our function is actually having some kind of an effect.

  2. Now it's time to head to our TutorialPawn class. Let's start by adding a variable to hold the amount of time ticking by, as our pawn is poisoned:

    var int PoisonCountdown;

  3. Our PoisonPlayer() function is what is actually being called by our weapon. This simple function resets our poison counter to 0, and prevents our damage from stacking.

    Next we use a standard SetTimer() function, which you will find is frequently used throughout UDK. We're telling the game to call the PoisonDmg() function every .5 seconds. This is the actual tick that damages our pawn. We could easily increase the amount of damage done by increasing the frequency at which PoisonDmg() is called.

    /********************************************************
    * Called by MyWeapon_PoisonDamge when the pawn is shot
    ********************************************************/
    function PoisonPlayer()
    {
    // Reset Poison Counter
    PoisonCountdown = 0;
    // Every .5 seconds PoisonDmg() will be called
    SetTimer(0.5, true, 'PoisonDmg');
    }

    Let's take a look at what PoisonDmg() actually does:

    /********************************************************
    * Actually does the damage to the pawn
    ********************************************************/
    function PoisonDmg()
    {
    // Does 5 damage to the pawn of type UTDmgType_Burning
    TakeDamage( 5, None, Location, vect(0,0,0) ,
    class'UTDmgType_Burning');
    PoisonCountdown=PoisonCountdown+1;
    // Increment timer
    'Log("***Pawn Health:" @Health);
    // Log for debugging
    // clear the infinitely looping 0.5 second timer after 10
    counts of damage
    if(PoisonCountdown >= 10)
    {
    ClearTimer('PoisonDmg');
    }
    }

    We start by calling the TakeDamage() function and informing the pawn how much damage it is taking (5), where it is being hit (Location), any momentum to be applied (None, as our vector reads 0,0,0) and finally the type of damage (UTDmgType_Burning). Burning is a DoT as well, so rather than create a new damage type, we just stick with what UDK provides.

    Our PoisonCoundown variable, which we created in the preceding code, is now being used as well. Each time the function is called (in our case, every 0.5 seconds), we add one to the count. Next, we create an if statement that clears our timer once we've accumulated 10 seconds worth of damage.

    Want to have the DoT effect last longer?

    Simply set the integer in our if(PoisonCountdown >= 10) statement to be greater than 10!

  4. Change your pawn's default inventory, so that it uses our new weapon and give it a spin! Once hit, you'll notice that the pawn flashes red very briefly, each time damage is received. Take a look at your log to see exactly how much health is drained with each hit.

How it works...

Because our MyWeapon class is so modular, we're able to create brand new functionality by only having to override one function within the class. When our instant hit projectile (ProcessInstantHit()) hits our TutorialPawn, it calls the PoisonPlayer() function within that class.

PoisonPlayer() then calls our PoisonDmg() function every half second. PoisonDmg() then sets a number of attributes, such as the amount of damage taken during each hit, where the pawn is hit, and how long the effect will last.

There's more...

Now that we have a projectile, which poisons our enemies, what's stopping us from creating a grenade that does the same, or even a pickup? See if you can create an object that causes poison damage when the pawn picks it up with the use function, or if a pawn runs it over (collides).

Looking further down the road, one excellent idea would be to create a grenade or explosive that heals people over time.

Take a look at the Bump() and Touch() functions in your pawn.

Summary

In this article, thus we have learned how we can create a gun that fires homing missiles, a gun that heals pawns, and a weapon that can damage over time.

Resources for Article :


Further resources on this subject:


UnrealScript Game Programming Cookbook Discover how you can augment your game development with the power of UnrealScript book and ebook.
Published: February 2013
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

About the Author :


Dave Voyles

Dave Voyles has worked as a coordinator for the last two Indie Games Uprisings on Xbox Live, an annual event organized to highlight the talented developers and their titles on Xbox Live Indie Games. Additionally, he has released a title of his own, Piz-ong on XBLIG, as well as projects using Unity and the Unreal Engine for game jams.

He's proficient in C# and UnrealScript, and all facets of the Unreal Engine, as well as a number of 3D modeling suites, including 3DS Max and Maya.

He has also worked as a technical reviewer on Unreal Development Kit Game Programming with UnrealScript: Beginner's Guide, Packt Publishing and Unreal Development Kit Beginner's Guide, Packt Publishing. Moreover, he works as managing editor at Armless Octopus, a site dedicated to cover Indie game development with an emphasis on XNA and XBLIG. You can find him on Twitter under the handle @DaveVoyles or at www.About.me/DaveVoyles.

Books From Packt


Unity Game Development Essentials
Unity Game Development Essentials

XNA 4.0 Game Development by Example: Beginner's Guide
XNA 4.0 Game Development by Example: Beginner's Guide

Unity 3.x Game Development Essentials
Unity 3.x Game Development Essentials

Unity iOS Game Development Beginners Guide
Unity iOS Game Development Beginners Guide

Monkey Game Development: Beginner's Guide
Monkey Game Development: Beginner's Guide

HTML5 Games Development by Example: Beginner’s Guide
HTML5 Games Development by Example: Beginner’s Guide

Microsoft XNA 4.0 Game Development Cookbook
Microsoft XNA 4.0 Game Development Cookbook

Unreal Development Kit Game Design Cookbook
Unreal Development Kit Game Design Cookbook


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
r
X
q
x
s
H
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software