Weighted Drop System
Two years ago, I uploaded a video about dynamic drops from boxes when the player destroys them. These items can range from food items, bandages that heal the player, pills to restore their energy, or ammo for any weapons they are carrying. The main inspiration for these came from this Valve dev community wiki page.
However, two years ago, I was an idiot.
The loot system that I wrote did not give items that the player needed. Instead, if a box is broken, it runs a random chance to see if it should drop an item; once it passes that and if the player needed a specific category of item (healing items, energy items, ammo, etc.) then it is added to a list referred to as a raffle
. The system chooses a random item from the raffle
to give to the player.
This system is what was used in my public demo, and only yesterday night did I try and revise it with my new knowledge so far.
Enter weights
Going back to that article, it said that it spawned an item that the player needed the most. So I needed to get a measurement of what the player needed.
I revised my LootSystem.gd
script to include weights variable.
var weights: Dictionary = {
"none": 0.0,
"health": 0.0,
"food": 0.0,
"mana": 0.0,
"ammo_pistol": 0.0,
"ammo_shotgun": 0.0,
"ammo_smg": 0.0,
"ammo_flamethrower": 0.0,
}
The weights are a value between 0.0 and 1.0, which are determined by subtracting 1 from the percentage of the respective category. Using health as an example: 1.0 - (player's current hp / player's maximum hp)
.
The function to set these weights were pretty simple because of that:
func calc_weights():
var player_hp: float = float(Global.player.hp) / float(Global.player.base_hp)
var percent_health: float = 1.0 - player_hp
var player_mana: float = float(Global.player.mana) / float(Global.player.base_mana)
var percent_mana: float = 1.0 - player_mana
var pistol = (Global.player.weapon_handler.weapon_search("W_Pistol"))
var shotgun = (Global.player.weapon_handler.weapon_search("W_Shotgun"))
var smg = (Global.player.weapon_handler.weapon_search("W_SMG"))
var flamethrower = (Global.player.weapon_handler.weapon_search("W_FlameThrower"))
# Update Health weight
# print(str("Chance to drop a medkit: ", percent_health))
weights["health"] = percent_health
# Update Mana weight
# print(str("Chance to drop a pep pill: ", percent_mana))
weights["mana"] = percent_mana
# Update Food weight
# print(str("Chance to drop random food: ", player_hp))
weights["food"] = max(0.0, 0.5 - player_hp)
# Update Pistol ammo weight
weights["ammo_pistol"] = _get_ammo_percentage(pistol)
# Update Shotgun ammo weight
weights["ammo_shotgun"] = _get_ammo_percentage(shotgun)
# Update SMG ammo weight
weights["ammo_smg"] = _get_ammo_percentage(smg)
# Update Flamethrower ammo weight
weights["ammo_flamethrower"] = _get_ammo_percentage(flamethrower)
func _get_ammo_percentage(weapon: Firearm):
return 1.0 - float(weapon.ammo) / float(weapon.max_ammo) if weapon != null else 0.0
With that done, all I had to do was update my dynamic_drop
function to use the new weights and choose the key with the highest value. And the result is as you’d expect – players will be given items they need the most.
{
ammo_flamethrower:0,
ammo_pistol:0.233333,
ammo_shotgun:0.775,
ammo_smg:0.666667,
food:0,
health:0.43,
mana:0.7,
none:0
}
Chosen category: ammo_shotgun
Possible changes
Some things that I would change are how food
and the ammo
types are dropped. Currently, these weights are linear; ideally I’d like for them to be exponential, where the closer to 0.0 the weights are, the higher it will be. That way the player isn’t getting ammo when they’re half way full of it and they really need something else.
And for food, it should be a quadratic function, where it’s most likely to drop if the player is at half health. If the player has high HP, they are less likely to get it, and the closer they are to 0, it won’t drop because that’s where the bandages will be given instead.
Closing Note
I’m really happy with this change. I’m also pretty happy about having my own written blog too! There’s a lot of minor things that I’ve done for my game + other separate game projects that I’d like to talk about or show, but it’s not really that possible since it wouldn’t be interesting visually for a short video. (Not to mention having to record it…)
Thank you for reading!