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.
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.
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?
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”
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?
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
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
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
Player
|
Player
|
Deserialize
|
Shoot |
Ball Replica |
Post Deserialize &
|
Ball Replica
|
Ball Position |
Collision Check |
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);
}
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...
}
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...
}
}
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
}
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);
}
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());
}
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;
}
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));
}
}
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);
}
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;
}
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;
}
}
}
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....
}
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;
}
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;
}
}
}
}
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...
}