Game Development Community

RPG Object Selection Hud (TGEA 1.8.x)

by Taras (TSK) Anatsko · 03/13/2009 (1:54 pm) · 3 comments

Original resource by Matt Huston
Tested with TGEA 1.8.1
Ported by TSK

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 but remember you will need to make sure the object type being added has its own CastRay function or it won't be picked up. ItemObjectType for instance doesn't have one so you will need to add it yourself.

Add guiObjectSelectionHud.cpp to your project in the engine/T3D/fps directory.

//-----------------------------------------------------------------------------
// guiObjectSelectionHud.cpp
// 
// Description: Action-RPG object selection gui element.
//
// Author: Matt Huston (matthuston@gmail.com)
// Date: June 28th, 2007
//
// TGEA 1.8.x port
// by TSK (TSK@TSKGames.com)
//-----------------------------------------------------------------------------

#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"

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;
	
   GameBase * control = dynamic_cast<GameBase*>(conn->getControlObject());
   if (!control) 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->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();
		
      
		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";
   };

About the author

Recent Blogs

• BricksOff! Lite
• S.Y.S.
• ColorTorque

#1
03/13/2009 (4:16 pm)
A screenshot would be nice. Sounds cool though.
#2
03/14/2009 (6:13 am)
Clever and very easy to modify, thanks to your (and Matt Huston) clean coding style. I immediately incorporated this into an 'world editor improvement' i have been working on, saved me 2 days(estimated) work!
#3
03/15/2009 (9:09 pm)
I did get the old version to work in TGEA, but thanks for the port so i could clean up my code :D