As production of "Pavel Piezo - Trip to the Kite Festival" draws to a close later this year I reviewed the material I collected for the Postmortem and found it too much and too diverse to put in one huge article. So, I identified the topics that can stand very well on their own, which was not limited to this specific game or production, and decided to write three short(er) posts in advance to the Postmortem.
- "Not so random randomness" in game design and programming
- Using a "Leitner system" to track a players exposition to content and mechanics
- Postmortem: "Pavel Piezo - Trip to the Kite Festival", a game for learning languages
This bit of information was first posted on Gamasutra as a short comment on Lucky Breaks. I use this technique in almost every game design and programming, it's a handy tool in the belt and justifies a longer explanation and some examples.
Does "randomness" have to be really random?
In game design, there is a kind of love-hate relationship with randomness. On the one hand it allows for variety with many types of content, on the other hand one can't "design" true randomness.
How about a function that provides random outcomes but within parameters that can be influenced to fit the game design? It's little effort compared to what is gained in control, it can easily be abstracted in code and only requires holding a few more variables for every not-really-random-randomness.
A quick example: how I have previously dealt with rolling dice for calculating "loot" in a "chest". (It was something different, but the mechanics are the same and the concepts of "chest" and "loot" are instantly recognizable to any game grognard.) Let's say the chance of finding a golden ticket, alongside the usual sell-loot, in a chest should be 1:10000. If you use true randomness the player may eventually find a ticket in three consecutive chests and after that, she will find no ticket in three months of play.
What we do first is to reduce the number 10000 by 100 with every chest opened, store this value in howLikelyIsPlayerToFindTicket and use this variable to calculate our random chance. 1:9900, 1:9800 and so on. The chances get better with every chest until, after 100 tries, a ticket is granted. Remember, it is still possible that the player finds a ticket with a chance of 1:8400 or all other combinations, the odds simply get better with every try.
Additionally, when the player has found a ticket, we want her to not find another ticket again, too shortly after. We set another variable absolutelyNoTicketFindable to, say, 10. With every chest looted we make sure that no ticket is in there and decrease the variable by 1. Once absolutelyNoTicketFindable reaches 0 we go back to our initial 1:10000 chance and to reducing it by 100 with every chest opened.
We introduced a few additional values: Base Chance (10000), Increase Chance (100), Blocker (10), Current Chance (X, between 10000 and 0) and Current Blocks (Y, between 10 and 0). If you are a programmer, you can already see where this is headed. I would hold BC, IC and B as statics (or in a parent class) and CC and CB as variables (or within an object or in a derived class). A function MyRandomSuccess() processes these statics and variables (or gets the classes and objects as parameters). It calculates the success with the current values, modifies the variables accordingly and simply returns true or false.
Depending on how you want to influence the outcome, you can introduce as many additional values as you wish.
- You can influence the success with buffs, power-ups, in-game events or what-have-you.
- You can reduce or increase "Increase Chance" with a buff or power-up
- For a level that is playing in a "poor" area, the player never finds a ticket and finds fewer of the lootable "rare" items.
- You don't have to define a completely different lootable for every occasion. Simply tag the area or the specific chest as "poor".
- If it fits better, the value of absolutelyNoTicketFindable can be an amount of time that counts down.
- You can influence variables for e.g. "hard hit" and "critical hit" depending on the level-difference between opponents to even out the playing field in a MOBA.
- You can generate filler enemies with not-so-random-randomness strengths and weaknesses based on the players' performance in the game thus far.
Properly abstracted, the function can be used for countless other decisions. I used this technique for (something comparable to) critical hits, enemy encounters, "random" goodies in gamification, chances in a lottery depending on real life weather, etc. Voila, randomness harnessed.
In conclusion: The mechanics in question are still "random", can be heavily influenced by game design and, in my experience, are far more easy to balance then having, for instance, 100 different lootables and just switch them around.