JAN. 13 – JAN. 23
This was a long week (10 days apparently!). On our weekly Monday meeting, Jan. 16, we decided on a milestone for me. Until the next meeting, I would make the game “playable”. That meant the multiplayer synching had to work. I needed to implement UI, and update it. Playing cards should only be possible if they can legally be played. Turn order had to be implemented, and the different phases of the turn be programmed. Cards you play should have an impact on the player’s resources.
I was confident to reach all of these goals. Maybe I was too confident.
I was already neck-deep into the multiplayer aspect before the meeting, and since the game is pointless without it, I kept working at it before anything else. At this point let me explain the term Equivision.
In our game, akin to the way Hearthstone did it, each player sees the game from the same camera.
The player is at the bottom of the screen, the opponent at the top. On one hand, the players see something different – they don’t look at the same table from opposing ends like you would in a physical game. You can see this fact in the shape of the board, it is not symmetrical. On the other hand, the players have the same point of view. There is one camera in the unity scene and both players are looking through the camera. However they see their own resources and Board-Cards on their side of the field and the opponent’s on their side. This is what I refer to as Equivision.
Part of the Equivision aspect -code wise- is that I refer to the two players as “host” or “guest”. The cardgame is always one-on-one so I can make use of that. Every action that a player takes tells the server the either “host” or “guest” as an argument, and the game handles it based on that.
Implementing Equivision, along with the other multiplayer aspects, took a lot of time from me. I had never made a multiplayer game before, not even a local one, so I came into this project with a lot of things to learn. I definitely underestimated this aspect of the programming!
To best explain how I implemented Equivision, and other multiplayer aspects, I should explain the main components of it. The most important Scripts for this are ServerAuthority, ClientController and PlayerController as well as ResourceContainer.
ServerAuthority is the script that handles the turn-order. In its Update function, it rotates through the two players’ phases and sets relevant variables. It is server-only and therefore does not exist on the client, which is why I needed this many scripts to communicate between the hosting and the remote client. Surely there had to be a better way to solve this, but I had already given up on writing pretty code. The ServerAuthority script does hold each player’s deck, but it does not hold their resources (Profit&Budget&Public Opinion), nor does it hold the “currentPhase” variable. The reason for this is the aforementioned, that the guest client cannot access the ServerAuthority script to ask for the values. These are instead held by the ResourceContainer.
ResourceContainer is exactly what the name implies – a container. It contains only variables and Set&Get methods, it does not execute any actions on its own.
ClientController contains all of the [ClientRpc] functions. ClientRpc functions are called from the host client and executed on both clients. I use this for everything part of the Equivision. In fact only for that. It updates the UI to display the current resources, it assigns newly drawn cards to the players’ hands, and it places cards on the board on the correct positions.
PlayerController is attached to the Player Prefab. It is therefore set to local player authority, which means that only the person who owns this player can call functions in this script. PlayerController contains all [Command] functions. Command functions are the counterpart to the ClientRpc functions, they are called by a client, and executed only on the host client. The Script handles most of the gamelogic, those parts which require the player to take action. It contains the functions that check whether a card can be played, and resolves playing them. It also contains the function that reacts to the player pressing the “next phase” button.
At the same time, I was working on the UI. I plonked the mockup assets I took from Andreas’ files on a Canvas and set it to scale with screensize. There is really not much else to say about it, but I didn’t have the final assets yet, so I had to come back to it later anyways.
The worst part of week 7 was attempting to make an automatic matchmaking scene. The Unity-provided UI for this were absolutely disgusting OnGUI generated buttons and I wanted the matchmaking to happen automatically anyways, instead of asking the player to click through a bunch of menus. Here is an impression of the work I put into it.
And after a total of around 10 hours, I had no working result. I hoped to get back to it in the next week, but I doubted I could spend even more time on it. After around 9 hours I was at the point where it worked as I wanted, but only if the hosting client is the Unity editor. I decided to stop trying to fix it about an hour later, and re-implemented the ugly Unity UI.
The worst thing about it is that the UI is pixelsize, and does not scale with screensize. This means that on a modern phone, you literally could not tap the buttons without equipping finger hands.
Funny Programming Fails
Number 2 will blow your mind!!
This is how tired me thinks encapsulation works:
I DO have a tendency for long names:
This is how I comment my code after a long session:
When I try to code, IF is written in capitals:
Ups and downs
While fiddling with the “Check if you can play the card” implementation I finally learned how to use delegates! I had seen them before, and they appeared to be extremely useful, but they also seemed very complicated at the time. However thanks to this video, I understood them within 10 minutes. And my mind was blown. Thankfully, someone re-enacted what was happening during that moment, so I can share it with you here.
But then, disaster struck. Delegates could not be used with networking Commands or ClientRpcs.
Ultimately, I had to funnel it through 4 different scripts (sound familiar) instead. But in the end it worked out, so all was well!
All of the multiplayer stuff I needed was done. The UI was rigged up and synched. Cards could only be played if there were free slots for them, and if the player had the resources, and the resources were affected by the cards. All I had left to do was implement the turn order. It was already past midnight of monday, my deadline, and I my code started to look bad.
I wrote code for about 90 minutes straight without compiling and testing inbetween and in what seems like nothing short of a miracle to me, the code worked as intended, instantly! (I forgot to assign a reference but that doesn’t count!)
I made builds for PC and Android and loaded them on the testing devices and checked if they worked. Then I collapsed into bed – tired, but satisfied.
I had reached all of my critical goals, and did some extra bits, but unfortunately not as much as I had hoped to, due to the matchmaking fiasko.
The fruits of my hard work were the reactions of my team members. They had fun playing around with the bugs I had left in, enough that we made one of them a feature! During the meeting I found that there was yet a bit more for me to do than I was aware of before, but I was still confident that I could implement a lot of features. The main workload now was implementing each and every card effect, and I hoped I could implement at least most of them.