Chapter 7 – Creating some followers
Implementing a new sort of Pawn
In this tutorial we will explore the implementation of a simple AI. After you master this basic implementation, I strongly suggest you take a look at how Dungeon defense implemented their AI.
We will begin by defining a new unreal script class that we will name FollowerPawn. This will represent the other member of your squad. As a simple observation, you would be right to say that this pawn is identical to the basic avatar that you used since chapter 1.
class FollowerPawn extends GamePawn;
simulated event PostBeginPlay()
{
super.PostBeginPlay();
SpawnDefaultController();
}
simulated function name GetDefaultCameraMode( PlayerController RequestedBy )
{
return 'Isometric';
}
defaultproperties
{
ControllerClass=class'FollowerAIController'
Begin Object Name=CollisionCylinder
CollisionRadius=+0034.000000
CollisionHeight=+0034.000000
BlockZeroExtent=FALSE
End Object
Components.Remove(Sprite)
Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
ModShadowFadeoutTime=0.25
MinTimeBetweenFullUpdates=0.2
AmbientGlow=(R=.01,G=.01,B=.01,A=1)
AmbientShadowColor=(R=0.15,G=0.15,B=0.15)
LightShadowMode=LightShadow_ModulateBetter
ShadowFilterQuality=SFQ_High
bSynthesizeSHLight=TRUE
End Object
Components.Add(MyLightEnvironment)
Begin Object Class=SkeletalMeshComponent Name=InitialSkeletalMesh
CastShadow=true
bCastDynamicShadow=true
bOwnerNoSee=false
LightEnvironment=MyLightEnvironment;
BlockRigidBody=true;
CollideActors=true;
BlockZeroExtent=true;
BlockNonZeroExtent=TRUE
bIgnoreControllersWhenNotRendered=TRUE
bUpdateSkelWhenNotRendered=FALSE
PhysicsAsset=PhysicsAsset'CH_AnimCorrupt.Mesh.SK_CH_Corrupt_Male_Physics'
AnimSets(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_AimOffset'
AnimSets(1)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
SkeletalMesh=SkeletalMesh'CH_IronGuard_Male.Mesh.SK_CH_IronGuard_MaleA'
End Object
Mesh=InitialSkeletalMesh;
Components.Add(InitialSkeletalMesh);
name='FollowerOne';
}
Implementing a new intelligence
With this basic pawn implementation we will set on to define a controller to help make it move. At some point I do hope he also dances but there is no default UT animations for that
Lets add a new unreal script file and name it FollowerAIController. I like to put the AI in the name so its clear that it is extending GameAIController for future refenrences. NOTE: copy/paste works fine even if text is not all visible.
class FollowerAIController extends GameAIController;
var() Vector TempDest;
var float OffsetToHero;
function SetOrders(name NewOrders, Controller OrderGiver)
{
local Actor DestActor;
`Log("Received order : "@NewOrders);
if(NewOrders == 'Follow')
{
if(IsInState('ScriptedMove'))
{
PopState(true);
}
DestActor = OrderGiver.Pawn;
ScriptedRoute = Route(DestActor);
ScriptedRouteIndex = 0;
if (ScriptedRoute.RouteList.length == 0)
{
//`warn("Invalid route with empty MoveList for scripted move");
ScriptedMoveTarget = DestActor;
PushState('ScriptedMove');
}
else
{
PushState('ScriptedRouteMove');
}
}
}
//Overwrite AIController's ScriptedMove state to make use of the NavigationHandle instead of the old way
state ScriptedMove
{
function bool FindNavMeshPath()
{
// Clear cache and constraints (ignore recycling for the moment)
NavigationHandle.PathConstraintList = none;
NavigationHandle.PathGoalList = none;
// Create constraints
class'NavMeshPath_Toward'.static.TowardGoal( NavigationHandle,ScriptedMoveTarget );
class'NavMeshGoal_At'.static.AtActor( NavigationHandle, ScriptedMoveTarget );
// Find path
return NavigationHandle.FindPath();
}
Begin:
`log("BEGIN STATE SCRIPTEDMOVE");
// while we have a valid pawn and move target, and
// we haven't reached the target yet
if( FindNavMeshPath() )
{
NavigationHandle.SetFinalDestination(ScriptedMoveTarget.Location);
`log("FindNavMeshPath returned TRUE");
FlushPersistentDebugLines();
NavigationHandle.DrawPathCache(,TRUE);
while( Pawn != None && ScriptedMoveTarget != None && !Pawn.ReachedDestination(ScriptedMoveTarget) )
{
if( NavigationHandle.ActorReachable( ScriptedMoveTarget) )
{
// then move directly to the actor
MoveTo( ScriptedMoveTarget.Location,ScriptedFocus, OffsetToHero, true );
}
else
{
// move to the first node on the path
if( NavigationHandle.GetNextMoveLocation( TempDest, Pawn.GetCollisionRadius()) )
{
// suggest move preparation will return TRUE when the edge's
// logic is getting the bot to the edge point
// FALSE if we should run there ourselves
if (!NavigationHandle.SuggestMovePreparation( TempDest,self))
{
MoveTo( TempDest, ScriptedFocus, OffsetToHero, true );
}
}
}
}
}
else
{
//give up because the nav mesh failed to find a path
`warn("FindNavMeshPath failed to find a path to"@ScriptedMoveTarget);
ScriptedMoveTarget = None;
}
`log("POPPING STATE!");
Pawn.ZeroMovementVariables();
// return to the previous state
PopState();
}
DefaultProperties
{
OffsetToHero=75;
}
Notes on the basis of the approach
I propose something akin to the UT bot’s SetOrders function so that you can be able to search for similar code in the UT game implementation. As such, I used a scheme like the one for controlling bots in team matches. It may not suit you for all your needs, but it will definitely make it easier to understand the principles of controlling the different basic behaviors of UT bots. If you are not familiar with the concept of state machines, I strongly suggest you get the book Programming Game AI by Example of Math Buckland. I gave this book to read to all my new employees to read. It generally makes it easier to understand behavior programming when you come from say a more IT background.
In the SetOrders function, we have only one command that the bot understand and its the ‘follow’ instruction. If the bot is asked to follow, it will check if it can navigate with path nodes first. if no such nodes exists, he will use the navmesh. You should notice that the bot will not come closer than the OffsetToHero variable defined in the DefaultProperties. The path node navigation is already defined in AIController so no need to redefine it here. What we added was a state to navigate on the navmesh. Now I do not have any merit here, I merely almost copied the example on the UDK page for navmesh implementation. Of note, this code will need further testing and fixing.
Chapter 7 spawning the followers
Nice tutorial. I have some issues when trying to increase the size of the CollisionCylinder (for bigger pawns), if I make it bigger 64×64, the pathfinding stop working, I try a solution found on the epic forums (http://forums.epicgames.com/showpost.php?p=27009970&postcount=3) that says that increasing the NavMeshGen_MaxPolyHeight in Scout.uc will solve the problem but it didn’t work. Any advice on making navmeshses with bigger characters?
You have to be careful about resizing that cylinder, there are specific sizes for pawns, vehicles and other that the system will treat as small, big or medium. If you change those, you will have to tweak the navigation function, you should check on the UDK documentation around here : http://udn.epicgames.com/Three/NavigationMeshTechnicalGuide.html
Thanks for this tutorial! We are having a slight problem where it seems as though there is some sort of debug mode for pathing pylons that has been turned on during gameplay. It looks like this: http://img137.imageshack.us/img137/4130/pathingissue.jpg
Every pylon with bots in them flashes their debug mesh on the screen. Taking the bots away solves the issue, but obviously that is not the solution. Any ideas?
You should not call the debug functions (this is a UDK function) that shows debug informations for paths of AI and nodes and such. Its just called by default in my code somewhere. Look into the code its pretty obvious.