+2 votes

In the game I'm making, I'm trying to set up a difficulty system that relies on choosing enemies based on a difficulty number. Every enemy had a number that represents its difficulty; the goal of the enemy spawner is to have the total enemy difficulty match the wave difficulty. However, I'm still a noob, and I have no idea how to get the code to find a set of numbers that match the target. Help would be very much appreciated!

in Engine by (103 points)

1 Answer

+2 votes
Best answer

Hey there!

So I assume what you're asking for is to be able to have a target number and then to come up with a bunch of random numbers (within a range) that add up to the target number.

Though I don't have all the details, the way I would approach this is as follows:

1) Take your target number (round difficulty) and divide it by the max difficulty an enemy can have (assuming the lowest difficulty an enemy can have is 0) divided by two. In other words:

var number_of_enemies = target_difficulty / (max_enemy_difficulty * 0.5)

This will give you how many enemies there will be in a round. Unfortunately, the desired answer is a function of the possible range of difficulty of an enemy, the target difficulty of the round, and the number of enemies. This is assuming that the number of enemies won't be exactly defined but rather determined by the input desired difficulty and the possible range of the enemy difficulty. If you want a specific number of enemies in your round guaranteed, simply take your max enemy difficulty, divide it by two, and then multiply that by how many enemies you want in the round and use that as your target difficulty.

2) Populate an array of numberofenemies length with maxenemydifficult * 0.5:

var all_enemies = []
for i in range(number_of_enemies):
    all_enemies.append(max_enemy_difficulty * 0.5)

This will be the array containing all the individual difficulties of all the enemies in the round. Right now, they'll all be set to half what they could be at the max. This is intended.

3) Here comes the magic -- pick two random indexes within the all_enemies array, increase one of them by one, and subtract one from the other one. That way, the sum of all the enemy difficulties stays the same no matter how many times you change it, but the actual individual enemies' difficulties get randomized.

var rand = RandomNumberGenerator.new()
rand.randomize()
for i in range(number_of_changes):
    # picking an index in the array, and then if it is at the max enemy difficulty, picking a different one (because this index will only be added to, and if its at the max you cant add anything)
    var ind_1 = rand.randi_range(0, all_enemies.size() - 1)
    while all_enemies[ind_1] >= max_enemy_difficulty:
        ind_1 = rand.randi_range(0, all_enemies.size() - 1)
    #same as before, but checking if the second index is at the minimum it can be (because this index will be subtracted from)
    var ind_2 = rand.randi_range(0, all_enemies.size() - 1)
    while all_enemies[ind_2] <= 0:
        ind_2 = rand.randi_range(0, all_enemies.size() - 1)
    all_enemies[ind_1] += 1
    all_enemies[ind_2] -= 1

this should be the end of it. As you can see, this code takes in the target difficulty/total round difficulty (which I set to 10 in this example), max difficulty of an enemy (which I set to 4) and the min difficulty (but that's assumed to be 0) and outputs an array with every individual enemy's difficulty:
screenshot

Hope this helped, and if I misunderstood the question or you need some further guidance or variables to be tweaked, don't hesitate to let me know.

(Just for clarity, here's what the final code that I used to generate that example looks like):
screenshot2

by (50 points)
selected by

Thank you so much for the in-depth answer, it works like a charm! However, I'm still not sure how to assign a number to an enemy, and refer to it using the numbers generated by your code.
Here's the code for choosing a random enemy (I know it's bad, I'm still new to RNG):

var Spitter = preload("res://Spitter.tscn").instance()
var Zombie = preload("res://Zombie.tscn").instance()
var Hunter = preload("res://Hunter.tscn").instance()
var enemyarray = [Zombie, Hunter, Spitter]
var random_enemy = enemyarray[randi() % enemyarray.size()]
var random_enemy2 = enemyarray[randi() % enemyarray.size()]
var random_enemy3 = enemyarray[randi() % enemyarray.size()]
parent_node.call_deferred("add_child", random_enemy)
parent_node.call_deferred("add_child", random_enemy2)
parent_node.call_deferred("add_child", random_enemy3)

I also have an issue with spawning multiple enemies of the same kind; it says they're already children of node "Enemies" (the node that I spawn them under.)
Thank you again for the help, it's been very helpful. You don't need to go so in-depth next time if you don't want to, maybe just a couple of strategies or commands if you have the time. Thanks!

Hello again!

So a few corrections to make about your code. First of all, what you'll want to do is take the .instance() off of the end of all the preloads. This will give you packed scenes which you can then clone with your code (which you do by referencing) as much as you want. You don't want to spawn multiple of the same instances. Next, add a for loop for however many enemies that you would like to spawn. Instead of doing individual enemy variables for every enemy, just add them individually. Also, to give the variables to the individual enemies, it's pretty easy. Just give the enemy scenes a difficulty property and create a function in the scene script for every enemy with a function called give(difficulty). Unfortunately this give function is necessary because otherwise you can't set a property that you created before the ready function fires, but this give function will allow you to do so. You can set that property with the give function when it's created. Your code would then become this:

var Spitter = preload("res://Spitter.tscn")
var Zombie = preload("res://Zombie.tscn")
var Hunter = preload("res://Hunter.tscn")
var enemyarray = [Zombie, Hunter, Spitter]
for i in range(number_of_enemies):
    var random_enemy = enemyarray[randi() % enemyarray.size()]
    random_enemy.instance()
    # this gives each enemy when created its respective and unique difficulty:
    random_enemy.give(all_enemies[i])
    # and then this is just a bit of clean up of your code. Feel free to use what you were using and then just input "random_enemy" as the input to the call deferred method, but this is how I would do it.
    get_parent().add_child(random_enemy)

and in each of your enemy scene scripts, you'd want to add

var difficulty: int

at the top, and somewhere else in the script:

func give(value: int):
    difficulty = value

This would actually be a great job for scene or script inheritance, but it's rather high-level and even I don't fully understand it yet. I would recommend looking into it though, it's a powerful capability of godot and gdscript that could be of great use to you.

If you have any more questions let me know!

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.