Dual Sticks shooter in UDK.

I’m starting to get my head around UnrealScript a bit more and I have managed to implement Dual Sticks to the game. This is intended to become a game along the lines of  Zombie Apocalypse, Lara Croft and the Guardian of Light and Geometry Wars, so for the time being, I am working on the presumption that the user will use a XBox controller. If you are now up in arms that for an engine that is mainly played on a PC, stop right there. I am making this for me at the moment and I really don’t like to play games on a PC. This is intended to be a balls to the wall, arcade style (Add Gauntlet, Alien Breed, Smash TV and Robotron to the list of influences) so for me, a mouse keyboard combo isn’t on my list of musts. That said a Keyboard mouse combo would be an eventual feature, W,A,S,D for movement and the mouse to rotate, but I had enough headaches getting to this point. Anyway, I do digress. Since last time I have added some more features, stripped out some and arrived at a point that I’m happy with, although it is occurring to me that shooting up a hill or ramp will be a problem, but I have an idea about that…

So what has changed? Well, the RetroIsoCamera.uc hasn’t changed at all. It still is placed far away with a tight FOV to give the faux Iso perspective. The GameInfo has changed (RetroshooterGameInfo.uc) but only to switch so it extends UTGame instead of GameInfo, this allows the weapons to be used without having to write a whole new bunch of code). Most of the new goodness is in the PlayerController and the Pawn.

MyPawn.uc

class MyPawn extends UTPawn;

simulated event PostBeginPlay()
{
  super.PostBeginPlay();
  `Log("Custom Pawn up");
}

simulated function name GetDefaultCameraMode( PlayerController RequestedBy )
{
    return 'Isometric';
}

simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV )
{
        return false;
}

simulated singular event Rotator GetBaseAimRotation() //Found from cached site http://webcache.googleusercontent.com/search?q=cache:GevFWh1ARvgJ:thronic.com/devnotes/UDK%2520Third%2520Person%2520Camera.htm+udk+override+aim&cd=4&hl=en&ct=clnk&gl=uk&source=www.google.co.uk
{
   local rotator   POVRot, tempRot;

   tempRot = Rotation;
   tempRot.Pitch = 0;
   SetRotation(tempRot);
   POVRot = Rotation;
   POVRot.Pitch = 0;

   return POVRot;
}  

simulated function FaceRotation(rotator NewRotation, float DeltaTime)
{
    SetRotation(NewRotation);
}

defaultproperties
{
}

So what does all this do then Guv?

Well, MyPawn now extends UTPawn which means all the setting of the meshes doesn’t need to be done (For now).The GetBaseAimRotation() function was found at Thronic.com but the link was dead, so this part was retrieved from a cached page on Google. The gist of this snippet is that it sets the aim of the player to be relative to the direction the player is facing, rather than the centre of the camera by restting the pitch to 0 degrees (Although you should be aware that Pitch, Roll and Yaw are not measured in degrees by a 16bit integer starting at 0 and finishing at 65535. From what I understand, it’s faster to send rotation information over a network as a double byte rather than a float. Anyway, 0 still = 0 degrees, by 180 degrees is now 32767 (Half of 65535). Most interestingly for this is 45 degrees; the angle I have placed the camera, comes out as 8191.

CalcCamera is in there for a reason I’m not too sure about. Without it, UDK sets the camera to firstperson and a bad firstperson at that. So I’ve left it in.

FaceRotation is called from the RetroShooterPlayerController so set the direction the player is facing.

RetroShooterPlayerController.uc

class RetroShooterPlayerController extends GamePlayerController;
var Rotator playerRot;
state PlayerWalking
{
ignores SeePlayer, HearNoise, Bump;
   function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
   {
    local Vector tempAccel;
    local Rotator CameraRotationYawOnly, playerRot;
    local bool moving, rotating;
    if( Pawn == None )
    {
      return;
    }
    moving = false;
    rotating = false;
    if(PlayerInput.aStrafe !=0.0 || PlayerInput.aForward !=0.0) moving = true;
    if(PlayerInput.aLookup !=0.0 || PlayerInput.aTurn !=0.0) rotating = true;
    tempAccel.Y =  PlayerInput.aStrafe * DeltaTime * 100;
    tempAccel.X = PlayerInput.aForward * DeltaTime * 100;
    CameraRotationYawOnly.Yaw = 8192;//Yaw, Roll and Pitch are measured from 0 to 65536
    tempAccel = tempAccel>>CameraRotationYawOnly; //transform the input by the camera World orientation so that it's in World frame
    Pawn.Acceleration = tempAccel;
    if(!rotating && moving)
    {
      playerRot.yaw = ((Atan2(PlayerInput.aForward,PlayerInput.aStrafe))+3.1416)*-10430.35;
    }else{
      if(rotating)
      {
        playerRot.yaw = ((Atan2(PlayerInput.aLookUp,PlayerInput.aTurn))+3.1416)*-10430.35;
      }
    }
    playerRot.Yaw-=8191;
    playerRot.Pitch=0;
    playerRot.Roll=0;
    Pawn.FaceRotation(playerRot,DeltaTime);
    CheckJumpOrDuck();
   }
}

function UpdateRotation( float DeltaTime )
{
  local Rotator   DeltaRot, ViewRotation;
  ViewRotation = Rotation;
  DeltaRot.Yaw   = 0;
  ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot );
}   

DefaultProperties
{
  CameraClass=class'RetroShooter.RetroIsoCamera';
}

simulated event PostBeginPlay()
{
super.PostBeginPlay();
`Log("I am alive");
}

So, whilst this isn’t that long at all, it has changed a lot. First change is the creation of two booleans to check if the player is moving or if the player is rotating. Every frame both are set to false and then I check to see if there is any input from the 360 controller.

PlayerInput.aForward is the input from the X axis of the left stick. If you imagine moving forwards in a FPS, this is the stick and the direction. PlayerInput.aStrafe is input from the Y axis of the same stick. You can find the Bindings for these in the DefaultInput.ini config file. In this instance the left stick will be used to move the player position (NOT ROTATION) so the boolean moving will be set to true or false depending on if the stick is pushed in either axis.

if(PlayerInput.aStrafe !=0.0 || PlayerInput.aForward !=0.0) moving = true;

Pulling the stick down will send a negative value through PlayerInput.aForward, up sends a positive value. Pulling the stick right sends a positive value through PlayInput.aStrafe, to the left a negative value. If the stick is central then the values a 0.0, thus moving is set to false if both axis register 0.0.

The same happens for the right stick, this time through PlayerInput.aLookup for up and down, PlayerInput.aTurn for left to right.

Moving down, the acceleration of the player has been set through the values of the left stick. Previously, the when the player presses up, the character moves in a diagonal direction, so to reset this the CameraRotationYawOnly.Yaw is set to 8191, a 45 degree angle and then used to calculate the real acceleration of the pawn.

Now for a tricky bit. The lines

    if(!rotating && moving)
    {
      playerRot.yaw = ((Atan2(PlayerInput.aForward,PlayerInput.aStrafe))+3.1416)*-10430.35;
    }else{
      if(rotating)
      {
        playerRot.yaw = ((Atan2(PlayerInput.aLookUp,PlayerInput.aTurn))+3.1416)*-10430.35;
      }
    }

are used to set the direction the pawn is facing is two different conditions. The first is if the player is moving without aiming the player. In the game I only want the pawn to face a different direction that the one it is running in if the player is firing. The two booleans Rotating and Moving can be used to see if the player is moving and if the pawn is NOT rotating. In this condition the pawn must face the direction it is facing.

As the InputPlayer aForward and aStrafe give us a 2D Cartesian coordinate, we can use trig to work out the angle. So it calculates something along the lines of ATan2(0.2,-0.5). The result of which produces a number between -3.1416 and 3.1416 (Pi). So by adding 3.1416 we are left with a number between 0 and 6.2832. So how do we get our angle? If UDK used degrees then we could multiply our answer by 360/6.2832 (57.295). So if the Atan gave use 5.5 then 5.5*57.295 would give us 315 degrees. As UDK uses a 16 bit integer then we divide 65536 by 6.2832 to give us 10430 to multiply our Atan2 answer. But one question probably remains, why multiply by a negative number? Simply put, when I first calculated this formula, the player span in the wrong direction, so it was necessary to use a negative to reverse the angle.

Is it over yet? No…

If we are still assuming that in this instance the player is only moving and not rotating then playerRot.yaw has been set tot he conclusion of our previous calucation. After the if statement, the playerRot.yaw then has -8191 subtracted from it to compensate for the 45 angle. As yaw uses an integer, if you subtract and the number goes under 0, then it simply loops around. So 5-10 becomes 65526.

The pitch and Roll are both set to 0 and the this new Rotator is sent through to the MyPawn FaceRotation function.

If the player is at anytime rotating, then instead of using the Left sticks input, we use the right (aStrafe and aTurn) to use the same method as the left stick to find the direction the player should be rotating in.

Almost there…

The UpdateRotation function simply prevents the player from point the weapon downwards, enabling the weapon to shoot forward. This may become an issue when slopes are implemented, but I have an idea to use a dummy object that is a few units ahead of the direction the player is facing to calculate any rises of drops. These changes will have to be calculted here.

So thats about it. There is a lot that has changed, but the result is spot on to my vision.

This entry was posted in UDK. Bookmark the permalink.