Flying Helmet Games playing a test build on their phones. |
The First Solution
The hardest part about reconnecting, of course, is what I hinted to in the first paragraph: ensuring your state on the controller is correct. The easiest way of doing this, without having to think of any special logic is to copy/paste the state from the main game onto the controller over the network. In programmer terms, serialize the state, and deserialize it on the other end. Given our time/budget constraints, we thought this was a pretty good idea.
However, once we implemented it, it turned out to be terrible in practice. The performance was abysmal, primarily because of how we organize our data on the main game. When we serialized the state, we also ended up picking up character models, animations, textures, and so on. And not just for your character, but for all player characters. We had reconnect times upwards of 8 minutes per controller when playing with 4 players. That was...not so hot.
We left the problem for a couple months, though, because we had more pressing needs in keeping the designers unblocked (which is another interesting discussion entirely). We'd lived that long without reconnect, what was a few more months?
Once we got back around to it, it was also clear from a design perspective that what we were doing wasn't going to hack it. Sending all of that data to the controllers was far too much on 512 MB devices. We were also using the same code to save our game to a file, and that was problematic due to the fact that patching the character or level data after the fact was pretty well impossible, not to mention save files bordering on 1GB+ of data.
The Final Solution
So serializing the state naively wasn't going to work, which meant that we were going to have to dig in and actually reason about what data we needed to send across (or save to disk!). Turns out it wasn't too bad at all.
Thanks to Unity's RPC framework guaranteeing delivery and order, it "simply" became a matter of writing a new network model on top of Unity's to decouple how they instantiated objects over a network, and then reinstantiate the player actor we needed on the controller. Since we knew what we started with baseline, it then became an exercise in determining how that state changed from the baseline. What abilities do you have unlocked? Gear and recipes? Treasure? Renown? Current attributes and status effects? Skills? Lore? Are we in the middle of a dialogue?
It was slightly more complex than that in the end, because we have a bunch of other synchronized tracking variables that had to be transferred as well, but most of our "state" in the previous paragraph are just prefab objects, so the majority of controller reconnect rehydration is telling the controller, "Hey, go instantiate this actor, and then go grab prefabs A, B, and C for lore, and unlock skills 1, 2, and 6. Also, update these synchronized variables." It's simple, and quite robust.
Unity Project Setup |
I say mostly because the Resources folder is special in Unity. We had to do some...shenanigans around Resources folders to get this system to work, and it's not quite perfect. but in terms of reconnect, it's great because when reconnecting, as long as the builds are compatible, I can be guaranteed that the correct data will be there on the controller. And if not, oh well. Think of it as the equivalent of updating tooltips in WoW. The backend server data might change, but the tooltips are local client data, and may get out of date. A patch will fix that right up later.
There are definite cons to this setup as well, like requiring patching the controllers more often, and the Resources folder shenanigans means sometimes extraneous data gets pulled into the controller build. But it's also super fast from a CPU time and networking perspective. We're not serializing tonnes of data to send over the (local) wire, so the reconnect now takes about 1 - 3 seconds rather than 8 minutes. Actually, the grand majority of the reconnect time is actually just loading the controller UI textures, the same as a normal load rather than anything reconnect specific.
Bottled Awesome
The best part about the latest version of reconnect? It's ultra solid. Our lead designer told me it was "bottled awesome". Pretty much any controller app bug we get can be worked around by killing the controller app and reconnecting, which has been an immense boon now that we're in Early Access.
We haven't seen many bugs come out of it either, which makes me pretty proud of the quality of the feature as a whole, too. The lack of bugs isn't for a lack of testing, either. We use reconnect all the time. I actually really regret not having a robust reconnect completed much earlier in the project. Mind you, part of why it's so robust is probably because it was done in a single 2-week period after most of the game was already complete. It's "easy" to build something like this and get it right in a single go rather than finish something nascent and update it as you add other systems.
We probably spent a good 5 - 6 weeks at a minimum between two senior engineers to get this feature correct. When we're talking about only having around 36 engineering months across the whole team, eating up 3% of total engineering project time on a single feature is pretty significant. Granted, it could have been a lot more, but when you look at the list of features as a whole, it's a bit scary how much of our schedule this took. We definitely underestimated as a team when we made the schedule.
Overall, this is a pretty interesting look at how many things are interconnected: reconnect and save rely on the same code; reconnect relies partly on how our builds are generated; reconnect and other systems such as dialogue, skills, powers, and so on rely on each other. Not to mention how the feature affects the technical requirements, such as build size, performance, and RAM usage. A bug in reconnect can cause display bugs on the controller, or bugs where the controller and the game get out of sync which would likely stall the game.
I'm glad it's done and it works like a charm, and it was a super interesting feature to work on. We've a few more rough edges to iron out around the reconnect experience, but otherwise, it's been great!
#EonAltar, #GameDevelopment, #IndieDev