Making an entity multiplayer-ready

Exclusive offer: get 50% off this eBook here
Mastering CryENGINE

Mastering CryENGINE — Save 50%

Use CryENGINE at a professional level and master the engine's advanced features to build AAA quality games with this book and ebook

$29.99    $15.00
by Michelle K. Martin Sascha Gundlach | April 2014 | Games Open Source

In this article by Sascha Gundlach and Michelle K. Martin, authors of the book Mastering CryENGINE, you will learn how to make an entity ready for a multiplayer environment.

In order to make our entity work properly in a multiplayer environment, certain changes need to be made to our script.

Right now, we are not taking into account whether our entity is operating on a client or a server. Let's go ahead and get our entity network ready. For this, we need to make sure the server serializes the script entities properly.

(For more resources related to this topic, see here.)

Understanding the dataflow of Lua entities in a multiplayer environment

When using your own Lua entities in a multiplayer environment, you need to make sure everything your entity does on one of the clients is also triggered on all other clients. Let's take a light switch as an example. If one of the players turned on the light switch, the switch should also be flipped on all other clients.

Each client connected to the game has an instance of that light switch in their level. The CryENGINE network implementation already handles all the work involved in linking these individual instances together using network entity IDs. Each light switch can contact its own instances on all connected clients and call its functions over the network. All you need to do is use the functionality that is already there.

One way of implementing the light switch functionality is to turn on the switch in the entity as soon as the OnUsed() event is triggered and then send a message to all other clients in the network to also turn on their lights. This might work for something as simple as a switch, but can soon get messy when the entity becomes more complex. Ping times and message orders can lead to inconsistencies if two players try to flip the light switch at the same time.

The representation of the process would look like the following diagram:

Not so good – the light switch entity could trigger its own switch on all network instances of itself

Doing it this way, with the clients notifying each other, can cause many problems. In a more stable solution, these kinds of events are usually run through the server. The server entity—let's call it the master entity—determines the state of the entities across the network at all times and distributes the entities throughout the network.

This could be visualized as shown in the following diagram:

Better – the light switch entity calls the server that will distribute the event to all clients

In the light switch scenario mentioned earlier, the light switch entity would send an event to the server light switch entity first. Then, the server entity would call each light switch entity, including the original sender, to turn on their lights.

It is important to understand that the entity that received the event originally does nothing else but inform the server about the event. The actual light is not turned on until the server calls back to all entities with the request to do so.

The aforementioned dataflow works in single player as well, as CryENGINE will just pretend that the local machine is both the client and the server. This way, you will not have to make adjustments or add extra code to your entity to check whether it is single player or multiplayer.

In a multiplayer environment with a server and multiple clients, it is important to set the script up so that it acts properly and the correct functions are called on either the client or the server.

The first step to achieve this is to add a client and server table to the entity script using the following code:

Client = {}, Server = {},

With this addition, our script table looks like the following code snippet:

Testy = {Properties={ fileModel = "",Physics = { bRigidBody=1, = 1, Density = -1, Mass = -1, }, Client = {}, Server = {}, Editor={ Icon="User.bmp", }, }

Now, we can go ahead and modify the functions so that they work properly in multiplayer. We do this by adding the Client and Server subtables to our script. This way, the network system will be able to identify the Client/Server functions on the entity.

The Client/Server functions

The Client/Server functions are defined within your entity script by using the respective subtables that we previously defined in the entity table. Let's update our script and add a simple function that outputs a debug text into the console on each client.

In order for everything to work properly, we first need to update our OnInit() function and make sure it gets called on the server properly. Simply add a server subtable to the function so that it looks like the following code snippet:

functionTesty.Server:OnInit() self:OnReset(); end;

This way, our OnReset() function will still be called properly. Now, we can add a new function that outputs a debug text for us. Let's keep it simple and just make it output a console log using the CryENGINE Log function, as shown in the following code snippet:

functionTesty.Client:PrintLogOutput(text) Log(text); end;

This function will simply print some text into the CryENGINE console. Of course, you can add more sophisticated code at this point to be executed on the client. Please also note the Client subtable in the function definition that tells the engine that this is a client function.

In the next step, we have to add a way to trigger this function so that we can test the behavior properly. There are many ways of doing this, but to keep things simple, we will simply use the OnHit() callback function that will be automatically triggered when the entity is hit by something; for example, a bullet. This way, we can test our script easily by just shooting at our entity.

The OnHit() callback function is quite simple. All it needs to do in our case is to call our PrintLogOutput function, or rather request the server to call it. For this purpose, we add another function to be called on the server that calls our PrintLogOutput() function.

Again, please note that we are using the Client subtable of the entity to catch the hit that happens on the client. Our two new functions should look as shown in the following code snippet:

functionTesty.Client:OnHit(user) self.server:SvRequestLogOutput("My Text!"); end functionTesty.Server:SvRequestLogOutput(text) self.allClients:PrintLogOutput(text); end

We now have two new functions: one is a client function calling a server function and the other one is a server function calling the actual function on all the clients.

The Remote Method Invocation definitions

As a last step, before we are finished, we need to expose our entity and its functions to the network. We can do this by adding a table within the root of our entity script that defines the necessary Remote Method Invocation (RMI). The Net.Expose table will expose our entity and its functions to the network so that they can be called remotely, as shown in the following code snippet:

Net.Expose { Class = Testy, ClientMethods = { PrintLogOutput = { RELIABLE_UNORDERED, POST_ATTACH, STRING }, }, ServerMethods = { SvRequestLogOutput = { RELIABLE_UNORDERED, POST_ATTACH, STRING}, }, ServerProperties = { }, };

Each RMI is defined by providing a function name, a set of RMI flags, and additional parameters. The first RMI flag is an order flag and defines the order of the network packets. You can choose between the following options:

  • UNRELIABLE_ORDERED
  • RELIABLE_ORDERED
  • RELIABLE_UNORDERED

These flags tell the engine whether the order of the packets is important or not. The attachment flag will define at what time the RMI is attached during the serialization process of the network. This parameter can be either of the following flags:

  • PREATTACH: This flag attaches the RMI before game data serialization.
  • POSTATTACH: This flag attaches the RMI after game data serialization.
  • NOATTACH: This flag is used when it is not important if the RMI is attached before or after the game data serialization.
  • FAST: This flag performs an immediate transfer of the RMI without waiting for a frame update. This flag is very CPU intensive and should be avoided if possible.

The Net.Expose table we just added defines which functions will be exposed on the client and the server and will give us access to the following three subtables:

  • allClients
  • otherClients
  • server

With these functions, we can now call functions either on the server or the clients. You can use the allClients subtable to call a function on all clients or the otherClients subtable to call it on all clients except the own client.

At this point, the entity table of our script should look as follows:

Testy = { Properties={ fileModel = "", Physics = { bRigidBody=1, bRigidBodyActive = 1, Density = -1, Mass = -1, }, Client = {}, Server = {}, Editor={ Icon="User.bmp", ShowBounds = 1, }, } Net.Expose { Class = Testy, ClientMethods = { PrintLogOutput = { RELIABLE_UNORDERED, POST_ATTACH, STRING }, }, ServerMethods = { SvRequestLogOutput = { RELIABLE_UNORDERED, POST_ATTACH, STRING}, }, ServerProperties = { }, };

This defines our entity and its network exposure. With our latest updates, the rest of our script with all its functions should look as follows:

functionTesty.Server:OnInit() self:OnReset(); end; functionTesty:OnReset() local props=self.Properties; if(not EmptyString(props.fileModel))then self:LoadObject(0,props.fileModel); end; EntityCommon.PhysicalizeRigid(self,0,props.Physics,0); self:DrawSlot(0, 1); end; functionTesty:OnPropertyChange() self:OnReset(); end; functionTesty.Client:PrintLogOutput(text) Log(text); end; functionTesty.Client:OnHit(user) self.server:SvRequestLogOutput("My Text!"); end functionTesty.Server:SvRequestLogOutput(text) self.allClients:PrintLogOutput(text); end

With these functions added to our entity, everything should be ready to go and you can test the behavior in game mode. When the entity is being shot at, the OnHit() function will request the log output to be printed from the server. The server calls the actual function on all clients.

Summary

In this article we learned about making our entity ready for a multiplayer environment by understanding the dataflow of Lua entities, understanding the Client/Server functions, and by exposing our entities to the network using the Remote Method Invocation definitions.

Resources for Article:


Further resources on this subject:


Mastering CryENGINE Use CryENGINE at a professional level and master the engine's advanced features to build AAA quality games with this book and ebook
Published: April 2014
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

About the Author :


Michelle K. Martin

Michelle K. Martin is a software engineer in the game industry, specializing in animation systems. She started her career with the German developer, Crytek, working on projects such as Crysis and Crysis 2. During her career, Michelle has helped develop and improve CryENGINE's animation system with several features. Being an expert in CryENGINE, she has provided a lot of support and training to CryENGINE licensees over the years, helping their team to get the most out of the engine.

In 2013, she founded MetalPop Games together with her partner and Crytek veteran Sascha Gundlach. It is an indie game development studio and they are currently working on their first title.

When she's not in front of the computer programming, she is most likely to be in front of the computer playing games.

More about Sascha and Michelle's company MetalPop Games can be found at www.metalpopgames.com.

Sascha Gundlach

Sascha Gundlach has been working in the games industry for over a decade and started his career as a script programmer in a small game studio in the early 2000s. He worked for Crytek for eight years, working on games such as Crysis, Crysis: Warhead, and Crysis 2.

He is a CryENGINE expert and has provided countless training sessions and individual training to CryENGINE licensees in the past years.

In 2013, he founded his own game development company, MetalPop Games, together with his partner and Crytek veteran Michelle K. Martin in Orlando, Florida.

He spends his days working on video game projects and provides consulting work for other game projects.

Books From Packt


Google SketchUp for Game Design: Beginner's Guide
Google SketchUp for Game Design: Beginner's Guide

CryENGINE 3 Game Development: Beginner's Guide
CryENGINE 3 Game Development: Beginner's Guide

CryENGINE Game Programming with C++, C#, and Lua
CryENGINE Game Programming with C++, C#, and Lua

CryENGINE 3 Cookbook
CryENGINE 3 Cookbook

Slick2D Game Development
Slick2D Game Development

 Unity 4.x Game Development by Example: Beginner's Guide
Unity 4.x Game Development by Example: Beginner's Guide

Irrlicht 1.7 Realtime 3D Engine Beginner's Guide
Irrlicht 1.7 Realtime 3D Engine Beginner's Guide

3D Game Development with Microsoft Silverlight 3: Beginner's Guide
3D Game Development with Microsoft Silverlight 3: Beginner's Guide


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software