Tutorial Overview
In this tutorial, we will create an AI that can follow a patrol path and opportunistically attack the player. We will also see how we can control the lifecycle of a bot, including preventing them from being restarted by the game. We will use the UT* branch of classes for this tutorial.
Downloading the map
- To start, download this base map.
- Place the map in UDKGame/Content/Maps
Patrol System
L9PatrolPath
- Create the class
L9PatrolPath
, have it extend PathNode, ensure it is placeable - Set its sprite component to
Texture2D'MobileResources.HUD.AnalogHat'
- Give it the Patrol property NextNode of type L9PatrolPath
L9AISpawner
- Create the class
L9AISpawner
, have it extend L9PatrolPath, ensure it is placeable - Give it the static mesh
StaticMesh'NodeBuddies.NodeBuddy_PerchUp'
- Give it the Spawner property BotsToSpawn of type int, have it default to 3
- Give it the Spawner property SpawnInterval of type float, have it default to 2
- Set the property
bStatic
to false as a default
PostBeginPlay()
- Override to set the timer to SpawnInterval, have it loop
- Execute super postbeginplay
L9Bot SpawnBot()
- Create this function
- Place the following code in it:
Timer()
- Implement this function
- If BotsToSpawn is <= 0, return
- Call the function SpawnBot. If it does not return None, decrement BotsToSpawn
local L9Bot NewBot;
local Pawn NewPawn;
local rotator StartRotation;
NewBot = Spawn(class'L9Bot');
if (NewBot == None)
{
`log("Couldn't spawn "$class'L9Bot'$" at "$self);
return None;
}
StartRotation = Rotation;
StartRotation.Yaw = Rotation.Yaw;
NewPawn = Spawn(class'UTPawn',,,Location,StartRotation);
if ( NewPawn == None )
{
`log("Couldn't spawn "$class'UTPawn'$" at "$self);
NewBot.Destroy();
return None;
}
NewPawn.SetAnchor(self);
NewPawn.LastStartTime = WorldInfo.TimeSeconds;
NewBot.Possess(NewPawn, false);
NewBot.Pawn.PlayTeleportEffect(true, true);
NewBot.ClientSetRotation(NewBot.Pawn.Rotation, TRUE);
WorldInfo.Game.AddDefaultInventory(NewPawn);
WorldInfo.Game.SetPlayerDefaults(NewPawn);
NewBot.EnterPatrolPath(self);
return NewBot;
L9LinkGun
- Create the L9LinkGun class, have it extend UTWeap_LinkGun
- Set the default property WeaponRange to 500
byte BestMode()
- Override this function to return 0
bool CanAttack(Actor Other)
- Have this function return true if its super version returns true and the distance between other and instigator is <= our WeaponRange
L9Game
- Create the class
L9Game
, have it extend UTGame - Set the array property element 0
DefaultInventory
to the classL9LinkGun
RestartPlayer(Controller aPlayer)
- Override this function to test if aPlayer is an L9Bot and is in the state Dead
- If they are, call destroy on them
- Otherwise, call our super version
Logout(Controller exiting)
- Override this function to test if
exiting
is an L9Bot- If it is, simply return
- Otherwise, call our super version
L9Bot
- Create the class L9Bot, have it extend UTBot
- Give it the following properties:
var float AggroDistance;
var float EscapeDistance;
var float AttackDistance;
var float PatrolPointReachedThreshold;
var L9PatrolPath NextPatrolPoint;
var L9AISpawner MySpawner;
var private Actor _intermediate;
var private float _dist;
- Set the default of PatrolPointReachedThreshold to 50
- Set the default of AggroDistance to 600
- Set the default of EscapeDistance to 1500
- Set the default of AttackDistance to 300
State FollowingPatrolPath
- Create this state, make it an auto state
- Create the
Begin
state label. Place the following code under it:- Check if NextPatrolPoint is not None
- If it's not, check if the next patrol point is directly reachable
- If it is, move towards it
- If not, assign an intermediate path into _intermediate
- Move toward _intermediate
- If it's not, check if the next patrol point is directly reachable
- Call LatentWhatToDoNext
- Check if NextPatrolPoint is not None
State HuntingPlayer
- Create this state
- Create the
Begin
state label. Place the following code under it:- Check if Enemy is not None and Pawn is not None
- Calculate the distance between our pawn and the Enemy then place this value in _dist
- If _dist is greater than the escape distance or a fast trace fails between our pawn's location and the enemy location, set Enemy to None
- Otherwise if _dist is <= attack distance
- Have the pawn switch to its best weapon then fire weapon at enemy
- Otherwise
- If enemy is directly reachable, move towards the enemy
- Otherwise, assign an intermediate path into _intermediate
- Move toward _intermediate
- Call LatentWhatToDoNext
- Check if Enemy is not None and Pawn is not None
bool IsPawnTouchingActor(Actor other)
- Implement this function
- Have this function return false if pawn is None or if other is None
- Return true if the distance between our pawn and other is less than the patrol point reached threshold
EnterPatrolPath(L9PatrolPath path)
- Implement this function
- Set the next patrol point to path
- if path is an L9AISpawner, assign it to MySpawner
- If path is none, go to state ''
- Otherwise, go to state 'FollowingPatrolPath'
PawnDied(Pawn P)
- Implement this function
- If MySpawner is not none, increment its BotsToSpawn variable
- Call the super version of this function
HuntEnemy(Pawn p)
- Implement this function
- If our Pawn is None, return
- Assign P into Enemy
- Assign P into Focus
- Go to state 'HuntingPlayer' at label 'Begin'
NotifyTakeHit(Controller InstigatedBy, Vector HitLocation, int Damage, class<DamageType> damageType, Vector momentum)
- Implement this function
- Call the super version of it
- If InstigatedBy is not None and its Pawn is not None
- Call HuntEnemy on its pawn
Possess(Pawn aPawn, bool bVehicleTransition)
- Implement this function
- Call the suerp version of this function
- If NextPatrolPoint is not None, go to state 'FollowingPatrolPath' at label 'Begin'
protected event ExecuteWhatToDoNext()
- Implement this function
- Create the local variable of type PlayerController PC
- If our pawn is None, call destroy() and return
- Call GetALocalPlayerController() and assign the result into pc
- If pc is not None and pc's Pawn is not none and the distance between our pawn and pc's pawn is <= aggro distance then call HuntEnemy on pc's pawn and return
- If the next patrol point is not none:
- Call IsPawnTouchingActor passing it Next Patrol point. If it returns true, assign NextPatrolPoint.NextNode into NextPatrolPoint
- If next patrol point is not none, go to state 'FollowingPatrolPath' at label 'Begin'
Updating the Map
- Open your map
- Place an L9AISpawner in one of the rooms
- Place another L9AISpawner in another room on the other side of the map
- Create two loops of L9PatrolPath objects spanning from each spawner
- Have the loops move through 4 rooms creating a cyclic path
- Connect the L9PatrolPath objects via their
NextNode
property- Include the L9AISpawner objects in this process
- Create a path node at every hallway entrance of each room and one additional path node in the center of each empty room
- Rebuild geometry
- Rebuild lights (without lightmass)
- Rebuild paths
- Save and exit
Completed Tutorial
Download the completed tutorial code here.
- Place the
Lab9
folder in Development/Src - Place
L9-Map.udk
in UDKGame/Content/Maps - Before running the game or opening the map in the editor, be sure to compile