sega 500 an introduction to replication jeff “ezeikeil” giles [email protected] jgiles
TRANSCRIPT
Sega 500
An Introduction to
Replication
Jeff “Ezeikeil” [email protected]://gamestudies.cdis.org/~jgiles
Today
Is about one of the core elements that make UT2003 what it is. The multiplayer experience.
In another word, Replication.
Replication, so what is it?
In a nutshell, Replication in UT is about how information gets sent across the wire.
As well as to whom and who from.
It’s how UT shares reality with all the players connected to that game.
Replication
A dictionary definition:
rep·li·cate To duplicate, copy, reproduce, or repeat. Biology To reproduce or make an exact copy
or copies of (genetic material, a cell, or an organism).
And there you have
A very simple introduction to object replication between client and server.
UT’s Defintion
Unreal views the general problem of "coordinating a reasonable approximation of a shared reality between the server and clients" as a problem of "replication". That is, a problem of determining a set of data and commands that flow between the client and server in order to achieve that approximate shared reality.
An important fact…
about UT, it is a client server architecture which allow players to join a game which is currently in progress.
Meaning one server, and zero or more client machines.
Why is this important?
The Server Is The Man.
the server is authoritative about the gameplay flow, the server's game state can always be regarded as the one true game state.
One true game
The version of the game state on client machines should always be regarded as an approximation subject to many different kinds of deviations from the server's game state.
The players
Actors that exist on the client machine should be considered proxies because they are a temporary, approximate representation of an object rather than the object itself.
Three Types of Replication
The network code is based on three primitive, low-level replication operations for communicating information about the game state between the the server and clients.
Actor replication
The server identifies the set of "relevant" actors for each client (actors which are either visible to the client or are likely to somehow affect the client's view or movement instantaneously), and tells the client to create and maintain a "replicated" copy of that actor. While the server always has the authoritative version of that actor, at any time many clients might have approximate, replicated versions of that actor.
Variable replication
Actor variables that describe aspects of the game state which are important to clients can be "replicated". That is, whenever the value of the variable changes on the server side, the server sends the client the updated value.
Function call replication
A function that is called on the server in a network game can be routed to the remote client rather than executed locally. Alternatively, a function called on the client side may be routed to the server, rather than called locally.
For example
You can hear other players gunshots because the server is replicating the ClientHearSound function to you. The ClientHearSound function is called for a PlayerPawn whenever that PlayerPawn hears a sound.
The replication statement
In UnrealScript, every class can have one replication statement.
The replication statement contains one or more replication definitions.
Each replication definition consist of a replication condition (a statement that evaluates to True or False), and a list of one or more functions and variables to which the condition applies.
The replication statement
may only refer to variables defined in that class, and functions defined first in that class (that is, it cannot apply to functions defined in a superclass but overridden in that class.
The replication statement
Therefore, if the Actor class contains a variable DrawType, then you know where to look for its replication condition: it can only reside there in the Actor class.
The replication statement
Essentially determines what gets sent across the wire, or what functions and values a relevant depending on the role of that class in a network game.
Each replication statement reads like a function, but definitions begins with "reliable if (condition)" or "unreliable if (condition)"
An example.
From Xpawnreplication{ // client to server reliable if ( Role < ROLE_Authority )
ServerDoCombo, ServerRequestRules, AdminMenu, ServerRequestPlayerInfo, ServerSpecViewGoal;
unreliable if( Role < ROLE_Authority )L33tPhrase;
reliable if ( Role == ROLE_Authority) ClientReceiveRule, ClientReceiveCombo;}
So what’s going on?
Unreliable: Functions replicated with the "unreliable"
keyword are not guaranteed to reach the other party and, if they do reach the other party, they may be received out-of-order.
The only things which can prevent an unreliable function from being received are network packet-loss, and bandwidth saturation.
So what’s going on?
Reliable: The replicated function will get through in the
proper order.
Variables are always reliable Variables are always guaranteed to reach the
other party eventually, even under packet-loss and bandwidth saturation condition.
A note on Packet loss
In a LAN game, we guesstimate that unreliable data is received successfully approximately 99% of the time. However, in the course of a game, hundreds of thousands of things are replicated, so you can be sure that some unreliable data will be lost.
A note on Packet loss
In a typical low quality 28.8K ISP connection, unreliable data is generally received 90%-95% of the time. In other words, it is very frequently lost.
And what’s “ROLE_Authority”?
ROLE_Authority is part of the ENetRole enum defined in actor.
enum ENetRole { ROLE_None, // Means the actor is not relevant in network play. ROLE_DumbProxy, // A dumb proxy. ROLE_SimulatedProxy, // A simulated proxy. ROLE_AutonomousProxy, // An autonomous proxy. ROLE_Authority, // The one authoritative version of the actor. };
What’s “ROLE_Authority”?
And it’s intended for use with two variables, Role and RemoteRole.
These variables describe how much control the local and remote machines, respectively, have over the actor:
Role==ROLE_DumbProxy
The actor is a temporary, approximate proxy which should not simulate any physics at all. On the client, dumb proxies just sit around and are only moved or updated when the server replicates a new location, rotation, or animation information.
This situation is only seen in network clients, never for network servers or single-player games.
Role==ROLE_SimulatedProxy
The actor is a temporary, approximate proxy which should simulate physics and animation. On the client, simulated proxies carry out their basic physics (linear or gravitationally-influenced movement and collision), but they don't make any high-level movement decisions. They just go.
This situation is only seen in network clients, never for network servers or single-player games.
Role==ROLE_AutonomousProxy
The actor is the local player. Autonomous proxies have special logic built in for client-side prediction (rather than simulation) of movement.
This situation is only seen in network clients, never for network servers or single-player games.
Role==ROLE_Authority
This machine has absolute, authoritative control over the actor.
This is the case for all actors in single-player games.
This is the case for all actors on a server. On a client, this is the case for actors that were
locally spawned by the client, such as gratuitous special effects which are done client-side in order to reduce bandwidth usage.
On roles Actor’s play
On the server side, all actors have Role==ROLE_Authority, and RemoteRole set to one of the proxy types.
On the client side, the Role and RemoteRole are always the exactly reversed relative to the server's value.
Core replication variables
To get your actors to replicate properly there is a rather long list of special replication variables to know about.
Here, I’m only gone to address the most important of them:
NetPriority
Each actor has a floating point variable called NetPriority. The higher the number, the more bandwidth that actor receives relative to others. An actor with a priority of 2.0 will be updated exactly twice as frequently as an actor with priority 1.0.
The only thing that matters with priorities is their ratio.
NetPriority
Since 3.0 is the highest on the list, there is no real difference between giving your actor a NetPriority of 4.0 and 4000.0, since they will both be placed at the top of the list.
bNetInitial
true if this is the first time this actor is being replicated across the network. Useful for variables that differ from the defaultproperties, yet will not change over the life of the actor.
Some others…
bNetOwner True if the player we are replicating to owns
this actor directly.
bAlwaysRelevant Always keep the actor relevant to network
play.
Determining relevance
An Unreal level can be huge, and at any time a player can only see a small fraction of the actors in that level. Most of the other actors in the level aren't visible, aren't audible, and have no significant effect on the player.
Determining relevance
The set of actors that a server deems are visible to or capable of affecting a client are deemed the relevant set of actors for that client.
This is a significant bandwidth optimization in Unreal's network code is that the server only tells clients about actors in that client's relevant set.
Determining relevance
The basic rules are:
1) The actor belongs to the ZoneInfo class, then it is relevant.
2) If the actor has static=true or bNoDelete=true, then it is relevant.
3) If the actor is owned by the player (Owner==Player), then it is relevant.
4) If the actor is a Weapon and is owned by a visible actor, then it is relevant.
Determining relevance
5) If the actor is hidden (bHidden=true) and it doesn't collide (bBlockPlayers=false) and it doesn't have an ambient sound (AmbientSound==None) then the actor is not relevant.
6) If the actor is visible according to a line-of-sight check between the actor's Location and the player's Location, then it is relevant.
7) If the actor was visible less than 2 to 10 seconds ago (the exact number varies because of some performance optimizations), then it is relevant.
Simulated functions
Declares that a function may execute on the client-side when an actor is either a simulated proxy or an autonomous proxy. All functions that are both native and final are automatically simulated as well.
For more reading
And a whole lot more specific information, read these:
http://unreal.epicgames.com/Network.htm
http://www.planetunreal.com/pipeline/tutorials/replication.html
And now for our example…
For today, I wanted to do something rather simple, and that was allow the players to throw some rocks at each other.
But not just any rocks, Karma rocks that replicate their positions over the network.
Krock
And keeping with the cheesy art skills, I used the editor to create a simple static mesh for the KRocks. Complete with Karma primitives…
The plan
Is to have the player spawn one of these whenever they fire their weapon.
Then they can shoot them around some and play a bit of “gun hockey” with them.
But too keep the network bandwidth reasonable, I’ve also created a cap on the number that players can spawn.
The plan
I’ve also created a few debug methods so that was can see what going on, either on the HUD or in the log.
Getting started…
For convenience, I created a gametype for testing the replication as the easies way to intercept the fire command from the player is through the controller, but we’ll talk about that in a second.
The Gametype
There is really nothing new going on here, just some defaults so that we can use the new controller and HUD.
class ERepGame extends xDeathMatch;
DefaultProperties{ HUDType="Ezerep.EHUD" CountDown=0 PlayerControllerClassName="EzeRep.Epc"}
The Gametype
Now, keep in mind that only the server has a gametype object. The clients do not.
Thus any attempt to access information in the gametype via Level.game will access none.
The Gametype
Which brings me to the one function in the gametype.
This function was intended to spit out information to the HUD or log based on the role of the player. However, due to the a for mention of the gamtypes existence, this function will not *properly*.
simulated function LogRole()
The Gametype
However, the logic is sound. Therefore, left in for reference purposes, just don’t try to access it from the client.
This function will work fine if it’s copied into other classes.
The Gametype
That being said…simulated function LogRole(){ if(role==ROLE_Authority) { log("--->We are Server"); BroadcastLocalizedMessage(class'RepMsg', 0); } if(Role<ROLE_Authority) { log("--->We are Client"); BroadcastLocalizedMessage(class'RepMsg', 1); }}
The Gametype
This is a very simple debug method which, based on our role, determines what function to execute.
if(role==ROLE_Authority){ log("--->We are Server"); BroadcastLocalizedMessage(class'RepMsg', 0);}
If we are the server, do this
The Gametype
All we do is splash some info to the log or screen, based on the role we play.
if(Role<ROLE_Authority){ log("--->We are Client"); BroadcastLocalizedMessage(class'RepMsg', 1);}
If we are a client, do this
The RepMsg
This is simply a local message handler to display info to the HUD.
Nothing of interest here, just some debug strings.
class RepMsg extends LocalMessage;
The KRock
Now this class likely isn’t going to to be what you’d expect…
What we end up doing here is setting up a bunch of variables. No replication work…
Krock
Starting with the physics…
//physics and collisionDrawType=ST_StaticmeshStaticMesh=staticmesh'ErepTest.box'
bCollideWorld=truebCollideActors=true
CollisionRadius=8Collisionheight=8
Krock
And The Karma Params//KarmaPhysics=PHYS_KarmaBegin Object Class=KarmaParams Name=KParams0
KAngularDamping=0KLinearDamping=0bHighDetailOnly=FalsebClientOnly=FalsebKDoubleTickRate=TrueName="KParams0"
End ObjectKParams=KarmaParams'KParams0'
Krock
And you can play with some of these params to change the Karma properties…but I leave that to you. I’m just using the defaults in the sample code, so it’s really slippery for the KRock’s
The only function to note is the tick function which calls KWake, so the KRock can never “sleep”.
Krock
And finally, the network parameters.
These are so that the object is deemed not “fixed in place”. And Karma can updated it’s position and send it over the wire…eg it’s deemed relevant.
bStatic=false //req'dbNoDelete=false //req'dbHidden=false
Krock
More to guarantee the objects relevance to the client…
Btw: I likely don’t need the bGameRelevant. But when I wrote up the lesson, I was no where near a network to test out its absence.
bGameRelevant=TruebAlwaysRelevant=True //req'dbNetRelevant=true //req'd
Krock
We update the movment oof the objet so they stay in sync, and change the update frequency to reflect how frequely this object is updated by the server.
bReplicateMovement=trueNetUpdateFrequency=10.000000NetPriority=2.500000bNetInitialRotation=True
Krock
As found in the actor class NetUpdateFrequency reflects how many seconds between net updates.
This should reflect the importance of your object.
E.g. if the NetPriority is high (important) I would expect the NetUpdateFrequency to be low (more frequent).
Krock
And lastly, change the remote role so that the client can anticipate where the Krock will be…
RemoteRole=ROLE_SimulatedProxybUpdateSimulatedPosition=True
Epc: the player controller
Now, it’s actually the player controller that does the work in this case.
This controller is really responsible for two actions, spawning the KRock through the fire command, and count the number spawned.
Epc: the player controller
Thus the fire function:exec function Fire( optional float F ){ local vector l; local Krock t;
if(ctr >= 5) return; else ctr++;
Simple counting
Epc: the player controller
Change what function gets called if we are not the server!
if(role < Role_AUTHORITY){ clientFire(f); return;}
Epc: the player controller
Then Spawn the Krock
l=pawn.location; l.z+=64; super.Fire(f); t= Spawn(class'Krock',pawn,,l); t.Velocity=50*vector(pawn.Rotation);
Epc: the player controller
But his is the interesting bit
The client must call this function so that the server know that a new KRock has been spawned…otherwise it’s only client side!
if(role < Role_AUTHORITY){ clientFire(f); return;}
Epc: the player controller
Which brings us to:simulated function clientFire( optional float F ){ local vector l; local Krock t;
l=pawn.location; l.z+=64; super.Fire(f); t= Spawn(class'Krock',pawn,,l); t.Velocity=50* vector(pawn.Rotation);}
Which does fundamentallythe same thing…
Epc: the player controller
But the function itself is SIMULATED.
Meaning that it can be called by a remote machine. This is how the server knows to spawn the KRock.
Epc: the player controller
And lastly, we need a replication statement.
replication{ reliable if(role < Role_AUTHORITY) clientfire; reliable if(role == Role_AUTHORITY) ctr;}
Epc: the player controller
Without the replication statement, even if the rest of the code is fine, we will not be able to see KRocks spawned on client machines.
On an interesting note:
One this is up and running, take a look in the client machines and the servers log files.
In the sample code I left in some log entries in the Epc.
On an interesting note:
In the fire function:
ClientFire function:
log("Server----Fire"@t);
log("Client----FIre"@t);
On an interesting note:
The client machine, shows no entries at all with regards to the client fire log...none…nada…zip.
However, the server machine show entries from both client and server.
On an interesting note:
This means that it is the server that is in fact doing the spawning Krock and execution of the of the simulated function, which gets sent back to the client machine.
And what we should have
Is a bunch of KRocks spinning around the level that get properly replictated on both client and server.
For fun
Try changing the parent class of Krock to one of the projectiles, or even replace the spawning of the KRock with a projectile.
Then remove the replication statement. Notice the changes this incurs.