17 min read

(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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here