Game Development Community

MMORPG Tutorial article 2 RPG style targeting

by Dreamer · 04/14/2005 (2:58 pm) · 52 comments

Download Code File

This is a continuation of the Dream MMORPG tutorial, this part covers targeting and implements a basic targeting GUI.

If you haven't already done so, please do this tutorial first...
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7514

I based my targeting setup on the Object Selection tutorial, of Dave Meyers, you will need to implement all of the code except for the change to allow for Highlighting via the showing of the bounding box.

The tutorial can be found here
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7335

Pay special attention to the comments further down in that tutorial, where it is explained how to add in the ability to clear the selection, that part is also critical.

After you have all of that compiled and working successfully, add the following to your engine/game/fps
guiTargetHud.cc
//-----------------------------------------------------------------------------
// 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);
}

Then add the following to your client/ui
TargetingGUI.gui
//--- OBJECT WRITE BEGIN ---
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";
		};
	};
};
Now add this to your client/scripts/client_commands.cs
function ClientCmdUpdateTargetDialog(%var,%TargetName){
	%var = detag(%var);
	%TargetName = detag(%TargetName);
	if(%var $="NewTarget"){
		$TargetName = %TargetName;
		
		Canvas.PushDialog(TargetGUI);
	}else{
		$TargetName = "";
		Canvas.PopDialog(TargetGUI);
	}
	echo("Recieved command to open TargetGUI command was"SPC %var SPC"and TargetName is"SPC %TargetName);
	TargetText.setText($TargetName);
}

In client/scripts/playgui.cs
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('Target', %mouseVec, %cameraPoint,'Player');
}

Finally in your server/scripts/commands.cs add the following
//-----------------------------------------------------------------------------
// object selection additions
//-----------------------------------------------------------------------------
function serverCmdTarget(%client, %mouseVec, %cameraPoint,%TargType)
{
   //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);
   %TargType = detag(%TargType); 
   if(%TargType $= "Player"){
   	%searchMasks = $TypeMasks::PlayerObjectType;
   }
   // 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);
      CommandToClient(%client,'UpdateTargetDialog','NewTarget',%targetObject.getshapeName());
      echo("Client has selected" SPC %scanTarg); 
   }else{
         if(%client.getSelectedObject()){
		%client.clearSelectedObject();
		echo("Client has cleared selection");
		CommandToClient(%client,'UpdateTargetDialog','ClearTarget');
	}
   }
}

         
function findObject(%client,%searchMasks,%distance,%LOS){
	if(%LOS){
		if(DoRaycast(%client,%distance,%searchMasks)){
			return(1);
		}else{
			return(0);
		}
	}else{
		if(DoRadiusSearch(%client,%distance,%searchMasks)){
			return(1);
		}else{
			return(0);
		}
	}
}

function DoRaycast(%client,%distance,%searchMasks)
{
   echo("We are doing raycast and player ="@%client.player);
   %player = %client.player;
   %eye = %player.getEyeVector();
   %vec = vectorScale(%eye, %distance);
   %startPoint = %player.getEyeTransform();
   %endPoint = VectorAdd(%startPoint,%vec);
   
   %object = ContainerRayCast (%startPoint, %endPoint, %searchMasks, %player); 

   echo( "\c1 /// RayCast Search /// " );
   echo("\c1 Object = " SPC %object);
   echo("\c1 Object Position = " SPC %object.getPosition());
   echo("\c1 Object Id = " SPC %object.getId());
   echo("\c1 Object Name = " SPC %object.getName());
   echo( "\c1 /// --------------- /// " );
   if(%object){
	return(1);
   }else{
	return(0);
   }
}

function DoRadiusSearch(%client,%distance,%searchMasks){
   %player = %client.player;
   %pos = %player.getPosition();
   InitContainerRadiusSearch(%pos, %radius, %searchMasks);
  
   while ((%targetObject = containerSearchNext()) != 0) {
	%dist = containerSearchCurrRadiusDist();
	%target = %targetObject.getTransform();
	%id = %targetObject.getId();
	%name = %targetObject.getName();
	
	echo( "\c3 /// Container Search /// " );
	echo( " Distans = " @ %dist);
	echo( " Player Position = " @ %pos);
	echo( " Target = " SPC %target);
	echo( " Target Id = " SPC %id);
	echo( " Target Name = " SPC %name);
	echo( "\c3 /// --------------- /// " );
   }
   if(%targetObject){
	return(1);
   }else{
	return(0);
   }

 }


For right now the only part we need to worry about on the server commands is serverCmdTarget, this function will allow us to target ONLY targets from the PlayerData class, for our purposes there is no need to be able to target anything else.

Anyways thats it for targeting, in our next tutorial we will cover adding your first tradeskill, and begin making a Server Side Roll Based Melee
Page«First 1 2 3 Next»
#41
06/28/2005 (9:00 am)
#42
09/06/2005 (8:57 am)
I do not know if i am doing something worong but I press tab and m then try to click myself and the npc running around and nothign happens. I believe i did the tutorial down to the letter and i implemented the change you made farther down.
#43
11/14/2005 (8:32 pm)
is there any way to sellect everything in game...for example terrain , static shapes...it seems the only objects that could be targeted is objects with datablock..i tried to add $TypeMask::TerrainObjectType to %searchMask but the target doesnt even detect the terrain..any help please...im really lost...
#44
11/15/2005 (12:23 am)
Add a binary or to the searchtype like this
%searchmask = ($TypeMasks::PlayerObjectType | $TypeMasks::TerrainObjectType);

Or if you just want everything selectable do
$searchmask = -1;
#45
11/15/2005 (1:44 am)
I tried that dreamer, unfortunately it didnt work.Im trying to make a spell cast where ever you click with the cursor on. I did some debugging and found out that scanTarg gives the specific position of the mouse click. So i point scanTarg to initialposition but the explosion doesnt hit the terrain and when ever i cast a spell to static shape (a tree for example), it doesnt hit exactly where the mouse clicks... do you know whats wrong.?...im still young with torque.
#46
11/15/2005 (2:16 am)
Actually and honestly, this resource probably isn't the best way to accomplish that.
I have managed to do just exactly that with the selection code from the RTS Kit, which is far superior to this, and shows quite clearly the mistakes I made when creating this.

For instance selection of objects should be handled client side and then notification of selection should be sent to the server.
Try this instead.
Where the selection logic takes place, have it happen clientside.
Then have your spell target the selected object using the client side id, pass it to the server and have the actual spell cast convert the clients version of the id to the servers ID and get the position from there.
#47
11/15/2005 (2:21 am)
So i implement the selection objects in client_commands.cs and send it to server side commands.cs. Am i in the right path?
#48
11/15/2005 (2:29 am)
In a nutshell yes, have the client figure out what you are trying to select, get it's ID and pass it to the server and store it sever side. Then just call it when needed and have the server figure out it's position etc.
This is how I will be doing it in the newer tutorials, and yes I am yet again revamping. This time I have more time and money to throw at it, and more experience behind me so I can do a better job.

Have I ever told you guys, that these tutorials while fairly decent, in hindsight were not even close to where I could have taken them, had I known then what I know now. Hopefully I'm coming close the the apex of my knowledge in this engine, and can quit rewritting these things though ;)
#49
11/15/2005 (3:30 am)
Dreamer. When the cast spell hits the player, does it hit only a specific area?...
#50
11/15/2005 (3:57 am)
No, on all the spells I've made thus far they are just modified projectiles and do damage as a projectile would.
There is no way that I can think of to allow for targeting just a specific area on a target w/o implementing Stefans Beffy Moses's tutorial on damage locations, even then your spell would have to be pretty specific like "Rock of Greater Left Leg Breaking" or something ;)
#51
11/16/2005 (10:32 am)
Alright so I followed this tutorial, everything works fine except for one problem. I can select/unselect object though everytime I do this the cursor disapears. In this case I would then have to goto first person then third again to unselect/select another object (I have it binded so when I'm in third person CursorOn(): is exec'd). Why is this happening? :S
#52
12/07/2005 (1:55 am)
I've made a quick-fix of GuiTargetBarHud for TSE:
take the original guiTargetHud.cc file and copy it to engine/game/fps renaming file to guiTargetHud.cpp
In the very beginning of file remove
#include "dgl/dgl.h"
line.
Replace getConnectionToServer (if used 1.4mod) with getServerConnection.
Change:
dglDrawRectFill with GFX->drawRectFill (two times in file)
dglDrawRect with GFX->drawRect

And we re done!
Add file to project, compile and enjoy!
Page«First 1 2 3 Next»