Lady Tallowmere has begun expanding her empire.
Networking with TNet
25 April 2016 • Chris McFarland
Below is a video of me stress-testing 20 mobs on 3 instances of Tallowmere 2, with TNet's message server being hosted on a server in California.
What needs to be synced?
My creatures share a common base Creature class. Creatures are either controlled by a Player or a CreatureAI module. In a sense, the creature is just a dumb host, and is brought to life by a parasite, a controller, eg either the Player or the AI.
After three rewrites of how to best code the network functionality for both Players and AI, I've devised to have Creatures use the following info/states for the needs of my two-dimensional world:
Creatures are then manipulated by setting the bools, states, and position and velocity data.
I've largely settled for using TNet's DataNode class to transmit this info.
DataNodes act like a generic XML or JSON type of Dictionary, with strings for keys for any type of data I wish to include. This means I don't have to write methods with super long parameters; just a single DataNode parameter is sufficient in most cases. This means I can include multiple sets of info into a single message, which I believe feels pretty efficient, and also makes my life easier as I write the code.
Rough overview of how this all works:
TNet's default code for TNManager.Instantiate had the newly-instantiated object be active from the start, but I modified the code to ensure it's disabled to start with, as I need the latest Creature data to be synced and parsed before enabling the Creature.
With this connection flow, when any new player joins the game, they are able to have all Players and Creatures be instantiated and synced, which is awesome.
As I develop this further, I plan to use object pooling instead of destroying Creatures when they die. So it's possible that a Creature might actually be dead, so a new player would receive data to create a Creature but wouldn't need to be activated.
Syncing Creature states
When it comes to telling a Creature to do something, I have my Player's Update method listen for input, and then sets the bools and states on its Creature as required.
Or if the Creature has its AI module enabled, its AI behaviour is processed during FixedUpdate, which again sets bools and states as required. AI behaviour is only handled by the host; it merely sets the values and sends the values to the other clients to interpret.
When it comes to juggling a Creature's movement state, facing east state, start-attacking state, shield state, start-jumping state, position, and velocity... creating methods for each of these things sounded like a lot of work.
Instead, I devised a simple SendDataToOthers method that gathers up the Creature's info, creates a DataNode, and sends the DataNode along. SendDataToOthers has no parameters. And while it's assembling the DataNode to create, if the previous DataNode it sent along contains the same state for something, it doesn't get included. For usage, I just set the bools and states and position as required before calling SendDataToOthers(), and then it does its thing. By not having any parameters, it feels very clean and simple to call and transmit what's needed.
The real beauty with this simple state manipulation is that I don't have to have to change code for both my Player and AI classes when it comes to making the Creature do something new.
Each Creature has a ProcessUpdate method. Players set bools and states when needed, and then calls its Creature's ProcessUpdate. AI does the same.
ProcessUpdate checks each state and reacts accordingly.
Is this the most efficient way of doing things? Maybe, or maybe not. But it works for me. Perhaps the actions for wanting to jump or start attacking could be put into their own remote function calls without using a DataNode, but the message has to be transmitted one way or another regardless.
Network bandwidth is minimal when there's not too much happening, but it currently shoots up between 20-30 KB/s during really extreme situations with lots of Creatures doing lots of things.
In comparison, Counter-Strike: Global Offensive uses roughly 40 KB/s for about 12 players, and this is the highest I've seen a game use, so I'm using 40 KB/s as my peak limit to try and ensure I stay under.
Instant feedback vs delayed feedback
First-person shooters and third-person action games tend to let the client move around immediately. The client then sends its movement commands to the server, and the server sends the commands to everyone else. This allows the local player to move instantly on their screen, which feels nice and responsive.
In contrast, with real-time strategy games and most online fighting/platforming games, movement is delayed, as you're forced wait for the server to receive your command to move, and then you move when the command is sent back to you.
For a platformer, if you're playing over the Internet with a non-instant ping, this means you're constantly having to fight the lag. It never feels smooth because there's always a delay.
So what I'm trying with Tallowmere 2 is to have your local movement be instant, giving the client the benefit of the doubt.
When it comes to attacking and using items though, I have two options (using an axe as an example):
But what if you had some grenades equipped instead? Using option 1, would your hands go through the animation of throwing, but you wouldn't actually throw a grenade until the server told you to instantiate a grenade and throw it? Or would you use option 2, where you'd only carry out the throwing animation once the server said so? I'm leaning towards the latter.
And what about raising your shield? Should the client do this instantly and have the final say if you've gotten hit or not? Or should it be delayed and let the server figure it out? I will need to experiment.
Ultimately, I will side with whatever feels most fun, responsive, and practical. You can never have perfect 1:1 syncing across the Internet, so it's a matter of getting it good enough. Still, the act of playing long-distance in real-time with other players is part of the magic with online multiplayer, so I'm sure it will feel fun in any case!
Tallowmere 2 © Chris McFarland 2016