Chapter 3 – Making the pawn move to our will
Now we have everything in place to get started on the fun part, namely player movement. Most of the following code will be harder than before, get ready to be lost a bit. I will try to the best of my ability to explain what is going on.
Most of the work from now on will occur in the IsometricGamePlayerController. In this chapter we will explore states and try to understand how they work.
Using the mouse to zoom in/out
Before tackling more challenging topics, lets try something easy to understand : user inputs. All user inputs are stored in a file in <UDKFolder>\UTGame\Config\UTInput.ini. Should you like to map a function to a specific input name the engine understand, its here you are going to do it. For more information, a thread is available here. Lets first examine what a binding line looks like :
Bindings=(Name=”XboxTypeS_LeftY”,Command=”GBA_MoveForward_Gamepad”)
What this line is declaring is that when the user inputs XboxTypeS_LeftY (this should be obvious its for the XBOX controller inputs) execute the following code command named GBA_MoveForward_Gamepad. Now if you search for “GBA_MoveForward_Gamepad” you will not find it in the code at all anywhere. Now inspect PlayerInput.uc and you will find that there is some difference between what is exactly declared here and what function it is linked to in the code. This is engine based and beyond the scope of this tutorial, lets understand that there is some functions pretty obvious and other more obscure. For what interest us at the moment are how to scroll with the mouse wheel. It appears there is an entry for that :
Bindings=(Name=”MouseScrollUp”,Command=”GBA_PrevWeapon”)
Bindings=(Name=”MouseScrollDown”,Command=”GBA_NextWeapon”)
Search for GBA_PrevWeapon in UTInput.ini and you will see that it was an alias for another command, both function PrevWeapon and NextWeapon exist in basic Player Controller and control the weapon switching. Since we are making and Isometric game tutorial with zoom we will redefine those functions.
exec function NextWeapon()
{
`Log(“MouseScrollUp”);
PlayerCamera.FreeCamDistance += 64;
}
exec function PrevWeapon()
{
`Log(“MouseScrollUp”);
PlayerCamera.FreeCamDistance -= 64;
}
This will change the zoom we saw in the Camera in chapter 1, look it up if its too far now. Notice that there will not be interpolation (smoothing) in this tutorial, you will have to implement it yourself. Fantastic how that was easy. Now we will tackle the mouse click.
Setting up advanced click
Now we could simply use the function StartFire which is called when the user click on the mouse, but that would be too easy. What we will do here, since we will want free movement (i.e. Holding the mouse and the pawn follows in the direction) we will have to check for
Implementing the mouse down event
For those of you knowledgeable about the mouse down function, it is not implemented by the engine so we have to do it ourselves with a little timer. Copy the code and read, compile later when I instruct you to, this part is rather long.
Declare a float variable that will be accumulating delta time between each frame. What is Delta time ? Its the elapsed time since last frame in float value. This is very handy for interpolating values and doing nifty maths over multiples frames. But since its a time value we can add it up to know how much time has passed.
var float DeltaTimeAccumulated; //Accumulate time to check for mouse clicks
we also need to know which mouse button was pushed :
var bool bLeftMousePressed; //Initialize this function in StartFire and off in StopFire
var bool bRightMousePressed; //Initialize this function in StartFire and off in StopFire
To accumulate properly time we need to do it each frame. What better function than PlayerTick ?
/******************************************************************
*
* TUTORIAL FUNCTION
*
* PlayerTick is called each frames.
*
******************************************************************/
event PlayerTick( float DeltaTime )
{
super.PlayerTick(DeltaTime);
//Set the location of the 3d marker that moves with the mouse.
MouseCursor.SetLocation(MouseHitWorldLocation);
//We use the right mouse button to move, change it to suit your need !
if(bRightMousePressed)
{
//accumulate the time for knowing how much time the button was pressed.
DeltaTimeAccumulated += DeltaTime;
}
}
Movement will occur on right mouse press in this tutorial, change it after the tutorial if you wish but for the sake of following properly, keep it like this for now. When the Right mouse is held, we accumulate time. Thats all that is to take note for the PlayerTick for the moment.
To initiate this mouse held or mouse click we need to initialize all the variables inside the StartFire function.
/******************************************************************
*
* TUTORIAL FUNCTION
*
* StartFire is called on mouse pressed, here to calculate a mouse click we
* set the timer to 0, then initialize mouseButtons according to function
* parameter and set the initial destination of the mouse press. Real
* process is in PlayerTick function.
*
******************************************************************/
exec function StartFire(optional byte FireModeNum)
{
//Pop all states to get pawn in auto moving to mouse target location.
PopState(true);
//Set timer
DeltaTimeAccumulated =0;
//Set initial location of destination
SetDestinationPosition(MouseHitWorldLocation);
//Initialize this to false, so we can at least do one state-frame and evaluate distance again.
bPawnNearDestination = false;
//Initialize mouse pressed over time.
bLeftMousePressed = FireModeNum == 0;
bRightMousePressed = FireModeNum == 1;
//comment these if not needed
if(bLeftMousePressed) `Log("Left Mouse pressed");
if(bRightMousePressed) `Log("Right Mouse pressed");
}
This function introduces an important new variables : bPawnNearDestination. You should add it now with the other variable on top of the file :
var bool bPawnNearDestination; //This indicates if pawn is within acceptable offset of destination to stop moving.
This variable will instruct our manual move states that the pawn is within acceptable range of the destination vector. We also need a variable to store remaining distance if you want to debug it to the hud at some point. For the moment we will only output to console, let it be your homework to plugin this variable on screen in real time when debug is activated (see chapter 2). Add the following close to bPawnNearDestination :
var float DistanceRemaining; //This is the calculated distance the pawn has left to get to MouseHitWorldLocation.
So now we are armed with a variable to let the pawn stop and one to calculate remaining distance. Lets define when the player releases the mouse button with StopFire :
/******************************************************************
*
* TUTORIAL FUNCTION
*
* StopFire is called on mouse release, here check the time the buttons have
* been pressed (this should be enhanced, but it was kept simple for the tutorial).
* if DeltaAccumulated < 0.1300 (medium time mouse click) then we calculate it as
* a mouse click, else simply stop any state running. EDIT: You must understand only
* a single timer has been kept for all mouse button, you should duplicate a timer
* for each individual mouse button if you want to support thing like auto-fire while
* walking in a direction.
*
******************************************************************/
exec function StopFire(optional byte FireModeNum )
{
`Log("delta accumulated"@DeltaTimeAccumulated);
//Un-Initialize mouse pressed over time.
if(bLeftMousePressed && FireModeNum == 0)
{
bLeftMousePressed = false;
`Log("Left Mouse released");
}
if(bRightMousePressed && FireModeNum == 1)
{
bRightMousePressed = false;
`Log("Right Mouse released");
}
//If we are not near destination and click occured
if(!bPawnNearDestination && DeltaTimeAccumulated < 0.13f)
{
//Our pawn has been ordered to a single location on mouse release.
//Simulate a firing bullet. If it would be ok (clear sight) then we can move to and simply ignore pathfinding.
if(FastTrace(MouseHitWorldLocation, PawnEyeLocation,, true))
{
//Simply move to destination.
MovePawnToDestination();
}
else
{
//fire up pathfinding
//ExecutePathFindMove();
}
}
else
{
//Stop player from going on in that direction forever. This normally needs to be done
//after a long mouse held. This will make the player stop its current MoveMousePressedAndHold
//state.
PopState();
}
//reset accumulated timer for mouse held button
DeltaTimeAccumulated = 0;
}
Now the ExecutePathFindMove() has been commented out on purpose here. We will visit that later, its just for you to see the global picture. Lets talk about the first Log function here, remember at then end of chapter 2 we discussed the @ for concatenation. Here we output the content of the variable with some text for presentation.
We then proceed to initialization of mouse buttons. Then if we did not reach destination and time accumulated is smaller than an average click (change this to your liking), we try to move. First thing we do is check for obstacle using a fastTrace that returns true if nothing is between you and the destination. If something is blocking the pawns view to destination, we need the navigation algorithms.
Keep the PopState under the MoveMousePressedAndHold comment, it will be handy later, even if we get some warnings. At the end of mouse release, initialize the timer again. This could be commented since we initialize in StartFire, this is double protection.
We must declare the function MovePawnToDestination if we ever want this code to compile, lets add it now :
/******************************************************************
*
* TUTORIAL FUNCTION
*
* MovePawnToDestination will push a MoveMouseClick state that will make
* the pawn go to a single destination with a mouse click and then
* stop near the destination.
*
******************************************************************/
function MovePawnToDestination()
{
`Log("Moving to location without pathfinding!");
SetDestinationPosition(MouseHitWorldLocation);
PushState('MoveMouseClick');
}
This function only set the destinationPosition (Built-in controller function) and pushes our first state we will see in this tutorial :
MouseMoveClick
/******************************************************************
*
* TUTORIAL STATE (MoveMouseClick)
*
* MoveMouseClick is the state when a mouse button is pressed
* once (simple click). Simply go to a set destination.
*
*
******************************************************************/
state MoveMouseClick
{
event PoppedState()
{
`Log("MoveMouseClick state popped, disabling StopLingering timer.");
//Disable all active timers to stop lingering if they are active.
if(IsTimerActive(nameof(StopLingering)))
{
ClearTimer(nameof(StopLingering));
}
}
event PushedState()
{
//Set a function timer. If the pawn is stuck it will stop moving
//by itself.
SetTimer(3, false, nameof(StopLingering));
if (Pawn != None)
{
// make sure the pawn physics are initialized
Pawn.SetMovementPhysics();
}
}
Begin:
while(!bPawnNearDestination)
{
`Log("Simple Move in progress");
MoveTo(GetDestinationPosition());
}
`Log("MoveMouseClick: Pawn is near destination, go out of this state");
PopState();
}
When the state is pushed into the state stack the function PushedState is called for the state. We set a timer here so that when the player is on auto-pilot, if he get stuck on a wall, he will stop jittering after 3 seconds. When the state pops, the PopState event will be called and we disable the timer. What the state basically do is While you are not near the destination, move to it, when finished pop the state.
Here is the StopLingering function :
/******************************************************************
*
* TUTORIAL FUNCTION
*
* This is a timer function, it prevents the MoveMouseClick state from
* looking to get stuck in an obstacle. After a set of seconds it
* pushes the entire state stack so the pawn revert to PlayerMove
* automatic state.
*
******************************************************************/
function StopLingering()
{
//Remove all current move state and query for input from now on.
`Log("Stopped lingering...");
PopState(true);
}
Do a compilation and if you did not miss a thing, everything should be fine. Run the game and click around, your pawn should move to a destination but a spring effect occurs. This is because the bPawnNearDestination is never set. We will fix this when a PlayerMove occurs. Now this function could be declared in each states, but again for simplicity we declare it at a global class level :
/******************************************************************
*
* TUTORIAL FUNCTION
*
* PlayerMove is called each frame, we declare it here inside the
* PlayerController so its general to all states. It can be possible
* to declare this function in each single state, having multiple
* PlayerMove scenario, but for the simplicity of the tutorial
* we have put it here in the class. It controls the player in that
* it does a distance check when moving. It calculates the remaining
* distance to the target. If target is within 2D(X,Y) offset, then
* set the var bPawnNearDestination for state control.
*
* Rotation
*
* This function overrides the controller rotation of the pawn. Depending
* on the situation (state) the pawn will either face a direction or rotate
* to face the destination.
*
******************************************************************/
function PlayerMove(float DeltaTime)
{
local Vector PawnXYLocation;
local Vector DestinationXYLocation;
local Vector Destination;
local Vector2D DistanceCheck;
super.PlayerMove(DeltaTime);
//Get player destination for a check on distance left. (calculate distance)
Destination = GetDestinationPosition();
DistanceCheck.X = Destination.X - Pawn.Location.X;
DistanceCheck.Y = Destination.Y - Pawn.Location.Y;
DistanceRemaining = Sqrt((DistanceCheck.X*DistanceCheck.X) + (DistanceCheck.Y*DistanceCheck.Y));
//`Log("DistanceCheck is"@DistanceCheck.X@DistanceCheck.Y);
//`Log("Distance remaining"@DistanceRemaining);
bPawnNearDestination = DistanceRemaining < 15.0f;
`Log("Has pawn come near destination ?"@bPawnNearDestination);
PawnXYLocation.X = Pawn.Location.X;
PawnXYLocation.Y = Pawn.Location.Y;
DestinationXYLocation.X = GetDestinationPosition().X;
DestinationXYLocation.Y = GetDestinationPosition().Y;
Pawn.SetRotation(Rotator(DestinationXYLocation - PawnXYLocation));
}
Suffice to say that this function get the distance between each vector and store the result as DistanceRemaining (we only check X and Y no height checks here). If the distance is less than 15 units we consider that the pawn has attained destination. The rest of the function fixes the pawn rotation to face the destination. For the rotation to work perfectly, we need to override certain controller functions :
function UpdateRotation( float DeltaTime )
{
/** This as been intentionnaly overriden and left blank for tutorial (see parent class to see what it does to controller) **/
}
function ProcessViewRotation( float DeltaTime, out Rotator out_ViewRotation, Rotator DeltaRot )
{
/** This as been intentionnaly overriden and left blank for tutorial (see parent class to see what it does to controller) **/
}
Compile and test all this. Everything should work and when you click in the map, try to click beyond obstacle and see that the pawn does not react because he knows his path is blocked. This concludes chapter 3. Congratulations, you have a pawn that moves on mouse clicks. Next chapter will be about making the pawn go around when you press and hold the mouse button.
EDIT 22 FEB 2010 : Some of you might have experienced the pawn floating, here is the fix for that from the forum thread : In your pawn add this in default properties or whichever script file you define your character skeletalmesh
CollisionType=COLLIDE_BlockAll Begin Object Name=CollisionCylinder CollisionRadius=+0021.000000 CollisionHeight=+0048.000000 End Object CylinderComponent=CollisionCylinder
Chapter 4 - Making it follow the mouse
Thank you for your superb tutorial.
It is exactly what I’ve wanted to see and expected to greatly expand my understanding on unreal script.
If you don’t mind, I’d like to make a quick question about ‘State’.
When looking through the logs, I found out that the ‘IsometircGamePlayerController’ starts in ‘PlayerWalking’ state.
In PlayerController.uc, however, the initial state, that is having ‘auto’ keyword, is PlayerWaiting which extends BaseSpectating. What I’m curious about is how the ‘PlayerWaiting’ is changed into ‘PlayerWalking’ in your tutorial.
Though I read through the related codes, the state changing point couldn’t be seen.
Thank you in advance.
@drzovil
PlayerWaiting is skipped when bDelayedStart=false is set in the GameInfo class, as the PlayerWaiting state only exists during the spectator/initiation phase before the default countdown.
Cheers for this great tutorial, its working wonders so far.
What i’m wondering if you know how to make it so instead of clicking to move the player, the player looks in the direction of the cursor and keeps WASD for movement. I’m working on a Slaughterhouse UT3 mod style game (If you’ve ever played it). Any help would be greatly appreciated.
Thanks
Tony: In the default code, unless you redefine WSAD keys they do move as you asked, I think this is default playercontroller class binding. Check it up in the class itself in the engine folder. For the players head, there is a sort of look at that you can inspect. It is also located in playercontroller’s PlayerMove function if I recall. It is pretty obvious where they set the head’s direction. For complete rotations (not straffing) you would have to reimplement a custom playerMove and check for velocity added to the player and do some rotation. To achieve nice rotation over time you would have to use interpolation. Check in Object.uc, all vector and rotator interpolation functions are located there, using find in files with visual studio you can locate code snippet that does it.
thx for this great tutorial, it really helps some newbies like me to make their own game.
I wonder that if I wanna change to left click and the character moves, is it just change the bRightMousePressed to bLeftMousePressed in playerTick()?
I’ve done that but I couldn’t make it works, it gave me something like “Warning: IsometricGamePlayerController_0 called PopStage() w/o a vaild state stack”, and the log “Left Mouse released” never appear. I just can’t get rid of that warning message, any help would be greatly apperciated.
thx a lot!!
I edited simulated function StopFire(optional byte FireModeNum ) to exec function StopFire.
Thank you for this awesome tutorial. I adapted your pawn class for my purposes. However, if I apply a force on a KActor object into the direction of the pawn, and the KActor hits the pawn, the pawn just disappears within the KActor object. The desired behaviour is either that the KActor object pushes the pawn in the moving direction or that the pawn stops the KActor object..
The KActor object has set up collision and the pawn has also set up the collision cylinder as described by the end of your post. In contrary, if I run with my pawn into the KActor object the pawn just stops..
Does anyone have an idea where the problem might lie? Should I rather try to find it in the pawn or in my KActor class?
Thanks a lot in advance
I love you man,
I was struggling to have my character move automatically by mouse left click then i used a wierd way by using tick function and a bool var to turn on and off the movement then i found moveto but couldn’t use it anyway with your great,well documented tutorial i did it.
cheers
Tony for getting the hit-trace of your mouse pointer you can use trace function
something like this
local TraceHitInfo hitInfo;
local Actor trac;
end = Location + normal(vector(Rotation))*32768; // trace to Infinity
trac = trace(loc, norm, end, Location, true,, hitInfo);
I’m an absolute begginer with UDK… just wanted to help…
From my last comment in chapter two, I’ve now continued on to the compile part of this section and it seems right click does work but only when I scroll out abit first, the cursor does also show up but only at the corners of the map. I’ve uploaded a video for you guys to see.. http://www.youtube.com/watch?v=R_TnrQUb4QQ I’ve basically been following it word for word on the march release of UDK, apart for some changes people have been mentioning in the comments. You’ll notice when I start right clicking as the white lines appear, I only use the WASD keys right at the start of the video. Sorry if you guys are against links here, just thought the video might help explain the problems that people will face in current UDK builds!
This looks like the ray is either cast indefinitly in the upper corner or it is casting at (0,0) 2d location and then translated in 3d coordinates which makes it look like its casting indefinitly. I suggest using the function that draw these rays to output the values of the destination coordinate inside the viewport. If I remember correctly, if you download the sources, it has commented lines for outputting text.
@Roychr
I think it maybe fixed using something here http://udn.epicgames.com/Three/DevelopmentKitGemsCreatingAMouseInterface.html#Getting%20a%20cursor%20on%20the%20screen but what in the code I need to change, I have no idea. (Completely new to scripting, I’m much more of a designer than a programmer ^^).
I used an other tutorial for my mouse, and now when i click, the pawn always goes in the middle of the map… any ideas? Using july version
Actually I used the one Alex linked