Frenzies: Progression
- The background:
Frenzies is an online multiplayer shooter, and one of the things we realised we needed to have was a progression system, to drive retention and give goals for the players to work towards. Near Light has a custom backend which was being developed at the time, so my task was to work with the backend engineer to support the client/gameplay server side, as well as to tie into the gameplay/UI.
Here's a marketing TikTok about the feature:
- Key features of the progression system:
- Backend communication. The information about levels, xp and so on are passed to and from the backend using a JSON schema to define the messages. This schema needed to be implemented in C++ to handle the data so it can be converted to and from JSON. Conveniently, the Unreal FJsonObjectConverter class has functions for converting between JSON and UStructs, so what was required here was implementing the schema in the correct UStructs, simple enough. Slightly less simple was controlling when messages should be sent; specifically requests to the backend for updates. Some examples are ensuring things are requested at a correct point in the boot flow, and the server requesting rank updates after each round, so you can see other player's ranks change mid-match.
- Server authoritative. While our game never really experienced any cheating, a key principle of our development was to ensure things that game clients do not have control over things they shouldn't have control over. For example, a client should never be able to tell the backend that it has gained XP (the same goes for other stat updates). Instead, that should come from the gameplay server. There is one case in Frenzies where the player gains XP without being connected to the server, which is the tutorial, however that is also handled to make sure the player is not requesting XP. They tell the backend that they have completed the tutorial, and the backend gives them the XP for doing so. Once the backend has marked the tutorial as completed, it will not give out the rewards again, so clients still cannot cheat the system. (Also, that reminds me, I was the one who implemented the tutorial, so maybe I'll make a post about that at some point.)
- Use of callbacks. Key to creating a system like this is avoiding any expensive polling of responses or behaviour in a tick. Using delegates and callbacks prevents this, both in awaiting backend responses and in handling messages that could be received at any time, for example stats on the client that have received a backend update after the gameplay server updated them. Exactly when the client receives these can vary, so it was important to make sure the handling functions are robust and able to handle whenever this happens, even if it's say in a loading screen.
- Notifications. Handling things received at any time segues nicely into the notification requirements. One part of the progression behaviour is displaying pop-up messages to the player when they level up or complete a challenge. Players will complete challenges during gameplay, but obviously seeing a pop-up while you're in the middle of combat wouldn't be good. As such, I added behaviour to store the notifications in the player save. That way, they won't be lost should a player suddenly quit the game. I then added a system to decide when it is safe to show the notifications, based on the match state machine. When the player enters a safe state, the game will check for notifications, and show them to the user. When the user acknowledges a notification it will be cleared for the save, and the game will check for any more. I also used a flag to store this, so if a notification arrives while the player is already in a safe state, they can see it immediately. This system makes sure the player will never miss a notification, regardless of when they received it.
- Queueing stat updates for efficiency. A pretty obvious one here, but when you've got a fast paced shooter with twelve players in game at once, the players generate a lot of stats. We track things such as number of times dashed, reloads, movement distance, kills, downs, assists, objectives achieved and so on. If we sent a message to the backend every time a stat was updated, we would be wasting a lot of network bandwidth on the server, quite apart from probably freezing the backend itself with the sheer volume of messages it would have to process. Instead, we group the stats into a batch to send every second or so, making sure to accumulate updates per stat so we only need to send one new value for each stat.
The progression system was my first experience working with a live-ops backend, and it was really interesting working with the backend engineer to make sure we could give the players XP, levels, challenges, all that good stuff. It also gave me a good grounding in the logic for backend communications and made it much easier for me to use other aspects of the backend, for example for notifications and monetization on Hands.
Comments
Post a Comment