Game Development Community

Top Down Shooter, cursor turning issue

by ben calder · in Torque Game Engine Advanced · 12/23/2008 (5:14 am) · 33 replies

Hi all,

I am trying to make a top down shooter, similar to PrototypeB (www.garagegames.com/mg/snapshot/view.php?qid=1675) But I am having some real difficulty trying to get the cursor functionailty. Basically at the moment I am trying to make it so that the player turns to face the cursor's location on the screen. I should probably mention that I am using AFX, and that I have set the camera in god mode. I have made some progress with this cursor issue, but It doesnt work properly. I have been modifying void afxTSCtrl::onMouseMove(const GuiEvent& evt), the code is shown below.

void afxTSCtrl::onMouseMove(const GuiEvent& evt)
{
	GameConnection *con = GameConnection::getConnectionToServer();
    
    if (con->isFirstPerson())
    {
       return;// 0.0f;
    }

    if (!con->getControlObject())
    {
       return;// 0.0f;
    }

    GuiCanvas* Canvas = getRoot();

    if (Canvas == NULL)
    {
       return;// 0.0f;
    }

    // rotate the player to look at where the mouse pointer is pointing to
	// If in 3rd person
	F32 yaw, pitch;
	Point2I point = evt.mousePoint;
	Point2I size =Canvas->getWindowSize();
	Point2I center = size / 2;

	// get the vector for the mouse pointer from the center of the screen
   MatrixF camTrans = con->getCameraObject()->getRenderTransform();      
		 
   VectorF camF;
   camTrans.getColumn(1, &camF);
   VectorF newVec;

   // Check if camera is straight down or on an angle
   if (mFabs(camF.z) == 1.0f) 
   {
      newVec = VectorF(-(point.y - center.y), -(point.x-center.x), 0.0f);
   }
   else 
   {
      newVec = VectorF(point.x-center.x, -(point.y-center.y), 0.0f);
   }
		
		// get the yaw of the vector
   MathUtils::getAnglesFromVector(newVec, yaw, pitch);

	
   MatrixF conTrans = con->getControlObject()->getWorldTransform();

	// get the current rotation around the Z-axis
	F32 curYaw = conTrans.toEuler().z;
	
   yaw = curYaw + yaw;

	// Check if we are taking the correct way round
    
	if( yaw > M_PI_F )
   {
      yaw -= M_2PI_F;
   }
   else
   if( yaw < -M_PI_F )
   {
      yaw += M_2PI_F;
   }

MathUtils::getAnglesFromVector

}

MathUtils::getAnglesFromVector

void getAnglesFromVector( VectorF &vec, F32 &yawAng, F32 &pitchAng )
{
   yawAng = mAtan( vec.x, vec.y );
   if( yawAng < 0.0f )
      yawAng += M_2PI_F;

   if( mFabs(vec.x) > mFabs(vec.y) )
      pitchAng = mAtan( mFabs(vec.z), mFabs(vec.x) );
   else
      pitchAng = mAtan( mFabs(vec.z), mFabs(vec.y) );
   if( vec.z < 0.0f )
      pitchAng = -pitchAng;
}

I have hit a brick wall with the code, and I really need some help.

There are two main bugs.

Firstly, the player never actually faces the cursor, he always faces slightly above the cursor. so if the mouse is moved directly to the right of the center of the screen, the player turns to face it, but faces a point slightly above the cursor. I am completely lost as to how I can solve this.

The second problem I have, is that when the cursor is very close to the player he doesnt face the cursor, the player will just face the top of the screen.

Any help would be appretiated,

Thanks,

Callum
Page «Previous 1 2
#1
12/26/2008 (5:30 pm)
I never could find a resource on how to do such a thing, so I tried my hand at it. I've implemented the code into a few of my games so far, and while it may not be very efficient, it certainly works. I've packaged all the necessary files, and you can use the WinMerge utility to merge the changes into your build. Or you can replace your files entirely, if you're using stock TGEA 1.8 at the moment. This code is best used in conjunction with the AdvancedCamera resource.

The password for the ZIP is 'mc6'
Linky

In a nutshell, the changes to the gameTSCtrl all have to do with getting the mouse cursor's world co-ords, by either casting a ray against the terrain, or against the player's Z-plane (picture the plane created when you select something in the editor). That position is stored in the gameConnection class and accessed in the MoveList::getNextMove function. Here is where the angle and 2D-style movement vectors are calculated.

In the player class, during Player::updateMove, that angle is used to rotate the player, and the moveXY vectors are used to make the player ALWAYS walk to the top of the screen when pressing W, bottom when S, left when A, and right when D. Therefore, aim and movement are independent of one another. You can be shooting to the north while moving south-west, etc. These changes are reflected across the network, and as far as I can tell are fairly accurate. The code isn't pretty though, and if you improve it any, let me know :)

Please note there may be some legacy code in there. I still need to go back and clean it up.
#2
12/27/2008 (1:50 pm)
Thankyou Mike, I have just had a quick look through the code, and realised that even though my code looked like it was almost there, it was actually a long way off, so you are a real life saver, thanks.
I am just about to try make the functions to use your code. After that I will try to go through the code properly:), but from what I have seen so far it works perfectly. The only thing I can see is that when the player faces sideways from the direction he is moving (ie left or right if he is moving up) the character doesnt switch to the sidestep animation. I imagine it is only a single alteration.
I will gladly give you any alterations I make to your code, I will upload them to a file share site just as you did, it is the least I can do after you were so generous and helpful. I am going to try alter the camera slightly so that the cursor position alters the camera position slightly ([link]http://uk.youtube.com/watch?v=wA2sxPqK_wg&feature=related[/link]) so if i get anywhere with that I will let you know.
Can i be a real pain and ask about your default.bind yaw/pitch functions, as if I call the cursor it ignores the ones I have and works without them.

Thanks again
#3
12/28/2008 (2:11 pm)
I just had a proper look at the code, it's painfully obvious why the strafe animation didn't work, lol, i didnt realise you had split it up to left and right. I have no idea how to make the 2 strafe animations work, instead of the one in place, but i'll keep trying till i get it done. I dont see why you thought it was messsy, it seems pretty tidy to me, your comments are really clear too. Once again thankyou for sharing the code.

There is one thing that I am confused with that I just wanted to run by you.

When I declare the cursor it disables all the mouse functions in default.bind? it seems to ignore the pitch and yaw functions I had and just operates, changing the player direction but without and script alterations, which is good, but I am stumped as to why this happens.
This also creates a problem when in first person perspective as the pitch and yaw functions are disabled (?) so you carnt turn the player. The default control scheme you create specifically for fp perspective works, its just there is no way to turn the player. It also stops the mose trigger functions that I had from working, but yet if I hold the right mouse button it stops the playerfrom turning. I am probably just being an idiot, but it has me completely confused.
Do you know how I might be able to get the mouse trigger functions and pitch/yaw functions for first person mode to work?
#4
12/28/2008 (10:20 pm)
Ah sorry! I forgot I had implemented a resource that split the strafe animation in two. To fix this, make the modifications below:

Go to line 123 (hit ctrl-g if using MSVS C++) or hit ctrl-f and find the following lines
//MAJ - Split strafe
   { "strafeleft", { 1.0f,0.0f,0.0f } },
   { "straferight", { -1.0f,0.0f,0.0f } },
replace them with this:
{ "side", { -1.0f, 0.0f, 0.0f } },       // SideLeftAnim,

In player.cpp, find or go to line 2566 and uncomment this:
//MAJ - Split strafe animation, now works like forward/backward anims
/*uncomment me!
if (i == PlayerData::SideLeftAnim && -d > curMax)
{
    curMax = -d;
    action = i;
     forward = false;
} 
*/

Now, in player.h, go to line 159 or find this, and comment out the bottom part:
SideLeftAnim,
      //MAJ - Split strafe
      //V Comment this out V
      //SideRightAnim,
then, a little bit below that, change
//MAJ - Split strafe
      NumMoveActionAnims = SideRightAnim + 1,
      NumTableActionAnims = JetAnim + 1,
      NumExtraActionAnims = 512 - NumTableActionAnims,
      NumActionAnims = NumTableActionAnims + NumExtraActionAnims,
      //MAJ - Split strafe
      ActionAnimBits = 10,
      NullAnimation = (1 << ActionAnimBits) - 1
to this:
NumMoveActionAnims = SideLeftAnim + 1, //change to this, was SideRightAnim + 1
      NumTableActionAnims = JetAnim + 1,
      NumExtraActionAnims = 512 - NumTableActionAnims,
      NumActionAnims = NumTableActionAnims + NumExtraActionAnims,

      ActionAnimBits = 9, //change to this, was 10
      NullAnimation = (1 << ActionAnimBits) - 1

Now you should have working strafe animations.

On to restoring FP pitch/yaw. My method isn't all that great, but to use it modify your toggleFirstPerson function in default.bind.cs to match this:
function toggleFirstPerson(%val)
{
   //FIXME: make this code work better, as it only works
   //if you hold in TAB for a few seconds
   %fp = ServerConnection.isFirstPerson();
   lockMouse(%fp);
   if(%fp) Canvas.cursorOff();
   else Canvas.cursorOn();
   if (%val)
   {
      ServerConnection.setFirstPerson(!%fp);
   }
}

Basically it turns the cursor off and locks the mouse when in first person, which makes the original pitch/yaw/fire functions work properly.

Please note: The above is a bit of a hack, and may require you to hold the toggle button (default: tab) in for a second or two to make it register. Something to do with the way it's handled over the network. I haven't had time to correct it though--if anyone has a better way, let me know! :)

I believe that is everything needed to implement a basic top-down mouse-aiming sytem that still retains regular FPS controls when in first person. It is possible I've forgotten to include something though, so if something still isn't working let me know.

P.S- I forgot to mention a change I made to guiShapeNameHud. Without this change, some mouse events will be lost in top-down mode, as the shapename ctrl covers the entire screen. To fix this, make these changes:

In guiShapeNameHud.cpp, add this in the GuiShapeNameCtrl class:
...
   virtual void onRender(Point2I offset, const RectI &updateRect); //find this
   //add everything below
   void onMouseDown(const GuiEvent &evt)
   {
   GuiControl *parent = getParent();
   if ( parent )
      parent->onMouseDown( evt );
   }
   void onMouseUp(const GuiEvent &evt)
   {
   GuiControl *parent = getParent();
   if ( parent )
      parent->onMouseUp( evt );
   }
   void onMouseMove(const GuiEvent &evt)
   {
   GuiControl *parent = getParent();
   if ( parent )
      parent->onMouseMove( evt );
   }
   void onMouseDragged(const GuiEvent &evt)
   {
   GuiControl *parent = getParent();
   if ( parent )
      parent->onMouseDragged( evt );
   }
   ...
#5
12/29/2008 (3:32 pm)
Thanks, the changes all work really well, and the script to toggle first person seems to work well too. It doesnt require holding the tab button, but the cursor does stay for a spilt second, its barely noticable though. There is a slight bug, where if you are in first person mode and you bring up the console window, the cursor reappears and disables the mouse functions, but it's not a very important issue.

Any idea how I could get the mouse button functions to work in the top down view when the cursor is still visible? At the moment I am just using the standard trigger functions that ship with tgea.

function mouseFire(%val){
   $mvTriggerCount0++;
}
function altTrigger(%val){
   $mvTriggerCount1++;
}

I have looked through your engine changes and carnt figure out why this problem occurs, as you have seperated the movement to work for god mode fp mode, but you seem to keep anything relevant to the triggers for both, like in MoveList::getNextMove(Move &curMove) and MoveList::getNextMoveFP(Move &curMove) Am i missing something?
#6
12/29/2008 (5:27 pm)
How would you go about restoring the standard WASD first-person movement where the player would face and move towards the mouse cursor in a third-person or GodMode view as opposed away/toward/left/right movement relative to the camera? I've been pounding my thick skull against the code (primarily what's in your version of MOVELIST.CPP), but I haven't been able to wrap my head around it as of yet.

Thanks in advance, and I appreciate the help

Greg
#7
12/29/2008 (7:33 pm)
Greg, Don't forget player.cpp, I think you will need to change as well as alterations to movemanager and movelist:
//MAJ - If first person, use default control scheme
      if(getControllingClient() && getControllingClient()->isFirstPerson())
	  {
		  zRot.getColumn(0,&moveVec);
		  moveVec *= move->x;
		  VectorF tv;
		  zRot.getColumn(1,&tv);
		  moveVec += tv * move->y;
	  }
	  else
	  {
		   //MAJ - Adjust player's movement based on camera's orientation (2D-style movement)
		   moveVec = move->moveX;
		   moveVec *= move->x;
		   moveVec += move->moveY * move->y;
	  }
back too:

if (mState == MoveState && mDamageState == Enabled)
   {
      zRot.getColumn(0,&moveVec);
      moveVec *= move->x;
      VectorF tv;
      zRot.getColumn(1,&tv);
      moveVec += tv * move->y;

I have had a brief look at the code in movelist and I carnt make heads or tails of bool MoveList::getNextMove(Move &curMove) either.

You might find that using the old control scheme makes it alott more difficult to control the player when using god mode, I personally prefer's Mike's way of having w as always up (it seems easier to control).
#8
12/29/2008 (7:48 pm)
Oh, you need to add this to client/scripts/playGui.cs:
function PlayGui::onMouseDown(%this,%x,%y)
{
   mouseFire(1);
}
function PlayGui::onMouseUp(%this)
{
   mouseFire(0);
}

and of course you need the onMouseDown and onMouseUp functions in gameTSCtrl, which you should have if you're using the file I put in that zip archive.
If not though, here they are:
void GameTSCtrl::onMouseDown(const GuiEvent &evt)
{
	char x[16];
         dSprintf(x, sizeof(x), "%d", evt.mousePoint.x);
	char y[16];
         dSprintf(y, sizeof(y), "%d", evt.mousePoint.y);
	Con::executef(this, "onMouseDown", x, y);
}
void GameTSCtrl::onMouseUp(const GuiEvent &evt)
{
	Con::executef(this, "onMouseUp");
}

@Greg: (Oops, Callum beat me to it :) )
Go to line 1584 in player.cpp, or search for this code block:
//MAJ - If first person, use default control scheme
          if(getControllingClient() && getControllingClient()->isFirstPerson())
	  {
		  zRot.getColumn(0,&moveVec);
		  moveVec *= move->x;
		  VectorF tv;
		  zRot.getColumn(1,&tv);
		  moveVec += tv * move->y;
	  }
	  else
	  {
		   //MAJ - Adjust player's movement based on camera's orientation (2D-style movement)
		   moveVec = move->moveX;
		   moveVec *= move->x;
		   moveVec += move->moveY * move->y;
	  }

change it to this:

//Always use FPS-style movement
		  zRot.getColumn(0,&moveVec);
		  moveVec *= move->x;
		  VectorF tv;
		  zRot.getColumn(1,&tv);
		  moveVec += tv * move->y;

You can also remove the moveX and moveY calculations in MoveList::getNextMove in moveList.cpp if you want.
#9
12/30/2008 (6:01 am)
Yep that work's, thanks mike. Two final things which I am currently looking at, and am quite confused with and that I thought I would raise.
If you go into fp mode and alter the position you are looking at and then return to the standard god view mode, then the player remians looking at the location. This is good in the sense that it keeps the yaw changes made to the player. However it also keeps the pitch alterations. So if you look at the floor at fp mode and then return to the birds eye view then the player will be looking at the floor with no way of altering it. What I am planning to do is reset the pitch when the player exits fp mode.
The second thing is a very minor thing, it is that holding the right mouse button locks the player (prevents player rotation based on the mouse cursor). I have had a look and have no idea why this happens.
#10
12/30/2008 (4:33 pm)
Hey mike, i'm interested in looking at your code in the link up there (i've been trying to do an overhead shooter as well), but i'm having trouble downloading it. is there any chance you could upload it to another site or anything? thanks a bunch.
#11
12/30/2008 (4:56 pm)
DOH! Thanks all...it looks like was focusing on the wrong part of the code. I actually do really like the movement in Mike's original file quite a bit, but I'm looking to compare the two in action, so speak.

Again, I appreciate the assist....I owe y'all one
#12
12/30/2008 (8:08 pm)
Any other tricks to enabling mousefire besides the playgui.cs addition and making sure the code is present in gameTSCtrl? I'm not able to get mouse events to function when the cursor is visible and I'm not seeing any culprits with the console trace enabled.

@Kyle: I'll see if I can't email you the files from Mike's download link.
#14
12/31/2008 (12:17 am)
About my gratitude?


you have it.
#15
12/31/2008 (1:57 am)
So i fired up that file into a blank TGEA 1.8 and implemented the advanced camera, and I'm running around with the awesome wasd movement always going towards the borders of the screen, top score. However, my cursor isn't guiding my character, he is still turning due to myself moving the mouse left and right. Anyone know whats up with my cursor-guided-character movement?

And also, from the reads of it, everyone here is leagues beyond myself in terms of coding. Forgive me if I don't understand what you say.

And thanks again. This has already saved me so much time.
#16
12/31/2008 (5:49 am)
@Greg, I havent figured it out yet, but I will have a good try at it later today. But there is nothing wrong with the gameTSCtrl, as Mike just kept the standard trigger code, you want to be looking at the functions in play.gui.cs.
#17
12/31/2008 (5:49 am)
@Kyle, have you declared the cursor?

In client/ui/play.gui change:

noCursor = "1";

to

noCursor = "0";

That should call the cursor and then all of mike's changes should work. If it doesnt make the changes work, then you have made a mistake with his engine changes. Just go back to the begining, make all the changes again and be extra careful.

@Mike, Any idea how I could reset the pitch when the player exits fp mode, so that he carnt end up looking at the ground or sky?
#18
12/31/2008 (9:51 am)
Well there's an easy way to fix pitch client-side, but I like to do things the networked way, so in moveManager.h (line 44) change this:
//MAJ
   Point3F moveX,moveY,aimPos;
   bool noPitch; //add this

then below that (line 82) change this:
static Point3F mMoveX;
   static Point3F mMoveY;

   static Point3F mAimPos;

   static bool	mNoPitch; //add this

Now, in moveManager.cpp (line 36) change this:
//MAJ
Point3F MoveManager::mMoveX = Point3F(0,0,0);
Point3F MoveManager::mMoveY = Point3F(0,0,0);
Point3F MoveManager::mAimPos = Point3F(0,0,0);

bool MoveManager::mNoPitch = false; //add this

right below that, change this:
const Move NullMove =
{
   16,16,16,
   0,0,0,
   0,0,0,   // x,y,z
   0,0,0,   // Yaw, pitch, roll,
   0,0,

   false,false,false,
   //MAJ
   Point3F(0,0,0),Point3F(0,0,0),Point3F(0,0,0),
   false /* < add this false for noPitch*/,false,false,false,false
};
then at line 176, change this:
stream->writeCompressedPoint(moveX);
	  stream->writeCompressedPoint(moveY);
	  stream->writeCompressedPoint(aimPos);
	  stream->writeFlag(noPitch); //add this
and line 205, change this:
//MAJ
	   stream->readCompressedPoint(&moveX);
	   stream->readCompressedPoint(&moveY);
   	   stream->readCompressedPoint(&aimPos);
	   noPitch = stream->readFlag(); //add this

All that to add a variable.
Now for changes to moveList.cpp. In MoveList::getNextMoveFP (line ~46), add this:
curMove.noPitch = false;
then, in MoveList::getNextMove, add this:
curMove.noPitch = true;

Finally, in player.cpp (line 1529), replace this:
F32 p = move->pitch;
      if (p > M_PI_F) 
         p -= M_2PI_F;
      mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
                        mDataBlock->maxLookAngle);
with this:
if(!move->noPitch)
	  {
               F32 p = move->pitch;
               if (p > M_PI_F) 
                       p -= M_2PI_F;
               mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
               mDataBlock->maxLookAngle);
	  }
	  else
		  mHead.x = 0;

Now the player's pitch will reset when in top-down mode.
#19
01/02/2009 (12:22 pm)
After banging my head against the problem, I finally dug up a reference to the same playgui/mousefire issue in an old post from the beginning of 2004 explaining to comment out or remove "new GuiShapeNameHud" from playgui.ui.

The original post is at www.garagegames.com/mg/forums/result.thread.php?qt=15500 for anyone interested.

Greg
#20
01/02/2009 (12:27 pm)
Greg, did that solve the mouse click issue?
Page «Previous 1 2