Godot Template: Enemy Drops

Overview

This bit of code determines what enemies drop and how often they drop it. It would be a good template for NPC conversations as well.

Exports

  • ITEM_DROP_PERCENT
    • The chance of an enemy dropping something when they die

    • This is a percent and should be from 0-100

  • ITEM_DROP_WEIGHT
    • Dictionary: the key is the scene path (-.tscn) to a pickup and the value is the weight of the pickup

    • Weights are not percentages, but simply relative to each other. An item with weight 4 has twice the likelihood of dropping as an item of weight 2.

Normalization

Because items are randomly chosen by adding them to a list and picking one, we need to make sure our list does not end up ridiculously long. Entity.normalize_item_drop_weights() tries to keep the total item weight under 100 and is run during Entity._ready()

  • Sum all of the weights.
    • We cannot force a dictionary export to be an integer, so we need to round them.

    • This will fail if they are not at least numeric, we want it to.

  • If the sum is greater than 100, then set our multiplier so that the sum would be 100.

  • For each value:
    • Multiply it by the multiplier

    • Round it, unless rounding it forces it to zero, in which case set it to 1.

  • Because of the rounding, our sum may end up being greater than 100, that’s not that important.

  • If it is important that you preserve initial weights for future adjustment (i.e. if single weights are adjusted in the code based on game-play) then you should make sure to do that somewhere. This process destroys the original list.

Enemy Death

When and entity of type EMEMY dies, it calls ‘Entity.enemy_drop()’_. This function determines what, if anything, they drop and instances the drop scene.

  • Get a random number between 0 and 99.

  • If it is strictly less that the drop percentage, then move on.

  • Go through all of the keys in ITEM_DROP_WEIGHTS. Add value number of copies key into drop_list.

  • Randomly select a key from drop_list and instance the scene.

Code

Entity.normalize_item_drop_weights()

func normalize_item_drop_weights():
    var sum = 0
    # force multiplier to be a float
    var multiplier = 1.0
    for key in ITEM_DROP_WEIGHTS:
        sum += round(ITEM_DROP_WEIGHTS[key])
    # if our sum is greater than 100 then we want then find the
    # multiplier that will bring it close to 100
    if sum > 100:
        multiplier = 100/sum

    for key in ITEM_DROP_WEIGHTS:
        # First do the multiplier
        ITEM_DROP_WEIGHTS[key] = multiplier * float(ITEM_DROP_WEIGHTS[key])
        # if rounding it will make it zero (i.e. it was .4) then make it 1
        if ITEM_DROP_WEIGHTS[key] > 0 && round(ITEM_DROP_WEIGHTS[key]) == 0:
            ITEM_DROP_WEIGHTS[key] = 1
        else:
            ITEM_DROP_WEIGHTS[key] = round(ITEM_DROP_WEIGHTS[key])

Code

Entity.enemy_drop()

func enemy_drop():
    # drop is a number between 0 and 99
    var drop = randi() % 100

    # if drop is strictly less than our percentage, then drop something
    if drop < ITEM_DROP_PERCENT:
        # Here we are basically filling a hat with names.
        # For each key, we'll put [value] entries of the key into the list
        var drop_list = []
        for key in ITEM_DROP_WEIGHTS:
            for i in range(ITEM_DROP_WEIGHTS[key]):
                drop_list.append(key)

        # index is a number between 0 and list size - 1
        var index = randi() % drop_list.size()
        # load the scene at index
        var scene = str("res://", drop_list[index], ".tscn")
        instance_scene(load(scene))