Ironing Out Netplay Bugs


Now that I feel the UI is at a solid point, I've moved back over to what I've been sketched out the most by: the netplay conversion of the game I created last year. 


When I started this crazy idea, I didn't even think it was going to work. And when it all worked better than I expected, i felt more than confident enough to actually make this conversion work. Well, I grossly underestimated the work it would take. Though it's a simple game, networking introduces way too many challenges, like state syncing. The Duelists of the Roses remake project currently uses Mirror as its networking stack. I chose Mirror because it was popular and recommended, but the problem is that documentation is actually somewhat poor. 


Not to mention if you have any problems with this library, because the keywords are so similar to Unity's old HLAPI/LLAPI, you end up getting people asking the SAME question but for an outdated tech stack. Most of this has been figuring out and testing on my own! 


The current state of the game is interesting because some days, I can play a few games against myself over a local network without any major bugs. But then, there are days like yesterday where the entire game is buggy and nobody is quite sure why because it worked last time I played it a few months ago!


Some of the most major bugs I recently fixed

1. Card Faces/Holo/Card Pos (ATK/DEF) were Synced via SyncVars, but were NOT following the recommended guidelines for SyncVar ease of use. These guidelines didn't exist when I started the conversion and provided solid examples of improper code. Sure enough, my code base had it and tweaking my code to follow recommended guidelines made this feature more stable. 

2. Player Hand/Card Drawing is buggy. It still kind of is, but it's much better than what it was before yesterday. Before yesterday, Player 2 definitely could not draw any cards and there were numerous syncing issues between the local state & network stored state.

At first, I thought I could sort this out with SyncLists but those are even MORE of a problem than what I was trying to do. For example, I would sync the Player's last drawn cards (size of 5) and then get 6 messages in the callback: 1 clear, and 5 adds. 

Then, I thought I would be clever and make the hand sending a two stage thing:

1. P1 looks at their last drawn cards and counts how many cards they need. For example, P1 needs 5 cards.

2. P1 tells the server (DORGameManager) that I need 5 cards from my deck. 

3. Server draws 5 cards from the deck and assigns the Player's "LastDrawnCards" (SyncList) to what they are. 

4. Server tells the Player, "Hey, you need to wait for 5 changes on your SyncList before you show the drawing animation"

5. Player says "OK server, I'm going to be waiting for 5 changes to my SyncList over the network"

6. As Player starts receiving items on the SyncList callback, it tallies up the changes and when the changes match the amount of cards we need, then we show the screen. 

Welp, this actually didn't work either. 


There were race conditions and many reasons why this didn't work. For example, the server tells Player 2 to wait for 5 cards and the event is called 3-4 times. Then, by the time the data is actually sent the Player forgets they were even waiting for it. 

There's also occasions where there would be tragic mismatches that would cause exceptions.


I ripped most of that code out and opted for a much simpler approach. The player still stores their _LastDrawnHand but it's no longer a sync list. The server (DORGameManager) also stores the last state. Before the player asks the server for cards, it tells the deck screen what our last drawn hand is so it knows what the hand looked like before we asked to draw. That way, the animation is proper by the time the cards come in from the server. I'm manually syncing this data via Commands and TargetRPC calls. So far, it's working fairly well but I do need to test it more. 


Surprisingly, the system that I'm most proud and that has worked most consistently is my Animation Syncing system. It's surprisingly simple and has proven to be indisposable.

All of the "field animations" are actually defined in static IEnumerators. These IEnumerators get passed the arguments for their animations. They don't know what cards are passed into it, they just take the arguments, store their initial positions and rotations, and perform the animation. 

Clients request for the server to play animations. This ensures that the animations are synced across clients. For example, P1 wants to attack a card that P2 has. The server looks at this situation and decides what animation to play. When it does, it tells the "SyncedAnimationControl" script what animation to sync via enum flags. This allows us to mix and match animations as we need to. The other argument is the amount of clients that we're syncing the animation with. This is usually 2, but it could be more as I'm still planning on supporting spectators. 

When the clients get the notification that we have new animation flags, the animations are played individually and locally. We tell Mirror to take a back seat for a second while both local clients manipulate the cards for their animations. When the animations are done, each client tells the server that we're done with the animation. When all clients have finished the animation, the server tells the clients to show their UI again and allow movement. 

This system actually inspired my (non working) idea for syncing last drawn cards. It just worked MUCH better in this scenario. 


I am still  delaying the implementation of the remainder of the effects. I believe this will be easier than trying to perfect the net code, so I would rather get net code close to stable before I start mass testing effects that may or may not work on the network. 


I do wish Unity had better networking options....but it's definitely too late to decide on something else. 


Until next log......

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

Leave a comment

Log in with itch.io to leave a comment.