Zombie, do thy bidding.

Once again, I want to state this builds on tegleg’s tutorial which can be found here http://forums.epicgames.com/showthread.php?t=755213

Script wise, this comes in two parts, one for the Pawn, on for the AI controller. So lets look at the Pawn first.

class TestZombiePawn extends UTPawn
Placeable;

var Pawn P; // variable to hold the pawn we bump into

// members for the custom mesh
var SkeletalMesh defaultMesh;
var MaterialInterface defaultMaterial0;
var AnimTree defaultAnimTree;
var array<AnimSet> defaultAnimSet;
var AnimNodeSequence defaultAnimSeq;
var PhysicsAsset defaultPhysicsAsset;

simulated function SetCharacterClassFromInfo(class<UTFamilyInfo> Info)
{
  Mesh.SetSkeletalMesh(defaultMesh);
  Mesh.SetMaterial(0,defaultMaterial0);
  Mesh.SetPhysicsAsset(defaultPhysicsAsset);
  Mesh.AnimSets=defaultAnimSet;
  Mesh.SetAnimTreeTemplate(defaultAnimTree);

}

simulated event Bump( Actor Other, PrimitiveComponent OtherComp, Vector HitNormal )
{
// `Log("Bump");

     Super.Bump( Other, OtherComp, HitNormal );

  if ( (Other == None) || Other.bStatic )
    return;

  P = Pawn(Other); //the pawn we might have bumped into

  if ( P != None)  //if we hit a pawn
  {
            if (P.Health >1) //as long as pawns health is more than 1
     {
             P.Health --; // eat brains! mmmmm
           }
        }
}

defaultproperties
{
  defaultMesh=SkeletalMesh'CH_IronGuard_Male.Mesh.SK_CH_IronGuard_MaleA'
  defaultAnimTree=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
  defaultAnimSet(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
  defaultPhysicsAsset=PhysicsAsset'CH_AnimCorrupt.Mesh.SK_CH_Corrupt_Male_Physics'
  Begin Object Name=WPawnSkeletalMeshComponent
    AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
  End Object
  RagdollLifespan=180.0
  ControllerClass=class'TestZombieBot'
  Health=10;
  SightRadius = 5000.0;
  HearingThreshold=2500;

}

I’ve got to be very honest here, at this point, I’ve changed hardly anything aside from the SightRadius and HearingThreshold for testing and reducing the Health of each Zombie Pawn to 10 so it’s a one hit kill.

Where I have changed more is in the AI.

class TestZombieBot extends GameAIController;

var Pawn thePlayer; //variable to hold the target pawn http://forums.epicgames.com/showthread.php?t=755213
var Vector ShambleTarget;
var Vector HuntTarget;

simulated event PostBeginPlay()
{
  super.PostBeginPlay();
}

auto state Looking
{
Begin:
  if (thePlayer != None)  // If we seen a player
    {
      hunt();
    }else{

      shamble();
    }
}

state gotoLocShamble
{
Begin:
  //DebugPrint("Going to move to random position");
  MoveTo(ShambleTarget);
  SetTimer(RandRange(1.0,10.0), TRUE, 'checkWhereToGo');
}

state gotoLocHunt
{
Begin:
  //DebugPrint("Move to player");
  MoveTo(HuntTarget);
  GoToState('Looking');
}

event SeePlayer(Pawn SeenPlayer) //bot sees player
{
    thePlayer = SeenPlayer; //make the pawn the target
    //DebugPrint("I see you");
    hunt();
}

simulated event checkWhereToGo()
{
  goToState('Looking');
}

function shamble()  
{
    
  local int OffsetX;
  local int OffsetY;
  thePlayer=None;
  Pawn.GroundSpeed = 50;
  //DebugPrint("Shambling Around");  
  OffsetX = 250*(1-Rand(3));
  OffsetY = 250*(1-Rand(3));
  ShambleTarget.X = Pawn.Location.X + OffsetX;
  ShambleTarget.Y = Pawn.Location.Y + OffsetY;
  ShambleTarget.Z = Pawn.Location.Z;
  GoToState('gotoLocShamble');
}

function hunt()
{
  //DebugPrint("BRAINS!!!!!!");
  ClearTimer();
  Pawn.GroundSpeed = 450;
  HuntTarget = thePlayer.Location;
  thePlayer=None;
    GoToState('gotoLocHunt'); 
}

simulated private function DebugPrint(string sMessage)
{
  GetALocalPlayerController().ClientMessage(sMessage);
}

defaultproperties
{

}

The first thing to notice is two new variables have been set up to keep track of where the pawn is to go.

var Vector ShambleTarget;
var Vector HuntTarget;

Initally I had one variable for the target destination, but I’ve got an idea to allow more complex AI in the future so I have two variables, one is ShambleTarget, a target destination for the zombie whilst it’s milling around in a idle state; the second is HuntTarget which records that last position that this zombie saw the player.

So a break down of each state, event and function.

  • States
    • Looking
      • In this state the enemy has a choice. Do I know what the target is? If thePlayer is equal to something, then I should be hunting it down – hunt(); If thePlayer isn’t equal to something, I don’t have a target or potential food source, then I should just be shambling around – shamble();
    • gotoLocShamble
      • In this state the zombie is shambling, it is moving to it’s a position declared by the function shamble() randomly moving to the right, to the left, back, stopping, general zombie behaviour. To make the zombie have a random life of it’s own, it will call the checkWhereToGo event at some point between 1 and 10 seconds so it shambles around.
    • gotoLocHunt
      • In this state the zombie is going straight for the player, no messing around once it reaches it’s target it will go back to the Looking state
  • Events
    • SeePlayer
      • These zombies are like us, they have a field of vision (The angle that you can see; you can’t see behind you), but are generally long sighted. When the zombie sees the player, this event is triggered. It saves a reference to the player it’s seen as thePlayer and calls the hunt() function.
    • checkWhereToGo
      • All this event does is set the zombie into the looking state. Why didn’t I just use the GoToState method? Because I wanted to have it possible to set a timer in the gotoLocShamble state to set a random timer to trigger the event and I couldn’t use GoToState there.
  • Functions
    • Shamble
      • This function causes the zombie to wander aimlessly, but to enable that we first to have to set thePlayer to none thePlayer=None;. If we don’t then the Looking state will over ride the shamble function because it checks to see if thePlayer has a value. Next the speed at which the zombie is moving is slowed down. Pawn.GroundSpeed = 50;. Then a random offset is set, but due to the Maths the result will be constrained to -250, 0, +250. Here’s the breakdown.OffsetX = 250*(1-Rand(3));Rand(3) will give an integer that is either 0,1 or 2.1-Rand(3) will subtract 0,1,or 2 from 1 give us…
        1-0=1
        1-1=0
        1-2=-1 

        250*(1-Rand(3)) will give us

        250*1 = 250
        250*0 = 0
        250*-1 = -250

        So by offsetting the X and Y we will have the options of

        (250,250) or (250,0) or (250,-250) or (0,250) or (0,0) or (0,-250) or (-250,250) or (-250,0) or (-250,-250)

        or to put it another way

        Top-right or Top or Top-left or right, or stand still or left or bottom right or bottom or bottom-left. This is then offset against the current X and Y of the zombie

         ShambleTarget.X = Pawn.Location.X + OffsetX;
        
        ShambleTarget.Y = Pawn.Location.Y + OffsetY;
        but the Z axis is set to the same as the Zombie current vertical position so it doesn’t fly up, even though gravity will still affect it.¬†Once shambleTarget has been set, then the zombie is placed into the gotoLocShamble state.
    • Hunt
      • Hunt is simpler. First we clear the timers so that the zombie doesn’t trigger the checkWhereToGo event that was set up in gotoLocShamble state. Next the speed of the zombie is ramped up to 450 so it really runs at the player. The position of the player is set to HuntTarget and we set the state to gotoLocHunt which will send the zombie towards the player. in all of this thePlayer has been set to none so that when the looking state is called it will be given the option to hunt or shamble.

One thing to note is that the SeePlayer event will set thePlayer around 3 times a second, constantly updating the position of the player.

Finally in your GameInfo class, you will need to add this line

DefaultPawnClass=class’RetroShooter.MyPawn’

Into your DefaultProperties.

Next, using Kismet

This entry was posted in Uncategorized. Bookmark the permalink.