Game Development Community

dev|Pro Game Development Curriculum

Sprites in Torque TGE1.5.2 Part 2

by Wesley Hopson · 10/17/2010 (3:15 am) · 3 comments

introduction
this part is should be a whole lot simpler and shorter than the last one simply because we already laid most the ground work for this one. for things such as naming of your animations in your IFL file or Change the log have a look at the last Part Here The video on the last resource should also literally be what you should be able to do with the sprite once you complete this part. Now assuming you have gotten everything to work for part 1 and no major complications lets get started.

Code
First create a couple new files for our use Sprite.h and Sprite.cc in the engine/game directory

For Sprite.h
#ifndef _SPRITE_H_
#define _SPRITE_H_

#include "game/aiPlayer.h"

class Sprite : public AIPlayer
{
public:

	typedef ShapeBase GreatGrandParent;
	typedef Player GrandParent;
	typedef AIPlayer Parent;

	DECLARE_CONOBJECT(Sprite);
	Sprite();
	~Sprite();

	bool onAdd();

	void pickActionAnimation();

	U8 mFacing;
	Point3F mLastDir;

	void updateAnimation(F32 dt);
	void SetFacing(Point3F dir);
	void SetFacing(U8 dir);
	void VelMaskDir(VectorF* Dir, VectorF Vel);

	enum {
		Left, //0.0f, 1.0f, 0.0f
		UpLeft, // 
		Up,
		UpRight,
		Right,
		DownRight,
		Down,
		DownLeft
	};

	enum {
      // *** WARNING ***
      // These enum values are used to index the ActionAnimationList
      // array instantiated in player.cc
      // The first Nine are selected in the move state based on velocity
      RootAnim,
      RunForwardAnim,
      BackBackwardAnim,
      SideLeftAnim,
	  SideRightAnim,
	  RightRunAnim,
	  LeftRunAnim,
	  RightBackAnim,
	  LeftBackAnim,

      // These are set explicitly based on player actions
      FallAnim,
      JumpAnim,
      StandJumpAnim,
      LandAnim,

      //
      NumMoveActionAnims = LeftBackAnim + 1,
      NumTableActionAnims = LandAnim + 1,
      NumExtraActionAnims = 512,
      NumActionAnims = NumTableActionAnims + NumExtraActionAnims,
      ActionAnimBits = 9,
      NullAnimation = (1 << ActionAnimBits) - 1
   };

	U32  packUpdate  (NetConnection *conn, U32 mask, BitStream *stream);
   void unpackUpdate(NetConnection *conn, BitStream *stream);
};

#endif

and for Sprite.cc

#include "game/sprite.h"
#include "console/consoleInternal.h"
#include "core/realComp.h"
#include "math/mMatrix.h"
#include "game/moveManager.h"

#include "platform/platform.h"
#include "platform/profiler.h"
#include "dgl/dgl.h"
#include "dgl/materialPropertyMap.h"
#include "math/mMath.h"
#include "core/stringTable.h"
#include "core/bitStream.h"
#include "core/dnet.h"
#include "core/resManager.h"
#include "console/simBase.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "collision/extrudedPolyList.h"
#include "collision/clippedPolyList.h"
#include "collision/earlyOutPolyList.h"
#include "sim/decalManager.h"
#include "ts/tsShapeInstance.h"
#include "audio/audioDataBlock.h"
#include "sceneGraph/sceneGraph.h"
#include "sceneGraph/sceneState.h"
#include "sceneGraph/detailManager.h"
#include "terrain/terrData.h"
#include "terrain/terrRender.h"
#include "terrain/waterBlock.h"
#include "game/game.h"
#include "game/moveManager.h"
#include "game/gameConnection.h"
#include "game/trigger.h"
#include "game/physicalZone.h"
#include "game/item.h"
#include "game/shadow.h"
#include "game/missionArea.h"
#include "game/fx/particleEngine.h"
#include "game/fx/splash.h"
#include "game/fx/cameraFXMgr.h"

IMPLEMENT_CO_NETOBJECT_V1(Sprite);

// Number of ticks before we pick non-contact animations
static const S32 sContactTickTime = 30;

Sprite::Sprite()
{
	Parent::AIPlayer();
}

Sprite::~Sprite()
{
}

bool Sprite::onAdd()
{
	 if(!GrandParent::onAdd())
      return false;
	 this->getTransform().getColumn(1,&mLastDir);
	SetFacing(mLastDir);
	return true;
}

void Sprite::pickActionAnimation()
{
   // Only select animations in our normal move state.
	if (mState != GrandParent::MoveState || mDamageState != Enabled)
      return;

   if (isMounted())
   {
      // Go into root position unless something was set explicitly
      // from a script.
      if (mActionAnimation.action != PlayerData::RootAnim &&
          mActionAnimation.action < PlayerData::NumTableActionAnims)
	  {
         setActionThread(PlayerData::RootAnim,true,false,false);
	  }
      return;
   }

   bool forward = true;
   U32 action = PlayerData::RootAnim;
   if (mFalling)
   {
      // Not in contact with any surface and falling
      action = PlayerData::FallAnim;
   }
   else
   {
      if (mContactTimer >= sContactTickTime) {
         // Nothing under our feet
         action = PlayerData::RootAnim;
      }
      else
      {
         // Our feet are on something
         // Pick animation that is the best fit for our current velocity.
         // Assumes that root is the first animation in the list.
         F32 curMax = 0.1f;
         VectorF vel;
         mWorldToObj.mulV(mVelocity,&vel);
		 const MatrixF& mat = this->getTransform();
		 Point3F dir;
		 mat.getColumn(1,&dir);
		 GameConnection* connection = GameConnection::getConnectionToServer();
		 ShapeBase* Cam = connection->getViewingObject();
		 Point3F CamDir;
		 MatrixF eye;
		 if (Cam)
		 {
			 const MatrixF& mat = this->getTransform();
			 Point3F dir;
			 mat.getColumn(1,&dir);
			 Cam->getEyeTransform(&eye);
			 //connection->getControlCameraTransform(0, &eye);
			eye.getColumn(1, &CamDir);
			//Con::printf("CamDir %f %f %f", CamDir.x, CamDir.y, CamDir.z);
			//CamDir.normalize();
			//CamDir.x = CamDir.x * -1;
			 if (this->getControllingClient())
			 {
				 //Con::printf("here too");
				 //Con::printf("  %f %f %f", vel.x, vel.y, vel.z);
				 if (mAbs(vel.x) > 0.4 || mAbs(vel.y) > 0.4)				 {
					 VectorF TempVel = dir;
					 VelMaskDir(&TempVel, vel);
					 TempVel.normalize();
					 this->SetFacing(TempVel);
				 }
				 CamDir = dir;
				 CamDir.normalize();
				 CamDir.x = CamDir.x * -1;
			 }
			 else
			 {
				 //Con::printf("if we are here blahahaha");
				 if (mLastDir != dir)
				 {//only need to call this stuff when there is a change afterall.
					 mLastDir = dir;
					 this->SetFacing(dir);
				 }
				 Cam->getEyeTransform(&eye);
				 eye.getColumn(1, &CamDir);
				 CamDir.normalize();
				 CamDir.x = CamDir.x * -1;
			 }
			 //Con::printf("Facing %i", mFacing);
			 //Con::printf("CamDir %f %f", CamDir.x, CamDir.y);
			 U8 Count = 0;
			 const F32 Ang = mDegToRad((F32) 45);
			 while (Count < mFacing)
			 {
				 //we rotate in 45 degree increments till we have the proper facing 0.707107 is an aprox of cos(45) and sin(45)
				 F32 x = CamDir.x * 0.707107 - CamDir.y * -0.707107;
				 F32 y = CamDir.x * -0.707107 + CamDir.y * 0.707107;
				 CamDir.x = x;
				 CamDir.y = y;
				 Count++;
			 }
		 }
		 if ((vel.x < 0.1 && vel.y < 0.1 && vel.z < 0.1) && (vel.x > -0.1 && vel.y > -0.1 && vel.z > -0.1))
			 mActionAnimation.thread->Moving = true;//quicker to check than a sequences name for root anims
		 else
			 mActionAnimation.thread->Moving = false;
         for (U32 i = 1; i < NumMoveActionAnims; i++)
         {
            PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
            if (anim.sequence != -1 && anim.speed) {
               F32 d = mDot(CamDir, anim.dir);
               if (d > curMax)
               {
                  curMax = d;
                  action = i;
                  forward = true;
				  //mActionAnimation.thread->Moving = false;
				  //Con::printf("action %i", action);
				  if (mActionAnimation.thread->Moving == true)
				  {
					  mActionAnimation.thread->action = i;
					  action = 0;
				  }
               }
            }
         }
      }
   }
   setActionThread(action,forward,false,false);
}

void Sprite::SetFacing(U8 dir)
{
	//Con::printf("got a facing of %i", dir);
	mFacing = dir;
}

void Sprite::SetFacing(Point3F dir)
{
	F32 curMax = 0;
	U32 DirID = 0;
	for (U32 i = 1; i < NumMoveActionAnims; i++)
	{
		PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
		if (anim.sequence != -1 && anim.speed) {
			F32 d = mDot(dir, anim.dir);
			//Con::printf("testing %f %f curMaxdotproduct of %f", anim.dir.x, anim.dir.y, d);
			if (d > curMax)
			{
				//Con::printf("%f %f is now closest to %f %f", anim.dir.x, anim.dir.y, dir.x, dir.y);
				curMax = d;
				DirID = i;
			}
		}
	}
	//Con::printf("decided DirID to be %i", DirID);
	switch (DirID)
	{
	case 0:
		break;
	case 1:
		//Con::printf("Facing Left");
		mFacing = Left;
		break;
	case 2:
		//Con::printf("Facing Right");
		mFacing = Right;
		break;
	case 3:
		//Con::printf("Facing Down");
		mFacing = Down;//might be backwards
		break;
	case 4:
		//Con::printf("Facing Up");
		mFacing = Up;//might be backwards
		break;
	case 5:
		//Con::printf("Facing UpLeft");
		mFacing = DownLeft;
		break;
	case 6:
		//Con::printf("Facing DownLeft");
		mFacing = UpLeft;
		break;
	case 7:
		//Con::printf("Facing UpRight");
		mFacing = UpRight;
		break;
	case 8:
		//Con::printf("Facing DownRight");
		mFacing = DownRight;
		break;
	default:
		break;
	}
}

void Sprite::VelMaskDir(VectorF* Dir, VectorF Vel)
{
	//Con::printf("VelMaskDir");
	F32 curMax = 0;
	U32 DirID = 0;
	for (U32 i = 1; i < NumMoveActionAnims; i++)
	{
		PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
		if (anim.sequence != -1 && anim.speed) {
			F32 d = mDot(Vel, anim.dir);
			//Con::printf("testing %f %f curMaxdotproduct of %f against %f %f", anim.dir.x, anim.dir.y, d, Vel.x, Vel.y);
			if (d > curMax)
			{
				//Con::printf("%f %f is now closest to %f %f", anim.dir.x, anim.dir.y, Dir->x, Dir->y);
				curMax = d;
				DirID = i;
			}
		}
	}
	//Con::printf("decided DirID to be %i", DirID);
	U8 Rotations = 0;
	switch (DirID)
	{
	case 0:
		break;
	case 1:
		Rotations = 0;
		break;
	case 2:
		Rotations = 4;
		break;
	case 3:
		Rotations = 6;
		break;
	case 4:
		Rotations = 2;
		break;
	case 5:
		Rotations = 7;
		break;
	case 6:
		Rotations = 1;
		break;
	case 7:
		Rotations = 3;
		break;
	case 8:
		Rotations = 5;
		break;
	default:
		break;
	}
	U8 Count = 0;
	F32 x;
	F32 y;
	while (Count < Rotations)
	{
		x = Dir->x * 0.707107 - Dir->y * -0.707107;
		y = Dir->x * -0.707107 + Dir->y * 0.707107;
		Dir->x = x;
		Dir->y = y;
		Count++;
	}
	//Con::printf("exitingVelMaskDir");
}

void Sprite::updateAnimation(F32 dt)
{
	//Con::printf("sprite");
   if ((isGhost() || mActionAnimation.animateOnServer) && mActionAnimation.thread)
   {
      mShapeInstance->advanceTime(dt,mActionAnimation.thread);
	  //Con::printf("pos should be getting an update");
   }
   if (mRecoilThread)
      mShapeInstance->advanceTime(dt,mRecoilThread);

   // If we are the client's player on this machine, then we need
   // to make sure the transforms are up to date as they are used
   // to setup the camera.
   if (isGhost())
   {
      if (getControllingClient())
      {
         updateAnimationTree(isFirstPerson());
         mShapeInstance->animate();
      }
      else
      {
         updateAnimationTree(false);
      }
   }
}

U32 Sprite::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
{
	U32 retMask = GrandParent::packUpdate(conn, mask, stream);
	stream->writeInt(mFacing,4);
	return retMask;
}

void Sprite::unpackUpdate(NetConnection *conn, BitStream *stream)
{
	GrandParent::unpackUpdate(conn,stream);
	mFacing = stream->readInt(4);
}

ConsoleMethod(Sprite, setFacing, void, 3, 3, "(Point3F offset)"
              "Set the Facing of the Sprite.nn") 
{
   Point3F vec;
   dSscanf( argv[2], "%f %f %f", &vec.x, &vec.y, &vec.z );
   object->SetFacing(vec);
}

ConsoleMethod(Sprite, AbsFacing, void, 3, 3, "(INT Facing)"
              "Set the Facing of the Sprite.nn") 
{
   //U8 Facing;
   //dSscanf( argv[2], "%i", &Facing);
   object->SetFacing(dAtoi(argv[2]));
}

I admit that is alot of includes but i just copied the includes that were at the top of player.cc just to be safe. Do whatever you feel is best and can get to work.

now since we are basicly still using playerdata for our datablock don't see any big reasons to go changing that. We need to add support for our extra move directions for our sprites

in Player.h inside of the PlayerData struct
find this enumeration
enum {
      // *** WARNING ***
      // These enum values are used to index the ActionAnimationList
      // array instantiated in player.cc
      // The first five are selected in the move state based on velocity
      RootAnim,
      RunForwardAnim,
      BackBackwardAnim,
      SideLeftAnim,

      // These are set explicitly based on player actions
      FallAnim,
      JumpAnim,
      StandJumpAnim,
      LandAnim,

      //
      NumMoveActionAnims = SideLeftAnim + 1,
      NumTableActionAnims = LandAnim + 1,
      NumExtraActionAnims = 512,
      NumActionAnims = NumTableActionAnims + NumExtraActionAnims,
      ActionAnimBits = 9,
      NullAnimation = (1 << ActionAnimBits) - 1
   };

change it to be
virtual enum {
      // *** WARNING ***
      // These enum values are used to index the ActionAnimationList
      // array instantiated in player.cc
      // The first five are selected in the move state based on velocity
      RootAnim,
      RunForwardAnim,
      BackBackwardAnim,
      SideLeftAnim,
	  SideRightAnim,
	  RightRunAnim,
	  LeftRunAnim,
	  RightBackAnim,
	  LeftBackAnim,

      // These are set explicitly based on player actions
      FallAnim,
      JumpAnim,
      StandJumpAnim,
      LandAnim,

      //
      NumMoveActionAnims = SideLeftAnim + 1,
      NumTableActionAnims = LandAnim + 1,
      NumExtraActionAnims = 512,
      NumActionAnims = NumTableActionAnims + NumExtraActionAnims,
      ActionAnimBits = 9,
      NullAnimation = (1 << ActionAnimBits) - 1
   };

these Enumeration values directly index an array of actionanimations so we need to update that structure to match now.

in Player.cc

Find this staticly declared array near the top of the file
PlayerData::ActionAnimationDef PlayerData::ActionAnimationList[NumTableActionAnims] =
{
   // *** WARNING ***
   // This array is indexed useing the enum values defined in player.h

   // Root is the default animation
   { "root" },       // RootAnim,

   // These are selected in the move state based on velocity
   { "run",  { 0.0f, 1.0f, 0.0f } },       // RunForwardAnim,
   { "back", { 0.0f, -1.0f, 0.0f } },       // BackBackwardAnim
   { "side", { -1.0f, 0.0f, 0.0f } },       // SideLeftAnim,

   // These are set explicitly based on player actions
   { "fall" },       // FallAnim
   { "jump" },       // JumpAnim
   { "standjump" },  // StandJumpAnim
   { "land" },       // LandAnim
};
Update it too
PlayerData::ActionAnimationDef PlayerData::ActionAnimationList[NumTableActionAnims] =
{
   // *** WARNING ***
   // This array is indexed useing the enum values defined in player.h

   // Root is the default animation
   { "root" },       // RootAnim,

   // These are selected in the move state based on velocity
   { "run",  { 0.0f, 1.0f, 0.0f } },       // RunForwardAnim,
   { "back", { 0.0f, -1.0f, 0.0f } },       // BackBackwardAnim
   { "side",{ -1.0f, 0.0f, 0.0f } },       // SideLeftAnim,
   { "Rside",{ 1.0f, 0.0f, 0.0f}},          // SideRightAnim,
   { "Rrun", {-0.707106f, 0.707106f, 0.0f}},           // Diagonal Right Runforward
   { "Lrun", {0.707106f, 0.707106f, 0.0f}},           // Diagonal Left Runforward
   { "Rback", {0.707106f, -0.707106f, 0.0f}},           // Diagonal Right RunBack
   { "Lback", {-0.707106f, -0.707106f, 0.0f}},           // Diagonal Right RunBack

   // These are set explicitly based on player actions
   { "fall" },       // FallAnim
   { "jump" },       // JumpAnim
   { "standjump" },  // StandJumpAnim
   { "land" },       // LandAnim
};

ok Congratulations you should now have fully working sprites

#1
10/18/2010 (11:17 am)
pretty badd ass, great work
#2
10/20/2010 (2:19 pm)
Do you think you could make a video of this resource working? Thanks
#3
10/20/2010 (7:06 pm)
@Tek he posted the video in part 1 :)


If this is using the same info as Playerdata then it probably will work with the UAISK kit. I only started doing part 1 Sunday night using stock TGE 1.5.2 If it works without a hitch Iam going to see how far I can merge it with my other mixed flavors of TGE.