Sunday, February 19, 2012

Replication of Variables in UDK

I will try to make this as brief as possible. If you're interested in something with a little more depth, I strongly recommend you read through Replicating a Flashlight's State; it isn't very long either, and probably makes my post obsolete, but I felt like presenting my understanding on the matter.


What is replication for?
Replication is used in a network game when you want to carry data from one client to other clients. This has a very wide range of uses, the simplest of which is merely telling all other clients that something has changed in the scene, and that they must all display that change. In the above tutorial's example, it is that a flashlight has been toggled on or off.

In order to properly understand replication, you must first understand that every client has their own view of the world. The only "true" world is the one defined by the server, but in theory, each client could be seeing entirely different things. Of course, we strive as developers to synchronize all representations of this world to what is defined on the server. This synchronization is done through the replication of variables.

How do we tell UDK that a variable is to be replicated?
Easily. This can be done in any class that extends Actor. There are two main steps.

First of all, we must define the variable that is to be replicated. This can be a single value (in the tutorial above, they use a boolean, which is likely the simplest implementation) or a struct (which can be used to group together several related values in a very tidy manner). We define it in a specific manner using the repnotify keyword.
var repnotify bool myReplicatedVariable;
UDK attempts to replicate all the values it is told to at the end of every tick. We don't necessarily want to replicate every replicatable value every tick, so we tell UDK which variables to send in the replication block of the code. Here we can use simple if statements to state whether or not a variable is to be included in the replication. One handy boolean is bNetDirty. This boolean is set to true if ever a variable in the class is set to a value. It is then set to false when the variable is replicated.
replication {
    if (bNetDirty)
        repAbility;
}
How do we get the variable to replicate?
An important detail here is that replication can only occur from the server - since the server is the only node that can communicate with all clients. A client can, however, instigate this process on its own. Let us suppose that we have a function whose purpose is to change the value of myReplicatedVariable.
simulated function Toggle() {
    myReplicatedVariable = !myReplicatedVariable;
}
 This function is to be called on the client, initially. What we want, however, is for the server to be updated with the change, and then to replicate the change on all clients. To do this, we write another function.
reliable server function ServerToggle() {
    Toggle();
}
 Note that we've used the keywords "reliable server". This means that the function is run on the server - even if we were to call ServerToggle() from a client. The function itself cannot run on a client. So, that is exactly what we do - in our Toggle function, we have the client tell the server that it too must call the Toggle function.
simulated function Toggle() {
    myReplicatedVariable = !myReplicatedVariable;
    if (Role < ROLE_Authority) {
        ServerToggle();
    }
}
You can see that I wrote a bit more code - the Role < ROLE_Authority bit ensures that we do not end up with any infinite loops. ROLE_Authority is only given to the server, so only a client (whose Role is less than that of the server) may enter the if statement and tell the server to make the call. If the server could enter the if statement, it would enter a loop of it repeatedly telling itself to call the function that tells it to call the function.

Based on the code we have now, the variable will be replicated and sent to all clients whenever it is updated on any one client. But we're not done yet.

So a variable has been replicated - but what do we do with it?
Any class extended from Actor also has an event called ReplicatedEvent, which takes in the name of the variable that has been replicated as a parameter. With this, our class is told exactly when a variable has been replicated, and which variable it is that we are dealing with.
simulated event ReplicatedEvent(name VarName) {
    if (VarName == "myReplicatedVariable") {
        if (myReplicatedVariable) {
            //the replicated variable is set to true, do something
        } else {
            //the replicated variable is set to false, do something else
        }
    }
}
Example of its use
While learning this all of six hours ago, I was using replication to play an animation on a pawn on all clients. While all AnimTree animation is handled for you, there are situations where you may wish to play an animation directly from your unrealscript. In order to play this on all clients, you must use replication.

I handled this by replicating a struct variable. This struct contained references to which animation it was I wished to play, and which pawn I wished to play it on. Each client recognizes a reference to a pawn. What I mean by this is that if Player1 sends a reference to its pawn via replication to all other clients, Player 3 will be able to look at the reference and understand which pawn it is, in the context of its own scene. I tried something similar with one of my own classes, but for some reason it kept being read by other clients as "None" (a null value). It's possible I was doing something wrong, but as far as I can tell, it seems like Pawn is a special case.

So once all the replication was done, each client received information regarding which animation to play, as well as a reference to the pawn that is to be animated. The rest is fairly simple, and is just a matter of telling the pawn to play that animation.

I ended up going longer than I wanted to on this topic, but I hope I was able to explain the concept in a manner that was concise enough. I myself am only just picking up these concepts, and trying to share what I've learned so far - if I have made any mistakes, please let me know.

2 comments:

  1. You seem to know a lot. Would you help me and my team please?

    We're trying to get a simple 3rd person melee custom game to work online using the server you create through the command prompt. Here's a short video of me explaining the problem we're having:

    http://www.youtube.com/watch?v=b-11LdzqPqw

    The basic issue it seems, is that the server isn't replicating certain player related Kismet events when I create a server and join the game. I'm trying to play on the same machine that the server is being created on. Would this be an issue?

    Thank you,

    -David Breen

    ReplyDelete
    Replies
    1. I'll apologize right off the bat - it's been over a year and a half since I last touched UDK, and when I did I barely touched Kismet (I was generally more comfortable writing code than setting things up visually).

      I do have a hunch, however, that the cause of the problem is that your level loaded node is not firing when the player joins the server - only when the server is first started. If LevelLoaded doesn't fire, your objects won't get attached, and everything will break.

      Funnily enough, I did a quick google search and ended up turning up my own damn thread (http://forums.epicgames.com/threads/892382-Kismet-Event-for-player-joining-server) on a similar topic that received no replies. I believe I ended up solving the problem using unrealscript instead of kismet, but I can't be sure.

      Try posting your questions to http://reddit.com/r/udk - that's where I got the most help when I was working on my senior project.

      Sorry for being mostly useless, and best of luck with solving this problem.

      Delete