You know, every time I sit down to start on something with a particular goal in mind, I immediately find a thousand other pieces of the puzzle I have to put together before I can reach the finish line... it's not a bad thing, mind you, just an observation; there's a reason normal developers don't try to make multiplayer projects like this on their own.
...Good thing I'm not normal ;)
Why REST APIs?
So, as I said in my last DevLog, the goal this week was to get the networking "match flow" started. I want players to get together in a lobby, hosted by a dedicated server, get moved to a match, complete the match with some basic win condition (like get X number of kills, for example), and then be moved back into the lobby. Simple, right?
HAHA NOPE WRONG.
Well, I won't go into all the details of why that's wrong, but I will focus on one particular aspect which is what I've been working on for the past week: getting a player's chosen customization options transferred across levels. Now, if you're UE4 savvy, you're probably thinking, "uh, dude, chill out and just use the PlayerState. Obviously."
Well hold on there, dude. That's all well and good, and I'll definitely be using PlayerState, but I can't just store the saved data in a local save-game file. Think about it; if it's local, that means it's open to tampering. And since this is an online multiplayer game, that's a huge deal... players could potentially unlock things they haven't earned yet, they could add currency to their account (this is planned to be a free-to-play game), they could equip multiple of the same weapon or change out abilities they're not supposed to mid-match... it'd just be a mess, really... The solution is to make sure NONE of that happens on the local client. But how would you do that within the game engine?!
Well... you can't. Hence, REST APIs!
A REST API is a pretty standard and natural choice, especially given that UE4 comes with a module for sending HTTP requests. Just send the requests from the dedicated server, have the REST app handle those requests by querying or posting to a database, send the response back to UE4, replicate, and voila! We can save and load data to a client without their grubby little hacker hands touching anything!
REST API Implementation
Before I get into some of the specifics, feel free to check out the video below showing it working in real-time. It's pretty freaking cool that you can (reasonably) easily hook up UE4 to an external database like this; the example below is just my initial testing, but you can already see the potential here. I could track stuff like scores, match quality statistics, leaderboards... whatever I want! Maybe even push this data out to a website where people can log in and view it. Lots of possibilities here:
Ok, so now that you've seen it actually work, I'll write out a few bullets on what's happening under the hood:
--> When the player enters the game, the GameMode class (which exists only on the server) sends an HTTP request to the REST app via a function on the PlayerController who logged in, asking it to retrieve the data for this player's unique ID (this ID will be platform-specific, like a Steam ID, for example).
--> The REST app gets the request and uses the "node-couchdb" module to talk to a CouchDB database. It searches through the documents in that database using a custom "view" in order to pull out loadout information for the player.
--> The loadout information is sent back as a response to UE4 in JSON format.
--> The JSON response is parsed inside UE4 and turned into a USTRUCT that contains String variables. These variables are set on the PlayerState.
--> Once we have the data on the PlayerState, we no longer need to make any more HTTP requests unless the player updates their customization preferences. Anytime they move to a new level, we simply grab the data from the PlayerState (which is replicated by design), set the appropriate class variables on the Server, replicate some pointers to those classes so the client can use them, and we're good to go!
Also note that there could eventually be hundreds of customization options in the game as it progresses, and I needed some way of using Strings to get the right references out of a big list. The way I'm handling this is by using DataAssets that contain parallel arrays: One array contains the names of the classes as Strings, the other array contains the actual class reference. So all I need to do is loop through the "Name" array once, looking for matches to the Strings we got from the database, and then use the class reference at that same index if it matches.
The image below is an example of this from the actual project. I'm using Blueprints for this function because it's MUCH easier to reference the DataAsset and BP classes this way. Notice that there's no "Break" on the For Loop; I'm not using a break for the Weapon List because it won't get large enough to matter, but I am obviously using a break for larger lists:
I may end up using a different algorithm for searching later on, but for now there's really no significant performance hit just doing a linear search. Additionally, this only happens once, just before the mach starts.
Anyway, this is all pretty exciting for me. I have a method of saving and loading player data completely separate from the client machine, which opens up a lot of fun possibilities! And, more importantly, it allows me to continue working on my initial goal xD
As mentioned in the title of this article, I also did a bit of refactoring, so I'll briefly touch on that here. Basically, my old code was storing weapon and magic ability references on the player's Pawn. Not an ideal choice for this game in hindsight, but it's hard to foresee all these things when you're a solo developer.
For those unfamiliar with how UE4's class structure works, the reason this was a problem is because every time a player dies and respawns with a new Pawn, those class references are destroyed because the Pawn is no longer relevant. That's not ideal, because it means I have to keep setting those references on the Server and replicating them down to the client every time the player spawns.
A better solution, which is what my refactoring entailed, was to simply move that stuff to the PlayerController, which persists until we transition to a new level (i.e. until the end of the match). This way, we just have to loop through those DataAsset lists I mentioned in the last section once, set the references before the match starts, and then just keep using them until the player decides to change weapons after dying or something.
Bit of a technical and "wordy" post today; I should probably provide a heads-up that this will be the norm for a while. I had my fun making cool texture effects, now it's time to make this a functional game :P
There's a reason I'm focusing on logic before cosmetics for this project... I've seen way too many developers (myself included) get caught up on the visuals and completely forget to make a working game, so I'd rather not make this mistake again!
- Flash <3