We’re definitely not close to done……. (hold your horses)
Quick documentation of how we’ve made certain decisions to add this feature as quickly as possible.
Interleaving Multiplayer in Single Player
RunawayJRider is a highly procedural game, so how do we make something that’s uniquely generated at every play through into a “shared” experience?
Our first key decision was to go with interleaving multiplayer code into single player. Of course this is a recipe for a lot of headaches, except that here at Poez Inc. we take clean engineering very seriously. “Decoupled” is the name of the game, so when we’re adding such features as multiplayer it is akin to starting anew (almost at least).
In RunawayJRider, a lot of the content is procedural. From the stage layout, enemies spawning and difficulty gradient curves (these are blogged about in previous posts btw). To first contain the chaos, a lot of the generation is happening before a round starts. So the procedural creation does happen, but is done for the whole round. This allows us to cache and share it with the participating players before the round starts.
A lot of the “on the fly” generation, needed to be serialized.
Serialize All The Procedural(s)!
The game not only dynamically generates the levels, but also dynamically scales difficulty. For the first part, it is easily serialized as we’d just store all the stage layout up to a certain distance (say 100 meters or miles) and share that with all the participating players to instance. For the latter part though, we have a machine-learning component which profiles the player’s capability in order to reflect that in how the level is generated and at which difficulty enemies and obstacles operate.
When more than one player share a space, we also have to find a way to mold their expertise profile. The mathematical convolution idea comes to mind!
Blending Feature Spaces
Without getting into too much detail, think of a player’s profile to be a collection of numbers that describe the player’s stats. When we have multiple players we can convolve these stats
(each line is a different player profile)
or do some k-means clustering to have an “average profile”.
(the white ‘X’ would be potential centers to use in order to create our “session player profile” to feed to our procedural engine)
This average profile will then be fed to our procedural generation engine in order to spit out the multiplayer round stage layout. Of course, this average, need not be perfectly barycentric-centered as we can weigh/bias it to a certain player or group of players.
As you can see in the picture above, the space created between feature-vector-profiles of players, allow us a lot of wiggle room to create a rich environment that balances players skill and difficulty. The average player profile could be anywhere in that space. The exact position would be set by a feedback loop using the stickiness factor of human players (or retention in the online mode).
The weights in question are controlled by humans (assisted learning), although we could relatively easily slap more machine learning and use player’s stickiness factor to back-propagate the weights in order to increase player retention.
As you can see in the above “example graph”, we’d use the Daily Active Users per Monthly Active Users as a guiding variable to pick the ideal weighted average player profile when we blend or convolve them (see Convolved Feature Spaces above).
Blah! hopefully not too technical, although I try to condense things in order to share wisdom without unveiling too much of our secret sauces 😉