Game Development Community

3rd person character control and mouse like in the World Editor?

by Joel Schilling · in Torque 3D Professional · 10/26/2009 (10:43 pm) · 19 replies

In the World Editor the default is to have a mouse pointer, and hold the right click to swing the camera around. When I play the game, the mouse pointer goes away and moving the mouse looks all around. I would like in Play mode to be able to be in 3rd person mode, walk with keyboard, use the mouse pointer to select things, and hold down the right mouse to change my view. As a bonus it would be nice to use the scroll wheel to zoom in and out like in Orbit camera.

I know it's possible since it is done in World Editor, I just don't know how to do it. Can someone point me in the right direction?

#1
10/27/2009 (12:52 am)
OK, so I can make the cursor appear by setting noCursor="0" in PlayGui.gui, and use "m" to pan around, as such:

moveMap.bind(keyboard, "m", toggleMouse);

function toggleMouse()
{
   if(Canvas.isCursorOn()) {
      hideCursor();
   }
   else
   {
      showCursor();
   }
}

But when I do the following, it doesn't work:

moveMap.bind(mouse, "button1", toggleMouse);

Of course, if I leave noCursor="1", that code works to make the mouse appear. Apparently when you have a cursor, you can't bind the cursor?

Back to the salt mine...
#2
10/27/2009 (1:27 am)
Hi, i did a thread like this but no one seems to have a easy solution for this, i'm still studying c++ to try to understand what can be made. look if you can find something usefull: http://www.garagegames.com/community/forums/viewthread/103493

and this one can help to: http://www.garagegames.com/community/forums/viewthread/103719/1#comment-687365

#3
10/27/2009 (2:00 am)
Thanks, Chico! Those threads really opened my eyes to what exactly was going on.

I did this, and it solved the problem:

function PlayGui::onRightMouseDown(%this, %pos, %start, %ray)
{
   GlobalActionMap.bind(mouse, "button1", toggleMouse);
}

function PlayGui::onRightMouseUp(%this, %pos, %start, %ray)
{
   GlobalActionMap.unbind(mouse, "button1", toggleMouse);
}
#4
10/27/2009 (2:02 am)
Next step, figuring out how to move the camera in and out using the mouse wheel...
#5
10/27/2009 (9:09 am)
in the torque 3d docummentation has a tutorial that has the code to zoom

http://docs.garagegames.com/torque-3d/official/content/documentation/Scripting/Advanced/RTSPrototype.html

add this to the default binds.cs :
// Adjusts the height of the camera using the mouse wheel
function mouseZoom(%val)
{
   // If wheel was scrolled forward
   // move camera closer to the ground
   if(%val > 0)
   {
      commandToServer('adjustCameraHeight', -3);
   }
   // If wheel was scrolled back
   // move camera away from the ground
   else
   {
      commandToServer('adjustCameraHeight', 3);
   }
}

moveMap.bind( mouse, zaxis, mouseZoom );

i used this code that you are using to do the rotation of the camera, but i got some problems with the menu and buttons are you having the same problems?
#6
10/27/2009 (11:38 am)
I will give that a try, thanks! I haven't noticed any problems, yet. But so far, I have virtually no GUI elements to interact with at this point.

That solution is adequate for right now, but in the long run I plan to get my head wrapped around the code better, and modify the C++ code to handle this in a more graceful manner. That is, if someone like you doesn't do it first :)

Thanks!
#7
10/27/2009 (6:08 pm)
From your description, it sounds like you want to use the mousewheel to zoom in and out with respect to the player, which is different from the RTS camera. If so, you can implement this fairly quickly in code...

In "T3D/shapeBase.h" around line 998, add some new public variables:

static F32  sWhiteoutDec;
   static F32  sDamageFlashDec;
      
   CubeReflector mCubeReflector;

   F32 mCamDistOverride;   // Add this line
   F32 mCamTargetDist;     // Add this line
   F32 mCamDeltaFactor;    // Add this line

In "T3D/shapeBase.cpp" around line 763, initialize the new variables:

mShapeBaseMount( NULL ),
   mMass( 1.0f ),
   mOneOverMass( 1.0f ),
   mSkinHash( 0 ),             // Add a comma to end of this line
   mCamDistOverride( 1.0f ),   // Add this line
   mCamTargetDist( 1.0f ),     // Add this line
   mCamDeltaFactor( 0.15f )    // Add this line
{
   mTypeMask |= ShapeBaseObjectType | LightObjectType;

Around line 965, update new variables from the shape datablock:

if ( isClientObject() )
   {      
      mCubeReflector.unregisterReflector();

      if ( mDataBlock->reflectorDesc )
         mCubeReflector.registerReflector( this, mDataBlock->reflectorDesc );      
   }

   mCamDistOverride = mDataBlock->cameraMaxDist;   // Add this line
   mCamTargetDist = mCamDistOverride;              // Add this line

   return true;
}

Starting around line 1119, add this chunk of code at the end of "processTick()":

if (mWhiteOut > 0.0)
   {
      mWhiteOut -= sWhiteoutDec;
      if (mWhiteOut <= 0.0)
         mWhiteOut = 0.0;
   }

   // New Code: start
   // Camera zoom smoothing
   F32 delta = mCamDistOverride - mCamTargetDist;

   if(delta > 0.001f || delta < -0.001f)
      mCamDistOverride -= mCamDeltaFactor * delta;
   // New Code: end
}

Starting around line 1709, add this chunk of code:

if (*pos != 0)
   {
      F32 min,max;
      Point3F offset;
      MatrixF eye,rot;
      getCameraParameters(&min,&max,&offset,&rot);
      getRenderEyeTransform(&eye);
      mat->mul(eye,rot);

      // New Code: start
      const Point3F& scale = getScale();
      mCamDistOverride *= scale.y;

      if(mCamDistOverride < min)
      {
         max = min + 0.01f;
         mCamTargetDist = min / scale.y;
      }
      else if(min < mCamDistOverride && mCamDistOverride < max)
         max = mCamDistOverride;
      else
         mCamTargetDist = max / scale.y;

      mCamDistOverride = max / scale.y;
      // New Code: end

At the very end of the file, add two new ConsoleMethods:

ConsoleMethod( ShapeBase, adjustCamDist, void, 3, 3, "adjustCamDist(F32 offset)")
{
   object->mCamTargetDist += dAtof(argv[2]);
}

ConsoleMethod( ShapeBase, setCamDeltaFactor, void, 3, 3, "setCamDeltaFactor(F32 val)")
{
   F32 val = dAtof(argv[2]);

   if(0.0f < val && val <= 1.0f)
      object->mCamDeltaFactor = val;
}

Now in your "default.bind.cs" add this code somewhere:

function wheelZoom(%val)
{
   %ctrlObj = ServerConnection.getControlObject();
   
   if(%ctrlObj.getClassname() !$= "Player")
      return;
   
   if(%val > 0)
   {
      %ctrlObj.adjustCamDist( -0.4 );
   }
   else
   {
      %ctrlObj.adjustCamDist( 0.4 );
   }
}

moveMap.bind( mouse, zaxis, wheelZoom );

That's it! Obviously, increasing the value passed to adjustCamDist() will cause the camera to zoom faster, and visa versa. This lets you zoom between "cameraMaxDist" and "cameraMinDist" which are set in the player datablock. The mCamTargetDist and mCamDeltaFactor variables are used to smooth the camera movement so that the camera does not jump a full step with each mousewheel turn. Instead, the mousewheel controls the target distance, and the real camera is moved toward the target by a factor of mCamDeltaFactor each tick (~32 ms). In the console, you can use the command,

ServerConnection.getControlObject().setCamDeltaFactor(%some_value);

to change this factor at runtime. A value of 1.0 will turn off smoothing, a very small value will make the movement sluggish, and a value of 0.0 is not allowed.


#8
10/27/2009 (7:51 pm)
Wow, thanks Ryan! I've got a bunch of things to do today and tomorrow, but I'm going to give this a try on Thursday.

My next step after this is to make NPC's selectable with the mouse. I found a blog explaining how to go about doing that. I'm hoping that will work. And then get Yack Pack to work with the mouse instead of the targeting crosshair.
#9
10/27/2009 (8:04 pm)
No problem! Hope it's what you're looking for. Also, if you are a decent programmer, I might suggest you take a different route with PlayGui. If you look at the code for the World Editor, its base class is EditTSCtrl. You can derive a new gui class from EditTSCtrl and customize it for your use. Much of the functionality you're talking about is already built into that class. And on top of that you already have a great example of how to customize the EditTSCtrl by looking at the World Editor class. This would probably end up being a cleaner and faster solution for you. This is exactly what I have done for my project, and it's not difficult if you're comfortable with C++.
#10
10/27/2009 (8:31 pm)
Yeah, that's the path I planned to take, eventually. I am a Senior Java programmer, so I am a bit spoiled... but I did do about 3 years of C++ programming professionally about 7 years ago.

I started this last night because I wanted to actually get to bed early, and I figured it was an easy change to make--and I ended up staying up till 2 am working on it, lol.

#11
10/28/2009 (11:14 am)
Joel, I updated the code above a little bit to add "smoothing" to the camera. The previous code was pretty simplistic in that you turn the mousewheel one notch, and the camera moved in or out by 0.4. This can result in jerky motion. Now when you turn the mousewheel, it moves a virtual target, and the camera will smoothly move to that target over time.
#12
10/28/2009 (12:00 pm)
Thanks!
#13
10/29/2009 (10:31 pm)
Ryan, i'm not a decent programmer :(, is possible to share how to customize the editTsCtrl to use in my project?
#14
10/30/2009 (2:25 am)
Ryan seems pretty generous with his sharing, so he probably will help you out.

If not, I will give you what I write. But I am pretty slammed with things to do right now, so it might be a few days.
#15
11/03/2009 (12:12 pm)
I implemented the mouse wheel functionality last night, and I can zoom in and out!

The maxCameraDist sets how far out I can zoom, but the minCameraDist seems to have no effect on how close you can get.

I have set the minCameraDist to 1.0, 2.0, 10.0, -1.0, etc, but it seems no matter what I do, the camera always zooms into the players head and stops.
#16
11/03/2009 (1:38 pm)
@ Joel

Yeah, they didn't implement "cameraMinDist" correctly in the stock code. As it is now, the minimum camera distance is ALWAYS located at the cam node, no matter what. So the reason your camera is zooming inside the head is because that's where the cam node is for that mesh. I've known this for a long time, but just never took the time to fix it. I just looked at it, though, and it's a very easy fix...

In "T3D/shapeBase.cpp" starting around line 1743, add this small block of code, and around line 1765 modify this one line:

// Use the eye transform to orient the camera
      VectorF vp,vec;
      vp.x = vp.z = 0;
      vp.y = -(max - min) * *pos;
      eye.mulV(vp,&vec);

      // New code: start
      VectorF minVec;
      vp.y = -min;
      eye.mulV(vp,&minVec);
      // New code: end

      // Use the camera node's pos.
      Point3F osp,sp;
      if (mDataBlock->cameraNode != -1) {
         mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp);

         // Scale the camera position before applying the transform
         const Point3F& scale = getScale();
         osp.convolve( scale );

         getRenderTransform().mulP(osp,&sp);
      }
      else
         getRenderTransform().getColumn(3,&sp);

      // Make sure we don't extend the camera into anything solid
      Point3F ep = sp + minVec + vec + offset;   // Modify this line to add "minVec"

@ Chico

I don't mind helping you, but your request is very broad and general, and I don't have time at the moment to write an entire tutorial on deriving and extending classes. I suggest you study the EditTSCtrl and the World Editor class to get an idea of how it works. If I get a chance, I will try to explain the basics, but you really need to be comfortable with programming before you take up a project that requires major code modifications.
#17
11/03/2009 (2:09 pm)
I figured it was a bug in the stock code--just couldn't figure out where. I'm slowly getting my head wrapped around how the engine works. Thanks for the help.

I extended EditTSCtrl and am using it in place of GameTSCtrl in PlayGUI.gui. I'm still faced with the dilemma that when lockMouse() gets called in EditTSCtrl::onRightMouseDown(), the class no longer receives the onRightMouseUp() event, to return back to the mouse pointer mode.

How did you overcome this problem? Right now the only way I can get this to work is to bind a mapping into the GlobalActionMap that unlocks the mouse, and unbinds the map. This feels hacked to me, but for the life of me, I can't figure out how World Editor is receiving the onRightMouseUp() event.
#18
11/03/2009 (2:35 pm)
Mine works just like the World Editor works. The only place I can see that you might be going wrong is if you simply changed "new GameTSCtrl(PlayGui)" to "new MyEditTSCtrl(PlayGui)". The GameTSCtrl and EditTSCtrl parameters are not the same, and just changing the class like that might be messing your EditTSCtrl up... for example, there's a parameter called "noCursor" that is set to true that could be causing problems if EditoTSCtrl actually uses it.

Also, another thing that I do slightly differently is that I left PlayGui alone, and looked at how the world editor is setup in "tools/worldEditor/gui/EditorGui.ed.gui". It uses a GuiContainer to wrap the World Editor control, so I created a GuiContainer to hold my EditTSCtrl derived class. That allows you to add windows and other gui elements by adding them to the container. Then I just use "Canvas.setContent()" to push my container onto the Canvas, just like the World Editor does.
#19
11/03/2009 (5:12 pm)
Cool, I'll give that a try. You definitely know your way around the Torque engine :)