sega 500 more replication jeff “ezeikeil” giles [email protected] jgiles
TRANSCRIPT
More Replication
Yes…that’s the plan for today.
Replication in UT is so important that it really is worth taking another class to look at another example.
Rolling back the clock
I figure I’d have some fun with this lesson and reuse some code that you are already familiar with.
We are going to create the network functionality for the King of the Hill Gametype we made in lesson 9.
As you recall…
The rules were fairly straight forward. When a player was killed for the first time, a special damage doubler would be spawned.
This doubler would be carried by the player until he was killed. At which time it get dropped for the next player to pickup.
As you recall…
Only the player with the special doubler was able to score and there could only ever be one in play.
And as you no doubt recall…
When we did the network test, it fail miserably.
The reason was that the client and server for the EzeUdam would go out of sync… in effect, being in 2 different locations, with only the one on the server being valid.
However, it worked fine in single player.
Now at this point…
It would be a good idea to refer back to lesson 9 and look at the code intend to modify.
It boils down to three classes: Gametype EzeUdam Message class
Gametype
Was essentially our rule of play with most of the work happening in the Killed function.
The Killed function did the spawning of the EzeUDam and broadcasting of messages to the player.
EzeGameMSG
Was simply the localized message class which is used for two reasons.
1. Formatting and placement of messages of that class is super easy.
2. Network optimization. As opposed to sending a string, it sends an index into this class… much more efficient.
EzeUDam
Essentially the pickup which enabled the player Pawns UDamage parameter.
The only real difference between ours an the UDamagePack was that ours broadcasted messages as well
EzeUDam
And spawned navigation information so that the bots would take after it…
Other than that, no real significant differences.
Making replication
Now, working with these classes, things replicate differently then they did with the KRock.
And this cause me much grief. I spend a couple of hours just trying to figure out why the client wouldn’t replicate properly.
It came down to the Gametype
Now as I said yesterday, and I’ll say it again…Clients don’t have gametypes.
And Because it was in the Gametype that I was updating and tracking changes.
This really caused me some puzzlement in trying to figure out how to track who had the EzeUdam, not to mention it’s location.
But as it turns out…
Once I got my head around it, it wasn’t that big of a deal.
I just had to remember that the clients don’t need the game info class because the server is the authority on what goes on in that level…It’s word is law.
Thus…
The client never makes any decisions for itself. The server makes all the decisions and then tells the clients.
Hence, most of the replication conditions are based on whether or not I am the server. If I am, replicate the condition. If not…skip it.
Getting started
In the Gametype, there are 2 variables that the clients have to be aware of
These are who has the EzeUDam and the EzeUdam itself.
var pawn HasUDam;var EzeUDam dam;
The Gametype
The server will use these to track who has the Udamage and if it can be pickup (not out of play).
Hence our replication statement :
reliable if(role==ROLE_AUTHORITY) HasUDam, dam, snd;
The Gametype
If I am the server, these variables are reliable and I can safely send them to the client.
reliable if(role==ROLE_AUTHORITY) HasUDam, dam, snd;
The Gametype
Which brings us down to the Killed function.
Now there is not a lot of anything new in this class…mostly some gameplay tweaks which we will come back to. Right now, we only care about the replication…Which in this case, is related to how the EzeUDam is flung into the air.
The Gametype
Which occurs here:
if(HasUDam == none && dam == none){ dam=spawn(class'EzeUDam',,,KilledPawn.location);
//toss the UDamage in to the air dam.Toss(); snd=none;} Does the flinging
The Gametype
Now this is a very important difference to make note of.
In the previouve version of the code, we just applied the velocity to the EzeUDam here. We can no longer do that.
We have to go into the EzeUdam itself and call the Toss function.
Why?
Well, it comes down to a matter of replication and we need to access a simulated function to ensure that it get run on the right machine.
Now, the Toss function itself is not simulated, but the one it calls, is.
EzeUDam
function Toss(){
bOnlyReplicateHidden = false;
//if I am the server, tell the clients the velocity if(Role==ROLE_Authority) { ClientToss(); return; }}
If I am the server call the Clienttoss. Otherwise, donothing
EzeUDam
And the ClientToss function is was does the actual flinging.
simulated function ClientToss(){ Velocity= 500*VRand(); Velocity.Z= RandRange(500,800); SetPhysics(PHYS_Falling);}
EzeUDam
Notice that the ClientToss is simulated where the Plan-Jane Toss is not.
By making the actual fling method simulated, it get replicated over the wire. I was not able to get this to run by simply calling the ClientToss from the Gametype.
EzeUDam
Which brings us to EzeUdam’s replication statement.
Not much, If I am the server, trust in the client toss function for replication.
reliable if(role==ROLE_AUTHORITY) ClientToss;
EzeUDam
And before we go any further with this class, we should have a look at the Defaults.
bStatic=falsebNoDelete=falsebHidden=false
No to surprising
EzeUDam
if true, only replicate actor if bNetDirty is true - useful if no C++ changed attributes (such as physics) bOnlyDirtyReplication only used with bAlwaysRelevant actors.
bOnlyDirtyReplication=falsebAlwaysRelevant = truebReplicateMovement=trueNetUpdateFrequency=100.000000NetPriority=2.000000bNetInitialRotation=True
EzeUDam
Still not any newness:
But do notice that I’m using DumbProxy.
If I were using some other replication property, the server would try to replicate its rotation as well…Very ugly.
RemoteRole=ROLE_DumbProxybUpdateSimulatedPosition=True
EzeUDam
And finally, we also update the gametype…if we are the server….from inside the touch function:
if(Role== ROLE_Authority){ EzeGame(level.Game).HasUDam=P; EzeGame(level.Game).PlayClientSeekSound();}
EzeUDam
Update the player who has the EzeUdam with whoever just picked it up.
if(Role== ROLE_Authority){ EzeGame(level.Game).HasUDam=P; EzeGame(level.Game).PlayClientSeekSound();}
EzeUDam
And have the server make some noise.
if(Role== ROLE_Authority){ EzeGame(level.Game).HasUDam=P; EzeGame(level.Game).PlayClientSeekSound();}
Gametype
Now, for the PlayClientSeekSound function I’m reaching into my bag of tricj on this one…
What I plan to do is play a sound on the client side only…excluding the player who has the EzeUdam.
Gametype
And because of where we are calling this from, we know that we are the server…
Hence, we have the gametype… Hence, we have access to all the players
in the game… Hence, we can manipulate any one (or all)
of them. Remember, the server makes the rules.
Gametype
So how am I going to pull this off you ask?
Well, being the server, we have the gametype, which has a list of all the controllers in play…
Just going to iterated over all those…
Gametype
simulated function PlayClientSeekSound(){ local controller c; snd=sound'XEffects.LightningSound';
For ( C=Level.ControllerList; C!=None; C=C.NextController ) { if(c.IsA('Playercontroller') && c.Pawn != HasUDam) Playercontroller(c).ClientPlaySound(snd); }}
ClientPlaySound
Which allows me to introduce a very interesting function. ClientPlaySound.
The function will only play sound on the client side…no other players will be able to hear it.
ClientPlaySound
Which, in of itself, is really cool…not to mention handy.
But even better, go over to playercontroller and look at how its implemented… Not how you might expect.
ClientPlaySoundsimulated function ClientPlaySound(sound ASound, optional bool
bVolumeControl, optional float inAtten, optional ESoundSlot slot )
{ local float atten;
atten = 0.9; if( bVolumeControl ) atten = FClamp(inAtten,0,1);
if ( ViewTarget != None )ViewTarget.PlaySound(ASound, slot, atten,,,,false);
}
ClientPlaySound
Now, most of this dosen’t rate to highly on the “YIPPEE” scale of things…except for the last line…
Where it’s using the viewport to play the sound.
ClientPlaySound
ViewTarget? What the heck is that?
<taps monitor> We are using the clients machine to play a
“none specific” sound to the client player. More exactly, we are playing the sound
through the clients player, kind of like the announcers voice.
ClientPlaySound
What this results in is a sound that only plays for that players who do not have the EzeUdam, giving a heads up that its been picked up.
As for the sound, well I found a really pleasing thunder clap effect .
Another effect…
And just to furthur make the player stand out, back in the ouch function of the EzeUdam class I added.
This is the derez effect material seen when you die…what it does for us:
p.OverlayMaterial=FinalBlend'DeRez.DeRezFinalBody';
And this should
Leave use some time today for poking at it and playing with the code…
Replication is a rather opaque thing, and the only way to get good at it, is to practice.