What you’ve encountered is the problem of difficulty scalability. It’s one of the key problems for every game and falls under wider umbrella of gameplay balancing.
Since balancing is a never-ending task, you want a system that has several “knobs” that let you tune the parameters, and thus - balance the system.
I’d suggest starting by making a prototype that’s completely separated from the game and just generates and finishes orders as you press a key. Once the system works well by itself - incorporate it into the game.
Make a scene with a single node with a script attached and do everything there.
You need 3 main things:
- a way to track player progress
- a way to specify an order
- a way to configure an order, and do so using player progress as a parameter
Let’s track the player progress first in the simplest possible way that was already suggested by someone above: Progress is just a number that gets incremented by 1 whenever an order is completed. The completion is simulated by pressing the space key:
extends Control
var progress: int = 0
func _input(event):
if event.is_action_pressed("ui_accept"):
complete_current_order()
func complete_current_order():
progress += 1
print("ORDER COMPLETED (%d)"%progress)
The output in the console as we press the space key is now:
ORDER COMPLETED (1)
ORDER COMPLETED (2)
ORDER COMPLETED (3)
...
Next, introduce a way to specify orders. I’ll simply store the order as a dictionary where the key is the item name and value is the required number of those items. Like this: {"apples": 10, "oranges": 5}
Extending our code with the order creation function and creating a new order whenever the current order is completed:
extends Control
var progress: int = 0
var current_order: Dictionary
func _ready():
current_order = create_order()
func _input(event):
if event.is_action_pressed("ui_accept"):
complete_current_order()
current_order = create_order()
func complete_current_order():
progress += 1
print("ORDER COMPLETED (%d)"%progress)
func create_order() -> Dictionary:
var order = {"apples": 10, "orranges": 5}
print("NEW ORDER: ", order)
return order
Our output is now:
NEW ORDER: { "apples": 10, "orranges": 5 }
ORDER COMPLETED (1)
NEW ORDER: { "apples": 10, "orranges": 5 }
ORDER COMPLETED (2)
NEW ORDER: { "apples": 10, "orranges": 5 }
We are now capable of tracking player progress and creating new dummy orders.
Next, we will create actual orders by parametrizing our order creation function. As input parameters it’ll take the ingredient count and a list of possible ingredients:
extends Control
var progress: int = 0
var current_order: Dictionary
const INGREDIENTS := ["apples", "oranges", "carrots", "bananas", "pears", "gems"]
func _ready():
current_order = create_order(INGREDIENTS, 3)
func _input(event):
if event.is_action_pressed("ui_accept"):
complete_current_order()
current_order = create_order(INGREDIENTS, 3)
func complete_current_order():
progress += 1
print("ORDER COMPLETED (%d)"%progress)
func create_order(ingredient_pool: Array, ingredient_count = 3) -> Dictionary:
var order = {}
for i in ingredient_count:
order[ingredient_pool.pick_random()] = randi_range(5, 10)
print("NEW ORDER: ", order)
return order
Our output is now:
NEW ORDER: { "carrots": 8, "gems": 6 }
ORDER COMPLETED (1)
NEW ORDER: { "carrots": 6, "bananas": 7, "apples": 7 }
ORDER COMPLETED (2)
NEW ORDER: { "gems": 5, "pears": 5, "oranges": 8 }
ORDER COMPLETED (3)
NEW ORDER: { "pears": 9, "gems": 7, "bananas": 5 }
Now for the most interesting and important part. We want to drive the order creation parameters with the player progress. We’ll start simply by increasing the number of item types in an order as the player progresses.
A neat tool for that are Curve resources. They let you specify one value depending on another in a visual way. So we’ll add an exported Curve property whose x axis will represent the player progress and y axis the number of item types in the order. In the inspector we can create this curve resource and adjust it. In the code we’ll sample the curve with the player progress as the x value and pass the returned sample into the order creation function.
extends Control
@export var item_types_per_order: Curve
var progress: int = 0
var current_order: Dictionary
const INGREDIENTS := ["apples", "oranges", "carrots", "bananas", "pears", "gems"]
func _ready():
current_order = create_order(INGREDIENTS, round(item_types_per_order.sample(progress)))
func _input(event):
if event.is_action_pressed("ui_accept"):
complete_current_order()
current_order = create_order(INGREDIENTS, round(item_types_per_order.sample(progress)))
func complete_current_order():
progress += 1
print("ORDER COMPLETED (%d)"%progress)
func create_order(ingredient_pool: Array, ingredient_count = 3) -> Dictionary:
ingredient_pool = ingredient_pool.duplicate()
var order = {}
for i in ingredient_count:
var item = ingredient_pool.pick_random()
ingredient_pool.erase(item)
order[item] = randi_range(5, 10)
print("NEW ORDER: ", order)
return order
All of a sudden, we’re getting quite powerful. We drive the item type count with the player progress and have the ability to tune/balance the relation between the progress and the count by merely tweaking a curve. Our output now looks like this:
NEW ORDER: { "oranges": 6 }
ORDER COMPLETED (1)
NEW ORDER: { "gems": 9 }
ORDER COMPLETED (2)
NEW ORDER: { "oranges": 8 }
ORDER COMPLETED (3)
NEW ORDER: { "apples": 9, "pears": 5 }
ORDER COMPLETED (4)
NEW ORDER: { "apples": 9, "oranges": 7 }
ORDER COMPLETED (5)
NEW ORDER: { "apples": 6, "oranges": 7, "bananas": 7 }
ORDER COMPLETED (6)
NEW ORDER: { "apples": 5, "oranges": 7, "carrots": 5 }
ORDER COMPLETED (7)
NEW ORDER: { "carrots": 7, "oranges": 7, "bananas": 10, "pears": 6 }
ORDER COMPLETED (8)
NEW ORDER: { "oranges": 10, "bananas": 10, "gems": 10, "apples": 8 }
ORDER COMPLETED (9)
NEW ORDER: { "carrots": 5, "pears": 7, "oranges": 9, "apples": 5, "bananas": 10 }
But why stop here. We can drive more parameters with more curves. Let’s add a curve that controls the average amount per item so that too can increase with the player progress:
extends Control
@export var item_types_per_order: Curve
@export var average_item_amount: Curve
var progress: int = 0
var current_order: Dictionary
const INGREDIENTS := ["apples", "oranges", "carrots", "bananas", "pears", "gems"]
func _ready():
current_order = create_order()
func _input(event):
if event.is_action_pressed("ui_accept"):
complete_current_order()
current_order = create_order()
func complete_current_order():
progress += 1
print("ORDER COMPLETED (%d)"%progress)
func create_order() -> Dictionary:
var ingredient_count: int = round(item_types_per_order.sample(progress))
var ingredient_pool: Array = INGREDIENTS.duplicate()
var amount: int = round(average_item_amount.sample(progress))
var order = {}
for i in ingredient_count:
var item = ingredient_pool.pick_random()
ingredient_pool.erase(item)
order[item] = randi_range(amount * 0.8, amount * 1.2)
print("NEW ORDER: ", order)
return order
And our output now looks like this:
NEW ORDER: { "pears": 1 }
ORDER COMPLETED (1)
NEW ORDER: { "bananas": 5 }
ORDER COMPLETED (2)
NEW ORDER: { "apples": 10 }
ORDER COMPLETED (3)
NEW ORDER: { "gems": 17, "carrots": 17 }
ORDER COMPLETED (4)
NEW ORDER: { "apples": 17, "pears": 25 }
ORDER COMPLETED (5)
NEW ORDER: { "carrots": 23, "apples": 25, "gems": 31 }
ORDER COMPLETED (6)
NEW ORDER: { "apples": 37, "bananas": 24, "oranges": 36 }
ORDER COMPLETED (7)
NEW ORDER: { "bananas": 28, "oranges": 29, "carrots": 41, "gems": 38 }
ORDER COMPLETED (8)
NEW ORDER: { "gems": 37, "bananas": 43, "pears": 42, "carrots": 36 }
ORDER COMPLETED (9)
NEW ORDER: { "carrots": 36, "oranges": 34, "pears": 41, "gems": 42, "bananas": 34 }
ORDER COMPLETED (10)
NEW ORDER: { "apples": 47, "bananas": 34, "oranges": 43, "pears": 39, "carrots": 41 }
ORDER COMPLETED (11)
NEW ORDER: { "gems": 46, "apples": 46, "pears": 38, "bananas": 47, "carrots": 42 }