Game Development Community

dev|Pro Game Development Curriculum

Mission area forcefield

by Leslie Young · 06/03/2003 (9:37 am) · 6 comments

Download Code File

In this resource I'll show you how to create a cool force field type thing for your mission bounds. Robert Blanchet Jr. originally posted a resource, Mission Area Bounds, to render the bounds of the mission area, took this and modified it some and added detection for when the player try to leave the bounds and then bounce him back.

Let's get going, open up game.h add following before the #endif:

void GameRenderMissionArea(F32 alpha, bool hit); // leslie

game.cc add, near the other includes:

#include "game/missionArea.h"
#include <stdio.h>

find void GameRenderWorld() in game.cc
and then find this in there:

glEnable(GL_DEPTH_TEST);
   glDepthFunc(GL_LEQUAL);
   glClear(GL_DEPTH_BUFFER_BIT);
   glDisable(GL_CULL_FACE);
   glMatrixMode(GL_MODELVIEW);

   dglSetCanonicalState();
   gClientSceneGraph->renderScene();
   glDisable(GL_DEPTH_TEST);
   collisionTest.render();

#if defined(GATHER_METRICS) && GATHER_METRICS > 1

then add the following between the gClientSceneGraph->renderScene(); and glDisable(GL_DEPTH_TEST); ; change to look like this:

dglSetCanonicalState();
   gClientSceneGraph->renderScene();


	// ****************************************************************
	// leslie - render mission area - start
   
		S8 wasHit = 0;
		bool hit = false;
		static S32 timeOut = 0; // crude, but it works :)

		wasHit = MissionArea::smWasHit;
		MissionArea::smWasHit = 0;

		if (wasHit>0) { 
			hit = true; 
			timeOut=200; 
		}
		
		if (timeOut>0) hit = true; // hit from last time any case still on

		timeOut--;
		if (timeOut<0) timeOut=0;		
	

		// render normal border
		GameRenderMissionArea(1.0f, false);
		
		// render a flash over it if needed
		if (hit) GameRenderMissionArea( (float)(timeOut/200.0f), true);

	// leslie - end
	// ****************************************************************

   glDisable(GL_DEPTH_TEST);
   collisionTest.render();

and finally add the GameRenderMissionArea function, anywhere in game.cc:

// --------------------------------------------------------------------------------------------------
// leslie - render mission area - start
void GameRenderMissionArea(F32 alpha, bool hit)
{

	F32 shakeFactor = MissionArea::smShakeFactor;
	F32 height = MissionArea::smFlightCeiling;
	S8 fn1 = MissionArea::smBorderTexNm;

	TextureHandle bitMap = 0;
	
	if (hit) {
		bitMap = TextureHandle("mm/data/borders/hit.png", MeshTexture);
	} else {
		char buff[256]={'[[6288a01084065]]'};
		sprintf(buff,"mm/data/borders/border%i.png",fn1);
		bitMap = TextureHandle(buff, MeshTexture);
	}

	if (!bitMap) return;
	
	TerrainBlock *terrain = gClientSceneGraph->getCurrentTerrain();  

	if(!terrain) return;

	const RectI &area = MissionArea::smMissionArea;
	Point2F min(area.point.x, area.point.y);
	Point2F max(area.point.x + area.extent.x, area.point.y + area.extent.y);

	// leslie : this will render the border a bit futher out sothat it doen't look like the
	// player keft the misison area - as he does for a short moment and the player character
	// will appear to be standing in the forcefield.
	
	// if you mess with the player size you might wanna change these some
	height += 1.0f;
	min.x -= 1.0f; min.y -= 1.0f;
	max.x += 1.0f; max.y += 1.0f;

	dglClearBitmapModulation();

   
	glDisable(GL_CULL_FACE);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glDisable(GL_LIGHTING);
	glColor4f(1.0, 1.0, 1.0, alpha);
	glEnable(GL_TEXTURE_2D);
	TextureObject* texture = (TextureObject*)bitMap;
	glBindTexture(GL_TEXTURE_2D, texture->texGLName);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

   // side 1
      glBegin(GL_TRIANGLE_FAN);
      glTexCoord2f(0.0 +gRandGen.randF(0,shakeFactor),0.0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, min.y, 0);
      glTexCoord2f((max.x - min.x)/5 +gRandGen.randF(0,shakeFactor),0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, min.y, 0);
      glTexCoord2f((max.x - min.x)/5 +gRandGen.randF(0,shakeFactor), height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, min.y, height);
      glTexCoord2f(0 +gRandGen.randF(0,shakeFactor),height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, min.y, height);
      glEnd();
   // side 3
      glBegin(GL_TRIANGLE_FAN);
      glTexCoord2f(0 +gRandGen.randF(0,shakeFactor),0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, max.y, 0);
      glTexCoord2f((max.x - min.x)/5 +gRandGen.randF(0,shakeFactor),0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, max.y, 0);
      glTexCoord2f((max.x - min.x)/5 +gRandGen.randF(0,shakeFactor),height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, max.y, height);
      glTexCoord2f(0 +gRandGen.randF(0,shakeFactor),height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, max.y, height);
      glEnd();
   // side 0
      glBegin(GL_TRIANGLE_FAN);
      glTexCoord2f(0 +gRandGen.randF(0,shakeFactor),0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, min.y, 0);
      glTexCoord2f((max.y-min.y)/5 +gRandGen.randF(0,shakeFactor),0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, max.y, 0);
      glTexCoord2f((max.y-min.y)/5 +gRandGen.randF(0,shakeFactor),height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, max.y, height);
      glTexCoord2f(0 +gRandGen.randF(0,shakeFactor),height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, min.y, height);
      glEnd();
   // side 2
      glBegin(GL_TRIANGLE_FAN);
      glTexCoord2f(0 +gRandGen.randF(0,shakeFactor),0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, min.y, 0);
      glTexCoord2f((max.y-min.y)/5 +gRandGen.randF(0,shakeFactor),0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, max.y, 0);
      glTexCoord2f((max.y-min.y)/5 +gRandGen.randF(0,shakeFactor),height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, max.y, height);
      glTexCoord2f(0 +gRandGen.randF(0,shakeFactor),height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, min.y, height);
      glEnd();
   // side 5 ROOF
      glBegin(GL_TRIANGLE_FAN);
      glTexCoord2f(0 +gRandGen.randF(0,shakeFactor)  ,0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, min.y, height);
      glTexCoord2f((max.y-min.y)/5 +gRandGen.randF(0,shakeFactor), 0 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, min.y, height);
      glTexCoord2f((max.y-min.y)/5 +gRandGen.randF(0,shakeFactor), height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(max.x, max.y, height);
      glTexCoord2f(0 +gRandGen.randF(0,shakeFactor), height/5 +gRandGen.randF(0,shakeFactor));
      glVertex3f(min.x, max.y, height);
      glEnd();

   glDisable(GL_TEXTURE_2D);
   glDisable(GL_BLEND);

}
// leslie - end
// --------------------------------------------------------------------------------------------------

Right, all this should render the mission area with some texture, don't run it yet, cause it won't work :b
Now on to detecting the 'collision'. Open up player.h and find void checkMissionArea(); , and add, after it:

void checkGhostMissionArea();

player.cc Find bool Player::updatePos(const F32 travelTime) and in there find:

if (!isGhost())  {
		// Collisions are only queued on the server and can be
		// generated by either updateMove or updatePos
		notifyCollision();

		// Do mission area callbacks on the server as well
		checkMissionArea();
   }

and change it to look like this (added the else):

if (!isGhost())  {
		// Collisions are only queued on the server and can be
		// generated by either updateMove or updatePos
		notifyCollision();

		// Do mission area callbacks on the server as well
		checkMissionArea();
   } else
		checkGhostMissionArea(); // leslie - mission area

Now find void Player::checkMissionArea() and change the function like this:

void Player::checkMissionArea()
{
   // Checks to see if the player is in the Mission Area...
   Point3F pos;
   MissionArea * obj = dynamic_cast<MissionArea*>(Sim::findObject("MissionArea"));
   const RectI &area = obj->getArea();
   getTransform().getColumn(3, &pos);

	// leslie - mission area - roof collision - start
	F32 height;
	height = obj->getFlightCeiling();
	if (pos.z >= height) {
		if(mInMissionArea) {
			MissionArea::smWasHit = 1; // leslie : this will alow flash on server too
			mInMissionArea = false; 
			Con::executef(mDataBlock,3,"onLeaveMissionAreaViaRoof",scriptThis());
		}
	} else
	// leslie - end

   if ((pos.x < area.point.x || pos.x > area.point.x + area.extent.x || 
       pos.y < area.point.y || pos.y > area.point.y + area.extent.y)) { 
      if(mInMissionArea) {
		 MissionArea::smWasHit = 1; // leslie : this will allow flash on server too
		 mInMissionArea = false;
         Con::executef(mDataBlock,3,"onLeaveMissionArea",scriptThis());
      }    
   }

   else if(!mInMissionArea)
   {
      mInMissionArea = true;
      Con::executef(mDataBlock,3,"onEnterMissionArea",scriptThis());
   }   
}

And add the following function after the previous one. This function will allow the flash to appear on the client's machine too, the above changes were not enough. The flash also appears on the other clients too.

//----------------------------------------------------------------------------
// leslie - mission area check - start
void Player::checkGhostMissionArea()
{

//	- this is to make this client make a flash on it's own mission bounds
//  - aa, this is soo cool, it also seems to be updating on other clients
//  - and the server when a client hits the border
//  - LESLIE : NOTE : if it seems not to be working it might be a timimg
//  - problem, cause it does work if a player left te mission area.

   // Checks to see if the player is in the Mission Area...
    Point3F pos;
	F32 height;

    const RectI &area = MissionArea::smMissionArea;
    getTransform().getColumn(3, &pos);
	height = MissionArea::smFlightCeiling;

	if (pos.z >= height) {
		   MissionArea::smWasHit = 1;
	}
	
    if ((pos.x < area.point.x || pos.x > area.point.x + area.extent.x || 
         pos.y < area.point.y || pos.y > area.point.y + area.extent.y)) { 
			MissionArea::smWasHit = 1;
    }
}  
// leslie - end
//----------------------------------------------------------------------------

On to missionarea.h , make changes like this:

class MissionArea : public NetObject
{
  private:
   typedef NetObject Parent;
   RectI             mArea;

   F32 mFlightCeiling;
   F32 mFlightCeilingRange;
   
  public:
   MissionArea();

	static RectI    smMissionArea;
	static F32	smFlightCeiling;// leslie
	static F32		smShakeFactor;	// leslie
	static S8		smWasHit;		// leslie
	static S8		smBorderTexNm;	// leslie

	 F32	mShakeFactor;	// leslie
	 S8	mWasHit;		// leslie
	 S8	mBorderTexNm;	// leslie

   static const MissionArea * getServerObject();

   F32 getFlightCeiling()      const { return mFlightCeiling;      }
   F32 getFlightCeilingRange() const { return mFlightCeilingRange; }

	// leslie - mission area render effect support - start
	void setBorderTexName(const S8 & s);
	void setWasHit(const S8 & s);
	void setShakeFactor(const F32 & f);
	void setFlightCeiling(const F32 & f);
	// leslie - end

   //            
   const RectI & getArea(){return(mArea);
}
...

Right, in missionarea.cc add after RectI MissionArea::smMissionArea...

IMPLEMENT_CO_NETOBJECT_V1(MissionArea);

RectI MissionArea::smMissionArea(Point2I(768, 768), Point2I(512, 512));
F32 MissionArea::smFlightCeiling = 300.0f;	// leslie
F32 MissionArea::smShakeFactor = 0.7f; 	// leslie
S8 MissionArea::smBorderTexNm = 0;		// leslie
S8 MissionArea::smWasHit = 0;			// leslie

Add in MissionArea::MissionArea() :

mShakeFactor = 0.7f;	// leslie
	mWasHit = 0;		// leslie
	mBorderTexNm = 0;	// leslie

In bool MissionArea::onAdd() just after setArea(mArea); before return(true) change like this:

if(!Parent::onAdd())
      return(false);
   
	setArea(mArea);
	setShakeFactor(mShakeFactor);		// leslie
	setFlightCeiling(mFlightCeiling);		// leslie
	setBorderTexName(mBorderTexNm);	// leslie
	setWasHit(mWasHit);			// leslie

   return(true);

then add the following functions (you can add 'em after the cSetArea function):

// --------------------------------------------------------------------------------------------------
// leslie - mission area render effect support - start
void MissionArea::setShakeFactor(const F32 & f) 
{ 
	mShakeFactor = MissionArea::smShakeFactor = f;
	if(isServerObject()) mNetFlags.set(UpdateMask);
} 
void MissionArea::setFlightCeiling(const F32 & f) 
{ 
	mFlightCeiling = MissionArea::smFlightCeiling = f;
	if(isServerObject()) mNetFlags.set(UpdateMask);
} 
void MissionArea::setBorderTexName(const S8 & s) 
{ 
	mBorderTexNm = MissionArea::smBorderTexNm = s;
	if(isServerObject()) mNetFlags.set(UpdateMask);
} 
void MissionArea::setWasHit(const S8 & s) 
{ 
	mWasHit = MissionArea::smWasHit = s;
	if(isServerObject()) mNetFlags.set(UpdateMask);
} 
// leslie - end
// --------------------------------------------------------------------------------------------------

and finally the packupdates ... Change like this:

void MissionArea::unpackUpdate(NetConnection *, BitStream * stream)
{
	// ghost (initial) and regular updates share flag..
	if(stream->readFlag())
	{
      mathRead(*stream, &mArea);
      stream->read(&mFlightCeiling);
      stream->read(&mFlightCeilingRange);
	  stream->read(&mShakeFactor);		// leslie
	  stream->read(&mBorderTexNm);	// leslie
	  stream->read(&mWasHit);		// leslie
	}
}

U32 MissionArea::packUpdate(NetConnection *, U32 mask, BitStream * stream)
{
	if(stream->writeFlag(mask & UpdateMask))
	{
      mathWrite(*stream, mArea);
      stream->write(mFlightCeiling);
      stream->write(mFlightCeilingRange);
   	  stream->write(mShakeFactor);		// leslie
	  stream->write(mBorderTexNm);		// leslie
	  stream->write(mWasHit);			// leslie
	}
	
	return(0);
}

Compile, and hopefully I remembered to show you everything that need be changed 8-p

Now for some scripts:
Open up player.cs from server/scipts:
Find function Armor::onLeaveMissionArea(%this, %obj) and change like this:

function Armor::onLeaveMissionArea(%this, %obj)
{
    // stop player from moving outside mission area
    // get player pos and push 'em BACK!!!

    %force = MissionArea.bounce;
    
    // take this out and you can actually get out if
    // you move forward slow enought
    if (%obj.getVelocity()<3) %force -= 3;

    // Thanx to Daniel Eden for helping me out with some code
    // and finally this one line worked fine
    %obj.setVelocity(VectorScale(%obj.getVelocity(), %force));

    // leslie fixme : maybe start somekind of effect with particles and play
    // a sound when collide with mission bounds
    // ...

    // inform the client
    %obj.client.onLeaveMissionArea();

}

and after that function add the following (this one is for when the player left the mission area via the roof, flight ceiling)

function Armor::onLeaveMissionAreaViaRoof(%this, %obj)
{
    // stop player from moving outside mission area via roof
    // get player pos and push 'em BACK!!!

    %force = MissionArea.bounce;
    if (%obj.getVelocity()<3) %force -= 3;
    %obj.setVelocity(VectorScale(%obj.getVelocity(), %force));

    // leslie fixme : maybe start somekind of effect with particles and play
    // a sound when collide with mission bounds
    // ...

    // Inform the client
    %obj.client.onLeaveMissionArea();
}

Another important thing is changes in the mission files, cause we added new fields in the code, and if you try loading the mission now it might not load, so check that your mission files has the following (for those that don't know, find your mission files in data/missions in fps, rw, or whatever path you are using). Open the .MIS (dot mis) files, in Tribal IDE or other text editor.

Look for something like this in there:

new MissionArea(MissionArea) {
      area = "-256 -256 256 256";
      flightCeiling = "250";
      flightCeilingRange = "20";
      locked = "true";
   };

and change it like this:

new MissionArea(MissionArea) {
      area = "-256 -256 256 256";
      flightCeiling = "250";
      flightCeilingRange = "20";
      texture = "0"; // leslie
      shake = "0.3"; // leslie
        bounce = "-2"; // leslie
        locked = "true";
   };

k, that will do it, all you have to do now is add the bitmaps that will be used to render the border. I hard coded these but if you know what your doing it should be easy to change. Any case, create path like fps/data/borders and add 2 - 64x64 png - files one named border0.png and another called hit.png.

Few notes:

This code will render a border for the mission's flight ceiling too, you can rip that out if not needed

Explanation of the changes in your mission file:

texture=0; will tell the engine to render border0.png from the data/borders path , so if you want this mission to maybe use another image just add border1.png and use in your mission file texture=1;

shake=0.3; is the force at which the field will shake, play with it.

bounce=-2; is the force at which the player will be forced back when he hits the border.

There is a load of kewl things you could add, like start a particle effect when the player hit the border or play a sound effect, etc. I included a simple mission and some borders in the zip file.

Hope ya like :-)

#1
06/03/2003 (12:22 pm)
Look like a useful resource! Thanks for posting this. Can't wait to give it a try.
#2
06/03/2003 (1:06 pm)
I've been playing with the pushback effect, here is another methid that's also quit usefull for pushing the player back ... not at an angle.

These are the changes I made to the functions in server/scripts/player.cs

function Armor::onLeaveMissionArea(%this, %obj)
{
    %obj.client.onLeaveMissionArea();
    %force = MissionArea.bounce;
    %min_x = getWord(MissionArea.area, 0);
    %min_y = getWord(MissionArea.area, 1);
    %max_x = %min_x + getWord(MissionArea.area, 2);
    %max_y = %min_y + getWord(MissionArea.area, 3);
    %x = getWord(%obj.getPosition(), 0);
    %y = getWord(%obj.getPosition(), 1);
    %vx = 0; %vy = 0; %vz = 0;
    if (%x<(%min_x+10)) %vx = %force;
    if (%y<(%min_y+10)) %vy = %force;
    if (%x>(%max_x-10)) %vx = -%force;
    if (%y>(%max_y-10)) %vy = -%force;
    %vec = %vx @ " " @ %vy @ " " @ %vz;
    %obj.setVelocity(%vec);
}

function Armor::onLeaveMissionAreaViaRoof(%this, %obj)
{
    %obj.client.onLeaveMissionArea();
    %force = MissionArea.bounce;
    %min_x = getWord(MissionArea.area, 0);
    %min_y = getWord(MissionArea.area, 1);
    %max_x = %min_x + getWord(MissionArea.area, 2);
    %max_y = %min_y + getWord(MissionArea.area, 3);
    %x = getWord(%obj.getPosition(), 0);
    %y = getWord(%obj.getPosition(), 1);
    %vx = 0; %vy = 0; %vz = %force * -1;
    if (%x<(%min_x+10)) %vx = %force;
    if (%y<(%min_y+10)) %vy = %force;
    if (%x>(%max_x-10)) %vx = -%force;
    if (%y>(%max_y-10)) %vy = -%force;
    %vec = %vx @ " " @ %vy @ " " @ %vz;
    %obj.setVelocity(%vec);
}

Now, MissionArea.bounce wouold contain a value like 10 for a gentle back movement or something like 50 or 60 to let 'em know ya don't want 'em near the borders :b
#3
06/12/2003 (4:57 pm)
Leslie,
Any chance you could package this up as a patch? Or at least provide the code as striaght text? Cutting and pasting out of HMTL doesn't work well at all.
#4
08/06/2003 (10:03 am)
Just paste into Word first and then to your code base :) (That is how I do it to get formatting sorted out)

But in the future I'll just include the original TXT files, I typed it in firts, in the zip too if size permit.
#5
10/19/2003 (8:34 am)
I assume that
char buff[256]={´\0´};

becomes

char buff[256]={'\0'};

:)
#6
12/22/2006 (2:36 pm)