Prototyping a 3rd person targeting system
by Inflight · in Torque Game Engine · 08/02/2007 (12:20 pm) · 7 replies
I'm prototyping a 3rd person targeting system using the GuiShapeNameHud. It currently shows a damage bar when you target a player. And now I am trying to add a crosshair that will follow the end point of the muzzle raycast. It will basically move around on the players body when you have a player targeted. Btw, I plan on turning this into a resource when its finished.
My issue at this point is trying to covert the world points from the raycast for gunRay into 2d points to display a bitmap crosshair in the correct location on the players body. I'm close but I seemed to be over looking
something. I'm not looking for code just some suggestions or tell me were I am going wrong. I've spent a week searching the forums trying to find something simular to what I am doing with no luck.
I am testing this with the Fluid Camera Resource. So if you would like to play around with this code I
suggest adding the Fluid camera resource to get the full effect.
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=8413
My issue at this point is trying to covert the world points from the raycast for gunRay into 2d points to display a bitmap crosshair in the correct location on the players body. I'm close but I seemed to be over looking
something. I'm not looking for code just some suggestions or tell me were I am going wrong. I've spent a week searching the forums trying to find something simular to what I am doing with no luck.
I am testing this with the Fluid Camera Resource. So if you would like to play around with this code I
suggest adding the Fluid camera resource to get the full effect.
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=8413
#include "dgl/dgl.h"
#include "dgl/gFont.h"
#include "gui/core/guiControl.h"
#include "gui/core/guiTSControl.h"
#include "console/consoleTypes.h"
#include "sceneGraph/sceneGraph.h"
#include "game/shapeBase.h"
#include "game/gameConnection.h"
#include "terrain/terrData.h"
#include "interior/interior.h"
//----------------------------------------------------------------------------
/// Displays name & damage above shape objects.
///
/// This control displays the name and damage value of all named
/// ShapeBase objects on the client. The name and damage of objects
/// within the control's display area are overlayed above the object.
///
/// This GUI control must be a child of a TSControl, and a server connection
/// and control object must be present.
///
/// This is a stand-alone control and relies only on the standard base GuiControl.
class GuiShapeNameHud : public GuiControl
{
typedef GuiControl Parent;
// field data
ColorF mFillColor;
ColorF mFrameColor;
ColorF mTextColor;
F32 mVerticalOffset;
F32 mDistanceFade;
bool mShowFrame;
bool mShowFill;
bool mShowDamage;
ColorF mDamageFillColor;
ColorF mDamageFrameColor;
Point2I mDamageRectSize;
Point2I mDamageOffset;
TextureHandle mCrosshairBmp;
protected:
void drawName( Point2I offset, const char *buf, F32 opacity);
void drawDamage(Point2I offset, F32 damage, F32 opacity);
void drawCrosshair(Point2I offset);
public:
GuiShapeNameHud(); // GuiControl
virtual void onRender(Point2I offset, const RectI &updateRect);
static void initPersistFields();
DECLARE_CONOBJECT( GuiShapeNameHud );
};
IMPLEMENT_CONOBJECT(GuiShapeNameHud);
//-----------------------------------------------------------------------------
/// Default distance for object's information to be displayed.
static const F32 cDefaultVisibleDistance = 500.0f;
GuiShapeNameHud::GuiShapeNameHud()
{
mFillColor.set( 0.25, 0.25, 0.25, 0.25 );
mFrameColor.set( 0, 1, 0, 1 );
mTextColor.set( 0, 1, 0, 1 );
mShowFrame = mShowFill = true;
mShowDamage = true;
mVerticalOffset = 0.5;
mDistanceFade = 0.1;
mDamageFillColor.set( 0, 1, 0, 1 );
mDamageFrameColor.set( 1, 0.6, 0, 1 );
mDamageRectSize.set(50, 4);
mDamageOffset.set(0,32);
}
void GuiShapeNameHud::initPersistFields()
{
Parent::initPersistFields();
addGroup("Colors");
addField( "fillColor", TypeColorF, Offset( mFillColor, GuiShapeNameHud ) );
addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiShapeNameHud ) );
addField( "textColor", TypeColorF, Offset( mTextColor, GuiShapeNameHud ) );
endGroup("Colors");
#2
08/02/2007 (12:23 pm)
More...// All ghosted objects are added to the server connection group,
// so we can find all the shape base objects by iterating through
// our current connection.
for (SimSetIterator itr(conn); *itr; ++itr)
{
if ((*itr)->getType() & ShapeBaseObjectType)
{
ShapeBase* shape = static_cast<ShapeBase*>(*itr);
if (shape != control && shape->getShapeName())
{
// Target pos to test, if it's a player run the LOS to his eye
// point, otherwise we'll grab the generic box center.
Point3F shapePos;
if (shape->getType() & PlayerObjectType)
{
MatrixF eye;
// Use the render eye transform, otherwise we'll see jittering
shape->getRenderEyeTransform(&eye);
eye.getColumn(3, &shapePos);
}
else
{
// Use the render transform instead of the box center
// otherwise it'll jitter.
MatrixF srtMat = shape->getRenderTransform();
srtMat.getColumn(3, &shapePos);
}
VectorF shapeDir = shapePos - camPos;
// Test to see if it's in range
F32 shapeDist = shapeDir.lenSquared();
if (shapeDist == 0 || shapeDist > visDistanceSqr)
continue;
shapeDist = mSqrt(shapeDist);
// Test to see if it's within our viewcone, this test doesn't
// actually match the viewport very well, should consider
// projection and box test.
shapeDir.normalize();
F32 dot = mDot(shapeDir, camDir);
if (dot < camFov)
continue;
// Test to see if it's behind something, and we want to
// ignore anything it's mounted on when we run the LOS.
RayInfo info;
shape->disableCollision();
ShapeBase *mount = shape->getObjectMount();
if (mount)
mount->disableCollision();
bool los = !gClientContainer.castRay(camPos, shapePos, losMask, &info);
shape->enableCollision();
if (mount)
mount->enableCollision();
if (!los)
continue;
// Project the shape pos into screen space and calculate
// the distance opacity used to fade the labels into the
// distance.
Point3F projPnt;
shapePos.z += mVerticalOffset;
if (!parent->project(shapePos, &projPnt))
continue;
F32 opacity = (shapeDist < fadeDistance)? 1.0:
1.0 - (shapeDist - fadeDistance) / (visDistance - fadeDistance);
// Render the shape's name
drawName(Point2I((S32)projPnt.x, (S32)projPnt.y),shape->getShapeName(),opacity);
}
}
}
// Restore control object collision
control->enableCollision();
// 3rd Person Targeting
// do a raycast from the muzzle. if we hit a player show a damage bar over
// the players head and track with crosshair.
// Ripped code from Max Robinson's 3rd/1st Person Super Crosshair Resource
// This has been heavily modified.
// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=3533
#3
08/02/2007 (12:24 pm)
More...// are we in 3rd person?
if(!conn->isFirstPerson())
{
// MUZZLE RAYCAST
MatrixF gun;
Point3F gunPos, gunVec;
conn->getControlObject()->getMuzzleTransform(0,&gun);
gun.getColumn(3, &gunPos);
gun.getColumn(1, &gunVec);
Point3F gunEndPos = gunVec;
gunEndPos *= gClientSceneGraph->getVisibleDistance();
gunEndPos += gunPos;
// disable collisions with control object
control->disableCollision();
RayInfo gunRay;
gunRay.object = 0;
gClientContainer.castRay(gunPos, gunEndPos, losMask, &gunRay);
// CAMERA LOS VERIFICATION
Point3F camVec;
conn->getControlCameraTransform(0,&cam);
cam.getColumn(3, &camPos);
cam.getColumn(1, &camVec);
RayInfo camRay;
camRay.object = 0;
gClientContainer.castRay(camPos, gunRay.point + camVec, losMask, &camRay);
// we are done with the raycasting
control->enableCollision();
Point3F targetPoint = gunPos;
bool hasObject = false;
F32 dmgLevel = 1;
Point3F crossHairPnt;
crossHairPnt.x = gunRay.point.x;
crossHairPnt.y = gunRay.point.y;
Point3F dist = (camRay.point - gunRay.point);
if (dist.len() < 1) // allow some small error
{
if (ShapeBase* shape = dynamic_cast<ShapeBase*>(gunRay.object))
{
if(shape->getShapeName())
{
hasObject = true;
dmgLevel = shape->getDamageValue();
MatrixF eye;
Point3F TargetshapePos;
// Use the render eye transform,
// otherwise we'll see jittering O.o
shape->getRenderEyeTransform(&eye);
eye.getColumn(3, &TargetshapePos);
Point3F projPnt;
TargetshapePos.z += mVerticalOffset;
if (!parent->project(TargetshapePos, &projPnt)){
return;
}
// if we have an object, render the hud part
if (hasObject)
{
if(mShowDamage){
drawDamage(Point2I((S32)projPnt.x, (S32)projPnt.y), shape->getDamageValue(), 1);
drawCrosshair(Point2I((S32)crossHairPnt.x, (S32)crossHairPnt.y));
}
}
}
}
else
return;
}
}
// Border last
if (mShowFrame)
dglDrawRect(updateRect, mFrameColor);
}
#4
08/02/2007 (12:25 pm)
Last part...//----------------------------------------------------------------------------
/// Render object names.
///
/// Helper function for GuiShapeNameHud::onRender
///
/// @param offset Screen coordinates to render name label. (Text is centered
/// horizontally about this location, with bottom of text at
/// specified y position.)
/// @param name String name to display.
/// @param opacity Opacity of name (a fraction).
void GuiShapeNameHud::drawName(Point2I offset, const char *name, F32 opacity)
{
// Center the name
offset.x -= mProfile->mFont->getStrWidth((const UTF8 *)name) / 2;
offset.y -= mProfile->mFont->getHeight();
// Deal with opacity and draw.
mTextColor.alpha = opacity;
dglSetBitmapModulation(mTextColor);
dglDrawText(mProfile->mFont, offset, name);
dglClearBitmapModulation();
}
//----------------------------------------------------------------------------
// Render Damagebar.
// Ripped from GuiCrossHairHud.cc
//----------------------------------------------------------------------------
void GuiShapeNameHud::drawDamage(Point2I offset, F32 damage, F32 opacity)
{
mDamageFillColor.alpha = mDamageFrameColor.alpha = opacity;
// Damage should be 0->1 (0 being no damage,or healthy), but
// we'll just make sure here as we flip it.
damage = mClampF(1 - damage, 0, 1);
// Center the bar
RectI rect(offset, mDamageRectSize);
rect.point.x -= mDamageRectSize.x / 2;
// Draw the border
dglDrawRect(rect, mDamageFrameColor);
// Draw the damage % fill
rect.point += Point2I(1, 1);
rect.extent -= Point2I(1, 1);
rect.extent.x = (S32)(rect.extent.x * damage);
if (rect.extent.x == 1)
rect.extent.x = 2;
if (rect.extent.x > 0)
dglDrawRectFill(rect, mDamageFillColor);
}
//----------------------------------------------------------------------------
// Render Crosshair.
//----------------------------------------------------------------------------
void GuiShapeNameHud::drawCrosshair(Point2I offset)
{
// TODO: add console methods
mCrosshairBmp = TextureHandle("starter.fps/client/ui/crossHair", BitmapTexture, true);
if(mCrosshairBmp){
TextureObject* crossHair = (TextureObject *) mCrosshairBmp;
dglClearBitmapModulation();
dglDrawBitmap(crossHair, Point2I(offset.x, offset.y), false);
}
}
#5
Here is the magic.
I replaced this...
With this
Of course, after I figured that out I ran into another snag. When I aimed the crosshair at the sky the game locked up on me. Doh!
Heh, easy fix really, just had to add EnvironmentObjectType to the losMask. Added the WaterObjectType just in case. Lockups suck ;)
Ok, time to start working on the console methods for loading that bitmap and add some high lighting color to that crosshair.
08/02/2007 (6:25 pm)
Well, I figured out what I was doing wrong and as usual I over looked the obvious. I needed to use Project() and create a pointer using the camRay.point. I really need to cut back on the coffee...Here is the magic.
I replaced this...
Point3F crossHairPnt;
crossHairPnt.x = gunRay.point.x;
crossHairPnt.y = gunRay.point.y;With this
Point3F crossHairPnt = camRay.point;
Point3F projCrossHair;
if (!parent->project(crossHairPnt, &projCrossHair)){
return;
}Of course, after I figured that out I ran into another snag. When I aimed the crosshair at the sky the game locked up on me. Doh!
Heh, easy fix really, just had to add EnvironmentObjectType to the losMask. Added the WaterObjectType just in case. Lockups suck ;)
static U32 losMask = TerrainObjectType | InteriorObjectType | ShapeBaseObjectType | EnvironmentObjectType | WaterObjectType;
Ok, time to start working on the console methods for loading that bitmap and add some high lighting color to that crosshair.
#6
08/02/2007 (6:33 pm)
This looks good, look forward to the resource, i currently using fluidcamera as well, but a more accurate crosshair would be awesome.
#7
Yeah, the cool part about this is you can use the stock GuiCrossHairHud for first person and use this resource for 3rd person. At least thats my plan anyway.
I think I'm going to add vertical and horizontal offsets so you can adjust the crosshair as well. But, this might be over kill, we will see.
Also plan to link a video showing it in action in the resource.
If you got any suggestion or "it would be cool if it did this" let me know.
08/02/2007 (8:38 pm)
Thanks Ian, Yeah, the cool part about this is you can use the stock GuiCrossHairHud for first person and use this resource for 3rd person. At least thats my plan anyway.
I think I'm going to add vertical and horizontal offsets so you can adjust the crosshair as well. But, this might be over kill, we will see.
Also plan to link a video showing it in action in the resource.
If you got any suggestion or "it would be cool if it did this" let me know.
Torque Owner Inflight
Default Studio Name
addGroup("Misc"); addField( "showFill", TypeBool, Offset( mShowFill, GuiShapeNameHud ) ); addField( "showFrame", TypeBool, Offset( mShowFrame, GuiShapeNameHud ) ); addField( "verticalOffset", TypeF32, Offset( mVerticalOffset, GuiShapeNameHud ) ); addField( "distanceFade", TypeF32, Offset( mDistanceFade, GuiShapeNameHud ) ); endGroup("Misc"); addGroup("Damage"); addField( "showDamage", TypeBool, Offset( mShowDamage, GuiShapeNameHud ) ); addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, GuiShapeNameHud) ); addField( "damageFrameColor", TypeColorF, Offset( mDamageFrameColor, GuiShapeNameHud) ); addField( "damageRect", TypePoint2I, Offset( mDamageRectSize, GuiShapeNameHud) ); addField( "damageOffset", TypePoint2I, Offset( mDamageOffset, GuiShapeNameHud) ); endGroup("Damage"); } //---------------------------------------------------------------------------- /// Core rendering method for this control. /// /// This method scans through all the current client ShapeBase objects. /// If one is named, it displays the name and damage information for it. /// /// Information is offset from the center of the object's bounding box, /// unless the object is a PlayerObjectType, in which case the eye point /// is used. /// /// @param updateRect Extents of control. void GuiShapeNameHud::onRender( Point2I, const RectI &updateRect) { // Background fill first if (mShowFill) dglDrawRectFill(updateRect, mFillColor); // Must be in a TS Control GuiTSCtrl *parent = dynamic_cast<GuiTSCtrl*>(getParent()); if (!parent) return; // Must have a connection and control object GameConnection* conn = GameConnection::getConnectionToServer(); if (!conn) return; ShapeBase* control = conn->getControlObject(); if (!control) return; // Get control camera info MatrixF cam; Point3F camPos; VectorF camDir; conn->getControlCameraTransform(0,&cam); cam.getColumn(3, &camPos); cam.getColumn(1, &camDir); F32 camFov; conn->getControlCameraFov(&camFov); camFov = mDegToRad(camFov) / 2; // Visible distance info & name fading F32 visDistance = gClientSceneGraph->getVisibleDistance(); F32 visDistanceSqr = visDistance * visDistance; F32 fadeDistance = visDistance * mDistanceFade; // Collision info. We're going to be running LOS tests and we // don't want to collide with the control object. static U32 losMask = TerrainObjectType | InteriorObjectType | ShapeBaseObjectType; control->disableCollision();