Optimizing Game Progression - Part I: Harnessing the Power of Sigmoid Functions in Python
The Power of applying data and coding to real-life game balancing problem
Game balancing, particularly in the realm of Game Progression, poses a significant challenge for game designers. Determining the appropriate difficulty levels and ensuring a smooth progression can be a daunting task, often compounded by the complexity of managing formulas in spreadsheets. In this enlightening journey, join me as we delve into the concept of crafting compelling level progressions using Sigmoid Functions in Python. Together, we will explore a step-by-step approach to design engaging game levels that strike the perfect balance, keeping players hooked and immersed in the gameplay. Get ready to unravel the secrets of effective game progression design and empower your game development endeavors.
Introduction: What is Sigmoid Function
According to wikipedia,A sigmoid function is a mathematical function having a characteristic "S"-shaped curve or sigmoid curve.
A common example of a sigmoid function is the logistic function shown in the first figure and defined by the formula:
In the world of game design, achieving a captivating user progression is paramount. Enter the S-Shaped progression curve – a powerful framework that divides the player journey into three distinct periods, each offering unique challenges and rewards.
First, the curve starts with a gentle upward slope (green) (Learner Phase), catering to inexperienced players as they become familiar with game mechanics. This phase encourages exploration and skill development, providing a supportive environment for players to acclimate.
Next, the curve transitions into a period of steady linear progression (yellow) (Grinder Phase). Here, players with a solid understanding of the mechanics can showcase their abilities and further hone their skills. The challenges increase incrementally, keeping the gameplay engaging and encouraging continued growth.
Finally, the curve takes a downward turn, gradually flattening out (red) (Master Phase) as players approach the skill ceiling. Seasoned masters of the game face complex challenges that push their limits, adding depth and longevity to the gameplay experience.
By leveraging the S-Shaped progression curve, game designers can create a dynamic and rewarding journey that caters to players of all skill levels. This framework not only fosters engagement but also ensures that players feel a sense of accomplishment and growth throughout their gaming experience.
So, embrace the power of the S-Shaped progression curve and unlock the full potential of user experiences in your games. Dive into the intricacies of each phase, tailor challenges accordingly, and witness the transformative impact it has on player engagement and satisfaction.
Of course, we need a more ‘custom’ curve for ourselves, which will be steeper or flatter based on designer intention. So feel free to modify them until you find that best fits you..
To sum up, basically, you just need to remember the sigmoid function is a S-shaped function, which can be varied from 0 to 1. Many people would have different ways of ‘modding’ this function, but to my preference, I would like to have them in my way:
Let's demystify the role of each parameter and how it influences the gameplay experience:
L (Limiting Value): L represents the upper bound or maximum value that the sigmoid function approaches as the game progresses. It signifies the desired maximum level or milestone you want your players to achieve. A higher L indicates a higher maximum value that the function can reach.
k (Steepness): The parameter k controls the steepness of the sigmoid curve. A higher k value makes the curve steeper, resulting in a more rapid transition from low to high values. Conversely, a lower k value creates a flatter curve, leading to a more gradual progression. Adjusting k allows you to fine-tune the difficulty and pace of progression for your players, providing easier or harder challenges in each session.
x0 (Midpoint): The parameter x0 determines the midpoint of the sigmoid curve. It represents the value of x at which the sigmoid function's output is halfway between its minimum and maximum values. By shifting x0 along the x-axis, you can strategically control when players encounter harder or easier progression milestones, enhancing their gameplay experience.
Understanding and carefully tuning these parameters in your game's sigmoid function will empower you to craft engaging and balanced progression, enabling players to reach desired milestones while maintaining an enjoyable level of challenge.
Application: Hay Day by Supercell
Well let’s back to one of the most classic and famous farmery-category games Hayday. It has a ton of systems that are based on user level progression. Take a moment to observe the exponential increase in experience points required to level up as you progress through the game, with the curve becoming steeper towards the later stages of the level system. Hayday's design exemplifies the challenge and satisfaction of advancing through higher levels, captivating players with its engaging progression mechanics.
At first glance, we can see that there is no correlation between this and sigmoid function, but if we do a small calculation of the difference between current level exp requires compare with previous one, you can see that they follow exactly the distribution of the function
On-job application: Game Balancing irl
So from now - on, let’s together build a game named ‘Turnip Land’, a basic farming - type game where players engage in planting and harvesting trees to earn money and advance through levels.
Source: Stardew Valley
Put in a basic context like this, we are tasked with creating a rewarding progression system that aligns with the time players spend in the game. Our goal is to calculate the duration required for players to reach different levels in the game, based on minutes spent. Below is a table showcasing the time progression for some specific levels:
The task here is to finish the whole progression balance that fits the sigmoid curve most based on the information above. To achieve a balanced progression curve that fits the sigmoid function based on the provided information, we can leverage the power of Python. Let's break it down into a few steps:
Import the module and add data, in here i will shorten the term level by ‘x’ values and Time to reach in ‘f(x)’:
import numpy as np from scipy.optimize import curve_fit # Given list of x and f(x) values x_data = [2, 4, 5, 6, 7, 50, 100] f_data = [1, 1.5, 2, 2, 3, 100, 500]
Define the Sigmoid function (as I stated above)
# Define the Sigmoid function f(x) def f(x, L, k, x_0): return L / (1 + np.exp(-k * (x - x_0)))
To solve this task, we need to find the value of L,k, x_0 that fit the data above, using scipy.optimize module
# Perform curve fitting to find optimal values for L, k, x_0 popt, pcov = curve_fit(f, x_data, f_data) # Extract the optimized parameters L_opt, k_opt, x_0_opt = popt # Print the optimized parameters print("Optimized parameters:") print("L =", L_opt) print("k =", k_opt) print("x_0 =", x_0_opt)
Visualize the curve, here we can have a small ‘manual’ adjustment to make the curve as we want, by changing the params L,k, x_0, in here I will not change anything of the curve
import matplotlib.pyplot as plt # Create a list of levels from 1 to 100 and get the f(x) values: x_values_config = list(range(1,101)) y_values_config = f(x_values_config,L_opt,k_opt,x_0_opt) # Visualize the curve: plt.title('Difference of time needs between current and previous level') plt.plot(y_values_config)
Now we have the complete function, let’s create a progression balance from level 1 to 100. First we need to have a based plant for everything, let’s assume that we have plant A, take exactly 1 minute to finish, which also a requirement for level 1:
# Create the list of level from 1 to 100 x_values_ = list (range(1,101)) # Create the plant config Plant_config = config = [ {'plant': 'A', 'time': 1, 'exp_gain': 10, 'unlock': 0} ]
Next, we apply the function to get the difference between previous and current level
# Apply the function to get expetected difference: y_values = f(x_values_,L_opt,k_opt,x_0_opt) np.round(y_values)
Finally, finish the progression balance
# Iterate to get the expected level balance time = Plant_config[0]['time'] progression_level = [] for i in y_values: time = time + i progression_level.append(time) progression_level_final = [ '%.0f' % elem for elem in progression_level ] # Visualize the outcome plt.figure(figsize = (10,5)) plt.title('Time needs to level up at each level') plt.plot(progression_level)
By following these steps, we can leverage Python to find the optimal parameters for the sigmoid function that best fits the given data. But still it’s not finish yet, based on each project and game mechanic, we need to have a tune at each state to get the desire expectation outcome of users, such as adding a very tough peak at level 40 for having a paywall, …. Remember, coding help us to move faster, but not meant to replace us because game designer is the only one who knows what should we put to make the game “fun and interactive“
Stay tuned for Part 2 of our series, where we will explore the intriguing connection between balancing in-game items and progression levels. We will delve into the intricacies of creating a harmonious relationship between these elements, ensuring a rewarding and enjoyable gameplay journey.
If you're interested in further connecting and discussing game design, progression systems, or any related topics, please feel free to reach out to me on LinkedIn. I look forward to connecting with fellow game enthusiasts and sharing insights to enhance our collective understanding of game development. Please spend less than a minute to help me together define which topics should I go next.
Hope you having a good-day of reading and working. Feel free to connect me for further discussion through:
References:
https://en.wikipedia.org/wiki/Sigmoid_function
https://www.jfurness.uk/sigmoid-functions-in-game-design/