Scarab Gun

Motivation

When we came up with the design for this game, we wanted to create futuristic weapons that had interesting Egyptian designs. One idea that we had was a gun that shot deadly scarabs, inspired by the movie The Mummy.

Design

The Scarab Gun needed to fulfill two roles for our game. First, it needed to be our shotgun weapon. Second, it was to fulfill our need for a zoning weapon, so it also needed to place traps. I wanted both of these functions to have great gameplay feel.

For the primary fire mode, the initial spread is a normal shotgun blast like what comes out of the UT3’s scattergun. However, on each ensuing update after the projectiles are released, each projectile generates a random deviation and moves in that direction. This gives the Scarab Gun’s blasts a “skittering” buglike motion, which is exactly what I wanted.

For the secondary fire mode, we decided to create particle swarms when the projectile hits the ground. These are essentially a large particle emitter on top of a collision volume that actually causes the damage effects. Finally, when a player enters the swarm, they begin to take damage over time, and the swarm particle effect is transferred to them to indicate this.

Code

Weapon Class

    
        
class AK_Weapon_ScarabGun extends AK_Weapon_Base;

var float shotSpreadConeAngle;
var float burstTimer;
var float burstTime;
var int currentBurstCount;
var int burstCount;

//--------------------------------------------- Firing Modes -----------------------------------------------//
simulated function CustomFire()
{
    local int offsetY, offsetZ;
    local vector positionWhereWeaponFired;
    local Rotator instigatorAimRotation;
    local vector instigatorAimDirectionX, instigatorAimDirectionY, instigatorAimDirectionZ;
    local class< Projectile > projectileClass;

    local Projectile firedProjectile;
    local vector randomizedYVector, randomizedZVector;

    IncrementFlashCount();

    if (Role == ROLE_Authority)
    {
        // fire position is where the projectile will be spawned
        positionWhereWeaponFired = GetPhysicalFireStartLoc();

        // get direction our instigator is aiming
        instigatorAimRotation = GetAdjustedAim( positionWhereWeaponFired );
        GetAxes( instigatorAimRotation, instigatorAimDirectionX, instigatorAimDirectionY, instigatorAimDirectionZ );

        // get the projectile we are firing 
        projectileClass = GetProjectileClass();

        for ( offsetZ = -1; offsetZ <= 1; ++offsetZ )
        {
            for ( offsetY = -1; offsetY <= 1; ++offsetY )
            {
                firedProjectile = Spawn( projectileClass, /*ownerOfSpawnedActor*/, /*stringNameOfSpawnedActor*/, positionWhereWeaponFired, /*rotationOfSpawnedActor*/, /*actorTemplateForSpawnedActor*/, /*boolShouldSpawnFailIfColliding*/);
                
                //Offset the projectile's direction by a set amount
                if( firedProjectile != None )
                {
                    //Set Projectile's direction to the player's direction plus a little bit of randomness because BUG BULLETS
                    randomizedYVector = ( 0.2 + 0.8 * FRand() ) * shotSpreadConeAngle * offsetY * instigatorAimDirectionY;
                    randomizedZVector = ( 0.2 + 0.8 * FRand() ) * shotSpreadConeAngle * offsetZ * instigatorAimDirectionZ;

                    firedProjectile.Init( instigatorAimDirectionX + randomizedYVector + randomizedZVector );
                }
            }
        }
    }
}

//--------------------------------------------------------------------------------------------------------
simulated function Projectile ProjectileFire()
{
    local vector                    positionWhereWeaponFired;
    local AK_Projectile_ScarabNest  SpawnedProjectile;

    // tell remote clients that we fired, to trigger effects
    IncrementFlashCount();

    if( Role == ROLE_Authority )
    {
        // this is the location where the projectile is spawned.
        positionWhereWeaponFired = GetPhysicalFireStartLoc();

        // Spawn projectile
        SpawnedProjectile = Spawn( class'AK_Projectile_ScarabNest',,, positionWhereWeaponFired );
        if( SpawnedProjectile != None && !SpawnedProjectile.bDeleteMe )
        {
            SpawnedProjectile.Init( Vector( GetAdjustedAim( positionWhereWeaponFired ) ) );
        }

        // Return it up the line
        return SpawnedProjectile;
    }

    return None;
}



//--------------------------------------------------------------------------------------------------------
DefaultProperties
{
    localizationSection = "AK_Weapon_ScarabGun"
    
    CrosshairImage=Texture2D'AK_hud.AK_HUD_Reticles'
    CrossHairCoordinates=( U=0, V=0, UL=256, VL=256 )

    //Key to press to switch to weapon
    InventoryGroup=3 

    //---------------------------- Firing Mode ----------------------------//
    shotSpreadConeAngle = 0.08              //JL: Changed from 0.1
    WeaponFireTypes(0)=EWFT_Custom
    WeaponProjectiles(0)=class'AK_Projectile_Scarab'
    WeaponFireTypes(1)=EWFT_Projectile
    WeaponProjectiles(1)=class'AK_Projectile_ScarabNest'

    
    WeaponFireSnd[0] = SoundCue'AK_Weapon_Sounds.AK_Weap_Scarab.AK_Weap_ScarabFire_Cue' //This is the sound of the gun firing
    WeaponFireSnd[1] = SoundCue'AK_Weapon_Sounds.AK_Weap_Scarab.AK_Weap_ScarabFire_Cue' //This is the sound of the gun firing

    //---------------------------- Ammunition ----------------------------//
    ShotCost(0)=1
    ShotCost(1)=3

    AmmoCount=12
    LockerAmmoCount=12
    MaxAmmoCount=12
    burstCount = 1
    burstTime = 0.3
    maxAmmoRechargedPerSecond = 2
    secondsBeforeStartingRecharge = 3
    bAutoCharge=true

    FireInterval(0)=+1.2                                 
    FireInterval(1)=+0.7


    //---------------------------- Appearance ----------------------------//
    // Mesh and animations used in player's view
    Begin Object class=AnimNodeSequence Name=MeshSequenceA
    End Object

    Begin Object Name=FirstPersonMesh
        SkeletalMesh=SkeletalMesh'AK_ScarabGun.Meshes.AK_Scarabgun_Mesh_01'
        AnimSets(0)=AnimSet'AK_ScarabGun.AK_ScarabGun_ANIMSET_01'
        Animations=MeshSequenceA
        Translation = (X=70.0,Y=25.0,Z=-30.0)
        Rotation=(Yaw=-16384)
        Scale= 2.5
        FOV=60.0
    End Object

    TeamMaterials(0) = MaterialInstanceConstant'AK_ScarabGun.Textures.AK_ScarabGun_MAT_Ra_INST'
    TeamMaterials(1) = MaterialInstanceConstant'AK_ScarabGun.Textures.AK_ScarabGun_MAT_Anubis_INST'

    //X = how far down the barrel ( positive = out of the screen )
    //Y = where it is spawned left/right ( positive = right )
    //Z = where it is spawned up/down ( positive = up )
    FireOffset = ( X = 7, Y = 12, Z = -5.0 )
    PlayerViewOffset=(X=16.0,Y=-5,Z=-3.0)

    WeaponFireAnim(0)=WeaponFire
    WeaponFireAnim(1)=WeaponAltFire

    InstantHitDamage(0)=100
    InstantHitDamage(1)=100
    InstantHitDamageTypes(0)=class'AK_DamageType_ScarabShot'
    InstantHitDamageTypes(1)=class'AK_DamageType_Jar'

    //Mesh and animations used in 3rd person view
    AttachmentClass = class'AK_Attachment_ScarabGun'

    MuzzleFlashSocket=Muzzle
    MuzzleFlashPSCTemplate=ParticleSystem'AK_Particle.Muzzle_Flashes.AK_GenericMuzzleFlash_PS_01'
    MuzzleFlashAltPSCTemplate=ParticleSystem'AK_Particle.Muzzle_Flashes.AK_GenericMuzzleFlash_PS_01'
    // Mesh used in the inventory pickup zone
    Begin Object Name=PickupMesh
        SkeletalMesh=SkeletalMesh'AK_ScarabGun.Meshes.AK_Scarabgun_Mesh_01'
    End Object
    PivotTranslation = ( Y = 10.0 )
}

    

Primary Projectile

    
        
class AK_Projectile_Scarab extends UTProjectile;

var vector initialFlightVector;
var float timeSinceLaunch;

function Init(vector Direction)
{
    timeSinceLaunch = 0.0;
    SetRotation(rotator(Direction));
    initialFlightVector = Direction;
    Velocity = Speed * Direction;
    Acceleration = AccelRate * Normal(Velocity);
}

//Every time we get new time, move the projectile a little bit, to simulate the scarabs "flying."
simulated function Tick(float DeltaTime)
{
    local vector newFlightVector;

    timeSinceLaunch += DeltaTime;
    if( timeSinceLaunch < 0.1 )
    {
        initialFlightVector = Normal( Velocity );
        return;
    }

    newFlightVector.X = initialFlightVector.X;
    newFlightVector.Y = initialFlightVector.Y * 2 * FRand();
    newFlightVector.Z = initialFlightVector.Z * 2 * FRand();

    Velocity = Speed * newFlightVector;
}

DefaultProperties
{
    Speed=2500                      //DH: Changed from 1400
    MaxSpeed=5000                   //DH: Changed from 5000
    AccelRate=1000                  //DH: Changed from 3000

    MyDamageType = class'AKHET.AK_DamageType_ScarabShot'
    Damage=12                       //DH: Changed from 26
    DamageRadius=0
    MomentumTransfer=0

    ProjFlightTemplate=ParticleSystem'AK_WeaponParticles.Scarab_Bullet.AK_ScarabBullet_PS_01'
    ProjExplosionTemplate=ParticleSystem'AK_Particle.ImpactParticle.AK_BulletImpact_PS_01'
    LifeSpan=3.0

    AmbientSound = SoundCue'AK_Weapon_Sounds.AK_Weap_Scarab.AK_Weap_ScarabPri_Cue' //XS:this is the scarab primary projectile sound. 

    RemoteRole=ROLE_SimulatedProxy
    bNetTemporary=false
}

    

Damage and Effects

    
        
simulated function SwarmWithScarabs()
{
    SetTimer( secondsBetweenSwarmDamage, true, 'SwarmDamageOverTime' );
    timeSinceSwarmHitSeconds = 0;

    WorldInfo.MyEmitterPool.SpawnEmitter(
        ParticleSystem'AK_Particle.PawnSwarm',  //Spawn the swarm emitter
        self.Location,                          //at our position
        rot( 0, 0, 0 ),                         //with normal rotation
        self );                                 //and attach it to us in case we move
}

function SwarmDamageOverTime()
{
    local float damageThisTick;
    local Controller damageInstigator;
    local vector momentumFromHit;

    damageThisTick = swarmDamagePerSecond * secondsBetweenSwarmDamage;
    damageInstigator = None; //This ideally should come from the original shooter.
    momentumFromHit = vect( 0, 0, 0 ); //Bugs are too small to give momentum!
    TakeDamage( damageThisTick, damageInstigator, self.location, momentumFromHit, class 'UTDmgType_Burning' );

    timeSinceSwarmHitSeconds += secondsBetweenSwarmDamage;
    if( timeSinceSwarmHitSeconds >= swarmLifetimeSeconds )
        ClearTimer( 'SwarmDamageOverTime' );
}