Battlezone-style weapon pickups in Unreal Engine 3

Given the vehicle class provided in the previous post, it is easy to create a pickup that can change the weapon selection for the vehicle.

To provide the simplest possible example that demonstrates the concept, this example makes the weapon pickup a subclass of Actor. It would be possible to implement the pickup as a subclass of some other base class as well, if those classes had functionality you wanted to keep. The weapon will be picked up when touched by the vehicle, so I’ve placed the pickup code inside of the class’ touch() event.

The crude version looks like this:

class CrudeWeaponChangePickup extends Actor;

var class<UTVehicleWeapon> weaponClass;

event touch(Actor other, PrimitiveComponent otherComp, vector hitLocation, vector hitNormal) {
  local WeaponChangeGoliath touchVehicle;

  touchVehicle = WeaponChangeGoliath(other);
  if (touchVehicle != none) {
    touchVehicle.toggleWeapons[touchVehicle.toggleWeaponIndex] = weaponClass;
    touchVehicle.changeWeapon(0, touchVehicle.toggleWeapons[touchVehicle.toggleWeaponIndex]);
    destroy();
  }
}

defaultproperties
{
  weaponClass=class'UTVWeap_MantaGun';
  
  Begin Object Class=SkeletalMeshComponent Name=PickupMesh ObjName=PickupMesh Archetype=SkeletalMeshComponent'UTGame.Default__UTBeamWeapon:PickupMesh'
    SkeletalMesh=SkeletalMesh'WP_RocketLauncher.Mesh.SK_WP_RocketLauncher_3P'
    ObjectArchetype=SkeletalMeshComponent'UTGame.Default__UTBeamWeapon:PickupMesh'
  End Object
  Components.Add(PickupMesh)

  Begin Object Class=CylinderComponent NAME=CollisionCylinder
    CollisionRadius=+00030.000000
    CollisionHeight=+00020.000000
    CollideActors=true
  End Object
  CollisionComponent=CollisionCylinder
  Components.Add(CollisionCylinder)

  bOnlyDirtyReplication=true
  NetUpdateFrequency=8
  RemoteRole=ROLE_SimulatedProxy
  bHidden=false
  NetPriority=+1.4
  bCollideActors=true
  bCollideWorld=true
  bOrientOnSlope=true
  bShouldBaseAtStartup=true
  bIgnoreEncroachers=false
  bIgnoreRigidBodyPawns=true
  bUpdateSimulatedPosition=true
}

Inside the touch event, it checks if the thing it touched was the WeaponChangeGoliath presented in the earlier post by attempting a typecast. If that is not null, then the thing that was touched was a WeaponChangeGoliath. In that case, the pickup changes the currently-selected weapon in the vehicle’s toggleWeapons array to the weaponClass of this pickup. In order to make the change effective, the pickup also calls the WeaponChangeGoliath’s changeWeapon method, passing the new weapon class. Once the weapon has been changed, the pickup is used up, so it destroys itself.

In Battlezone, weapons are categorized by their type, so a “Gun” type weapon can only replace another “Gun” type weapon. To support this behavior, a modified version of the WeaponChangeGoliath and WeaponChangePickup can be used:

class WeaponChangeGoliath extends UTVehicle_Goliath_Content;

var int toggleWeaponIndex;
var array< name > toggleWeaponNames;
var array< class<UTVehicleWeapon> > toggleWeapons;

exec function toggleWeapon() {
  toggleWeaponIndex++;
  if (toggleWeaponIndex >= toggleWeapons.length)
    toggleWeaponIndex -= toggleWeapons.length;
    
  changeWeapon(0, toggleWeapons[toggleWeaponIndex]);
}

simulated function switchWeapon(byte newGroup) {
  local int newWeaponIndex;
  
  newWeaponIndex = newGroup-1;
  if (newWeaponIndex >= 0 && newWeaponIndex < toggleWeapons.length && newWeaponIndex != toggleWeaponIndex) {
    toggleWeaponIndex = newWeaponIndex;
    changeWeapon(0, toggleWeapons[newWeaponIndex]);
  }
}

function changeWeapon(int seatNum, class<UTVehicleWeapon> newGunClass) {
  if (seats[seatNum].gun != none) {
    // Might be overkill on the "stop firing" stuff here.
    if (seatNum == 0)
      stopFiringWeapon();
    seats[seatNum].gun.endFire(0);
    seats[seatNum].gun.endFire(1);
    seats[seatNum].gun.forceEndFire();
    seats[seatNum].gun.destroy();
  }

  seats[seatNum].GunClass = newGunClass;
  seats[seatNum].Gun = UTVehicleWeapon(InvManager.CreateInventory(seats[seatNum].GunClass));
  seats[seatNum].Gun.SetBase(self);

  if (seats[seatNum].gun != none) {
    seats[seatNum].gun.SeatIndex = seatNum;
    seats[seatNum].gun.MyVehicle = self;
    if (seatNum == 0) {
      weapon = seats[seatNum].gun;
      invManager.setCurrentWeapon(seats[0].gun);
    }
  }
}

defaultproperties
{
  toggleWeaponIndex=0
  toggleWeapons(0)=class'UTVWeap_GoliathTurret';
  toggleWeapons(1)=class'UTVWeap_GoliathMachineGun';
  toggleWeaponNames(0)=Cannon
  toggleWeaponNames(1)=Gun
}

The only changes made to the WeaponChangeGoliath were to add the toggleWeaponNames array, which will store the “type” of each weapon.

class WeaponChangePickup extends Actor;

var name weaponType;
var class<UTVehicleWeapon> weaponClass;

event touch(Actor other, PrimitiveComponent otherComp, vector hitLocation, vector hitNormal) {
  local int i;
  local WeaponChangeGoliath touchVehicle;

  touchVehicle = WeaponChangeGoliath(other);
  if (touchVehicle != none) {
    for (i=0;i<touchVehicle.toggleWeapons.length && i< touchVehicle.toggleWeaponNames.length;i++) {
      if (touchVehicle.toggleWeaponNames[i] == weaponType && touchVehicle.toggleWeapons[i] != weaponClass) {
        touchVehicle.toggleWeapons[i] = weaponClass;
        if (touchVehicle.toggleWeaponIndex == i)
          touchVehicle.changeWeapon(0, touchVehicle.toggleWeapons[i]);
        destroy();
        return;
      }
    }
  }
}

defaultproperties
{
  weaponType=Gun
  weaponClass=class'UTVWeap_MantaGun'
  
  Begin Object Class=SkeletalMeshComponent Name=PickupMesh ObjName=PickupMesh Archetype=SkeletalMeshComponent'UTGame.Default__UTBeamWeapon:PickupMesh'
    SkeletalMesh=SkeletalMesh'WP_RocketLauncher.Mesh.SK_WP_RocketLauncher_3P'
    ObjectArchetype=SkeletalMeshComponent'UTGame.Default__UTBeamWeapon:PickupMesh'
  End Object
  Components.Add(PickupMesh)

  Begin Object Class=CylinderComponent NAME=CollisionCylinder
    CollisionRadius=+00030.000000
    CollisionHeight=+00020.000000
    CollideActors=true
  End Object
  CollisionComponent=CollisionCylinder
  Components.Add(CollisionCylinder)

  bOnlyDirtyReplication=true
  NetUpdateFrequency=8
  RemoteRole=ROLE_SimulatedProxy
  bHidden=false
  NetPriority=+1.4
  bCollideActors=true
  bCollideWorld=true
  bOrientOnSlope=true
  bShouldBaseAtStartup=true
  bIgnoreEncroachers=false
  bIgnoreRigidBodyPawns=true
  bUpdateSimulatedPosition=true
}

For the pickup, two changes were made. First, the addition of a weaponType variable allows us to specify what type of weapon this is. Subclasses of the WeaponChangePickup can specify a different weaponType and weaponClass in their defaultproperties block, or they could be set in UnrealEd or through other Unrealscript code.

The second change is to the logic inside of the touch event. In this version, the pickup loops through all of the weapons on the vehicle, and replaces the first one with a matching weaponType that is not already this weapon type.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s