I don’t even know where to begin with this one.
Rabbit Hole 1
So last night I mentioned I got into a fight with ChatGPT for three hours and then eventually gave up on Reflection helping me auto-wire up some of my more annoying code… Well I got into another fight with it (and the code), but this time I stuck with it, and after another 3ish hours, I actually came out the other side with a working extension to automatically take any of my objects and transform them into the form they need to be in for Godot’s multiplayer and transform them back.
I also added a library and wrote some more extensions that automatically take any of my objects and do database transactions with them. The reason this matters is that last night I mentioned that any time I made a change to any of these objects, it required me to
- Add the property to the database
- Change the code that pulls the data from the database
- Change the code that transforms the database data into a readable format
- Change the serialization extension which allows the data to move from server to client
- Change the deserialization extension which allows the client to turn the data back into the format it wants
- Change the actual model so that it can utilize the data
After the 6 total hours I spent on wiring all this up, I’ve reduced this to
- Add the property to the database
- Change the code that pulls the data from the database
- Change the actual model so that it can utilize the data
Which is so much easier. It may have cost me 6 hours now, but will save me literally thousands of hours over the life of this game.
Rabbit Hole 2
So when I logged off last night, I was having some misgivings about stuff. My current goal was to facilitate sending character data back and forth between players so that when you, for example, right click on a player to inspect them, you can see some data about their character.
I was having a lot of trouble making that work in a way that made sense. I was slinging data back and forth like crazy and generally just felt like it was a bit of a hassle and not working properly.
And then, there was another issue…
When a player (client) connects to the server (server), they have several unique IDs given to them. When you log into an account, that account has a unique ID, and when you log into a character, that character has a unique ID. But also when you connect to the server, there’s a unique ID for the connection between the client and server. It literally tells the server where to send data.
By default, Godot’s multiplayer solution demands that you expose that ID to every other player. Any sufficiently advanced programmer could easily pull that ID out of the game and use it to pretend to be another player, pull data that doesn’t belong to them, or many other things ranging from merely mischievous to downright malicious.
So I decided I wanted to find another way. I don’t want to expose that ID. I want to use the Character’s ID instead – because that Character ID is public knowledge, anyone is allowed to see it. Not like the connection ID, which needs to stay secret. Needs to stay safe.
I did a ton of research … a ton of research. After about 2 hours what I walked away realizing was that Godot’s built-in multiplayer solution is ……. not for this. It’s genuinely just not made for the purposes I’m using it for. It’s good for prototyping, it’s good for very small games with very few players, and it’s good for games where you aren’t really concerned about cheating.
By default, Godot’s solutions (MultiplayerSynchronizer and MultiplayerSpawner) are peer-to-peer, meaning all of the players are connecting to one another and sharing data around. But I’m writing a game that’s client-server, meaning the players all connect to one server and only the server shares data back.
So I ripped out about half of my game’s multiplayer, discarded it, and rewrote it all from scratch using custom code with Rpc.
And honestly, I came away feeling really good about it. It was a huge undertaking, it took hours, but in the end, I have a lot more control over where data is going, where it’s coming from, who’s sending it, how much of it gets sent, and when it gets sent.
In fact, the rewrite gave me all of that character data I was complaining about not having available to me, just purely by accident.
Rabbit Hole 3
When prototyping a game, you often name things based on how you think you’ll use them, and then they evolve into something else over time, and the name stops making sense. Or sometimes you name them based on the implementation you start with, and then you change the implementation, and now the name is totally wrong.
Changing those names can often be really expensive in a game engine, because if I rename a property that’s set in a scene, I have to set that property all over again. Multiply by dozens of properties in dozens of scenes. Sometimes it breaks things in unexpected ways.
But it had to be done. I had to. Even if it was just to help me keep things straight.
When I started the game, I had a class called Player. Player was intended to be all the stuff about the person playing the game, from account level to character level, including stats, skills, display, all of it.
When I actually wrote Player, it ended up just being display. It’s just the sprites, the animations, and the movement. I ended up writing another class for stats and skills called Character. That split made sense because Character data needs to be sent around to other players, but Player was full of crap that no other players would ever need to see and should never be sent around. And then there was the Account class to hold things at an account level, which is mainly only ever sent to the person who’s logged in – because duh. Why would I send that around to everyone?
Also, when I started with all the multiplayer, I was using Godot’s naming. Everything was a Peer. But I’m using a Client-Server model, so I wanted things to be Clients and Servers.
Between the player/character stuff and the client/server stuff, that was almost all of the most important code in my system being renamed.
So yeah I went down that rabbit hole too. Now things have names that make a whole lot more sense.
In the End, It Doesn’t Even Matter
I mean, actually, objectively, it matters a lot. It feels weird that this is another day in a row that I’m ending the day right back where I started, and that all of the checklist items I thought were done are actually so far away from done. It’s not frustrating, exactly… It’s not disappointing… It was good work, it was necessary work. The bones of the thing are a lot stronger now than they were before. They would have collapsed eventually if not for this work.
But man, I really just want to move on to the actual game part.
What’s Next?
Speaking of moving on to the actual game part… I think we’re ready. The changes today were long, winding, and comprehensive, but they’re done.
The next things on my checklist are
- make a prototype UI for logging in – which includes actual character data coming back from the server instead of the hard-coded data I have right now
- make it so you can right click on another player, a list of things you can do with/to that player pop up, and then when you click “inspect”, it opens up a panel with some information about that character
It doesn’t sound like much, but it introduces the basic skeleton for a lot of cool work to come.