Latest  | Search | Go
Edit this page   |   Attach file 

  Home | Tutorials | Technical Reference | Runtime | API Documentation | NetworkingSystem  

Reality Networking System Overview

Author:Jeremy Stieglitz

What is the Reality Networking System?

Reality's networking system is a Client-Server architecture based on two methods of sending abstract data: NetworkActors (a networked derivation of Actor) and MNetworkActors write their own data to be synchronized to the Clients (per-Client supported), and Clients send their Input to the Server. This approach keeps the Server authoritative about the World state. The Client does interpolate and extrapolate from the NetworkActor physics data sent to it by the Server, but that is simply to improve the smoothness and real-time accuracy of the Client-side visual presentation of the data. As far as the Server is concerned, the Client is just an Input-sending dumb terminal; it is up to the Server to decide what to do with that Input (comprised of mouse yaw, mouse pitch, and key presses). Usually the Server uses that Input to control a NetworkActor.

What does the Server do?

First off, it is important to understand that the only things which the Server can abstractly send via the ScriptingSystem is messages written by MNetworkActors and chat. This ensures that the network game is structured around NetworkActors (and their managed script version, MNetworkActors), which have a common set of functions to write data for network sending. Other than the NetworkActors, what you can send is targeted and global chat messages. So, the network game as it's programmed in the EvalKit is very much oriented around the synchronization of NetworkActors, even when non-physical data such as a Team Score needs synchronization it is contained in a non-physical NetworkActor called GameState (in GameState.cs).

On the Server, connected Clients are represented as objects called NetworkClients, in the ScriptingSystem they are called MNetworkClients. Each NetworkClient gets a dedicated message buffer for each NetworkActor. This ensures that while most NetworkActor messages are written to all NetworkClients, you also have the capability to override that behavior for targeted messages or send extra targeted messages should you so desire. This allows, among other things, per-NetworkClient adaptive degradation of network physics data, wherein NetworkClients who are farther away from a NetworkActor get less frequent physics updates than those which are close.

It's worth nothing, however, that innately NetworkClients are not Actors and have no physical representation. They don't have a "Location" in the World themselves, but they do have an "ActorAvatar" pointer which the Server can set to allow a NetworkClient to associate itself with an Actor in the World, as an avatar of physical representation. For instance, in the game sample, when a NetworkClient spawns as a Player that Player sets itself as the ActorAvatar for the NetworkClient, and thereafter the NetworkClient's ActorAvatar's Location is checked when doing the distance calculations of adaptive degradation. ActorAvatars are also useful for deleting the Actor when a NetworkClient leaves the game, in the LogicCore's function for this purpose (for instance, GameCore.NetworkClientLeft(MNetworkClient) deletes the Player ActorAvatar associated with the leaving NetworkClient).

So apart from chat, what the Server sends to the clients consists of the NetworkActor data buffers for each NetworkClient. There are 3 types of data buffers that each NetworkActor can write to: Reliable, Unreliable, and Spawn. Let's examine the usage of these buffers one by one:

  • Reliable: These are guaranteed to be delivered, with high priority, in order. Consequently they are the most demanding on the network to deliver, and should only be used for critical changes in state such as boarding a vehicle, taking damage, or picking up a weapon, rather than rapid-fire physics data.

  • Unreliable: These are not guaranteed to be delivered, nor is their deliverly order guaranteed, but when they are delivered only the most recently sent packet is run. (i.e. the client will never process them in reverse order). They should be used for things that will not affect the critical client-side World state if dropped, such as rapid-fire physics data.

  • Spawn: This buffer has the same reliable functionality as the Reliable buffers but when received by the Client, it results in special behavior: the client creates the NetworkActor of the type which wrote the Spawn message. Thus you should only write to this buffer when the NetworkActor first spawns, to send initial state data, or when a NetworkClient joins a game in progress (in which case you write to that NetworkClients' specific Spawn buffer for the NetworkActor), to synchronize him up to current state. When a NetworkActor is first created on the Server, or a new NetworkClient joins the game in progress, a Spawn message will be generated even if you don't write anything to this buffer, in order to ensure client-side spawning of the NetworkActor. However you do have an opportunity to write to the Spawn buffers in the appropriate events (which I'll cover below), but those events are the only time you should write to the Spawn buffer. In all other cases, you'll want to use Reliable or Unreliable.

Now that you know what the 3 NetworkActor message buffers do, let's examine how the Server can write to them in order to send NetworkActor messages to the NetworkClients.

First, let's see how to write to the Reliable buffer to send critical state messages, in this case a message setting a new Health value for a Destroyable NetworkActor:

   /// <summary>
   /// Sets the Destroyable's current Health
   /// (if decreasing Health on the Server, use Server_TakeDamage instead for damage logic)
   /// </summary>
   /// <param name="health"></param>
   public virtual void SetHealth(int health) 
   {
      if (health == Health)
         return;

      int OldHealth = Health;
      Health = health;

      if (Health < OldHealth)
         DoDamageFX(OldHealth - Health);
      else if(Health > OldHealth)
         DoHealingFX(Health - OldHealth);

      if (Health > MaxHealth)
         Health = MaxHealth;

      if (MyWorld.IsServer)
      {
         // THESE LINES WRITE TO THE RELIABLE MESSAGE BUFFER FOR ALL NETWORKCLIENTS

         WriteMessageType(MSGID_DESTROYABLE_SET_HEALTH, PACKET_RELIABLE);
         WriteInt(Health, PACKET_RELIABLE);
      }
   }

Look at the bottom of the function where it says "if (MyWorld.IsServer)" -- we use that statement because this SetHealth function may be called both on the Client and on the Server, but we only want to write the network messages when this function is called on the Server. The next two lines of code write to the Reliable message buffers of every NetworkClient in the game; that is, everyone connected will get these messages. There is a method to write to the message buffers of a specific NetworkClient, which we'll examine later. But since we consider it fine that everyone gets this Health update message, we'll write it for everyone. So, what those two lines do: the first line writes a MessageType value to the buffer, which you'll always want to do at the beginning of each "data set" you send (consider a "data set" to be a case where the client will know exactly what data to read following the MessageType ID). The second line simply writes the Health value to the Reliable buffer as an integer. That's all we need to do to ensure that the NetworkClients will reliably receive this message. We'll look at how the Client will process this message in the following section.

Now that we've seen how to write to the Reliable message buffers, let's look at how to do write to the Unreliable buffers. It's a similar operation with one important difference:

   // Generates Server state update messages to send to a particular NetworkClient in the session
   public override void Server_MakeTickMessages(MNetworkActorPackets packet)
   {
      if (Occupied())
      {
         // THESE LINES WRITE TO A SPECIFIC UNRELIABLE MESSAGE BUFFER PASSED TO US FROM A SPECIFIC NETWORKCLIENT

         WriteMessageType(packet.GetMessagePacket(PACKET_UNRELIABLE), MSGID_VEHICLE_SEAT_YAWPITCH);
         WriteChar(packet.GetMessagePacket(PACKET_UNRELIABLE), SeatIndex);
         WriteFloat(packet.GetMessagePacket(PACKET_UNRELIABLE), curRotX);
         WriteFloat(packet.GetMessagePacket(PACKET_UNRELIABLE), curRotY);
      }
   }

Here we're in a very different function. Whereas SetHealth() is called as a distinct event whenever the Destroyable's Health changes, Server_MakeTickMessages is called at regular intervals when the NetworkActor is due to update its physics synchronization, perhaps 15 times per second on average (depends on the adaptive degradation of a particular NetworkClient with respect to this NetworkActor, and the Server's own specified rate, as well as the NetworkClient's requested rate). The Server_MakeTickMessages function will actually pass a MNetworkActorPackets for a particular NetworkClient, so here you are writing to the specific buffer for this NetworkActor of a distinct NetworkClient. Namely, what you write using the above functions will not be sent to everyone, but rather just one particular NetworkClient --- however Server_MakeTickMessages is called eventually for every NetworkClient in the game (albeit potentially at different rates), so everyone does get the data they need. This "blaster" approach works well for Unreliable data, which is sent so rapidly and Client-side is used with interpolation and prediction. So let's examine what the code starting with WriteMessageType does.

We're familiar with WriteMessageType, but note the packet.GetMessagePacket(PACKET_UNRELIABLE) statement. That gets the unreliable buffer from the MNetworkActorPackets object we've been passed, since MNetworkActorPackets actually contains all 3 buffers (Reliable, Unreliable, and Spawn) itself. But we want to write specifically to the Unreliable buffer of this MNetworkActorPackets object, so we get that using the GetMessagePacket function, passing it the MessagePacket Type we want to get (Unreliable). Then we write our value in the same essential manner as with Reliable messages. We do the same for the following float values, and then we're done with the Unreliable writing.

So I'll reiterate what the difference between the Reliable and Unreliable are: as described above, Reliable messages are guaranteed ordered delivery. Unreliable messages are not guaranteed delivery, but are always sequenced, outdated packets will not be processed by the Client if a newer packet has already been processed. Furthermore, in the above code snippets, for Reliable packets we wrote to all NetworkClients (though you can write to a specific NetworkClient's buffer with Reliable packets too, it's not usually necessary), whereas for Unreliable packets in the Server_MakeTickMessages function we wrote to a specific NetworkClients' Unreliable buffer that we were passed, since the network update rate may be different for each NetworkClient.

There's just one more type of buffer to examine now, the Spawn buffer. The Spawn buffer has two uses, sending initialization network data when a NetworkActor first spawns ("OnSpawn"), and synchronizing the entire state (that which you want sent over the network, at least) of the NetworkActor when a Client joins a game in progress ("OnJoin"). Let's look at each of these two uses individually. First, using the Spawn buffer to send an Actor's initialization data when the NetworkActor is first spawned on the Server ("OnSpawn"):

        // Send initial spawn data to all connected NetworkClients
   public override void Server_MakeSpawnMessages()
   {
      WriteMessageType(MSGID_NETWORKACTOR_VELOCITY, PACKET_SPAWN);
      WriteVector(Velocity, PACKET_SPAWN);

      WriteMessageType(MSGID_NETWORKACTOR_LOCATION, PACKET_SPAWN);
      WriteVector(Location, PACKET_SPAWN);

      if (QuadDamage)
         WriteMessageType(MSGID_GRENADE_QUADDAMAGE_ON, PACKET_SPAWN);
   }

When a NetworkActor first spawns, we may want to send some initial data about that object's starting state. The Server_MakeSpawnMessages function is called after the first Tick of that NetworkActor, to allow it to collect initial state after the constructor Server-side. So in the Server_MakeSpawnMessages function, we write information about this state that will be sent to and processed by all the connected NetworkClients when they first spawn this NetworkActor on their machines. As you can see, the functions are very similar to writing to the Reliable buffer, the only difference is that instead of the PACKET_RELIABLE enum we write to PACKET_SPAWN. We'll examine how the Clients process these Spawn messages in the next section.

The other use for the NetworkClients' Spawn buffers is when a new NetworkClient joins a game in progress, the entire state of the World needs to be sent to him so that he's all up to date. We use that newly-joining NetworkClient's own Spawn buffers for each NetworkActor to write its complete state for that NetworkClient to process as a Spawn message. In this way, the newly-joining NetworkClient can get the extended information it needs to get its World state up to date. Let's look at how the GameState NetworkActor generates its "OnJoin" Spawn messages in this manner, to send all the current Team Scores of the GameState to a newly joining NetworkClient:

   /// <summary>
   /// Server generates SPAWN messages for a specific MNetworkClient who has 
   /// joined after this Actor has already been spawned (should include any unique state at the time of joining 
   /// that the Client should know about, and physics data).
   /// When the Client receives these messages, they're processed in Client_HandleSpawnMessage() 
   /// immediately after constructing the NetworkActor
   /// </summary>
   public override MNetworkActorPackets Server_MakeOnJoinSynchMessages(MNetworkClient client)
   {
      MNetworkActorPackets packet = client.GetOrCreateNetworkActorPackets(this);

      for (int i = 0; i < NumTeams; i++)
      {
         // Write Team Score
         WriteMessageType(packet.GetMessagePacket(PACKET_SPAWN), MSGID_GAMESTATE_TEAM_SCORE);
         WriteInt(packet.GetMessagePacket(PACKET_SPAWN), i);
         WriteInt(packet.GetMessagePacket(PACKET_SPAWN), TeamInfos[i].Score);
      }

      return packet;
   }

The Server_MakeOnJoinSynchMessages is called whenever a new NetworkClient joins a game in progress. It's purpose is to generate the synchronization messages necessary for the newly joining Client to get the full network state of the NetworkActor in question. These "OnJoin" messages are written into the Spawn buffer for this newly joining Client, and the MNetworkActorPackets object containing the buffer is returned so that the Server which calls this function can immediately send it out. Note that the statement "MNetworkActorPackets packet = client.GetOrCreateNetworkActorPackets(this);" is a way for the NetworkActor to Get its NetworkActorPackets for a particular NetworkClient -- so given a NetworkClient, you can get the buffers associated with it. In this manner, you can do targeted writes per NetworkClient, allowing you to customize who gets what data for performance or security reasons. This technique can be used with Reliable or Unreliable messages to. Beyond that, writing these "OnJoin" Spawn messages is not much different from the "OnSpawn" Spawn messages -- the sole difference is conceptual, that you want to send the entire network state necessary for a newly joining Client, which in the case of this GameState NetworkActor consists of all the team scores.

Thus you now know the different aspects of functionality and usage of the 3 Server-sent message buffer types. With Reliable messages you have the ability for guaranteed delivery of ordered messages, useful for critical events & discrete state changes. With Unreliable messages you have the ability for rapid-fire non-guaranteed (but sequenced) messages that come at minimal bandwidth cost, useful for updating physics and continuous state changes. And with Spawn messages, you have guaranteed delivery of ordered messages but used specifically for "OnSpawn" initial state sending and "OnJoin" full state sending, since the client will process Spawn messages in a special function that is different from Reliable and Unreliable messages. We've explored the ways in which the Server can write NetworkActor's data to NetworkClients, which is the primary way for the Server to communicate game information with clients. Now let's examine how the Client actually handles these NetworkActor messages, and sends its own input to the Server for processing!

What does the Client do?

The Client-side Networking system consists of two basic tasks: processing the NetworkActor messages received from the Server, and sending Input to the Server. First let's examine how the Client processes the NetworkActor messages.

It's important to understand that the Client processes Spawn (PACKET_SPAWN) Messages in a different function from "Tick" messages (comprising both PACKET_RELIABLE and PACKET_UNRELIABLE). The reason for this is because Spawn messages may be optimally handled in a different manner than Tick messages, for instance not needing to interpolate or predict the data in a one-off Spawn message, not giving visual or audio feedback for an initial Spawn value (with, say, low Health), or simply reading a different set of data than with a Tick message of the same MessageType ID. For these reasons, Spawn messages are handled in Client_HandleSpawnMessages, while Tick messages are handled in Client_HandleTickMessage.

Let's examine Client_HandleTickMessage first. You will find that, aside from a few differences relating to interpolation and prediction, Client_HandleSpawnMessage is nearly identical (though Client_HandleSpawnMessage may handle fewer MessageTypes than Client_HandleTickMessage, knowing that perhaps not all MessageTypes will actually be sent as PACKET_SPAWN). So here's an example of a Client_HandleTickMessage, which processes PACKET_RELIABLE and PACKET_UNRELIABLE messages:

   /// <summary>
   /// Client-side processes a state UPDATE message
   /// </summary>
   /// <param name="message"></param>
   /// <param name="packetBuffer"></param>
   public override void Client_HandleTickMessage(byte message, MReadPacketBuffer packetBuffer)
   {
      switch (message)
      {
         // read current health value
         case MSGID_DESTROYABLE_SET_HEALTH:
            SetHealth(ReadInt(packetBuffer));
            break;
    case MSGID_NETWORKACTOR_LOCATION: 
            //pass the location to the networkactor's prediction function, with interpolation                          
            Client_ReadLocation(packetBuffer, true, .14f);
       break;
         default:
            base.Client_HandleTickMessage(message, packetBuffer);
            break;
      }
   }

This function handles the Reliable and Unreliable messages (but now Spawn messages) for a particular class of NetworkActor (here it's Destroyable). It uses a switch statement for all the MessageTypes unique to this class of NetworkActor, then passes unhandled messages to the base class which will in turn process its own MessageTypes. As written earlier, on the Server the SetHealth function writes to the Reliable buffer the MSGID_DESTROYABLE_SET_HEALTH MessageType and a Health Integer. The Client, as you can see, simply Reads that Health Integer in the switch for the MSGID_DESTROYABLE_SET_HEALTH MesageType. In this way, the Client is reading what the Server wrote; that's the central idea, writing to the buffer on the Server and then reading from the message buffers on the Client.

Note that Client_HandleTickMessage is used for both Reliable and Unreliable messages -- functionally in script there's no difference between the two, only difference is the transmission method used to deliver those packets on a low-level in the engine. Note that as we proceed further into NetworkActors' base classes, more MessageTypes would be handled until eventually in MNetworkActor itself the core Physics MessageTypes are handled. Derived MNetworkActors can override the default handling of these MessageTypes simply by including them in their own switch statements in Client_HandleTickMessage and/or Client_HandleSpawnMessage!

Now that we've handled the Reliable and Unreliable messages in Client_HandleTickMessage, let's look at how Spawn messages are processed (either "OnSpawn" or "OnJoin") in Client_HandleSpawnMessage:

   /// <summary>
   /// Client-side processes a state SPAWN message
   /// </summary>
   /// <param name="message"></param>
   /// <param name="packetBuffer"></param>
   public override void Client_HandleSpawnMessage(byte message, MReadPacketBuffer packetBuffer)
   {
      switch (message)
      {
         //read current health value
         case MSGID_DESTROYABLE_SET_HEALTH:
            SetHealth(ReadInt(packetBuffer));
            break;
    case MSGID_NETWORKACTOR_LOCATION: 
            //pass the location to the networkactor's prediction function, with no interpolation                          
            Client_ReadLocation(packetBuffer, false, 0);
       break;
         default:
            base.Client_HandleSpawnMessage(message, packetBuffer);
            break;
      }
   }

As you can see, it's nearly identical to the Client_HandleTickMessage. The only key difference is that it passes a false to the Client_ReadLocation function to indicate no interpolation, since it's a spawn message you'll want the Actor immediately set to that Location. That's usually the extent of the difference between Client_HandleTickMessage and Client_HandleSpawnMessage, but the different function does afford you the ability to differentiate the processing to whatever degree.

That comprises the extent of what the Client needs to do in order to read the NetworkActor messages written to it by the Server. It's up to you to the Server to determine how often the Client gets physics update messages, and what state is synchronized. Be sure that whatever data you write on the Server, you read on the Client, and that you read the appropriate MessageTypes in Client_HandleTickMessage and/or Client_HandleSpawnMessage depending on which buffer (Reliable, Unreliable, Spawn) that the Server writes them to.

The Client also has the ability to send Input to the Server. That's done with the following functions:

// if not the Server, send mouse state to the Server
if (!MyWorld.IsServer)
   MInput.Client_SendMouseUpdate(this);

// send single control press event to the Server
if (GameInput.ControlJustPressed(GameInput.KEY_RELOAD))
   GameInput.SendControlPress(this, GameInput.KEY_RELOAD, true);

// send movement key states (held, unheld) to the Server
GameInput.SendControlState(this, GameInput.KEY_WALK_FORWARD);

The first function sends the current Mouse Yaw and Pitch to the Server. The second function sends a single control press to the Server, when you don't care about held-unheld it's more efficient to just send the "press." The third function sends a "held-unheld" state to the Server, it will filter out redundant state and only send an update for held-unheld when the value changes.

These input messages will then be processed by the Server dependent on the corresponding NetworkClient's ActorAvatar on the Server, with the following functions:

/// <summary>
/// Handles the server-side effect of a change in key pressing state, 
/// such as Activating when the Activate network handle ID is received and the received "IsDown" is true.
/// </summary>
public override void Server_HandleNetworkKeyInput(bool isDown, int NetworkKeyHandle)
{
// reload weapon
if (NetworkKeyHandle == GameInput.KEY_RELOAD.NetworkHandleID && isDown)
{
   if (CurrentWeapon != null)
         CurrentWeapon.Reload();
   return;
}
}

/// <summary>
/// Handles a network mouse update that the Server has received from a NetworkClient for which this player is the avatar. 
/// This function calculates the Player's current Rotation from the received Yaw/Pitch values.
/// </summary>
public override void Server_HandleNetworkMouseUpdate(float mouseYaw, float mousePitch)
{
   // set the player's Rotation to correspond to the new yaw and pitch
   Rotation = MMatrix.LookTowards(MVector.MakeDirection(mouseYaw,mousePitch,0));
}

For instance, if you set a Player as the NetworkClient's ActorAvatar on the Server, when Input is received from that NetworkClient, that Player will get those functions called on it with the received information. If no ActorAvatar is set, Input events from that NetworkClient will be ignored on the Server.

Now you know how to process Tick and Spawn mesages on the Client, as well as send Input from the Client & handle it on the Server. This approach can form the basis for a networked action game, as in the Eval Kit Game Demo. Next let's examine some of the more additional features of the networking system, such as Server-side Adaptive Degradation.

Custom Adaptive Degradation for Server-Side Network Update Optimization

Adaptive Degradation is the dynamic adjustment of the rate at which the Server sends reptitious, continuous updates (primarily Physics) to NetworkClients, based on logic pertaining to that NetworkClient within the World. Commonly this is just a distance-based calculation: if the NetworkClient has an ActorAvatar such as a Player, then the farther away that the NetworkClient's ActorAvatar is from the NetworkActor sending updates, less updates per second (for the particular NetworkActor) shall be sent to that NetworkClient. That, at least, is the default behavior for NetworkActors.

You can, however, provide your own custom Adaptive Degradation logic by setting "Server_UseAdaptiveDegradationCallback = true" in your MNetworkActor's ctor. Then you should override the "Server_AdaptiveDegradation" and calculate the Adaptive Degradation Factor (% of default send rate, with 0 resulting in no sends) based on your desired logic. I'll illustrate this with the Vehicle's Server_AdaptiveDegradation function, which increases the send rate considerably when you're actually riding in the vehicle to provide for the most detailed motion in that case:

   public override float Server_AdaptiveDegradation(MNetworkClient client)
   {
      float AdaptiveDegradationFactor = 1;

      if (client.IsServer)
         return AdaptiveDegradationFactor;

      MActor avatar = client.ActorAvatar;
      if(avatar != null)
      {
         if (IsPassenger(avatar))
            AdaptiveDegradationFactor = 1.75f;
         else
         {
            float distance = (Location - avatar.Location).Length();
            if (distance < 20)
               AdaptiveDegradationFactor = 1.0f;
            else if (distance < 50)
               AdaptiveDegradationFactor = .9f;
            else if (distance < 100)
               AdaptiveDegradationFactor = .72f;
            else if (distance < 160)
               AdaptiveDegradationFactor = .4f;
            else
               AdaptiveDegradationFactor = .2f;
         }
      }

      return AdaptiveDegradationFactor;
   }

All this function does is, firstly, return 1.0 if you are the Server since then you don't need a high send rate (you may wonder why the Server has any send rate at all... it does for Recording Network Games as you will see in the next section). Then, if you're riding in the Vehicle, it returns a factor of 1.75f to ensure you get a heightened update rate for extra detailed motion, important to driving a Vehicle. Finally, if you're not riding in the Vehicle, it has ranges of distance for which different update rates are applied. Sometimes it's useful to use a custom Adaptive Degradation function just to change these update rate values, so that you can have whatever fall-off you want for Actors of different update rates (linear, exponential, or custom-step like this). In any case, using a custom Adaptive Degradation function for key MNetworkActors is an advanced technique that will allow you to get maximum visible performance from the networking system at minimal bandwidth cost -- after all, if an object is 300 meters away from a Player, the Player certainly doesn't need to see it updated as often as if it was 3 meters away.

Recording Network Games

The concept behind recording games is actually quite simple: as a Server Reality stores all the network packets it sent to everyone, or as a Client Reality stores all the packets it received, into a file with time-stamps. Then upon playback, Reality processes those packets stored in the file, at the appropriate times, as if the game was running as a networked client.

What this means however is that only objects which are networked become "saved" in the Recording --- non-networked objects like (in the Eval Kit) TimedLights will still exist, but they won't be "recorded" per-se, nor do they need to be since their appearance is totally determined by the time of day (the SkyController Actor, which is networked). So for your own recordings, you should take care that any object which you want to have 100% reproducable behavior in the recording is either reliably dependent on a NetworkedActor (such as the TimedLights), or is itself networked (such as the SkyController). This approach will ensure that the scene you witness while recording is faithfully reproduced every time during playback.

If you are recording as a Server (the preferred method), Reality writes 30 World updates per second into the recorded file, giving you very detailed and smooth movement of NetworkActors during playback. If you are recording as a Client, whatever update rate you received during gameplay will be recorded, and adaptive degradation may also affect the quality of the recording.

To record a game, enable RecordGame = true in the INI or Game Settings/Networking menu. A .rec file with the map name (numerically incremented if a file with that name is already present) will be generated in your "Recordings" directory. Upon ending the recording (loading a new map or shutting down the application), and restarting the application, you will see the "FileName (Game Recording)" listed in the New Game menu, which you can load like a map.

To record Spectator-camera transformations, enable RecordSpectator = true in the INI when also RecordGame = true, and a new .rec file will be generated (numerically incremented), with the GameState outputting its Spectator transformations to the recorded file. This allows you to create a "cinematic" second pass from a "raw footage" recorded file, and can be used to generate real-time cutscenes with cuts, time alteration, and precision camera movement (the controls for which are described at RealityBinaries) -- it will all be recorded at 30 FPS.

Voice Chat

Coming Soon!

NetworkingSystem   Edit | Attach | Ref-By | Printable | Diffs | r1.6 | > | r1.5 | > | r1.4 | More