GeorgeGularte
[email protected]
  • Home
  • Blog
  • Projects
  • Resume
Picture
My research is to better understand networking games.  I analyzed a Raknet example and elaborated and demonstrate the Irrlicht example  

The Irrlicht Example is a basic First person Peer to peer shooter example in conjunction with irrlicht engine as the source of render, scene management, collision detection and the 3rd party associated audio plugin irrklang.
 
Most of the following notes are from the original Author: Jenkins, I broke them down by section blocks and added my notes on top of his.


Picture
What is Irrlicht?


The Irrlicht Engine is a cross-platform high performance realtime 3D engine written in C++.

What Features are used in this example?
  • Asset  loader
  • Scene management
  • Renderer
  • GUI
  • Basic Collision Detection
  • User Input
  • Animation
  • External Sound Plugin “IrrKlang”

Picture
What is Raknet?

RakNet is a cross platform, open source, C++ networking engine for game programmers. A lower level framework powers higher-level features such as game object replication, voice chat, and patching



What Features are used in this example?
  • RakPeerInterface
  • NetworkIDManager 
  • Replica Manager
  • Nat Punchthrough Client 
  • CloudClient 
  • Fully Connected Mesh 2 
  • Player Replica
  • miniupnpc-1.5


Picture

Network Flow Overview

We are going to connect our FPS game to Raknets Master Server..

To first establish a peer to peer connection we are going to use NAT punchthrough to  anyone who may connecting to us else, we will fall back to using a proxy server to route communication as an intermediary between relaying data. 

We will be able to host a game automatically if none are existing at the moment.
Other users (Clients) will automatically connect to an open existing game if one exists if NAT punch through is successful. On connection the host  and give us the connections the other Users


We are going to upload our player to other Users in the peer connection as well as Download all the other players from their connections. 

We are going to send serialized and received De serialiezed player info packets (pos, rotation, etc)  to all the other players whom we are connected to.

We are going to use interpolation to provide smooth movement for lag compensation

To save bandwidth when we Fire to shoot our projectile, ("Ball") we are only going to broadcast just the direction. We are going to do a linear determination for the updated position. 

We are going to broadcast our selves getting hit to the other players 


Tutorial Video Setup

In this tutorial I show you how to install Raknet and Irrlicht in a Visual studio 2010  environment and showcase the mentioned FPS example.

Network Flow

Section 1

Instantiate
RakNet 

Section 2

NAT

Section 3

Incoming 
Packets

Section 4

New
Connections

Section 5

Your Replica 


Section 6

 Player 
  Replica

Section 7

Player
Serialization   

Section 8

Deserialize
&
Interploation

Section 9

Shoot

Section 10

Ball Replica


Section 11

Post Deserialize &
GUID

Section 12

Ball Replica
Update

Section 13

Ball Position

Section 14

Collision Check

Section 15

Death Timeout


Section 1: "Instantiate RakNet"

1st  once the user press Start Demo,

  • Instantiate RakNet Classes is called.

  • It allocates all RakNet classes including the dependent plugins.

  • It will also try to connect to the NATCompleteServer sample hosted  hosted by Jenkins Software.
void InstantiateRakNetClasses(void)
{
	static const int MAX_PLAYERS = 32;
	static const unsigned short TCP_PORT = 0;
	static const RakNet::TimeMS UDP_SLEEP_TIMER = 30;

	//Basis of all UDP communication
	rakPeer = RakPeerInterface::GetInstance();
	//using fixed port so we can use Advertsisesystem and connect on the LAN, Howerver NOT Implemeneted
	SocketDescriptor sd(1234, 0);
	sd.socketFamily = AF_INET; // Only IPV4 supports broadcast on 255.255.255.255
	//This is Kool he checks which ports are in use and finds one that is available 
	while(IRNS2_Berkley::IsPortInUse(sd.port, sd.hostAddress, sd.socketFamily, SOCK_DGRAM)== true);
		sd.port++;

	    //+1 is for the connection to the Nat PuchThrough Server
		RakNet::StartupResult sr = rakPeer->Startup(MAX_PLAYERS +1, &sd, 1);
		RakAssert(sr == RakNet::RAKNET_STARTED);
		rakPeer->SetMaximumIncomingConnections(MAX_PLAYERS);

		//fast disconnects for easier testing of host migration
		rakPeer->SetTimeoutTime(5000, UNASSIGNED_SYSTEM_ADDRESS);

		//ReplicaManager 3 replies on NetworkManager. It assignes Numbers to objects so they can be looked up over the Network
		//Its a class  in case you wanted to have multiple worlds, then you could have multiple instances of networkManager
		networkIDManager = new NetworkIDManager;

		//Automatically sends around new /deleted / changed game objects
		replicaManager3 = new ReplicaManager3Irrlicht;
		replicaManager3->SetNetworkIDManager(networkIDManager);
		rakPeer->AttachPlugin(replicaManager3);

		//Automatically destroy connections, dut dont create them so we have more control over when a system is considerd to ready to play
		//This is the AUTO Connect
		replicaManager3->SetAutoManageConnections(false, true);
		
		//Create and register the network objects that represents the player
		playerReplica = new PlayerReplica;
		replicaManager3->Reference(playerReplica);

		//Lets you connect through routers
		natPunchthroughClient = new NatPunchthroughClient;
		rakPeer->AttachPlugin(natPunchthroughClient);

		//uploads game instance, bassically client half of a directory server
		//Server code is in NATcompleteServer sample
		cloudClient = new CloudClient;
		rakPeer->AttachPlugin(cloudClient);

		fullyConnectedMesh2 = new FullyConnectedMesh2;
		fullyConnectedMesh2->SetAutoparticipateConnections(false);
		fullyConnectedMesh2->SetConnectOnNewRemoteConnection(false, "");
		rakPeer->AttachPlugin(fullyConnectedMesh2);

		//Connect to the NAT punchthrough server
		ConnectionAttemptResult car = rakPeer->Connect(DEFAULT_NAT_PUNCHTHROUGH_FACILITATOR_IP, DEFAULT_NAT_PUNCHTHROUGH_FACILITATOR_PORT,0,0);
	     RakAssert(car==CONNECTION_ATTEMPT_STARTED);
}
Network Flow

Section 2: "NAT"

  • Once we are connected to the NATPunchthroughServer (See ID_CONNECTION_REQUEST_ACCEPTED), UPNP will run to open the router if possible. It will try to open the external port connected to the NATPunchthroughServer. 
  • It will map that to the internal port used by RakNet. If it succeeds, NATPunchthrough should automatically succeed for this system.

  • Next, the cloudServer will be queried for active connections. 

  • If any connections are returned, NATPunchthroughClient::OpenNATGroup() to open the router for those systems.

  • On success, those systems are connected to.

  •  If there are no existing games, or on failure, a new game is started
void CDemo::UpdateRakNet(void)
{

 //Code Break...
case ID_CONNECTION_REQUEST_ACCEPTED:
			{
				PushMessage(RakNet::RakString("Connection request to ") + targetName + RakNet::RakString(" accepted."));
				if (packet->systemAddress==facilitatorSystemAddress)
				{
					isConnectedToNATPunchthroughServer=true;

					// Open UPNP.
					struct UPNPDev * devlist = 0;
					devlist = upnpDiscover(1000, 0, 0, 0);
					if (devlist)
					{
						char lanaddr[64];	/* my ip address on the LAN */
						struct UPNPUrls urls;
						struct IGDdatas data;
						if (UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr))==1)
						{
							// External port is the port people will be connecting to us on. This is our port as seen by the directory server
							// Internal port is the port RakNet was internally started on
							char eport[32], iport[32];
							natPunchthroughClient->GetUPNPPortMappings(eport, iport, facilitatorSystemAddress);

							int r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
								eport, iport, lanaddr, 0, "UDP", 0);

							if(r==UPNPCOMMAND_SUCCESS)
							{
								// UPNP done
							} 

						}
					}
					
					// Query cloud for other running game instances 
                                        //Cloud Server					
                                        RakNet::CloudQuery cloudQuery;
				        cloudQuery.keys.Push(RakNet::CloudKey("IrrlichtDemo",0),_FILE_AND_LINE_);
					cloudClient->Get(&cloudQuery, packet->guid);
				}
			}
			break;

//Code Break...
//Find an existing room or create one if none already exists
case ID_CLOUD_GET_RESPONSE:
			{
				RakNet::CloudQueryResult cloudQueryResult;
				cloudClient->OnGetReponse(&cloudQueryResult, packet);
				if (cloudQueryResult.rowsReturned.Size()>0)
				{	
					PushMessage(RakNet::RakString("NAT punch to existing game instance"));
					natPunchthroughClient->OpenNAT(cloudQueryResult.rowsReturned[0]->clientGUID, facilitatorSystemAddress);
				}
				else
				{
					PushMessage(RakNet::RakString("Publishing new game instance"));

					// Start as a new game instance because no other games are running
					RakNet::CloudKey cloudKey("IrrlichtDemo",0);
					cloudClient->Post(&cloudKey, 0, 0, packet->guid);
				}

				cloudClient->DeallocateWithDefaultAllocator(&cloudQueryResult);
                        }
			break;

//Code Break...
}
Network Flow

Section 3: "Incoming Packets"

  • Incoming packets are checked in UpdateRakNet(). 
  • If the NAT punchthrough fails, it will use the proxy server instead. CDemoderives from UDPProxyClientResultHandler, which will get the results of the proxy connection attempt through it callback interfaces.
void CDemo::UpdateRakNet(void)
{
	RakNet::SystemAddress facilitatorSystemAddress(DEFAULT_NAT_PUNCHTHROUGH_FACILITATOR_IP, DEFAULT_NAT_PUNCHTHROUGH_FACILITATOR_PORT);
	RakNet::Packet *packet;
	RakNet::TimeMS curTime = RakNet::GetTimeMS();
	RakNet::RakString targetName;
	for (packet=rakPeer->Receive(); packet; rakPeer->DeallocatePacket(packet), packet=rakPeer->Receive())
	{
		if (strcmp(packet->systemAddress.ToString(false),DEFAULT_NAT_PUNCHTHROUGH_FACILITATOR_IP)==0)
		{
			targetName="NATPunchthroughServer";
		}
		else
		{
			targetName=packet->systemAddress.ToString(true);
		}
		 
		switch (packet->data[0])
                //Code Break...
     }
}
Network Flow

Section 4: "New Connections"

  • When another user connections to us (either ID_NEW_INCOMING_CONNECTION or ID_CONNECTION_REQUEST_ACCEPTED), we create a new connection object and call ReplicaManager3::PushConnection().
  • This tells the automatic object replication system that this connection is ready to participate in the game.
void CDemo::UpdateRakNet(void)
{
   //...Code Break 
   case ID_FCM2_VERIFIED_JOIN_ACCEPTED:
			{
				DataStructures::List systemsAccepted;
				bool thisSystemAccepted;
				fullyConnectedMesh2->GetVerifiedJoinAcceptedAdditionalData(packet,     &thisSystemAccepted, systemsAccepted, 0);
				if (thisSystemAccepted)
					PushMessage("Game join request accepted\n");
				else
					PushMessage(RakNet::RakString("System %s joined the mesh\n", systemsAccepted[0].ToString()));
				
				// DataStructures::List participantList;
				// fullyConnectedMesh2->GetParticipantList(participantList);

				for (unsigned int i=0; i < systemsAccepted.Size(); i++)
					replicaManager3->PushConnection(replicaManager3->AllocConnection(rakPeer->GetSystemAddressFromGuid(systemsAccepted[i]), systemsAccepted[i]));
			}

   //...Code Break
}
Network Flow

Section 5: "Your Replica"

  • On pushing a new connection to ReplicaManager3, all existing Replica3 objects are sent to that server. 
  • Our Player Pushed to the Server
  • In this case, it is our own player, PlayerReplica, which was created in InstantiateRakNetClasses.
void InstantiateRakNetClasses(void)
{
                //Code Break...         
                //Create and register the network objects that represents the player
		playerReplica = new PlayerReplica;
		replicaManager3->Reference(playerReplica);
}
Network Flow

Section 6: "Player Replica"

  • PlayerReplica derives from BaseIrrlichtReplica, which derives from Replica3. BaseIrrlichtReplica implements all the interfaces necessary for peer to peer multiplayer

    •  particularly returning QueryConstruction_PeerToPeer,

    • QueryRemoteConstruction_PeerToPeer, and QuerySerialization_PeerToPeer.

  • It also has a member variable position, which is used by all derived classes. This variable is automatically synchronized in SerializeConstruction and Serialize.
//Raknetstuff.h

class PlayerReplica :public BaseIrrlichtReplica, irr::scene::IAnimationEndCallBack
{
      //Code break...
}

class BaseIrrlichtReplica: public RakNet::Replica3
{
     //Code Break...
     virtual RakNet::RM3ConstructionState QueryConstruction(RakNet::Connection_RM3 *destinationConnection,     RakNet::ReplicaManager3 *replicaManager3)
	     {return QueryConstruction_PeerToPeer(destinationConnection);}
     //Allows Peer to create objects/ overrides the Server/Client	    
      virtual bool QueryRemoteConstruction(RakNet::Connection_RM3 *sourceConnection) 
	     {return QueryRemoteConstruction_PeerToPeer(sourceConnection); }

//Code Break...
// real is written on the owner peer, read on the remote peer
	irr::core::vector3df position;
}

/---------------------------------------------------------------------------------------------------------
//RakNetstuff.CPP
//Only Use it when you are creating an object that it needs to be serilized
void PlayerReplica::SerializeConstruction(RakNet::BitStream *constructionBitstream, RakNet::Connection_RM3 *destinationConnection)
{
	BaseIrrlichtReplica::SerializeConstruction(constructionBitstream, destinationConnection);
	constructionBitstream->Write(rotationAroundYAxis);
	constructionBitstream->Write(playerName);
	constructionBitstream->Write(IsDead());
}
Network Flow

Section 7: "Player Serialization"

  • PlayerReplica additionally serializes

    • playerName,

    • isMoving,

    • isDead, and

    • rotationAroundYAxis.

    • playerName never changes so is sent only in SerializeConstruction.

  • isMoving and isDead are serialized per-tick, and are used to control what animation is played on remote systems.

  • rotationAroundYAxis is the camera rotation, which rotates the player on the remote system.
//RakNetStuff.CPP
RM3SerializationResult PlayerReplica::Serialize(RakNet::SerializeParameters *serializeParameters)
{
	//Find out why Zero?
	BaseIrrlichtReplica::Serialize(serializeParameters);
	serializeParameters->outputBitstream[0].Write(position);
	serializeParameters->outputBitstream[0].Write(rotationAroundYAxis);
	serializeParameters->outputBitstream[0].Write(isMoving);
	serializeParameters->outputBitstream[0].Write(IsDead());
	return RM3SR_BROADCAST_IDENTICALLY;
}
Network Flow

Section 8: "Deserialization & Interpolation"

  • Both position and rotationAroundYAxis are interpolated on the remote system, using positionDeltaPerMS and rotationDeltaPerMS.

  • When we deserialize either of these values, that amount is added per-tick based on the amount of time elapsed, until the real position is reached.

  • This happens in Update(), which is called from the CDemo.
void PlayerReplica::Deserialize(RakNet::DeserializeParameters *deserializeParameters)
{
	BaseIrrlichtReplica::Deserialize(deserializeParameters);
	deserializeParameters->serializationBitstream[0].Read(position);
	deserializeParameters->serializationBitstream[0].Read(rotationAroundYAxis);
	deserializeParameters->serializationBitstream[0].Read(isMoving);

	//Play the death sound Once if he was deadnt before, but not he is
	bool wasDead=isDead;
	deserializeParameters->serializationBitstream[0].Read(isDead);
	if (isDead==true && wasDead==false)
	{
		demo->PlayDeathSound(position);
	}
	//InterPerlation Smoothing effects for Lag COMPENSATION

	//Create a temp Vector 3
	core::vector3df posOffset;
	//Diffrence = CurrentPos - LastFramePos
	posOffset = position - model->getPosition();
	
	//PosPerMS = diffrence / 100ms
	positionDeltaPerMS = posOffset / INTERP_TIME_MS;

	float rotationOffset;
	rotationOffset= GetRotationDifference(rotationAroundYAxis,model->getRotation().Y);
	rotationDeltaPerMS = rotationOffset / INTERP_TIME_MS;
	interpEndTime = RakNet::GetTimeMS() + (RakNet::TimeMS) INTERP_TIME_MS;
}

void PlayerReplica::Update(RakNet::TimeMS curTime)
{
	// Is a locally created object? //ThisIs the acive User compares ID
	if (creatingSystemGUID==rakPeer->GetGuidFromSystemAddress(RakNet::UNASSIGNED_SYSTEM_ADDRESS))
	{
		// Local player has no mesh to interpolate
		// Input our camera position as our player position
		playerReplica->position=demo->GetSceneManager()->getActiveCamera()->getPosition()-irr::core::vector3df(0,CAMREA_HEIGHT,0);
		playerReplica->rotationAroundYAxis=demo->GetSceneManager()->getActiveCamera()->getRotation().Y-90.0f;
		isMoving=demo->IsMovementKeyDown();

		// Ack, makes the screen messed up and the mouse move off the window
		// Find another way to keep the dead player from moving
	    //	demo->EnableInput(IsDead()==false);
		return;
	}

	//Update interpolation curTime is being Passed in
	RakNet::TimeMS elapsed = curTime - lastUpdate;

	if(elapsed <= 1)
		return;
	if(elapsed > 100)
		elapsed = 100;
	
	lastUpdate = curTime;
	irr::core::vector3df curPositionDelta = position - model->getPosition();
	
	//interperPOS = posPerMS * timeElapsed
	irr::core::vector3df interpThisTick = positionDeltaPerMS*(float) elapsed;

	//2Checks is the current time less than ineterpEndTIme
	//and is the position betwenn reallity and the estimate far appart
	if (curTime < interpEndTime && interpThisTick.getLengthSQ() < curPositionDelta.getLengthSQ())
	{
		model->setPosition(model->getPosition() + positionDeltaPerMS * (float) elapsed);
	}
	else
	{
		model->setPosition(position);
	}

	//rotaion interperlartion
	float curRotationDelta = GetRotationDifference(rotationAroundYAxis,model->getRotation().Y);
	float interpThisTickRotation = rotationDeltaPerMS*(float)elapsed;
	if (curTime < interpEndTime && fabs(interpThisTickRotation) < fabs(curRotationDelta))
	{
		model->setRotation(model->getRotation()+core::vector3df(0,interpThisTickRotation,0));
	}
	else
	{
		model->setRotation(core::vector3df(0,rotationAroundYAxis,0));
	}

}
Network Flow

Section 9: "Shoot"

  • When the player presses the shoot button, CDemo::shoot() is called.

  • If the player is not dead, CDemo::shootFromOrigin is called, which acts the same as in the original demo.

  • It creates a moving ball, and plays a particle effect in the amount of time it would take the ball to hit the nearest terrain object.

  • in the same function, a new instance of BallReplica is created and referenced. ReplicaManager3 will automatically transmit this new object to connected systems (also systems that connect later).
void CDemo::shoot()
{
	if (playerReplica->IsDead())
		return;

	scene::ISceneManager* sm = device->getSceneManager();
	scene::ICameraSceneNode* camera = sm->getActiveCamera();
	core::vector3df camPosition = camera->getPosition();
	core::vector3df camAt = (camera->getTarget() - camPosition);
	camAt.normalize();

	BallReplica *br = new BallReplica();
	br->demo=this;
	br->position=camPosition;
	br->shotDirection=camAt;
	br->shotLifetime=RakNet::GetTimeMS() + shootFromOrigin(camPosition, camAt);
	replicaManager3->Reference(br);
}
Network Flow

Section 10: "Ball Replica"

  • BallReplica is initialized with the same parameters as the animated particle created in shootFromOrigin. 

  • Its position is a different variable, but the math works the same so the replicated object is always in the same spot as the particle you see.
RakNet::TimeMS CDemo::shootFromOrigin(core::vector3df camPosition, core::vector3df camAt)
{
	scene::ISceneManager* sm = device->getSceneManager();
	scene::ICameraSceneNode* camera = sm->getActiveCamera();

	if (!camera || !mapSelector)
		return 0;
	
	SparticleImpact imp;
	imp.when = 0;

	// get line of camera

	core::vector3df start = camPosition;
	core::vector3df end = (camAt);
	//end.normalize();
	start += end*8.0f;
	end = start + (end * camera->getFarValue());

	core::triangle3df triangle;

	core::line3d line(start, end);

	// get intersection point with map
	 scene::ISceneNode* hitNode = NULL;
	if (sm->getSceneCollisionManager()->getCollisionPoint(
		line, mapSelector, end, triangle, hitNode))
	{
		// collides with wall
		core::vector3df out = triangle.getNormal();
		out.setLength(0.03f);

		imp.when = 1;
		imp.outVector = out;
		imp.pos = end;
	}
	else
	{
		// doesnt collide with wall
		core::vector3df start = camPosition;
		core::vector3df end = (camAt);
		//end.normalize();
		start += end*8.0f;
		end = start + (end * camera->getFarValue());
	}

	// create fire ball
	scene::ISceneNode* node = 0;
	node = sm->addBillboardSceneNode(0,
		core::dimension2d(BALL_DIAMETER,BALL_DIAMETER), start);

	node->setMaterialFlag(video::EMF_LIGHTING, false);
	node->setMaterialTexture(0, device->getVideoDriver()->getTexture(IRRLICHT_MEDIA_PATH "fireball.bmp"));
	node->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);

	f32 length = (f32)(end - start).getLength();
	const f32 speed = SHOT_SPEED;
	u32 time = (u32)(length / speed);

	scene::ISceneNodeAnimator* anim = 0;
	  
	// set flight line

	anim = sm->createFlyStraightAnimator(start, end, time);
	node->addAnimator(anim);
	anim->drop();

	anim = sm->createDeleteAnimator(time);
	node->addAnimator(anim);
	anim->drop();

	if (imp.when)
	{
		// create impact note
		imp.when = device->getTimer()->getTime() + (time - 100);
		Impacts.push_back(imp);
	}

	//Code break...

	return (RakNet::TimeMS) time;
}
Network Flow

Section 11: "Post Deserialize & GUID"

  • BallReplica::PostDeserializeConstruction is called on remote systems when a new ball is created. It calls shootFromOrigin to create the particle visible effect. 

  • It also causes the remote player with the same creatingSystemGUID to play the attack animation.

  • creatingSystemGUID is a value automatically set by ReplicaManager3, and identifies which system originally instantiated this object.
void BallReplica::PostDeserializeConstruction(RakNet::BitStream *constructionBitstream, RakNet::Connection_RM3 *destinationConnection)
{
	// Shot visible effect and BallReplica classes are not linked, but they update the same way, such that
	// they are in the same spot all the time
	demo->shootFromOrigin(position, shotDirection);

	// Find the owner of this ball, and make them play the attack animation
	unsigned int idx;
	for (idx=0; idx < PlayerReplica::playerList.Size(); idx++)
	{
		if (PlayerReplica::playerList[idx]->creatingSystemGUID==creatingSystemGUID)
		{
			PlayerReplica::playerList[idx]->PlayAttackAnimation();
			break;
		}
	}
}
Network Flow

Section 12 : "Ball Replica Update"

  • In BallReplica::Update, if this is our own ball, we check if the ball has existed long enough that it should hit a wall. If so, we destroy it, and send out this destruction packet to the other systems.
void BallReplica::Update( RakNet::TimeMS curTime)
{
	//is a locally created object?
	if(creatingSystemGUID == rakPeer->GetGuidFromSystemAddress(RakNet::UNASSIGNED_SYSTEM_ADDRESS))
	{
		//Destroy if shot expired 
		if(curTime > shotLifetime)
		{
			// Destroy on network
			BroadcastDestruction();
			delete this;
			return;
		}
	}

	//Code break....
}
Network Flow

Section 13 : "Ball Position"

  • Note that the position variable in BallReplica works differently than with PlayerReplica. In PlayerReplica, it is updated from the remote system because it can change at random. In BallReplica, it represents only the origin of when the ball was created, and doesn't otherwise change. 

  • This can be done because the path the ball takes is deterministic, and saves bandwidth and programming.
void BallReplica::Update( RakNet::TimeMS curTime)
{
	//Code Break....
        
         //Keep at the same position as the visible effect
	// Its Dermistic, so no need to actally transmit position
	//The variable position is the origin at the ball was created at, for the player its their actual position
	RakNet::TimeMS elapsedTime;
	// Due to ping variances and timestamp miscalculations, it's possible with very low pings to get a slightly negative time, so we have to check
	if (curTime>=creationTime)
		elapsedTime = curTime - creationTime;
	else
		elapsedTime = 0;
        
        //Brilliant: The Legnth of this vector increases, no reason to update a member variable Pos
	irr::core::vector3df updatedPosition = position + shotDirection * (float) elapsedTime * SHOT_SPEED;
}
Network Flow

Section 14: "Collision Detection"

  • In BallReplica::Update, if this is a ball created by a remote system, we check if the ball has hit our own player. The function GetSyndeyBoundingBox() is needed because our own player has no model, it is only a camera. 
  • Were the game to use other models, we would need to calculate the bounding box for whatever player model we are using.
// RakNet - Precalculate bounding box of Sydney.md2, since our own player's model is never loaded
// This only works on the assumption that all players have the same model
const core::aabbox3df& CDemo::GetSyndeyBoundingBox(void) const
{
	return syndeyBoundingBox;
}


void BallReplica::Update( RakNet::TimeMS curTime)
{
	  //Code Break....
       
	//see if the bullet hit us
	if (creatingSystemGUID!=rakPeer->GetGuidFromSystemAddress(RakNet::UNASSIGNED_SYSTEM_ADDRESS))
	{
		if(playerReplica->IsDead()  == false)
		{
			irr::core::vector3df positionRelativeToCharacter = updatedPosition-playerReplica->position;//+core::vector3df(0,playerHalfHeight,0);
			if (demo->GetSyndeyBoundingBox().isPointInside(positionRelativeToCharacter))
			{
				// We're dead for 3 seconds 
				playerReplica->deathTimeout=curTime+3000;
			}
		}
	}
}
Network Flow

Section 15: "Death Timeout"

  • If we die, PlayerReplica::deathTimeout is set, and is sent to remote systems in PlayerReplica::Serialize as a single boolean read into the isDead member variable.
/When the player can get back in the game
bool PlayerReplica::IsDead(void) const
{
	return deathTimeout > RakNet::GetTimeMS();
}
RM3SerializationResult PlayerReplica::Serialize(RakNet::SerializeParameters *serializeParameters)
{
	BaseIrrlichtReplica::Serialize(serializeParameters);
	serializeParameters->outputBitstream[0].Write(position);
	serializeParameters->outputBitstream[0].Write(rotationAroundYAxis);
	serializeParameters->outputBitstream[0].Write(isMoving);
	serializeParameters->outputBitstream[0].Write(IsDead());
	return RM3SR_BROADCAST_IDENTICALLY;
}

void PlayerReplica::Deserialize(RakNet::DeserializeParameters *deserializeParameters)
{
	BaseIrrlichtReplica::Deserialize(deserializeParameters);
	deserializeParameters->serializationBitstream[0].Read(position);
	deserializeParameters->serializationBitstream[0].Read(rotationAroundYAxis);
	deserializeParameters->serializationBitstream[0].Read(isMoving);

	//Play the death sound Once if he was deadnt before, but not he is
	bool wasDead=isDead;
	deserializeParameters->serializationBitstream[0].Read(isDead);
	if (isDead==true && wasDead==false)
	{
		demo->PlayDeathSound(position);
	}
        //Code break...
}
Network Flow
Powered by Create your own unique website with customizable templates.