Game Development Community

dev|Pro Game Development Curriculum

Mouse controlled player movement

by Ron Yacketta · 12/14/2002 (7:35 am) · 67 comments

RTS/RPG Mouse controlled player movement

The following is a long awaited resource that I have been struggling to get out the door for a couple of the months now. The idea of the resource came from playing the likes of Dungeon Siege, Diablo, Baldurs Gate and Never Winter Nights.

I started off by utilizing a couple resources located on the GarageGames web site as well as document sent to me by Jason from 21-6 productions (still needs to be worked in) and one sent my way for review from Davide (do not recall the chaps Surname).

The first resource I used was "Object Selection in Torque" (www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=2173). The only exception was that I did not add the selection code mentioned in the resource. I also made a few modifications to the serverCmdSelectObject() function, which I will outline

function serverCmdSelectObject(%client, %mouseVec, %cameraPoint)
{
//Determine how far should the picking ray extend into the world?
   %selectRange = 200;
// scale mouseVec to the range the player is able to select with mouse
   %mouseScaled = VectorScale(%mouseVec, %selectRange);
// cameraPoint = the world position of the camera
// rangeEnd = camera point + length of selectable range
   %rangeEnd = VectorAdd(%cameraPoint, %mouseScaled);


   %searchMasks = ($TypeMasks::TerrainObjectType      | $TypeMasks::InteriorObjectType    |
                   $TypeMasks::PlayerObjectType       | $TypeMasks::StaticShapeObjectType |
  	           $TypeMasks::VehicleObjectType      | $TypeMasks::ForceFieldObjectType  |
                   $TypeMasks::PhysicalZoneObjectType | $TypeMasks::StaticTSObjectType);

   %scanTarg = ContainerRayCast (%cameraPoint, %rangeEnd, %searchMasks, %player);
   
// a target in range was found so select it
if (%scanTarg)
{
$pos = getWords(%scanTarg,1,3);
%client.player.setMoveDestination($pos);
}
}

One could change the name of the function to something more suitable like "serverCmdSetDestination", but I will leave that up to you to decide.

The following bit of information was taken from Davide's document with additions and modifications made by me.

How to make an RPG-like interface

Instead of using the default "first person" or "fixed third person" that cames with the Torque demo, you can
program a classic RPG interface, that is, the camera follows the player and to move the camera around you
have to move the cursor to the end of the screen, and the camera turn around the player but he doesn't change
his direction.

In the FILE "fps/client/ui/playgui.gui" remove the following:

noCursor=true;

This will enable the cursor to be displayed at all times.

In the FILE "fps/client/scripts/default.bind.cs" INSERT BOTTOM these functions:

function incCameraDistance()
{
   $CameraDist = ($CameraDist + 1.0) > 5.0 ? 5.0: $CameraDist+1.0;
   $CameraZooming = true;
}

function decCameraDistance()
{
   $CameraDist = ($CameraDist - 1.0) < 1.0 ? 1.0 : $CameraDist-1.0;
   $CameraZooming = true;
}

function changeCameraDistance(%val)
{
   if (%val>0) incCameraDistance();
   else decCameraDistance();
}

The functions are self explanatory, the variable $CameraDist and $CameraZooming will be added later in the
engine code.

Now in the FILE "config.cs" and "default.bind.cs", bind the keys with these function (really you have to write these lines also in the default.bind.cs
but if you already have the config.cs file you have to write these lines in this file because is the last file loaded and it delete
the previsious moveMap), to do this INSERT BOTTOM

moveMap.bind(keyboard, "m", toggleFirstPerson);
moveMap.bind(mouse, "xaxis", yaw);
moveMap.bind(mouse, "yaxis", pitch);
moveMap.bind(mouse, "zaxis", changeCameraDistance);	//mousewheel
moveMap.bind(keyboard, "i", incMaxCameraDistance);	//these two lines are here if you don't have a 
mousewheel
moveMap.bind(keyboard, "k", decMaxCameraDistance);
moveMap.bind(keyboard, "c", toggleMouseLock);

Note that I have changed the bind of the toggleFirstPerson, don't use the tab key, because when the cursor is
on, when you press the tab key, the engine thinks that you want to change control and it doesn't execute the
toggleFirstPerson function.

Ok, now let's go into the engine code
First we add the two variable's to

File : gameConnection.h
class : GameConnection
in the private section, after the mCameraSpeed add:

static F32 mCameraDist;
static bool mCameraZooming;

File : gameConnection.cc
after the S32 GameConnection::smVoiceConnections[MaxClients]; add:

F32 GameConnection::mCameraDist = 3.0f;			//3.0 is the distance  I have chosed, change it as you will
bool GameConnection::mCameraZooming = false;

File : gameConnection.cc
Function: getControlCameraTransform(......)

change the block that starts with if (dt) { .... } with

if (dt) 
   {
  	if (mFirstPerson) mCameraDist = 0.0;	//When you change to first person the camera has a distance of 0.0
  	else
	  if (mCameraDist == 0.0)		//If you aren't in first person but the distance is 0.0
	  {
		  mCameraDist = 3.0f;				//move the camera back
		  mCameraZooming = true;
	  }

      if (mCameraZooming || mFirstPerson || obj->onlyFirstPerson()) 
      {
         if (mCameraPos > mCameraDist)
         {
            if ((mCameraPos -= mCameraSpeed * dt) <= mCameraDist)
	    {
               mCameraPos = mCameraDist;
	       mCameraZooming = false;
	    }
        }
        else 
	if (mCameraPos < mCameraDist)
            if ((mCameraPos += mCameraSpeed * dt) > mCameraDist)
	{
	 mCameraPos = mCameraDist;
	 mCameraZooming = false;
	}
    }
 }

change the next line from
if (!sChaseQueueSize || mFirstPerson || obj->onlyFirstPerson())

to

if (!sChaseQueueSize || mCameraZooming || mFirstPerson || obj->onlyFirstPerson())
	      obj->getCameraTransform(&mCameraPos,mat);


File : gameConnection.cc
Funciton: GameConnection::readPacket( ....)

find the block

bool fp = bstream->readFlag();
      if(fp)
         mCameraPos = 0;
      else
         mCameraPos = 1;

and replace the mCameraPos = 1; with mCameraPos = mCameraDist;

Now we export the variables so we can use them in the script

File : gameConnection.cc
Function: GameConnection::consoleInit()

add these 2 lines

Con::addVariable("CameraDist", TypeF32, &mCameraDist);
Con::addVariable("CameraZooming", TypeBool, &mCameraZooming);

Now, we have to manage the mousemove event, so when the cursor is near the margin of the window, the
camera has to move.

File: : gameTSCtrl.h
Class : GameTSCtrl

add the following variable decleration in the public section

F32 mMouseMove;

File : gameTSCtrl.cc
Function: GameTSCtrl::GameTSCtrl()

add the following

mMouseMove = 0.009f;

File : gameTSCtrl.cc
Function: onMouseMove(..)

Comment out (or remove) the code already present and replace it with this:

Point2I ext = getExtent()- evt.mousePoint;
	Point2I pos = evt.mousePoint - getPosition();
	F32	YawSpeed = MoveManager::mYawRightSpeed;
	F32	PitchSpeed = MoveManager::mPitchUpSpeed;

	if (ext.x < 50 )						//50 is the distance from the margin 
	{
		MoveManager::mYawRightSpeed -= mMouseMove ;
	} 
	else
	if (pos.x < 50)
	{
		MoveManager::mYawRightSpeed += mMouseMove ;

	}

	if (ext.y < 50)
	{
		MoveManager::mPitchUpSpeed += mMouseMove ;
	}
	else
	if (pos.y < 50)
	{
		MoveManager::mPitchUpSpeed -= mMouseMove ;
	}


	//if you enter this function and the speed isn't changed this means that the cursor is out of the "moveble area" so
	//stop the movement

	if (MoveManager::mYawRightSpeed == YawSpeed)
		MoveManager::mYawRightSpeed = 0;

	if (MoveManager::mPitchUpSpeed == PitchSpeed)
		MoveManager::mPitchUpSpeed = 0;

Ok, the last step. We have to send the MouseMove event to this class, which is the correct class to manage this
type of event (at least I think so!! :-)), but in the script playGui.gui is defined the GuiShapeNameHud that is
almost the same size as the canvas itself, so every mouse message is sent to this class, so we have to pass the
message to the parent of this class

File : guiShapeNameHud.cc (located ./engine/game/fps ver. 1.1.2)
class : GuiShapeNameHud

In the public section add:

virtual void onMouseMove(const GuiEvent& event);

then in the same file, for example before the onRender method, add:

void GuiShapeNameHud::onMouseMove(const GuiEvent &event)
{
	//Let's let the parent execute its event handling (if any)

	GuiTSCtrl* parent = dynamic_cast<GuiTSCtrl*>(getParent());
	if (parent) parent->onMouseMove(event);
}

Ok, that's all.
Try to run the game, if everything is ok, you can move the mouse at the edge of the screen and you should see the camera rotating around the
player, use the mousewheel to zoom in and out.
Davide

I noticed that the rotation is clamped, that is you can not do a full 360 rotation around the player. To solve this I modified the following

FILE : Player.cc
Function: updateMove

I replace the following block of code

if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson())))
      {
         mHead.z = mClampF(mHead.z + y,
                           -mDataBlock->maxFreelookAngle,
                           mDataBlock->maxFreelookAngle);
      }

with

if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson())))
      {
         mHead.z += y;
      }

Now onto the part you all have been waiting for. Mouse controlled player movement.
I started out with a copy of aiPlayer.cc and went forth, after a few emails to Tim Gift; he sent me down the road to glory. On the way he snuck in behind me (just kidding Tim) and released a new aiPlayer.cc that contained basicly what I was setting out to-do. Wonder if that boils down to great minds think alike? Nah, I am not even in the same league as Tim, he is up their with the likes of Carmak!!

Anways, on to the changes

FILE : fps\client\server\scripts\game.cs
Function : createPlayer

Replace the entire function with

function GameConnection::createPlayer(%this, %spawnPoint)
{
   if (%this.player > 0)  {
      // The client should not have a player currently
      // assigned.  Assigning a new one could result in 
      // a player ghost.
      error( "Attempting to create an angus ghost!" );
   }

   // Create the player object
   %player = new AIPlayer() {
      dataBlock = LightMaleHumanArmor;
      client = %this;
   };
   MissionCleanup.add(%player);
   
   // Player setup...
   %player.setTransform(%spawnPoint);
   
   %player.setEnergyLevel(60);
   %player.setShapeName(%this.name);
   
   
   // Update the camera to start with the player
   %this.camera.setTransform(%player.getEyeTransform();
   
   
   // Give the client control of the player
   %this.player = %player;
   %this.setControlObject(%this.camera);
}

I think that should wrap it up for this tutorial.
#21
06/23/2004 (2:09 pm)
I had very similar problems as Eustacia did, specifically in question #2.

As it turns out, it looks like the code development went "around" hooking AIPlayer to a player spawn, specifically in player.cc at roughly line 1041 or so. If you change the beginning of the function Player::processTick to look like the following:

void Player::processTick(const Move* move)
{
   PROFILE_START(Player_ProcessTick);

   // If we're not being controlled by a client, let the
   // AI sub-module get a chance at producing a move.
   Move aiMove;
//   if (!move && isServerObject() && getAIMove(&aiMove))
// let's try this:
   if ( getAIMove(&aiMove) )
      move = &aiMove;

It will hook in the AI's movement to whatever point was selected in setMoveDestination.

It's not perfect--has trouble with slopes, and sometimes turns about unusually, but in general it does allow the AIPlayer movement methods to "take charge".
#22
07/31/2004 (11:59 am)
in gameconnection.cc, I couldn't find S32 GameConnection::smVoiceConnections[MaxClients]; (which you said to add "F32 GameConnection::mCameraDist = 3.0f..." after)

I'm using release 1_2_2 and it's definitely not in here. Will it work if I put it somewhere else?

would appreciate any help
#23
10/22/2004 (7:18 pm)
My config files keeps reverting to its older state, any help? thanks.
#24
10/23/2004 (7:40 am)
I think i figured it out, but i can only select stuff when my camera is directly on top of them? anyone know why? thanks.
#25
10/23/2004 (10:39 am)
And i cant click to move
#26
12/26/2004 (5:09 pm)
In the FILE "fps/client/ui/playgui.gui" remove the following:
noCursor=true;
But in some MOD, like the DEMO,I can't find this code.And the mouse can't work.What should I do?
Another Q:I do as this post,the mouse has work,but the keyboard can't work.
I want both of them all work.
#27
01/04/2005 (5:34 pm)
everything is OK now!
I add a bool member mRinkAdd_move in cladd player.
in aiplayer.h
add a member function
void clearrinkadd();
then in aiplayer.cc
in the AIPlayer::AIPlayer()
add: mRinkAdd_move=false;
AIPlayer::setMoveDestination //when you click and want to move ,this one will be called
add: mRinkAdd_move=true;
void AIPlayer::clearrinkadd()
{
mRinkAdd_move=false;
}

ConsoleMethod( AIPlayer, clearrinkadd, void, 2, 2, "()"
"Stop use mouse.")
{
object->clearrinkadd();
}
export this,when we press a key,call this to chang that var.

in player.cc
edit Player::processTick
replace
if (!move && isServerObject() && getAIMove(&aiMove))
to
if (mRinkAdd_move && getAIMove(&aiMove))

to script:
in default.bind.cs
in function moveleft and other functions to move
add: commandToServer('changtokey');
in command.cs
add a function
function serverCmdchangtokey(%client)
{
echo("changtokey");
%client.player.clearrinkadd();
}

then you an use both of keyboard an mouse move!!!
#28
03/30/2006 (1:04 pm)
I am using the advanced camera resource, is it possible to use this system without the extra camera stuff?
#29
07/01/2006 (4:46 am)
yes, i've gotten this to work with advanced camera Isaac.

Just implement the Object Selection resource, and the only things from this resource you need to add are the new serverCmdSelectObject and Player::Create functions, note that in the Player::create, there needs to be one change:

%this.setControlObject(%this.camera); should be;

%this.setControlObject(%this.advCamera);

then it should work! I haven't been able to implement Nicolas' addition though, getting an : Unknown Command getSelectedObject(), which is a shame, but ill look into that! good luck :)

edit:

// Select AI Players to control, and click on terrain to move.
function serverCmdSelectObject(%client, %mouseVec, %cameraPoint)
{
//Determine how far should the picking ray extend into the world?
   %selectRange = 200;
// scale mouseVec to the range the player is able to select with mouse
   %mouseScaled = VectorScale(%mouseVec, %selectRange);
// cameraPoint = the world position of the camera
// rangeEnd = camera point + length of selectable range
   %rangeEnd = VectorAdd(%cameraPoint, %mouseScaled);

%player = %client.player;

   %searchMasks = ($TypeMasks::TerrainObjectType      | $TypeMasks::InteriorObjectType    |
                   $TypeMasks::PlayerObjectType       | $TypeMasks::StaticShapeObjectType |
  	           		$TypeMasks::VehicleObjectType      | $TypeMasks::ForceFieldObjectType  |
                   $TypeMasks::PhysicalZoneObjectType | $TypeMasks::StaticTSObjectType);

 if ($firstPerson)
  {
  %scanTarg = ContainerRayCast (%cameraPoint, %rangeEnd, %searchMasks, %player);
 }
 else //3rd person - player is selectable in this case
  {
   %scanTarg = ContainerRayCast (%cameraPoint, %rangeEnd, %searchMasks);
   }
   
// a target in range was found so select it
if (%scanTarg)
{
%targetObject = firstWord(%scanTarg);
if (%targetObject.getClassName() $= "AIPlayer") {
	%client.player = %targetObject;
	%client.advCamera.setplayerObject(%targetObject);
}
$pos = getWords(%scanTarg,1,3);
%client.player.setMoveDestination($pos);
}
}

for the click to select, and again to move on advanced Camera :))

Only thing I'm missing now is how do I get the bounding boxes to draw? Since, beside the controlobject adjustements, there isn't much difference between the original function, and the modified one, but still the bounding boxes aren't drawing :(
#30
07/29/2006 (2:47 pm)
nevermind... An invisible bit of the chathud was blocking my onmousedown event. Remember to resize it in 1.4!! it's larger then the chathud itself!
#31
12/04/2006 (12:41 pm)
hello there:-)

very nice ressource works like it should but..:-)

when the player gets close to the destination it gets really jittery sometimes is there a way to fix this?

what i mean is it gets close to the loc then slows down and then move like in tiny steps chaning direction
#32
01/03/2007 (6:14 pm)
does anyone know how to pass the chathud events to CuiTSCTRL it isnt as simple as shapenamehud.
#33
06/09/2007 (3:52 am)
if (ext.x < 50 ) //50 is the distance from the margin
{
//MoveManager::mYawRightSpeed -= mMouseMove ;
MoveManager::mYawRightSpeed = (50.0f - ext.x)/5 * (-1 * mMouseMove);
}
else
if (pos.x < 50)
{
//MoveManager::mYawRightSpeed += mMouseMove ;
MoveManager::mYawRightSpeed = (50.0f - pos.x)/5 * mMouseMove;
}
if (ext.y < 50)

{

//MoveManager::mPitchUpSpeed += mMouseMove ;
MoveManager::mPitchUpSpeed = (50.0f - ext.y)/5 * mMouseMove;
}
else
if (pos.y < 50)
{
//MoveManager::mPitchUpSpeed -= mMouseMove ;
MoveManager::mPitchUpSpeed = (50.0f - pos.y)/5 * (-1 * mMouseMove);
}

I've updated the Camera update code so it would be relative to the mouse position on the margin (coz when u move horizontally on any of the margins on the side the rotation will speed up)
Seems that there is a very few ppl already checking this resource (no one is saying anything) but anyway, thought that it would b a good idea to share any thoughts i have.

btw it would b great if some1 would post a resource with a similar functionality but through script by editing the engin

thnx all :)
#34
07/25/2007 (3:47 am)
Hi guys,

could someone help me out here ?

Visual Compiling Log

Linking...
gameConnection.obj : error LNK2001: unresolved external symbol "private: static bool GameConnection::mCameraZooming" (?mCameraZooming@GameConnection@@0_NA)
../example/TGEA_DEBUG.exe : fatal error LNK1120: 1 unresolved externals

Gr Johnny
#35
07/25/2007 (3:54 am)
there is a previos post in this thread by 'thamonkey king' mentioned previously, this is what it says

Thanks for the update Nicolas. As to my problem, it appears that in build 1.2, the line

"S32 GameConnection::smVoiceConnections[MaxClients];"

is no longer in gameConnection.cc, so I was adding the declaration lines in the wrong place. Seems to work ok if I add those back to gameConnection.cc, though I'm still having positioning problems with the camera (starts out under the terrain). I'll let everyone know if I figure out why.

thanks for all the help
#36
07/25/2007 (4:15 am)
Lol I know for sure I tested all the variants as you wrote above and now it works... thnx man

Gr Johnny
#37
07/25/2007 (5:51 am)
Ok one last thing, then I hope it works completely :

Log writes : starter.fps/server/scripts/game.cs (306): Unknown command createPlayer
I know I probable have to do www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=3248 but I don't understand it.

Thnx in advance

Johnny
#38
07/25/2007 (7:12 am)
hmmm

there seems to be an incorrect function call of createPlayer function which is in line 306 in the file game.cs (I suppose u know that :D )
if u called the function urself try to add "GameConnection::" before the function call so it would be GameConnection::createPlayer( YOUR ARGUMENTS WHICH ARE IMPORTANT)
if u are working on a multiplayer game u will need to send the correct gameconnection to the function, u'll need to tweak a little, but that I dont know abt (I'm no good in multiplayer stuff)

I hope that helps, I'll reply quickly for ur questions within the next 4 or 5 hrs so no problem to shower me with them :)

I hope that helps
#39
07/25/2007 (8:30 am)
Hi Ehab thnx for your responses, well it was the order that made the error,
now to the last one in the log ;-)

starter.fps/server/scripts/game.cs Line: 277 - Syntax error.

function GameConnection::createPlayer(%this, %spawnPoint)
{
if (%this.player > 0)
{
// The client should not have a player currently
// assigned. Assigning a new one could result in
// a player ghost.
error( "Attempting to create an angus ghost!" );
}
// Create the player object
%player = new AIPlayer() {
dataBlock = LightMaleHumanArmor;
client = %this;
};
MissionCleanup.add(%player);
// Player setup...
%player.setTransform(%spawnPoint);
%player.setEnergyLevel(60);
%player.setShapeName(%this.name);
// Give the client control of the player
%this.player = %player;
%this.setControlObject(%this.camera);
// Update the camera to start with the player
%this.camera.setTransform(%player.getEyeTransform(); This is line 277
}

Don't see what wrong here... it's the same as in the tutorial above.

Gr Johnny
#40
07/25/2007 (8:33 am)
missing bracket man :D

it should be %this.camera.setTransform(%player.getEyeTransform() );

no problem happens all the time ;)

good luck