6. Launch Netcode Multiplayer Playmode (Write Better Netcode)


Launching a Netcode session in the Write Better Netcode project can be done in not one, not two, but three ways!

It supports launching via Multiplayer Playmode (MPPM) tags, via Command Line arguments (CmdArgs), and via interactive GUI (next article).

I implemented all three in about 6 hours while also fixing some bugs and adding the Components Registry.

I mention this because it came as a surprise to myself – I attribute this in part to the fact that I had code available to scrap – although I rewrote 90% of it. But also the interface to NetcodeState is so simple and effective.

Launch Netcode Via MPPM

Multiplayer Playmode (MPPM) allows you to connect up to three Virtual Players (VP) to the Main Editor Player for a total of four participants.

MPPM makes the often misspelled ParrelSync (not: “ParallelSync”) obsolete – except for those not yet using Unity 6.

MPPM Setup

In Project Settings you define Player Tags for use with MPPM:

Launch Netcode in 321
We’ll use these tags to specify each MPPM player’s Netcode role.

In the Multiplayer Play Mode window (spelled inconsistently) you can assign both tags and the so-called Multiplayer Role:

Editor is a Client, plus another Client VP and a Host VP

This is what it looks like in Play Mode, if you were to squeeze things together to fit it into a not-so-wide screenshot:

Logs confirm: both VP clients connected to Player 3 [Host]

Note that each window is actually a separate editor instance with the same project open. The VPs largely share the Library and Assets with the main editor, thus disk space usage of MPPM is less than that of ParrelSync.

Why Not Use Multiplayer Role?

Ah, yeah. So we’re adding Tags to these VPs in order to decide in code which multiplayer role the VPs assume but then there’s also a Multiplayer Role setting. Huh?

The Multiplayer Role (MPR) feature is from the Dedicated Server package. Its main purpose is to allow for code and asset stripping based on whether the build contains Client-only or Server-only code and assets, or both (no stripping).

Technically it would be possible to use MPR to make the role decision. However if MPR is set to “Client and Server” that doesn’t mean it’s going to be the host – the VP may also want to play as Client, or be the Server.

So it’s best to use Tags for greatest flexibility and to avoid confusion. It also lets you test the unsupported cases, such as trying to launch as Client in a Server-only build or vice versa – you may want to handle such cases with a user-facing error message.

This may seem trivial but consider that certain role-specific components may have been stripped from the build and thus calling into that code may throw an exception.

How Does MPPM Launch Work?

I added an MPPM Launcher game object to the scene with the MppmLauncher script on it, as well as a utility script DestroyInBuilds which does exactly what it says since MPPM is an editor-only feature.

MPPM is an editor-only feature, therefore DestroyInBuilds

The MppmLauncher script looks like this:

Where’s the #endif? At the very end of the script.

First, we try to get the network role from the VP player tags:

Just enumerating tags to see if it matches a NetcodeRole identifier

If we find a valid role, we start networking with that role:

Pass the DTOs (data transfer objects) and call RequestStartNetwork

The role is assigned to a NetcodeConfig instance which has the connection limit set to the number of VPs. Although strictly speaking you could have more connections since you can also connect builds to the editor or VPs.

For the TransportConfig it just uses whatever the NetworkManager Inspector settings are:

Roger. Copy that!

Note: I follow the standard C# pascal naming convention which dictates that there must not be consecutive uppercase letters: MPPM is written as Mppm, GUI is Gui, SQL is Sql, and so on.

RequestStartNetwork

The NetcodeState class now has this method:

This just logs the DTOs and then assigns them to the corresponding Statemachine variables. Where’s the magic happening?

In the Statemachine, of course!

IsNetcodeRole not equal to None then … oooh, start!

If you recall, IsNetcodeRole respectively its negated counterpart IsNotNetcodeRole checks the NetcodeConfig variable:

And off the Statemachine goes to start networking, with or without Relay. This now just happens automagically. It’s a solved problem!

MPPM, Relay, Latency And The Simulator

In the future I could allow enabling Relay for MPPM. I already had this working for MultiPal, primarily to test with additional latency.

This however requires providing clients automated access to the host’s Relay join code via the file system, or other means, which complicates matters for very little benefit.

If you want to test with latency, you can use the Network Simulator and set it to the Home DSL connection preset which is a good, but not great connection.

Respect the Network Simulatah!

I always have the Network Simulator active during development with a not-so-great connection preset to develop and test all features from the perspective of an average player!

The ideal scenario where there’s practically no latency for locally connected clients does NOT exist in the real world! Plenty of devs are running afoul of not considering this fact for far too long.

I learned that lesson in the 90s when we were developing GameBoy games on a PC emulator, playing with our keyboards. Writing an EEPROM cartridge took an hour, and we only had two. The pinpoint accuracy of a keyboard’s arrow keys is the exact opposite of the GameBoy’s wobbly directional pad. This only became obvious when we worked on our first twitch-action sports game.

The MppmLauncher could be expanded to support additional tags that adjust the latency for individual VPs. You could then force VP4 to play through a 2G connection to observe its teleportativeness.

What If I Want To Skip MPPM Launch?

Not every time you enter playmode would you want to auto-connect your VP players. Perhaps you want to test the GUI, then what?

Well, all it takes is to remove the Tags from the VPs and they’ll stop auto-launching the network session.

MPPM 1.3 introduces the Scenarios window where you can define and quickly switch between various layouts (roles, tags, and so on).

Launch Netcode Via CmdArgs

And now for something completely different: Command Line Args.

First ever recorded use of a Command Line Aaaaaaarghnnnnn.

I also added a CMD Launcher with a CmdLauncher script:

Behold the extremely complex CmdLauncher:

Start networking via CmdArgs

Nothing special here nor is StartNetworkWithRole:

It does push out reading in the command line arguments to the DTO objects. Let’s check TransportConfig as an example:

For Transport, it’s important to get the defaults first and then only override them with whatever was specified by the command line arguments since we may get only some of them specified. The existing values are passed as the default value (2nd parameter).

The RelayConfig contains no surprise either, minus the default values being, well, default:

Without arguments the default is to not use the Relay Service. Omitting MaxConnections will cause it to be 0 which is interpreted as 100 by TransportSetup which is Relay’s connection limit.

Did you notice the use of the nameof() expression?

This is not just for convenience (eg avoid typos). It also ensures that each command line parameter always matches the DTO’s field name. I can refactor-rename a field and the command line parameter will also be updated accordingly.

This is fine during development. Though after release, if users are actually supposed to use those parameters, you will want to be more careful in renaming them.

Parsing Command Line Arguments

The gist of parsing is in the static CmdArgs class which holds a dictionary of the arguments (keys) and their values (if any):

The Args are lazy-initialized. First time the Args property is accessed, the command line arguments are parsed once and stored in the s_Args field.

(click image to view the source code)

Above code is a bit technical so I’ll focus on the takeaways:

  • all command line arguments are case insensitive
  • all command line arguments start with a dash
  • each argument’s key is stored without the dash
  • each argument may have an optional value at args[i+1]
  • duplicate arguments are ignored and a warning is logged

There’s an extra method Log() not shown here which just dumps the command line arguments to the Player.log for .. debugging.

Getting an argument’s value is similar to PlayerPrefs/EditorPrefs:

Basic dictionary TryGetValue with a default value.

For the bool type I also call TryParse to see if its convertible and still use the default if it’s not. This requires you to type boolean values as true or false on the command line, which I find acceptable.

Mind The Culture-Gap!

Note that TryParse is not without gotchas.

Without specifying NumberStyles and CultureInfo any number may be treated differently based on the machine’s locale!

That’s why using the InvariantCulture is so important. This forces all users to use the US notation of -1,234,567.89 for floating point numbers. The same holds true for integer values.

Imagine the argument -someFloat 1,234.0 put into a batch file and shared with others around the globe. Some users may find that this batch isn’t working for them.

That’s why we always have to consider locale when reading input data from users. One of the most prominent examples of a file format that does awkwardly, unnecessarily read/write differently based on the user’s locale is … can you guess it?

Yes, CSV! It defaults to being either comma or semicolon delimited depending on the machine’s locale. You know it when you import a CSV and all data is crammed into the leftmost column.

Next …

Read on with 7. Launch Netcode GUI

Return to the Write Better Netcode Overview

Source Code on GitHub (GPL3 License)

Join my Patreon – it’s free! Get the latest updates by email.

Leave a comment below if you have any questions or feedback!

One response

  1. […] seen similar code in the last article. It creates an instance of each of the NetcodeConfig, TransportConfig and RelayConfig data objects […]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

WordPress Cookie Notice by Real Cookie Banner