Acaratus is an episodic turn based, tactical RPG for PC set in a medieval steampunk world.

Report RSS Devpost - Acaratus Network Implementation

Here comes a great indepth view of the networking that is still in its early stage but already provides enough functionality to play against each other with a client/service model and to get the general idea of how it will expand.

Posted by on

New Track

We want to start of with thanking Rafael Krux for another great piece in our growing soundtrack! Here is a new sample of the latest track we did for the map exploration menu:

Network Implementation

Overview and concept

Libraries used

  • c++ boost standalone ASIO Think-async.com
  • cURL for http and remote requests to database for example

The networking is still in its early stage but already provides enough functionality to play against each other with a client/service model and to get the general idea of how it will expand.
For starters the network has been added late but by using a component based object system is not that much of an issue if you did some planning for it, usually it is a big no no to not implement the network at the start.

Below I will describe what I have added to make a singleplayer/splitscreen turn based game playable over the network without making any changes to the actual game logic.

Here is the outline of how the relevant projects are related to each other and what additional parts have been added for the multiplayer functionality.

Client project (application)

  • Client Network Subsystem inherits from
    • Engine Network Subsystem
    • Client Interface (network lib)
  • Client Network Component inherits from
    • Engine Network Component

Server project (application)

  • Server Network Subsystem inherits from
    • Engine Network Subsystem
    • Server Interface (network lib)
  • Server Network Component inherits from
    • Engine Network Component

Network project (dll)

  • Implements Client Interface
  • Implements Server Interface

Engine project (lib)

  • Provides Engine Network Subsystem
  • Provides Engine Network Component

Since all of the gameplay is implemented with lua scripting I have decided to serialize/deserialize function calls and send over the network, this might sound dangerous but no code is executed and only string/numbers are sent with a lot of restrictions on field numbers and table depth. For those without lua knowledge here is a snippet of how the body of a message could look like before being serialized.

lua code:
t =
{
  self = component_network_id,
  method = "member_function_of_self",
  arguments =
  {
    65, true, "hello world", complex_object
  }
}

complex_object =
{
  id = object_id,
  ...
}
 

Now you might wonder what will happen to the “complex_object” that could have a lot more data within it. The custom serializer will check if the table has the correct metatable and that the id value matches a real object, if thats the case it will ask for a network id and just send that network id instead together with a special flag so it can be deserialized back to the object table at the other end.

The above will only work if the object actually has a network component and that the network component has been verified by the server, otherwise the table will be sent will all the keys and values but with a lot of restrictions on everything.

Synchronization of objects

So now that we can send messages to specific objects across the network we must find a way to create objects so they share the same network id on the server and client, this can be done by letting the server create the object and then send a create object event to all clients like this.

lua code:
object:add_component("network",
{
  sync =
  {
    create = true,
    destroy = true
  }
})
 

Now the object that we attached the component on will send a create message to all clients right away and then a destroy message when the component is removed from the object, this is all good until we get more complex object interaction and creation so I added a shared token method to synchronize objects.

lua code:
object:add_component("network",
{
  sync =
  {
    shared = "token that will be same on client and server",
    destroy = true
  }
})
 

This way you can create objects with the client that could maybe be synchronized by the server later if the server created a shared object with the same token.

Actual implementation and use

With all this backend technology we can now simply do something like this to transmit all order requests that a client performs on a unit.

lua code:
object:add_component("network",
{
  client =
  {
    order = WAIT_FOR_CONFIRM,
    select_skill = NOTIFY
  }
})

Here we added to functions “order” and “select_skill” these will now behave differently since they will be routed through a proxy function that will in the case of “order” just transmit to the server and when the server responds with an OK then we call the real function on the client along with any modifications made by the server.
The “select_skill” will send a notification to the server but at the same time just call the function as normal and does not wait or expects a response from the server.


This is just a basic example of the functionality and my implementation has more features and a lot more ways to deal with allowance and security, but this is more than enough to show the basic concept that adds easy online implementation for a non performance heavy network game.

If you aren’t tired of reading by now here is an example of a more complex synchronization example.

lua code:
controller =
{
  on_created = function(self, args)

    self:add_component("network",
    {
      sync = {create = true, destroy = true}
     
      ...
    }

    -- now we can add other objects that can be shared providing
    -- that this object looks the same on the client and server
    self.child = instance_create("child", {token = "unique_value"})
  end
}

child =
{
  on_created = function(self, args)

    self:add_component("network",
    {
      sync = {shared = args.token, destroy = true}
     
      ...
    }
  end
}
 

So in this case if the server creates the controller it will sync itself and then all the shared objects it created will be synced right after.

Hope you enjoyed the short example of how the major network structure has been implemented in Acaratus, I choose to go with ease of use instead of extreme control over what will be sent over the network and how well packaged it is.

Kristoffer - Lead Programmer

Dont forget that you can subscribe to the monthly newsletter (here) where you will get personal letters from the team members where they talk more open about their work on the project!


Post comment Comments
eliza_kocurowa
eliza_kocurowa - - 23 comments

likes!

Reply Good karma Bad karma+2 votes
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: