Game Development Community

Creating explosions on the server

by Alex Scarborough · 06/29/2005 (12:33 pm) · 4 comments

So, I encountered a minor problem in a project I'm working on. I needed to create explosions, which is all fine and dandy, minus the fact that the object spawning these explosions was a server only object. After doing a cursory glance through the forums, I found that there were some solutions to this, but apparently caused a dedicated server to crash. So, instead of opting for a script solution, or making my object ghostable, I decided to create a new NetEvent.

As you can see, NetEvents can be very useful tools. They make it very easy to have controlled communication between server and client. By using a NetEvent, I was able to have the server do all necessary server side explosion details (damage, script callbacks, etc.) and then send a NetEvent to all the connected clients to actually create and render the explosion.

First, we need to subclass NetEvent to suit our needs.

class ExplosionMessageEvent: public NetEvent
{
	typedef NetEvent Parent;
	
	public:
	Point3F pos;
	Point3F normal;
	U32 objectType;
	ExplosionData* dataBlock;
	
	ExplosionMessageEvent();
	~ExplosionMessageEvent();
	
	virtual void pack(NetConnection *conn, BitStream *bstream);
	virtual void write(NetConnection *conn, BitStream *bstream);
	virtual void unpack(NetConnection *conn, BitStream *bstream);
	virtual void process(NetConnection *conn);
 
	DECLARE_CONOBJECT(ExplosionMessageEvent);
};
This gives us the basic methods required for a NetEvent, as well as four variables which are required to instanciate an explosion. There is nothing in the destructor, and the constructor simply resets everything to 0 or NULL.

Moving on, we need a pack, unpack, and process function (write is identical to pack).

void ExplosionMessageEvent::pack(NetConnection *conn, BitStream *bstream)
{
	mathWrite(*bstream, pos);
	mathWrite(*bstream, normal);
	bstream->write(objectType);
	bstream->writeRangedU32( dataBlock->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
}

void ExplosionMessageEvent::unpack(NetConnection *conn, BitStream *bstream)
{
	mathRead(*bstream, &pos);
	mathRead(*bstream, &normal);
	bstream->read(&objectType);
	U32 dataBlockID = bstream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast);
	if( Sim::findObject( dataBlockID, dataBlock ) == false)
	{
		Con::errorf( ConsoleLogEntry::General, "ExplosionMessageEvent: Invalid packet, bad datablockId(ExplosioNData): 0x%x", dataBlockID);
	}
}


void ExplosionMessageEvent::process(NetConnection *conn)
{
	Explosion* pExplosion = NULL;
	pExplosion = new Explosion;
	pExplosion->onNewDataBlock(dataBlock);

	if( pExplosion )
	{
		MatrixF xform(true);
		xform.setPosition(pos);
		pExplosion->setTransform(xform);
		pExplosion->setInitialState(pos, normal);
		pExplosion->setCollideType( objectType );
		if (pExplosion->registerObject() == false)
		{
			Con::errorf(ConsoleLogEntry::General, "ExplosionMessageEvent::explode: couldn't register explosion");
			delete pExplosion;
			pExplosion = NULL;
		}
	}
}

One more thing is needed. We have to actually post this NetEvent to all the clients. This should be in whatever function you have that needs to create an explosion.
SimGroup* pClientGroup = Sim::getClientGroup();
	for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++)
	{
		NetConnection* nc = static_cast<NetConnection*>(*itr);

		ExplosionMessageEvent* pEvent = new ExplosionMessageEvent;
		pEvent->pos = p;
		pEvent->normal = n;
		pEvent->objectType = collideType;
		pEvent->dataBlock = explosion;
			
		nc->postNetEvent(pEvent);
	}

And there you go. p should be replaced by the position of the explosion, n by the normal, collideType by the typemask that created the explosion, and explosion by the variable that holds the datablock pointer. Also, I used
IMPLEMENT_CO_CLIENTEVENT_V1(ExplosionMessageEvent);
to make this a NetEvent that could only be sent from server to client. If this were sent from the client to the server, it would crash the server. Thus it is probably best to ensure that that doesn't happen.

There are a few other examples of NetEvents in the engine (some of this code was taken from lightning.cc for instance) that would be good to study if you intend to use NetEvent for other things. Finally, READ THE DOCS. This cannot be said enough. If you don't have an internet connection, then remember that all the docs are pulled straight out of the source code, and you can find them in the various header files. There is no excuse to not read the docs if you own a copy of Torque.

#1
06/30/2005 (7:00 am)
Thanks for posting this...clears it up a bit for me ( even after reading the docs --about 8 times ;) ).
#2
06/30/2005 (8:19 am)
Sometimes the docs aren't the easiest things in the world to read and understand. Typically I'll look for examples of the code in the engine and read through those with the docs at hand. I've found that's the most useful, as it clears up parts of the docs I don't understand, and clears up parts of the code I don't understand.
#3
07/02/2005 (3:39 pm)
This should go into HEAD if it works correctly without side-effects.. Ben reading this?
#4
07/02/2005 (3:54 pm)
it hasn't been thoroughly tested yet, but it should work correctly without side-effects. Should.