RPG Object Selection (T3D)
by Matt Huston · 10/09/2009 (5:07 am) · 16 comments
Tested with T3D 1.0
Overview
Object selection similar to Deus Ex, System Shock and other Action-RPGs. A outline rectangle is drawn and the objects name is printed at the top right corner. To add new objects, check the ObjectMask. Also, if you are using a custom object - you will need to make sure it has a castRay function. Check Player::castRay for a good example.
Copy the following source code into a newly created guiObjectSelectionHud.cpp and add to your project. (I use engine/T3D/fps/guiObjectSelectionHud.cpp directory.
Compile!
Add the following to the bottom of you playGui.gui.
OutlineColor - Object selection rectangle color.
TextColor - Object names text color.
TextOutlineColor - Text rectangles outline color.
iTextFillColor - Rectangle behind texts fill color.
Offset - Offset from the objects bounding box to the actual rectangle.
Overview
Object selection similar to Deus Ex, System Shock and other Action-RPGs. A outline rectangle is drawn and the objects name is printed at the top right corner. To add new objects, check the ObjectMask. Also, if you are using a custom object - you will need to make sure it has a castRay function. Check Player::castRay for a good example.
Copy the following source code into a newly created guiObjectSelectionHud.cpp and add to your project. (I use engine/T3D/fps/guiObjectSelectionHud.cpp directory.
//-----------------------------------------------------------------------------
// guiObjectSelectionHud.cpp
//
// Description: Action-RPG object selection gui element.
//
// Author: Matt Huston (matthuston@gmail.com)
// Date: June 28th, 2007
// Updated: October 9, 2009
//
//-----------------------------------------------------------------------------
#include "gui/core/guiControl.h"
#include "gui/3d/guiTSControl.h"
#include "console/consoleTypes.h"
#include "sceneGraph/sceneGraph.h"
#include "T3D/shapeBase.h"
#include "T3D/gameConnection.h"
#include "gfx/gfxDrawUtil.h"
class GuiObjectSelectionHud : public GuiControl
{
typedef GuiControl Parent;
S32 mOffset;
ColorF mTextColor;
ColorF mTextOutlineColor;
ColorF mTextFillColor;
ColorF mOutlineColor;
protected:
void drawTargetingRect( const Point2I &a, const Point2I &b, F32 offset, const ColorI &color );
void drawName( Point2I offset, const char *buf, F32 opacity);
public:
DECLARE_CONOBJECT( GuiObjectSelectionHud );
GuiObjectSelectionHud();
static void initPersistFields();
void onRender( Point2I, const RectI &);
};
// Objects we want the castRay to detect
static const U32 ObjectMask = PlayerObjectType | VehicleObjectType;
IMPLEMENT_CONOBJECT( GuiObjectSelectionHud );
GuiObjectSelectionHud::GuiObjectSelectionHud()
{
mOffset = 10.0f;
mTextColor.set(0.2f, 0.2f, 0.2f, 1.0f);
mTextOutlineColor.set(0.0f, 1.0f, 0.0f, 1.0f);
mTextFillColor.set(0.8f, 0.8f, 0.8f, 0.8f);
mOutlineColor.set(0.0f, 1.0f, 0.0f, 1.0f);
}
void GuiObjectSelectionHud::initPersistFields()
{
Parent::initPersistFields();
addField( "Offset", TypeS32, Offset( mOffset, GuiObjectSelectionHud ) );
addField( "TextColor", TypeColorF, Offset( mTextColor, GuiObjectSelectionHud ) );
addField( "TextOutlineColor", TypeColorF, Offset( mTextOutlineColor, GuiObjectSelectionHud ) );
addField( "TextFillColor", TypeColorF, Offset( mTextFillColor, GuiObjectSelectionHud ) );
addField( "OutlineColor", TypeColorF, Offset( mOutlineColor, GuiObjectSelectionHud ) );
}
void GuiObjectSelectionHud::onRender(Point2I offset, const RectI &updateRect)
{
// Must have a connection and player control object
GameConnection* conn = GameConnection::getConnectionToServer();
if (!conn)
return;
ShapeBase* control = dynamic_cast<ShapeBase*>(conn->getControlObject());
if (!control || !(control->getType() & ObjectMask) || !conn->isFirstPerson())
return;
GuiTSCtrl *parent = dynamic_cast<GuiTSCtrl*>(getParent());
if (!parent)
return;
// Parent render.
Parent::onRender(offset, updateRect);
// Get control camera info
MatrixF cam;
Point3F camPos;
conn->getControlCameraTransform(0, &cam);
cam.getColumn(3, &camPos);
// Extend the camera vector to create an endpoint for our ray
Point3F endPos;
cam.getColumn(1, &endPos);
endPos *= 10.0f;
endPos += camPos;
// Collision info. We're going to be running LOS tests and we don't
// want to collide with the control object, terrain or interiors.
static U32 losMask = TerrainObjectType | InteriorObjectType | ShapeBaseObjectType;
control->disableCollision();
RayInfo info;
if (gClientContainer.castRay(camPos, endPos, losMask, &info))
{
if (ShapeBase* obj = dynamic_cast<ShapeBase*>(info.object))
{
// If the object has a name, we will allow it to be selected
if (obj->getShapeName())
{
Box3F bounds = obj->getRenderWorldBox();
// Generate the 8 bounding points of the box.
Point3F pts[8];
U32 i = 0;
pts[i].x = bounds.minExtents.x;
pts[i].y = bounds.minExtents.y;
pts[i].z = bounds.minExtents.z;
i++;
pts[i].x = bounds.minExtents.x;
pts[i].y = bounds.minExtents.y;
pts[i].z = bounds.maxExtents.z;
i++;
pts[i].x = bounds.minExtents.x;
pts[i].y = bounds.maxExtents.y;
pts[i].z = bounds.minExtents.z;
i++;
pts[i].x = bounds.minExtents.x;
pts[i].y = bounds.maxExtents.y;
pts[i].z = bounds.maxExtents.z;
i++;
pts[i].x = bounds.maxExtents.x;
pts[i].y = bounds.minExtents.y;
pts[i].z = bounds.minExtents.z;
i++;
pts[i].x = bounds.maxExtents.x;
pts[i].y = bounds.minExtents.y;
pts[i].z = bounds.maxExtents.z;
i++;
pts[i].x = bounds.maxExtents.x;
pts[i].y = bounds.maxExtents.y;
pts[i].z = bounds.minExtents.z;
i++;
pts[i].x = bounds.maxExtents.x;
pts[i].y = bounds.maxExtents.y;
pts[i].z = bounds.maxExtents.z;
i++;
Point3F boxCenter;
bounds.getCenter(&boxCenter);
Point3F rectCenter;
parent->project(boxCenter, &rectCenter);
RectI rect(rectCenter.x, rectCenter.y, 1, 1);
Point3F pt;
for(S32 i = 0 ; i < 8; i++)
{
parent->project(pts[i], &pt);
if (pt.x < rect.point.x)
{
rect.point.x = pt.x;
}
if (pt.y < rect.point.y)
{
rect.point.y = pt.y;
}
}
for(S32 i = 0 ; i < 8 ; i++)
{
parent->project(pts[i], &pt);
if (pt.x > rect.point.x + rect.extent.x)
{
rect.extent.x = pt.x - rect.point.x;
}
if (pt.y > rect.point.y + rect.extent.y)
{
rect.extent.y = pt.y - rect.point.y;
}
}
// Draw box
drawTargetingRect(rect.point,
Point2I(rect.extent.x + rect.point.x - 1, rect.extent.y + rect.point.y - 1),
20.0f,
mOutlineColor);
drawName(Point2I(rect.point.x, rect.point.y), obj->getShapeName(), 1.0f);
}
}
}
// Restore control object collision
control->enableCollision();
}
void GuiObjectSelectionHud::drawName(Point2I offset, const char *name, F32 opacity)
{
offset.x += 5.0f;
offset.y += 5.0f;
// Text outline rectangle
GFX->getDrawUtil()->drawRect(Point2I(offset.x, offset.y),
Point2I(offset.x + mProfile->mFont->getStrWidth((const UTF8 *)name) + mOffset,
offset.y + mProfile->mFont->getHeight()),
mTextOutlineColor);
// Text outline fill rectangle
GFX->getDrawUtil()->drawRectFill(Point2I(offset.x + 1.0f, offset.y + 1.0f),
Point2I(offset.x + mProfile->mFont->getStrWidth((const UTF8 *)name) + mOffset - 1.0f,
offset.y + mProfile->mFont->getHeight() - 1.0f),
mTextFillColor);
// Position text in the rectangle and draw
offset.x = offset.x + (mOffset / 2);
GFX->getDrawUtil()->setBitmapModulation(mTextColor);
GFX->getDrawUtil()->drawText(mProfile->mFont, offset, name);
GFX->getDrawUtil()->clearBitmapModulation();
}
void GuiObjectSelectionHud::drawTargetingRect( const Point2I &a, const Point2I &b, F32 offset, const ColorI &color )
{
// GFX->getDrawUtil()->setBaseRenderState();
// NorthWest and NorthEast facing offset vectors
Point2F l(-0.5f, -0.5f);
Point2F r(+0.5f, -0.5f);
for(int i = 0 ; i < 4 ; i++)
{
GFXVertexBufferHandle<GFXVertexPC> verts (GFX, 8, GFXBufferTypeVolatile );
verts.lock();
switch(i)
{
// Top Left Corner
case 0:
verts[0].point.set( a.x, a.y - l.y, 0.0f );
verts[1].point.set( a.x, a.y + l.y, 0.0f );
verts[2].point.set( a.x + offset, a.y + r.y, 0.0f );
verts[3].point.set( a.x + offset, a.y - r.y, 0.0f );
verts[4].point.set( a.x - l.x, a.y, 0.0f );
verts[5].point.set( a.x + l.x, a.y, 0.0f );
verts[6].point.set( a.x + r.x, a.y + offset, 0.0f );
verts[7].point.set( a.x - r.x, a.y + offset, 0.0f );
break;
// Top Right Corner
case 1:
verts[0].point.set( b.x, a.y - l.y, 0.0f );
verts[1].point.set( b.x, a.y + l.y, 0.0f );
verts[2].point.set( b.x - offset, a.y + r.y, 0.0f );
verts[3].point.set( b.x - offset, a.y - r.y, 0.0f );
verts[4].point.set( b.x - l.x, a.y, 0.0f );
verts[5].point.set( b.x + l.x, a.y, 0.0f );
verts[6].point.set( b.x + r.x, a.y + offset, 0.0f );
verts[7].point.set( b.x - r.x, a.y + offset, 0.0f );
break;
// Bottom Left Corner
case 2:
verts[0].point.set( a.x, b.y - l.y, 0.0f );
verts[1].point.set( a.x, b.y + l.y, 0.0f );
verts[2].point.set( a.x + offset, b.y + r.y, 0.0f );
verts[3].point.set( a.x + offset, b.y - r.y, 0.0f );
verts[4].point.set( a.x - l.x, b.y, 0.0f );
verts[5].point.set( a.x + l.x, b.y, 0.0f );
verts[6].point.set( a.x + r.x, b.y - offset, 0.0f );
verts[7].point.set( a.x - r.x, b.y - offset, 0.0f );
break;
// Bottom Right Corner
case 3:
verts[0].point.set( b.x, b.y - l.y, 0.0f );
verts[1].point.set( b.x, b.y + l.y, 0.0f );
verts[2].point.set( b.x - offset, b.y + r.y, 0.0f );
verts[3].point.set( b.x - offset, b.y - r.y, 0.0f );
verts[4].point.set( b.x - l.x, b.y, 0.0f );
verts[5].point.set( b.x + l.x, b.y, 0.0f );
verts[6].point.set( b.x + r.x, b.y - offset, 0.0f );
verts[7].point.set( b.x - r.x, b.y - offset, 0.0f );
break;
}
for (int j = 0 ; j < 8 ; j++)
verts[j].color = color;
verts.unlock();
GFXStateBlockDesc rectFill;
rectFill.setCullMode(GFXCullNone);
rectFill.setZReadWrite(false);
rectFill.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
GFX->createStateBlock( rectFill );
GFX->setVertexBuffer( verts );
GFX->drawPrimitive( GFXTriangleStrip, 0, 6 );
}
}Compile!
Add the following to the bottom of you playGui.gui.
new GuiObjectSelectionHud() {
canSaveDynamicFields = "0";
Profile = "GuiDefaultProfile";
HorizSizing = "relative";
VertSizing = "relative";
Position = "0 0";
Extent = "640 480";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
hovertime = "1000";
TextColor = "1 1 1 1";
TextOutlineColor = "1 1 1 1";
TextFillColor = "0.3 0.3 0.3 0.75";
OutlineColor = "1 1 1 1";
Offset = "10";
};OutlineColor - Object selection rectangle color.
TextColor - Object names text color.
TextOutlineColor - Text rectangles outline color.
iTextFillColor - Rectangle behind texts fill color.
Offset - Offset from the objects bounding box to the actual rectangle.
About the author
www.atomicbanzai.com
#2
Nice to see a T3D port :D Thanks for the effort !
10/09/2009 (1:36 pm)
Integrated previous iterations of this in TGE and TGEA !Nice to see a T3D port :D Thanks for the effort !
#4
10/09/2009 (6:26 pm)
your drawing of the box rocks! :D
#5
10/09/2009 (8:31 pm)
how do yo do for show in third person? how to transform the coordinates?
#6
10/10/2009 (7:47 am)
That's very usefull VERY! Thanks for the resource.
#7
For the boxes, that will depend on the position of your camera, so you won't be able to hardcode it.
10/11/2009 (5:36 pm)
@Javier: This works in 1st person because the camera position and the eye position of the player object are the same. I suspect you need to change the starting point for your ray from the camera to the player object (around line 90).For the boxes, that will depend on the position of your camera, so you won't be able to hardcode it.
#8
Fiammbye is correct, if there is a 3rd person crosshair resource out there, you could rip out the castRay code from that and use it with this code.
10/11/2009 (7:05 pm)
@Javier: Fiammbye is correct, if there is a 3rd person crosshair resource out there, you could rip out the castRay code from that and use it with this code.
#9
On a similiar note, do you know of any resource where I can select a player-character (like a circle or square or star on the ground) like in Dungeon Siege. Or perhaps a glowing outliine of the selected character. I already know about the AFX Core package. it is on my purchase bucket list.
10/13/2009 (5:37 pm)
Almost what I was looking for. Does this resource work in TGE 1.4On a similiar note, do you know of any resource where I can select a player-character (like a circle or square or star on the ground) like in Dungeon Siege. Or perhaps a glowing outliine of the selected character. I already know about the AFX Core package. it is on my purchase bucket list.
#10
10/14/2009 (4:53 pm)
Check my resources for a TGE version - though it is TGE 1.5.2. Regarding square or circle on the ground - that is what AFX is for.
#11
I then made the client resolve the ghost ID to its index ID and then forward the information to the server. The server then pulled the real name and did whatever I wanted with it server side.
My problem is that I want to pull the datablock name from the item back from across the selection instead of it being NULL.
So at this point I can make the network always on first connect forward the datablock name data to the client ghost objects or something else.
So, what do you recommend Matt?
11/23/2009 (6:02 am)
I wanted to use this in a multiplayer type environment. I also wanted it to selected Item type objects. I worked on coding in a way for the selector to choose item's based on a boxcollide rather then a raycast. Mostly because item's don't always have something you can physically collide a raycast with. I then told it to add a dynamic field that included the ghost ID of the object it is currently pointed at.I then made the client resolve the ghost ID to its index ID and then forward the information to the server. The server then pulled the real name and did whatever I wanted with it server side.
My problem is that I want to pull the datablock name from the item back from across the selection instead of it being NULL.
So at this point I can make the network always on first connect forward the datablock name data to the client ghost objects or something else.
So, what do you recommend Matt?
#12
First I had to expand the class Item to include another variable I called mine mDB. So in the public declarations for the class I put in
Then in packUpdate :
11/25/2009 (8:57 am)
Ok, So after doing some research and asking a ton of questions on mIRC "Just a note the mIRC channel is awesome". I figured out how network a field name like I wanted.First I had to expand the class Item to include another variable I called mine mDB. So in the public declarations for the class I put in
Public: //some code here omited String mDB; //some code here omited
Then in packUpdate :
//This is the the packData function that contained the rotation and static of the item class object //ommited code here stream->writeFlag(mCollideable); mDB = getString(mDatablock().getName()); char buffer[32]; buffer[0] = copyString(mDB, 32); //U forgot the copyString command sorry stream->writeString(buffer); //omited code herethen in unpackUpdate :
mCollideable = stream->readFlag(); char buffer[32]; stream->readString(buffer); mDB = copyString(buffer, 32);Now the ghosted objects that the client gets have an attribute that the engine can call that is equal to mDB. If you want to make it so you can call it from script you have to do another step.
#13
01/18/2010 (5:44 pm)
thanks
#14
06/09/2010 (10:31 pm)
Nice One..........
#15
Unless you update this line:
to:
09/05/2011 (10:54 am)
This does not work in T3D 1.1.Unless you update this line:
if (!control || !(control->getType() & ObjectMask) || !conn->isFirstPerson())
to:
if (!control || !(control->getTypeMask() & ObjectMask) || !conn->isFirstPerson())
#16
EDIT: In T3D 1.1, I'm having issues where the bottom part of the rectangle clips into the terrain the object is sitting on. I thought this might be caused by the state block never actually being set, but I added a call to GFX->setStateBlock, and it didn't seem to have any effect. Ideas?
01/28/2012 (8:24 am)
There are also a few files that have moved:#include "scene/sceneManager.h" #include "T3D/gameBase/gameConnection.h"Great resource!
EDIT: In T3D 1.1, I'm having issues where the bottom part of the rectangle clips into the terrain the object is sitting on. I thought this might be caused by the state block never actually being set, but I added a call to GFX->setStateBlock, and it didn't seem to have any effect. Ideas?
Torque 3D Owner Aldavidson