State of the Game: Netplay is working.


Greetings fans and friends, today I've got a longform blog post about converting my Duelists of the Remakes tech demo from a single player-only experience into a rough multiplayer game.


Now I say ROUGH because a lot of things just straight up don't work. This is what the game looks like as of April 22nd, 2020.

 

(post processing is currently disabled)

Not much different, just a lot less UI. Most of that UI is currently broken as the core of the game is what the took biggest chunk of the rewrite.


Currently: you can move your deck leader & cards, you can flip and change position of your cards, you can summon cards, you can change turns. All of these things happen over the network and are synced between clients. When player 1 moves the cursor, it also moves on player 2's screen almost as if you're watching the AI move in the original game. Player 2 cannot move the cursor while it's player 1's turn.


I also plan on adding support for spectators. Spectators will only be able to watch the duel. In some games, they may chat alongside the other clients. Spectators will only be able to see face up cards, no hands.


I didn't originally plan to make Duelists of the Remakes netplay to begin with. I wanted just to create a feature accurate port of it and consider netplay later. However, after toying around with Mirror (the spiritual successor to Unity's UNET, but way better) in a dedicated project, the concepts began to solidify. I decided it would be better to revisit netplay while the game was still in a relatively early state. I'm sure glad I did.


I had a solid base already and an expectation of what the game should work like (both in-terms of the PS2 original and also where the remake was at up to this point). At the same time, I knew I needed to have a clean separation between server & client, yet I still wanted the client to be able to act a host so 2 Unity processes aren't required. Mirror has a lot of host functionality built in, but if you're not careful or don't understand what Mirror does and doesn't do for you, you can fall into traps where you can't figure out why things aren't working or why values aren't syncing.


Rearchitecting A Gem

Duelists of the Roses originally wasn't designed around netplay. And why would it be? With an initial release date of 2001, most people were still too excited over having their own personal DVD player to care about netplay. The original PS2 didn't even come with an ethernet jack, why design a game around something that may or may not be coming? (Spoiler alert, it did come. The PS2 had an ethernet upgrade that attached to the back of it. Later slimmer models included it to begin with)

So where do you start? I outlined how I wanted to rearchitect my scene. The first and easiest thing to do is to add the NetworkManager to the scene, but of course I implemented my own NetworkManager for some custom logic & handling along with a player list.

Anything that spawns or instantiates will only be visible client side. The first time you convert a project or even work with Mirror, you may find yourself debugging these cases where objects spawn client side but not server side. In addition to Instantiating and setting up the object, you need to tell the NetworkServer you're spawning an object. You also need to make sure the prefab has been registered with the NetworkManager. The server then handles letting the clients know what prefab spawned, where it is, and any extra data that gets serialized over the network. 

Client & Server: Joined at the hip

Mirror is very lenient when it comes to the host instance. For example, the host could have a fully populated scene with "functioning" UI & logic. Server is running, another client joins and he sees a totally different picture. Or maybe nothing at all. Objects can communicate like they usually do on a host instance, because it's just your game with the server tacked on and running. There's still work involved with decoupling client & UI logic from the bits that have to communicate with the server.

It's certainly not always easy either, I'm not saying my implementation or advice here is perfect but it's how I've been handling things during this conversion process.


Replace GameObject References with Net ID Instances

Most of the time when you want to command or otherwise control another object from one object, you'll either send signals directly to the object or communicate via other methods. The easiest way to do things in Unity is to directly reference the object in the scene (or ScriptableObject). 

That doesn't work over the Network.


Instead if you want to reference things in the scene, you should use a SyncVar to sync the netID of the object. Any object that exists on client & server (thus, existing over the network) will have a NetworkIdentity component attached to them along with a netID. The netID is unique per object, and inside of any NetworkBehaviour you can simply call `NetworkIdentity.spawned[idOfSpecificObject]` to get that GameObject. If it doesn't exist on the Network, you don't get anything! Simple as that.

Commands & RPC

I'm going to keep this short and simple.

If you want to communicate client -> server, you use a Command call.

If you want to communicate server -> all clients, you use a ClientRpc call.

If you want to communicate server -> client, specifically in response to a Command: use a TargetRpc call.


It's easy to have one NetworkBehaviour that serves Client & Server. Inside of that object, they can communicate relatively painlessly between Command and TargetRpc calls. Client calls a Command to do something, server does it. If client needs a response, use a TargetRpc to provide it. However, what do you do when something else needs a response from the server? 

C# events are an awesome way to communicate between a NetworkBehaviour and a regular MonoBehaviour. MonoBehaviours cannot receive TargetRpc events. However, if a MonoBehaviour references a NetworkObject and subscribes to a C# event on it, the TargetRpc call can be used to notify that object of the response and even pass along something beyond just a net ID or small serializable struct. 


Keep it simple, and know how to debug

I'm not going to try and tell you HOW to debug. Only recommend that you do lots of testing. Now, we're indie devs of course. If you have a dayjob where you setup unit tests all day and know how to properly decouple and write the code around (or dependency inject), you already know how to test and debug like a pro. I wish I could be at that level, but I'm not yet. 

I'm an indie dev, and I want to get shit done. I'm debug logging as much as I can and stepping through the logic small bits at a time. The decoupling of server & client in itself can be frustrating, but you can also write better code. By understanding the boundaries between client & server, even in a tightly coupled state like a Mirror host, is important and will speed up your development. 




If you stuck around this long, I appreciate it. Mirror is the NetCode backend I'm using for Duelists of the Remakes. It's free, I enjoy it, and it's got a great support Discord. I hope to have some more work done on it this week. By the end of the week, I'd like to post an update video to show you all what it's like. 

Get Yu-Gi-Oh Duelists of the Roses Remake (ALPHA)

Comments

Log in with itch.io to leave a comment.

Thank you so much for your work on this and I hope this finishes as an amazing multiplayer experience or an enhanced roses experience!

you mentioned discord is there a discord server for this project?

Thank you for your kind words! There is not a Discord server yet for this project, if there is enough interest I will make one.