Object selection in Torque (v1.2 onward)
by Dave Myers · 03/12/2005 (11:48 pm) · 241 comments
Overview
I originally wrote this tutorial back in Feb 2002 and I still receive emails on this asking for help or just saying thanks. It's very rewarding to know that in some small way this resource helps people new to the Torque Engine. This version has been updated to work with the latest release of TGE, so hopefully it will help answer many of the questions that people had with the original. -Dave
This tutorial provides one method to enable object selection in your TGE-based game. This implementation will show how to implement a bounding box cursor that will be displayed when an object is selected with the mouse. It could easily be enhanced to allow you to implement a wide variety of features.
Using this Tutorial
Bold font is used to show new code in the different code snippets throughout the tutorial. Also, code that is not important has been left out and is denoted by a comment in italics reading "irrelevant code not shown for brevity".
Step 1 - Capture the mouseDown event
First, let's quickly create a new bind and client function that will toggle the cursor. Add the following code to the bottom of the script file.
/starter.fps/client/scripts/default.bind.cs
NOTE: Make sure and delete the current config.cs and config.cs.dso files found in /starter.fps/client. This file (config.cs) is created automatically each time you shutdown the game and is executed when starting up the game.
Second, let's capture any left button mouse click events on the client. To do that, we need to add in a method to the GameTSCtrl. This control is our canvas in the game - so clicking somewhere on this canvas (i.e. our game screen) will generate a mouseDown event, and we can capture that event and deal with it in GameTSCtrl.
I chose to add new members and "getter" methods for the point and vector to the control rather than pass the information back to the client scripts as parameters. Either way seems fine, I just chose the former.
engine/game/gameTSCtrl.h
Now let's add the implementation of the onMouseDown handler method. What we need to do is take the 2D screen coordinates of the mouse and create 3D screen coordinates. We then will create a vector that has the camera world coordinates as the first point and the 3D screen coordinates as the second point. Once we have calculated these two elements, we then call a client script function, which I also named onMouseDown, and let the scripts control it from there. The idea here is that later we will take the 3D screen coordinates and the vector and shoot a picking ray into our 3D world. We'll then check to see if that ray collides with an object in the world.
I believe the code is commented pretty well, so it should be easy to follow along.
engine/game/gameTSCtrl.cc
I then exposed the "getter" methods to the scripting engine, also in engine/game/gameTSCtrl.cc
Currently, the Torque demo has a GUI element called GuiShapeNameHud that is used to display player names and damage over the heads of the models. This control is actually almost the same size as the canvas itself, which means that any mouse events are actually handled by this control, not our GameTSCtrl. I preferred to keep the control, but modify it so that it will call to my new mouse down event handler in GameTSCtrl. To do that I inserted the following onMouseDown handler, which simply calls up to its parent - in this case, our GameTSCtrl.
engine/game/fps/guiShapeNameHud.cc
Step 2 - Client handler for mouse event
We now need to add a handler to the scripts for PlayGui, which is the script name of our canvas GameTSCtrl. This new method will grab the vector and point and call to a server script function SelectObject.
example/starter.fps/client/scripts/playGui.cs
Step 3 - Set up our connection to handle our selected object
Before we can actually select an object, we need to set aside a place to save this information, and provide a way for the server to update the client so that we can render (at least in this example) a bounding box cursor around our selected object. I have chosen to save this information as part of the GameConnection class.
engine/game/gameConnection.h
We'll need to pass the selected object around between the server and client, so in order to do so we need to modify the readPacket and writePacket methods of our GameConnection class. For a brief description of why, take a look at Justin Mette's tutorial on node hiding, starting with Step 2 - Updating ghosts. Basically, we will be saving off the selected object on the server, but our client needs to know what object is selected so that it can render the cursor around the ghost object.
We also need to create the "getter/setter" functions so we can get and set the selected object from the server scripts.
engine/game/gameConnection.cc
Step 4 - Check if we have selected something
The server is responsible for determining if we have selected something, and if so it will save off the selected object for use later. Let's keep this simple and add our new server command SelectObject to an existing script file (I personally put this and other related server commands in a separate file). The code below is pretty well-commented, so it should be pretty easy to follow the logic there.
fps/server/scripts/commands.cs
Step 5 - Example implementation
Your use of object selection will be specific to your game needs. However, let's go ahead and test it by making a small addition to the ShapeBase::renderObject method. There is already code there to show a bounding box if in debug mode, so let's just reuse it in the case we have something selected as a 3D cursor of sorts. First, we need to get the server connection, which is where we stored our selected object earlier. We then simply get the object id from the object and compare it to object id of the shapebase we are rendering. If they match, we go ahead and show the bounding box:
engine/game/shapeBase.cc
NOTE: If you want to select your own player you can hit the 'Tab' key to put yourself in third-person camera view and select him. You cannot hit F8 and drop a camera and then click on him, because in that case the game still thinks you are in first-person mode ($firstPerson = true still) and the check we made in commands.cs (serverCmdSelectObject) will filter out the player:
Conclusion
If you have any problems or suggestions with the tutorial, let's discuss them in the feedback.
Enjoy!
Dave Myers
Lumpy Games
My mad neural firings
I originally wrote this tutorial back in Feb 2002 and I still receive emails on this asking for help or just saying thanks. It's very rewarding to know that in some small way this resource helps people new to the Torque Engine. This version has been updated to work with the latest release of TGE, so hopefully it will help answer many of the questions that people had with the original. -Dave
This tutorial provides one method to enable object selection in your TGE-based game. This implementation will show how to implement a bounding box cursor that will be displayed when an object is selected with the mouse. It could easily be enhanced to allow you to implement a wide variety of features.
Using this Tutorial
Bold font is used to show new code in the different code snippets throughout the tutorial. Also, code that is not important has been left out and is denoted by a comment in italics reading "irrelevant code not shown for brevity".
Step 1 - Capture the mouseDown event
First, let's quickly create a new bind and client function that will toggle the cursor. Add the following code to the bottom of the script file.
/starter.fps/client/scripts/default.bind.cs
[b]moveMap.bind(keyboard, "m", "toggleMouseLook");
function toggleMouseLook(%val)
{
if(%val)
{
if(Canvas.isCursorOn())
CursorOff();
else
CursorOn();
}
}[/b]NOTE: Make sure and delete the current config.cs and config.cs.dso files found in /starter.fps/client. This file (config.cs) is created automatically each time you shutdown the game and is executed when starting up the game.
Second, let's capture any left button mouse click events on the client. To do that, we need to add in a method to the GameTSCtrl. This control is our canvas in the game - so clicking somewhere on this canvas (i.e. our game screen) will generate a mouseDown event, and we can capture that event and deal with it in GameTSCtrl.
I chose to add new members and "getter" methods for the point and vector to the control rather than pass the information back to the client scripts as parameters. Either way seems fine, I just chose the former.
engine/game/gameTSCtrl.h
class GameTSCtrl : public GuiTSCtrl
{
private:
typedef GuiTSCtrl Parent;
[b] // object selection additions
Point3F mMouse3DVec;
Point3F mMouse3DPos; [/b]
public:
GameTSCtrl();
bool processCameraQuery(CameraQuery *query);
void renderWorld(const RectI &updateRect);
void onMouseMove(const GuiEvent &evt);
void onRender(Point2I offset, const RectI &updateRect);
[b] // object selection additions
void onMouseDown(const GuiEvent &evt); //left-mouse click
Point3F getMouse3DVec() {return mMouse3DVec;};
Point3F getMouse3DPos() {return mMouse3DPos;};[/b]
DECLARE_CONOBJECT(GameTSCtrl);
};Now let's add the implementation of the onMouseDown handler method. What we need to do is take the 2D screen coordinates of the mouse and create 3D screen coordinates. We then will create a vector that has the camera world coordinates as the first point and the 3D screen coordinates as the second point. Once we have calculated these two elements, we then call a client script function, which I also named onMouseDown, and let the scripts control it from there. The idea here is that later we will take the 3D screen coordinates and the vector and shoot a picking ray into our 3D world. We'll then check to see if that ray collides with an object in the world.
I believe the code is commented pretty well, so it should be easy to follow along.
engine/game/gameTSCtrl.cc
[b]//--------------------------------------------------------------------------
// object selection additions
//--------------------------------------------------------------------------
void GameTSCtrl::onMouseDown(const GuiEvent &evt)
{
MatrixF mat;
Point3F vel;
if ( GameGetCameraTransform(&mat, &vel) )
{
//get the camera position
Point3F pos;
mat.getColumn(3,&pos);
//take our mouse coordinates and create (x,y,z) screen coordinates
Point3F screenPoint(evt.mousePoint.x, evt.mousePoint.y, -1);
//take our screen coordinates and get the corresponding
//world coordinates (this is what unproject does for us)
Point3F worldPoint;
if (unproject(screenPoint, &worldPoint))
{
mMouse3DPos = pos;
//create a vector that points from our starting point (the
//camera position) and heads towards our point we have chosen
//in the world
mMouse3DVec = worldPoint - pos;
mMouse3DVec.normalizeSafe();
//call client script handler
Con::executef(this, 1, "onMouseDown");
}
}
}[/b]I then exposed the "getter" methods to the scripting engine, also in engine/game/gameTSCtrl.cc
[b]ConsoleMethod( GameTSCtrl, getMouse3DVec, const char*, 2, 2, "()")
{
char* retBuffer = Con::getReturnBuffer(256);
const Point3F &vec = object->getMouse3DVec();
dSprintf(retBuffer, 256, "%g %g %g", vec.x, vec.y, vec.z);
return retBuffer;
}
ConsoleMethod( GameTSCtrl, getMouse3DPos, const char*, 2, 2, "()")
{
char* retBuffer = Con::getReturnBuffer(256);
const Point3F &pos = object->getMouse3DPos();
dSprintf(retBuffer, 256, "%g %g %g", pos.x, pos.y, pos.z);
return retBuffer;
}[/b]Currently, the Torque demo has a GUI element called GuiShapeNameHud that is used to display player names and damage over the heads of the models. This control is actually almost the same size as the canvas itself, which means that any mouse events are actually handled by this control, not our GameTSCtrl. I preferred to keep the control, but modify it so that it will call to my new mouse down event handler in GameTSCtrl. To do that I inserted the following onMouseDown handler, which simply calls up to its parent - in this case, our GameTSCtrl.
engine/game/fps/guiShapeNameHud.cc
class GuiShapeNameHud : public GuiControl {
typedef GuiControl Parent;
// field data
ColorF mFillColor;
ColorF mFrameColor;
ColorF mTextColor;
F32 mVerticalOffset;
F32 mDistanceFade;
bool mShowFrame;
bool mShowFill;
protected:
void drawName( Point2I offset, const char *buf, F32 opacity);
public:
GuiShapeNameHud();
// GuiControl
virtual void onRender(Point2I offset, const RectI &updateRect);
[b] // object selection additions
virtual void onMouseDown(const GuiEvent &evt);[/b]
static void initPersistFields();
DECLARE_CONOBJECT( GuiShapeNameHud );
};
[i] // irrelevant code not shown for brevity[/i]
void GuiShapeNameHud::initPersistFields()
{
Parent::initPersistFields();
addField( "fillColor", TypeColorF, Offset( mFillColor, GuiShapeNameHud ) );
addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiShapeNameHud ) );
addField( "textColor", TypeColorF, Offset( mTextColor, GuiShapeNameHud ) );
addField( "showFill", TypeBool, Offset( mShowFill, GuiShapeNameHud ) );
addField( "showFrame", TypeBool, Offset( mShowFrame, GuiShapeNameHud ) );
addField( "verticalOffset", TypeF32, Offset( mVerticalOffset, GuiShapeNameHud ) );
addField( "distanceFade", TypeF32, Offset( mDistanceFade, GuiShapeNameHud ) );
}
[b]//--------------------------------------------------------------------------
// object selection additions
//--------------------------------------------------------------------------
void GuiShapeNameHud::onMouseDown(const GuiEvent &evt)
{
// Let's let the parent execute its event handling (if any)
GuiTSCtrl *parent = dynamic_cast<GuiTSCtrl*>(getParent());
if (parent)
parent->onMouseDown(evt);
}[/b]
/**
Core render method wich does all the work.
This method scans through all the current client ShapeBase objects
and if they are named, displays their name and damage value. If the
shape is a PlayerObjectType then values are displayed offset from it's
eye point, otherwise the bounding box center is used.
*/
void GuiShapeNameHud::onRender( Point2I, const RectI &updateRect)
{
[i] // irrelevant code not shown for brevity[/i]Step 2 - Client handler for mouse event
We now need to add a handler to the scripts for PlayGui, which is the script name of our canvas GameTSCtrl. This new method will grab the vector and point and call to a server script function SelectObject.
example/starter.fps/client/scripts/playGui.cs
[b]//-----------------------------------------------------------------------------
// object selection additions
//-----------------------------------------------------------------------------
function PlayGui::onMouseDown(%this)
{
// mouseVec = vector from camera point to 3d mouse coords (normalized)
%mouseVec = %this.getMouse3DVec();
// cameraPoint = the world position of the camera
%cameraPoint = %this.getMouse3DPos();
commandToServer('SelectObject', %mouseVec, %cameraPoint);
}
[/b]Step 3 - Set up our connection to handle our selected object
Before we can actually select an object, we need to set aside a place to save this information, and provide a way for the server to update the client so that we can render (at least in this example) a bounding box cursor around our selected object. I have chosen to save this information as part of the GameConnection class.
engine/game/gameConnection.h
class GameConnection : public NetConnection
{
[i]// irrelevant code not shown for brevity[/i]
private:
/// @name Move Packets
/// Write/read move data to the packet.
/// @{
void moveWritePacket(BitStream *bstream);
void moveReadPacket(BitStream *bstream);
/// @}
[b] ///object selection additions
SimObjectPtr<ShapeBase> mSelectedObj;//pointer to our selected object
bool mChangedSelectedObj; //flag used to determine if we have changed our selected object[/b]
public:
[b] ///object selection additions
void setSelectedObject(ShapeBase *so) { mSelectedObj = so; mChangedSelectedObj = true; }
ShapeBase* getSelectedObject() { return mSelectedObj; }
bool hasChangedSelectedObject() { return mChangedSelectedObj; }[/b]
/// @name Protocol Versions
///
/// Protocol versions are used to indicated changes in network traffic.
/// These could be changes in how any object transmits or processes
/// network information. You can specify backwards compatability by
/// specifying a MinRequireProtocolVersion. If the client
/// protocol is >= this min value, the connection is accepted.
///
/// Torque (V12) SDK 1.0 uses protocol = 1
///
/// Torque SDK 1.1 uses protocol = 2
/// @{
static const U32 CurrentProtocolVersion;
static const U32 MinRequiredProtocolVersion;
/// @}We'll need to pass the selected object around between the server and client, so in order to do so we need to modify the readPacket and writePacket methods of our GameConnection class. For a brief description of why, take a look at Justin Mette's tutorial on node hiding, starting with Step 2 - Updating ghosts. Basically, we will be saving off the selected object on the server, but our client needs to know what object is selected so that it can render the cursor around the ghost object.
We also need to create the "getter/setter" functions so we can get and set the selected object from the server scripts.
engine/game/gameConnection.cc
GameConnection::GameConnection()
{
[i]// irrelevant code not shown for brevity[/i]
//blackout vars
mBlackOut = 0.0f;
mBlackOutTimeMS = 0;
mBlackOutStartTimeMS = 0;
mFadeToBlack = false;
[b] //object selection additions
mSelectedObj = NULL;
mChangedSelectedObj = false;[/b]
}
[i]// irrelevant code not shown for brevity[/i]
ConsoleMethod( GameConnection, getControlObject, S32, 2, 2, "")
{
argv;
SimObject* cp = object->getControlObject();
return cp? cp->getId(): 0;
}
[b]//--------------------------------------------------------------------------
// object selection additions
//--------------------------------------------------------------------------
ConsoleMethod( GameConnection, setSelectedObject, bool, 3, 3, "(%object)")
{
ShapeBase *sb;
if(!Sim::findObject(argv[2], sb))
return false;
object->setSelectedObject(sb);
return true;
}
//--------------------------------------------------------------------------
// object selection additions
//--------------------------------------------------------------------------
ConsoleMethod( GameConnection, getSelectedObject, S32, 2, 2, "()")
{
SimObject* so = object->getSelectedObject();
return so? so->getId(): 0;
}[/b]
ConsoleMethod( GameConnection, isAIControlled, bool, 2, 2, "")
{
return object->isAIControlled();
}
void GameConnection::readPacket(BitStream *bstream)
{
char stringBuf[256];
stringBuf[0] = 0;
bstream->setStringBuffer(stringBuf);
clearCompression();
if (isServerConnection())
{
mLastMoveAck = bstream->readInt(32);
if (mLastMoveAck < mFirstMoveIndex)
mLastMoveAck = mFirstMoveIndex;
if(mLastMoveAck > mLastClientMove)
mLastClientMove = mLastMoveAck;
while(mFirstMoveIndex < mLastMoveAck)
{
AssertFatal(mMoveList.size(), "Popping off too many moves!");
mMoveList.pop_front();
mFirstMoveIndex++;
}
[b] // object selection additions
// selected object - do we have a change in status?
if (bstream->readFlag())
{
// do we have a selected object now?
bool hasSelectedObj = bstream->readFlag();
if ((hasSelectedObj) && (bstream->readFlag()))
{
S32 gIndex = bstream->readInt(10);
ShapeBase* obj = static_cast<ShapeBase*>(resolveGhost(gIndex));
if (mSelectedObj != obj)
setSelectedObject(obj);
}
else
mSelectedObj = NULL;
}[/b]
mDamageFlash = 0;
mWhiteOut = 0;
if(bstream->readFlag())
{
if(bstream->readFlag())
mDamageFlash = bstream->readFloat(7);
if(bstream->readFlag())
mWhiteOut = bstream->readFloat(7) * 1.5;
}
[i]// irrelevant code not shown for brevity[/i]
}
void GameConnection::writePacket(BitStream *bstream, PacketNotify *note)
{
[i] // irrelevant code not shown for brevity[/i]
else
{
// The only time mMoveList will not be empty at this
// point is during a change in control object.
bstream->writeInt(mLastMoveAck - mMoveList.size(),32);
S32 gIndex = -1;
[b] // object selection additions
// selected object - have we changed the status of the selected object?
if (bstream->writeFlag(hasChangedSelectedObject()))
{
// do we have a selected object?
if ((bstream->writeFlag(mSelectedObj != NULL)) && (!mSelectedObj.isNull()))
{
gIndex = getGhostIndex(mSelectedObj);
if (bstream->writeFlag(gIndex != -1))
bstream->writeInt(gIndex,10);
}
// reset the status of our object
mChangedSelectedObj = false;
}
gIndex = -1;[/b]
// get the ghost index of the control object, and write out
// all the damage flash & white out
if (!mControlObject.isNull())
{
gIndex = getGhostIndex(mControlObject);
F32 flash = mControlObject->getDamageFlash();
F32 whiteOut = mControlObject->getWhiteOut();
if(bstream->writeFlag(flash != 0 || whiteOut != 0))
{
if(bstream->writeFlag(flash != 0))
bstream->writeFloat(flash, 7);
if(bstream->writeFlag(whiteOut != 0))
bstream->writeFloat(whiteOut/1.5, 7);
}
}
else
bstream->writeFlag(false);
[i] // irrelevant code not shown for brevity[/i]
}Step 4 - Check if we have selected something
The server is responsible for determining if we have selected something, and if so it will save off the selected object for use later. Let's keep this simple and add our new server command SelectObject to an existing script file (I personally put this and other related server commands in a separate file). The code below is pretty well-commented, so it should be pretty easy to follow the logic there.
fps/server/scripts/commands.cs
[b]//-----------------------------------------------------------------------------
// object selection additions
//-----------------------------------------------------------------------------
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);
// Search for anything that is selectable – below are some examples
%searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::CorpseObjectType |
$TypeMasks::ItemObjectType | $TypeMasks::TriggerObjectType;
// Search for objects within the range that fit the masks above. If we are
// in first person mode, we make sure player is not selectable by setting
// fourth parameter (exempt from collisions) when calling ContainerRayCast
%player = %client.player;
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);
%client.setSelectedObject(%targetObject);
}
}[/b]Step 5 - Example implementation
Your use of object selection will be specific to your game needs. However, let's go ahead and test it by making a small addition to the ShapeBase::renderObject method. There is already code there to show a bounding box if in debug mode, so let's just reuse it in the case we have something selected as a 3D cursor of sorts. First, we need to get the server connection, which is where we stored our selected object earlier. We then simply get the object id from the object and compare it to object id of the shapebase we are rendering. If they match, we go ahead and show the bounding box:
engine/game/shapeBase.cc
void ShapeBase::renderObject(SceneState* state, SceneRenderImage* image)
{
[i] // irrelevant code not shown for brevity[/i]
dglNPatchEnd();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
dglSetViewport(viewport);
uninstallLights();
[b] // object selection additions
// if we have been selected, then render a cursor around us
// for now this is simply a bounding box
GameConnection* conn = GameConnection::getServerConnection();
ShapeBase* selectedObj = NULL;
if (conn)
selectedObj = conn->getSelectedObject();
S32 selectedId = -1;
if (selectedObj != NULL)
selectedId = selectedObj->getId();
// Debugging Bounding Box
//object selection change - added || (get() == selectedId) to existing statement
//if (!mShapeInstance || gShowBoundingBox) {
if (!mShapeInstance || gShowBoundingBox || (getId() == selectedId)) {[/b]
glDisable(GL_DEPTH_TEST);
Point3F box;
glPushMatrix();
dglMultMatrix(&getRenderTransform());
box = (mObjBox.min + mObjBox.max) * 0.5;
glTranslatef(box.x,box.y,box.z);
box = (mObjBox.max - mObjBox.min) * 0.5;
glScalef(box.x,box.y,box.z);
glColor3f(1, 0, 1);
wireCube(Point3F(1,1,1),Point3F(0,0,0));
glPopMatrix();
}
[i] // irrelevant code not shown for brevity[/i]NOTE: If you want to select your own player you can hit the 'Tab' key to put yourself in third-person camera view and select him. You cannot hit F8 and drop a camera and then click on him, because in that case the game still thinks you are in first-person mode ($firstPerson = true still) and the check we made in commands.cs (serverCmdSelectObject) will filter out the player:
if ($firstPerson)
{
%scanTarg = ContainerRayCast (%cameraPoint, %rangeEnd, %searchMasks, %player);
}
else //3rd person - player is selectable in this case
{
%scanTarg = ContainerRayCast (%cameraPoint, %rangeEnd, %searchMasks);
}Conclusion
If you have any problems or suggestions with the tutorial, let's discuss them in the feedback.
Enjoy!
Dave Myers
Lumpy Games
My mad neural firings
About the author
Considerable experience developing with Torque-based technologies and produced the first third-party game using any Torque technology (Orbz). Game designer, programmer, and producer, and credits include the innovative title Orbz and the colorful BuggOut.
#2
Edit: Found a little hickup ;)
should be:
...last character to be removed (Dave corrected my correction) :)
03/13/2005 (11:19 am)
Hi Dave and thank you for your time on this! I was thinking of making use of it for picking up inventory items during the game...I currently have a little system that uses ContainerRadiusSearch to find any items in the area that are available for pickup. Being able to select an item with the mouse will be nice :)Edit: Found a little hickup ;)
if (!mShapeInstance || gShowBoundingBox || (getId() == selectedId)) {)should be:
if (!mShapeInstance || gShowBoundingBox || (getId() == selectedId)) {...last character to be removed (Dave corrected my correction) :)
#3
03/13/2005 (1:46 pm)
Thanks for updating, Dave!
#4
03/13/2005 (1:51 pm)
@Jacob: Thanks - I removed the extra paren actually to fix it.
#5
03/13/2005 (1:54 pm)
@Dave: Right - I see what you mean :)
#6
03/13/2005 (6:15 pm)
Cool must try this out! THanks!
#7
1 Question. How do I make it so that it selects objects such as the crossbow clips, health kits etc?
Dan
03/13/2005 (10:00 pm)
Nice resource Dave! Worked right out of the box.1 Question. How do I make it so that it selects objects such as the crossbow clips, health kits etc?
Dan
#8
03/13/2005 (10:20 pm)
@Dan: Thanks. The health kits, for instance, are already selectable. Just watch out for that crosshair in starter.fps - it is right in front of you and it will intercept the picking ray.
#9
03/14/2005 (7:27 am)
I seem to ONLY be able to select NPCs, any ideas about what could be going wrong here?
#10
03/14/2005 (7:28 am)
Another thing... How do I clear my selected target?
#11
The other problem other people seemed to have the last time was the 500x300 pixel region (approx) at the top left of the screen where the thing didn't seem to work. I tracked it down to the chathud: take a look at chathud.gui and find this control:
new GuiControl() {
profile = "GuiDefaultProfile";
horizSizing = "relative";
vertSizing = "bottom";
position = "0 0";
extent = "400 300";
minExtent = "8 8";
visible = "1";
helpTag = "0";
If you change its profile to "GuiModelessDialogProfile" then it will automatically pass clicks through it. It's not easy to implement the same elegant engine fix that Dave made to the guiShapeNameHud class since in this case we're talking about a base GuiControl object..
03/14/2005 (4:07 pm)
Great resource Dave! -- I just spent the last couple of days with your original version, and found the same 'issues' as some of the others discussing it. I'm guessing the question from Creative Edge Computing is one of the two main issues revisited? The answer is that any shape is actually selectable, but only those that use ShapeBase::renderObject as part of their render routine will have the debug bounding box shown as in Dave's example. For other objects, you could simply add the few lines of code into their renderObject function. The solution that some people came to the last time, where they put a collision mesh on the objects was, I assume, more of a work-around than a bug-fix: I'm guessing that the collision mesh's bounding box is what they were then seeing, and that *it* therefore uses the ShapeBase::renderObject function.The other problem other people seemed to have the last time was the 500x300 pixel region (approx) at the top left of the screen where the thing didn't seem to work. I tracked it down to the chathud: take a look at chathud.gui and find this control:
new GuiControl() {
profile = "GuiDefaultProfile";
horizSizing = "relative";
vertSizing = "bottom";
position = "0 0";
extent = "400 300";
minExtent = "8 8";
visible = "1";
helpTag = "0";
If you change its profile to "GuiModelessDialogProfile" then it will automatically pass clicks through it. It's not easy to implement the same elegant engine fix that Dave made to the guiShapeNameHud class since in this case we're talking about a base GuiControl object..
#12
03/16/2005 (1:35 pm)
What kind of changes would this take to work in Torque2D?
#13
03/20/2005 (10:17 am)
I used the click n pick resource and been having trouble with it. Should I remove that whole thing and replace it with this?
#14
I think this will work:
in gameConnection.cc where the other methods are add this.
ConsoleMethod( GameConnection, clearSelectedObject, void, 2, 2, "()")
{
object->clearSelectedObject();
}
in gameConnection.h near the other code add this.
void clearSelectedObject(void) { mSelectedObj = NULL; mChangedSelectedObj = true; }
in the script, commands.cs where it gets the scanTarget add in an else
if (%scanTarg)
{
%targetObject = firstWord(%scanTarg);
%client.setSelectedObject(%targetObject);
}
else
{
%client.clearSelectedObject();
}
Should work when you click the mouse someplace other than the object you selected.
03/22/2005 (8:55 am)
@Dreamer clearing the selected target by clicking the mouse away from the target is what you want?I think this will work:
in gameConnection.cc where the other methods are add this.
ConsoleMethod( GameConnection, clearSelectedObject, void, 2, 2, "()")
{
object->clearSelectedObject();
}
in gameConnection.h near the other code add this.
void clearSelectedObject(void) { mSelectedObj = NULL; mChangedSelectedObj = true; }
in the script, commands.cs where it gets the scanTarget add in an else
if (%scanTarg)
{
%targetObject = firstWord(%scanTarg);
%client.setSelectedObject(%targetObject);
}
else
{
%client.clearSelectedObject();
}
Should work when you click the mouse someplace other than the object you selected.
#15
04/01/2005 (9:40 am)
Thanx Howard thats exactly what I was looking for!
#16
04/01/2005 (12:07 pm)
If you name your GuiCrossHairHud in playgui.gui to CrossHair, this is VERY helpful.function toggleMouseLook(%val)
{
if(%val)
{
if(Canvas.isCursorOn()){
CursorOff();
CrossHair.visible = 1;
}else{
CursorOn();
CrossHair.visible = 0;
}
}
}
#17
I've modified the GuiHealthBarHUD to show the health of a selected target, but need to know if this will work in Multiplayer Mode or not.
create a new .cc Name it guiTargetHud.cc and add it to your project under game/fps
04/01/2005 (2:17 pm)
Just a quicck question...I've modified the GuiHealthBarHUD to show the health of a selected target, but need to know if this will work in Multiplayer Mode or not.
create a new .cc Name it guiTargetHud.cc and add it to your project under game/fps
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "gui/guiControl.h"
#include "console/consoleTypes.h"
#include "game/gameConnection.h"
#include "game/shapeBase.h"
//-----------------------------------------------------------------------------
/// A basic health bar control.
/// This gui displays the damage value of the current PlayerObjectType
/// control object. The gui can be set to pulse if the health value
/// drops below a set value. This control only works if a server
/// connection exists and it's control object is a PlayerObjectType. If
/// either of these requirements is false, the control is not rendered.
class GuiTargetBarHud : public GuiControl
{
typedef GuiControl Parent;
bool mShowFrame;
bool mShowFill;
bool mDisplayEnergy;
bool mFlipped;
ColorF mFillColor;
ColorF mFrameColor;
ColorF mDamageFillColor;
S32 mPulseRate;
F32 mPulseThreshold;
F32 mValue;
public:
GuiTargetBarHud();
void onRender( Point2I, const RectI &);
static void initPersistFields();
DECLARE_CONOBJECT( GuiTargetBarHud );
};
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT( GuiTargetBarHud );
GuiTargetBarHud::GuiTargetBarHud()
{
mShowFrame = mShowFill = true;
mFlipped = mDisplayEnergy = false;
mFillColor.set(0, 0, 0, 0.5);
mFrameColor.set(0, 1, 0, 1);
mDamageFillColor.set(0, 1, 0, 1);
mPulseRate = 0;
mPulseThreshold = 0.3f;
mValue = 0.2f;
}
void GuiTargetBarHud::initPersistFields()
{
Parent::initPersistFields();
addGroup("Colors");
addField( "fillColor", TypeColorF, Offset( mFillColor, GuiTargetBarHud ) );
addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiTargetBarHud ) );
addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, GuiTargetBarHud ) );
endGroup("Colors");
addGroup("Pulse");
addField( "pulseRate", TypeS32, Offset( mPulseRate, GuiTargetBarHud ) );
addField( "pulseThreshold", TypeF32, Offset( mPulseThreshold, GuiTargetBarHud ) );
endGroup("Pulse");
addGroup("Misc");
addField( "flipped", TypeBool, Offset( mFlipped, GuiTargetBarHud ) );
addField( "showFill", TypeBool, Offset( mShowFill, GuiTargetBarHud ) );
addField( "showFrame", TypeBool, Offset( mShowFrame, GuiTargetBarHud ) );
addField( "displayEnergy", TypeBool, Offset( mDisplayEnergy, GuiTargetBarHud ) );
endGroup("Misc");
}
//-----------------------------------------------------------------------------
/**
Gui onRender method.
Renders a health bar with filled background and border.
*/
void GuiTargetBarHud::onRender(Point2I offset, const RectI &updateRect)
{
// Must have a connection and player control object
GameConnection* conn = GameConnection::getServerConnection();
if (!conn || !conn->getSelectedObject())
return;
ShapeBase* control = conn->getSelectedObject();
if (!control || !(control->getType() & PlayerObjectType))
return;
if(mDisplayEnergy)
{
mValue = control->getEnergyValue();
}
else
{
// We'll just grab the damage right off the control object.
// Damage value 0 = no damage.
mValue = 1 - control->getDamageValue();
}
// Background first
if (mShowFill)
dglDrawRectFill(updateRect, mFillColor);
// Pulse the damage fill if it's below the threshold
if (mPulseRate != 0)
{
if (mValue < mPulseThreshold)
{
F32 time = Platform::getVirtualMilliseconds();
F32 alpha = mFmod(time,mPulseRate) / (mPulseRate / 2.0);
mDamageFillColor.alpha = (alpha > 1.0)? 2.0 - alpha: alpha;
}
else
mDamageFillColor.alpha = 1;
}
// Render damage fill %
RectI rect(updateRect);
if(mBounds.extent.x > mBounds.extent.y)
{
if(mFlipped)
{
S32 bottomX = rect.point.x + rect.extent.x;
rect.extent.x = (S32)(rect.extent.x * mValue);
rect.point.x = bottomX - rect.extent.x;
}
else
{
rect.extent.x = (S32)(rect.extent.x * mValue);
}
}
else
{
if(mFlipped)
{
rect.extent.y = (S32)(rect.extent.y * mValue);
}
else
{
S32 bottomY = rect.point.y + rect.extent.y;
rect.extent.y = (S32)(rect.extent.y * mValue);
rect.point.y = bottomY - rect.extent.y;
}
}
dglDrawRectFill(rect, mDamageFillColor);
// Border last
if (mShowFrame)
dglDrawRect(updateRect, mFrameColor);
}Please let me know if this would work in Multiplayer mode, it compiles and runs fine on my side in SinglePlayer mode, but my MultiPlayer setup got hosed due to a HD crash and I have yet to try it under that mode.
#18
04/01/2005 (6:31 pm)
Here's a nice gui to go along with the Object Selection, requires my code above to run though.new GuiControl(TargetGUI){
profile = "GuiModelessDialogProfile";
position = "500 0";
extent = "100 60";
new GuiBitmapBorderCtrl() {
profile = "ChatHudBorderProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "500 0";
extent = "100 60";
minExtent = "8 8";
visible = "1";
new GuiTextCtrl(TargetText) {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "20 10";
extent = "8 18";
minExtent = "8 2";
visible = "1";
Text = $TargetName;
maxLength = "255";
};
new GuiTargetBarHud(TargetHealth) {
profile = "GuiDefaultProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "10 30";
extent = "80 10";
minExtent = "8 2";
visible = "1";
fillColor = "0.000000 0.000000 0.000000 0.500000";
frameColor = "0.000000 1.000000 0.000000 0.000000";
damageFillColor = "0.800000 0.000000 0.000000 1.000000";
pulseRate = "0";
pulseThreshold = "0.3";
flipped = "0";
showFill = "1";
showFrame = "1";
displayEnergy = "0";
};
new GuiTargetBarHud(TargetEnergy) {
profile = "GuiDefaultProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "10 40";
extent = "80 10";
minExtent = "8 2";
fillColor = "0.000000 0.000000 0.000000 0.500000";
frameColor = "0.000000 1.000000 0.000000 0.000000";
damageFillColor = "0.000000 0.000000 0.800000 1.000000";
pulseRate = "0";
pulseThreshold = "0.3";
flipped = "0";
showFill = "1";
showFrame = "1";
displayEnergy = "1";
};
};
};
#19
Have you been able to target a CorpseObjectType ?
04/02/2005 (6:42 am)
That looks good Dreamer, I dont see anything wrong with it. We are using a modified version of the crosshair health bar for targeting so I can't try that out since we are so close to final approach.Have you been able to target a CorpseObjectType ?
#20
04/11/2005 (2:15 pm)
Well it works in Multiplayer mode, as for targeting corpses I have had no need, as long the target died while still targeted his corpse is also targeted.
Torque Owner Josh Moore