From the last tutorial, we have a camera to the right of the positive X axis but the player can still rotate to face any direction and move in whatever direction the player is facing. What we need is to prevent the player from turning and also have the player only move back and forth along the X axis. Player Movement On every frame, the PlayerController’s PlayerMove function is called. This function often overridden and implemented in the different movement states to provide state specific movement behavior. We’ll be overriding the function in the PlayerWalking state. This is the state the player is in when moving on solid ground. The function makes use of the current input to calculate the players new acceleration.
GetAxes(Pawn.Rotation,X,Y,Z); // Update acceleration. NewAccel = PlayerInput.aForward*X + PlayerInput.aStrafe*Y; NewAccel.Z = 0; NewAccel = Pawn.AccelRate * Normal(NewAccel);
GetAxes uses the pawns rotation to get 3 normalized vectors each representing the rotation in the 3 axes. i.e. the X vector points to the direction the player is facing. The forward input is then used to scale the X vector and calculate the forward acceleration.
Moving the player back and forth using the strafe inputs
In a sides-scroller, the player moves back and forth using the strafe inputs. To implement this, we need to scale our X/forward vector by the value of aStrafe instead of the aForward input and zero out the movement along the Y axis (the Z axis is already zeroes out by default).
NewAccel = PlayerInput.aStrafe*X; NewAccel.Y = 0; NewAccel.Z = 0;
The player now only moves along the X axis.
Disabling the player’s turn
You’ll notice that the player can still turn, we only want the player to turn to either face the positive X axis or the negative X axis i.e. always a 180 degree turn.
The player’s rotation is calculated within the PlayerController.UpdateRotation function, which goes something like this:
1) Calculate Delta to be applied on ViewRotation based on the input.
DeltaRot.Yaw = PlayerInput.aTurn; DeltaRot.Pitch = PlayerInput.aLookUp;
2) Let the ProcessViewRotation function calculate our rotation using the Delta.
ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot ); SetRotation(ViewRotation);
3) Finally set the paw’s rotation.
NewRotation = ViewRotation; NewRotation.Roll = Rotation.Roll; if ( Pawn != None ) Pawn.FaceRotation(NewRotation, deltatime);
What we need to do is calculate a new Delta depending on which direction the player is trying to move. While facing the positive X direction i.e. to the right, if the forward input is used (D on the keyboard) the Delta needs to be 0 and if the back input (A) is used the Delta needs to be 180 degrees.
Start by overriding the PlayerController.UpdateRotation function.
NOTE: I initially wanted to override the function within the PlayerWalking state and leave the logic within the other states intact but then I realised that this being a sidescroller game, the other states should default to sidescroller behavior and explicitly override when they need to.
The logic is that, with the player facing a certain direction, if the input is such that it requests the player to move in the opposite direction, we need to turn the player 180 degrees.
We’ll make use of vector dot products. An interesting property of the dot product of 2 vectors is that it it is less than zero, the two vectors are facing opposite directions i.e. the angle between them is between 90 and 180 degrees.
The two vectors we are referring to are
1) the player’s forward direction
To get the player’s forward direction, we’ll use the GetAxis function that we have seen previously.
GetAxes(Pawn.Rotation,X,Y,Z);
The player’s forward direction is off-course the X vector.
2) The forward input vector.The forward input comes from the PlayerInput.aStrafe (only because in a sidescroller, forward is the the strafe input ) which is positive when D is pressed (forward) and negative when A is pressed(backward). We start by creating a vector pointing in the positive x axis.
... local Vector ForwardInput; ForwardInput = vect(1.f, 0.f, 0.f); ...
Then multiply this vector by the forward input, which will make it either positive or negative. We then normalize the vector before we use it to calculate the dot product.
...
if(PlayerInput.aStrafe!= 0.f)
{
ForwardInput = vect(1.f, 0.f, 0.f);
// normalize the vector
ForwardInput = Normal(ForwardInput);
}
NOTE: This is done within an if to make sure that there was a forward input, if we multiplied by a zero input, we’d end up with a zero vector which would not give us what we expect from the dot product.
Next we get the dot product of the player’s X vector and the forward input, if the two vectors are facing away from each other, we need to rotate the player by 180 degrees.
...
ForwardInput = Normal(ForwardInput);
if(X dot ForwardInput <= 0.f)
{
DeltaRot.Yaw = 180 * DegToUnrRot;
}
...
Run the game now to see what we have and try out the A and D keys.
You’ll notice a problem, the A key turns the player but, the player runs backward instead. The problem is that the A is still the back key. The A key should theoretically switch to the forward key when we are looking to the left. There are a number of ways to fix this.
Just to understand what going on we’ll first go the longer route then implement the easy fix.
Method 1 – Rotating the players X vector if we are facing left
Within the PlayerMove function, before we calculate the pawn’s new acceleration, we need to figure out if the player if facing the positive X direction (right) or the negative X direction (left). If the pawn/player is facing left, we rotate the X vector by 180 degrees so that it faces the opposite direction.
GetAxes(Pawn.Rotation,X,Y,Z);
if(X.X < 0)
{
X = X >> MakeRotator(0, DegToUnrRot * 180, 0);
}
// Update acceleration.
NewAccel = PlayerInput.aStrafe*X;
NOTE: the vector’s >> operator rotates the vector by the given rotator.
Method 2 – Using the camera’s rotation
Our camera is always looking at the player from the player’s side, the camera’s Y vector is therefore parallel to the player’s X vector and always pointing in the positive X direction, regardless of which way the player is looking. We’ll use the GetAxes function to get the camera’s Y vector and use it to calculate our new acceleration.
...
PlayerCamera.GetCameraViewPoint(CameraLocation, CameraRotation);
GetAxes(CameraRotation,X,Y,Z);
// Update acceleration.
NewAccel = PlayerInput.aStrafe*Y;
And the full code listing of our player controller class.
class SKPlayerController extends UDKPlayerController;
function UpdateRotation( float DeltaTime )
{
local Rotator DeltaRot, newRotation, ViewRotation;
local Vector ForwardInput, X, Y, Z;
ViewRotation = Rotation;
if (Pawn!=none)
{
Pawn.SetDesiredRotation(ViewRotation);
}
GetAxes(Pawn.Rotation,X,Y,Z);
ForwardInput = vect(1.f, 0.f, 0.f);
if(PlayerInput.aStrafe != 0.f)
{
ForwardInput *= PlayerInput.aStrafe;
ForwardInput = Normal(ForwardInput);
if(X dot ForwardInput <= 0.f)
{
DeltaRot.Yaw = 180 * DegToUnrRot;
}
}
// Calculate Delta to be applied on ViewRotation
DeltaRot.Pitch = PlayerInput.aLookUp;
ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot );
SetRotation(ViewRotation);
ViewShake( deltaTime );
NewRotation = ViewRotation;
NewRotation.Roll = Rotation.Roll;
if ( Pawn != None )
Pawn.FaceRotation(NewRotation, deltatime);
}
// Player movement.
// Player Standing, walking, running, falling.
state PlayerWalking
{
function PlayerMove( float DeltaTime )
{
local vector X,Y,Z, NewAccel, CameraLocation;
local eDoubleClickDir DoubleClickMove;
local rotator OldRotation, CameraRotation;
local bool bSaveJump;
if( Pawn == None )
{
GotoState('Dead');
}
else
{
PlayerCamera.GetCameraViewPoint(CameraLocation, CameraRotation);
GetAxes(CameraRotation,X,Y,Z);
// Update acceleration.
NewAccel = PlayerInput.aStrafe*Y;
NewAccel.Y = 0;
NewAccel.Z = 0;
NewAccel = Pawn.AccelRate * Normal(NewAccel);
if (IsLocalPlayerController())
{
AdjustPlayerWalkingMoveAccel(NewAccel);
}
DoubleClickMove = PlayerInput.CheckForDoubleClickMove( DeltaTime/WorldInfo.TimeDilation );
// Update rotation.
OldRotation = Rotation;
UpdateRotation( DeltaTime );
bDoubleJump = false;
if( bPressedJump && Pawn.CannotJumpNow() )
{
bSaveJump = true;
bPressedJump = false;
}
else
{
bSaveJump = false;
}
if( Role < ROLE_Authority ) // then save this move and replicate it
{
ReplicateMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
}
else
{
ProcessMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
}
bPressedJump = bSaveJump;
}
}
}
defaultproperties
{
CameraClass=class'SKPlayerCamera'
}