Game Development Community

Top Down 8 way Directional Control

by Kyle Lane · in Torque 3D Beginner · 05/19/2012 (2:10 pm) · 3 replies

Overview
I've been working on trying to make an RPG system with Torque 3D that would resemble a Final Fantasy style RPG. One of the aspects I've been trying to create is a directional control system similar to that of player movement on Final Fantasy world maps. I've found a lot of information, but nothing that worked how I envisioned. This thread will show how to implement directional control using only Torque Script. Parts of this thread are based off of Oliver Ridgway's Top Down Shooter resource (http://www.garagegames.com/community/resources/view/21608). If you haven't gone through it already you should because it's a great resource, but it won't stop you from completing this tutorial. I've been programming with C++ for about 10 years now, but I'm new to Torque Script. If I've done something terribly wrong, or if you know of an easier way please let me know.

We'll begin by creating a new project using the full template. I'm calling my project DirectionalControl. When the project is created we need to edit three script files.

We start with scripts/server/gameCore.cs. This is where we'll set up the camera to give us a top down view. This is almost an exact copy of the code from the Top Down Shooter resource, except I've set the camera to look straight forward instead of a 45 degree isometric view. This will make the directional math more straight forward. Later I may add some code to allow it to work with any camera angle. For now add this snippit to the end of the function GameCore::preparePlayer right after the line %game.loadOut(%client.player);.

%client.setControlObject(%client.camera);
%client.setCameraObject(%client.camera);  

// Set a schedule so we can control the player
%client.lockControlSchedule = %client.schedule(100, setControlObject, %client.player);     

%client.setFirstPerson(false);  
	
%client.camera.setOrbitObject(%client.player, "0.8 0 0", 3, 10, 0, false, "0 0 0", true);

Next we need to edit scripts/client/default.bind.cs. Here we need to set up an input system that will collect all of the input signals, and let us deal with them all at the same time. We do this by creating global flags for whether a key is pressed or not. When a key is pressed it tries to schedule the UpdateMove function. If another key hasn't already scheduled an update, and set the $movePending flag to true, then the key schedules the UpdateMove function and sets the flag to true. This flag will be set to false once the update is finished running. The $updateDelay variable allows tuning of how responsive the update is. Too fast of an update can confuse key presses, too slow adds a noticable delay to the input system. 50ms works well for me, but tune to your liking. Anyway, the code,

Replace
function moveleft(%val)
{
   $mvLeftAction = %val * $movementSpeed;
}

function moveright(%val)
{
   $mvRightAction = %val * $movementSpeed;
}

function moveforward(%val)
{
   $mvForwardAction = %val * $movementSpeed;
}

function movebackward(%val)
{
   $mvBackwardAction = %val * $movementSpeed;
}

With
$leftPressed = 0;  
$rightPressed = 0;  
$upPressed = 0;  
$downPressed = 0; 
$movePending = false; 
$updateDelay = 50;

function moveleft(%val) {  
   $leftPressed = %val;
   if(!$movePending)
   {
	  $movePending=true;
		  schedule($updateDelay,0,"UpdateMovement");
   }
}    
function moveright(%val) {  
   $rightPressed = %val;
   if(!$movePending)
   {
	  $movePending=true;
	  schedule($updateDelay,0,"UpdateMovement");
   }
}  
function moveforward(%val) {  
   $upPressed = %val;
   if(!$movePending)
   {
	  $movePending=true;
	  schedule($updateDelay,0,"UpdateMovement");
   }
}  
function movebackward(%val) {  
   $downPressed = %val;
   if(!$movePending)
   {
	  $movePending=true;
	  schedule($updateDelay,0,"UpdateMovement");
   }
}

You should also disable mouse aiming by commenting the following lines,

moveMap.bind( mouse, xaxis, yaw );
moveMap.bind( mouse, yaxis, pitch );

We now have a movement scheduled when a key is pressed. It's time to put that to work. The UpdateMove function I've added to scripts/server/player.cs. This fuction figures out which way to rotate, and how far, based on the current rotation and desired rotation. First we collect the curren rotation of the player object. Then we figure out which key or keys are pressed. Since we set the camera looking straight ahead we can hard code the directions. These directions always need to be between -pi and pi radians (-180 to 180 degrees). Depending on which keys are pressed, the %move variable is set to 1, and the %newRot variable is set to the desired direction. We subtract the current rotation from the desired rotation to get how much of a rotation is needed. However, if this value is greater than pi radians (180 degrees) or less than -pi radians (-180 degrees), the object would have to turn more than half way around to get to it's desired rotation so the value needs to be modified to turn the opposite way. Finally, we set $movePending to false to allow the next key press to call UpdateMove again.

function UpdateMovement() { 
	%euler = LocalClientConnection.player.getEulerRotation();
	%rot = GetWord(%euler, 2);
	
	%move = 0;
	%newRot = 0;
   
	if($upPressed) 
	{
	   if($leftPressed)
	   {
		   %newRot = -45;
		  %move = 1;
	   }
	   else if($rightPressed)
	   {
		   %newRot = 45;
		  %move = 1;
	   }
	   else
	   {
		   %newRot = 0;
		  %move = 1;
	   }
	} 
	else if($downPressed) 
	{
	if($leftPressed)
	   {
		   %newRot = -135;
		  %move = 1;
	   }
	   else if($rightPressed)
	   {
		   %newRot = 135;
		  %move = 1;
	   }
	   else
	   {
		   %newRot = 180;
		  %move = 1;
	   }
	} 
	else if($leftPressed) 
	{
	   %newRot = -90;
	   %move = 1;
	} 
	else if($rightPressed) 
	{
	  %newRot = 90;
	   %move = 1;
	}
	else
	{
	   %newRot=%rot;
	}

   %clamp=mPi();
	   
   if(%rot!=%newRot)
   {
	  $mvYaw=mDegToRad(%newRot-%rot);
		  
	  if($mvYaw>%clamp)
	  {
		 $mvYaw= -(%clamp -($mvYaw-%clamp));
	  }
	  else if($mvYaw<-%clamp)
	  {
		 $mvYaw= %clamp + ($mvYaw+%clamp);
	  }
   }
	   
   $mvForwardAction = %move;
	   
   // Clear the pending flag so the function can be called again
   $movePending=false; 
}

So there's a basic setup for a Top Down Directional Control System. Feel free to post questions, suggestions, or whatever.

About the author

Recent Threads


#1
07/02/2012 (3:29 pm)
If you're finding that the camera unlinks from the player on death/respawn in multiplayer then do this:

On death, gameCore.cs calls:

%client.camera.setMode("Corpse", %client.player);

The most important part was its comment:

"Switch the client over to the death cam and unhook the player object"

That's the problem I was running into, upon death and subsequent respawn the camera was unhooked from the player.

So I went into scripts\server\camera.cs and under "case "Corpse":" I commented this out:

%this.setMode(%obj,"Observer");

Now the player dies, has the death cam and on respawn resumes the proper top down camera.
#2
07/03/2012 (7:38 pm)
Thanks for the addition Mack. I hadn't tested that with any AI that could kill me. I'll add that to my code. Good catch.
#3
07/16/2012 (12:02 am)
Hey Kyle, would you have any suggestions on having the camera rotate on mouse movement while maintaining the %client.camera.setOrbitObject?