Advertisement
Scroll to top

A common mechanic in action games is for enemies to drop some kind of item or reward upon dying. The character can then collect this loot to gain some advantage. It is a mechanic that is expected in a lot of games, like RPGs, since it gives the player an incentive to get rid of the enemies—as well as a small blast of endorphins when discovering what the immediate reward is for doing so.

In this tutorial, we'll review the inner workings of such a mechanic and see how to implement it, whatever the type of game and the coding tool/language you might be using.

The examples I use to demonstrate this were made using Construct 2, a HTML5 game making tool, but are in no way specific to it. You should be able to implement the same mechanic whatever your coding language or tool is.

The examples were made in r167.2 and can be opened and edited in the free version of the software. You can download the latest version of Construct 2 here (since I started writing this article, at least two newer versions have been released) and mess around with the examples to your liking. The example CAPX source files are attached to this tutorial in the zip file.

The Basic Mechanic

Upon an enemy's death (so, when its HP is less than or equal to zero) a function is called. The role of this function is to determine whether there is a drop or not, and, if so, the kind of drop it should be.

The function can also handle the creation of the visual representation of the drop, spawning it at the former screen coordinates of the enemy.

Consider the following example :

Click the Slay 100 Beasts button. This will execute a batch process that creates 100 random beasts, slays them, and displays the result for each beast (that is, whether the beast drops an item, and, if so, what kind of item). Statistics at the bottom of the screen display how many beasts dropped items, and the how many of each type of item was dropped.

This example is strictly text to show the logic behind the function, and to show that this mechanic can be applied to any type of game, whether it is a platformer on which you stomp on the enemies, or a top-down view shooter, or an RPG.

Let's look at how this demo works. First, the beasts and drops are each contained in arrays. Here's the beast array:

Index (X)
Name (Y-0)
Drop rate (Y-1)
Item rarity (Y-2)
0 Boar 100 100
1 Goblin 75 75
2 Squire 65 55
3 ZogZog 45 100
4 Owl 15 15
5 Mastodon 35 50

And here's the drops array:

Index (X)
Name (Y-0)
Item rarity (Y-1)
0 Lollipop 75
1 Gold 50
2 Rocks 95
3 Jewel 25
4 Incense 35
5 Equipment 15

The X value (the Index column) for the array acts as a unique identifier for the beast or item type. For example, the beast of index 0 is a Boar. The item of index 3 is a Jewel.

These arrays act as lookup tables for us, containing the name or type of each beast or item, as well as other values that will allow us to determine the rarity or the drop rate. In the beast array, there are two more columns after the name: 

Drop rate is how likely the beast is to drop an item when slain. For example, the boar will have a 100% chance to drop an item when killed, whereas the owl will have a 15% chance to do the same.

Rarity defines how uncommon the items that can be dropped by this beast are. For example, a boar will be likely to drop items of a rarity value of 100. Now, if we check the drops array, we can see that the rocks is the item with the biggest rarity (95). (Despite the rarity value being high, due to the way I programmed the function, the bigger the rarity number is, the more common the item is. It has more chances to drop the rocks than an item with a lower rarity value.)

And that's interesting to us from a game design perspective. For the balance of the game, we don't want the player to get access to too much equipment or too many high-end items too soon—otherwise, the character might get overpowered too early, and the game will be less interesting to play.

These tables and values are just examples, and you can and should play with and adapt them to your own game system and universe. It all depends on the balancing of your system. If you want to learn more on the subject of balancing, I recommend checking out this series of tutorials: Balancing Turn-Based RPGs.

Let's now look over the (pseudo)code for the demo:

1
CONSTANT BEAST_NAME = 0
2
CONSTANT BEAST_DROPRATE = 1
3
CONSTANT BEAST_RARITY = 2
4
CONSTANT DROP_NAME = 0
5
CONSTANT DROP_RATE = 1
6
//Those constants are used for a better readability of the arrays
7
8
On start of the project, fill the arrays with the correct values
9
array aBeast(6,3)   //The array that contains the values for each beast
10
array aDrop(6,2)    //The array that contains the values for each item
11
array aTemp(0)      //A temporary array that will allow us what item type to drop
12
array aStats(6)     //The array that will contain the amount of each item dropped
13
14
On button clicked
15
    Call function "SlainBeast(100)"
16
17
Function SlainBest (Repetitions)
18
    int BeastDrops = 0 //The variable that will keep the count of how many beasts did drop item
19
    Text.text = ""
20
    aStats().clear //Resets all the values contained in this array to make new statistics for the current batch
21
    Repeat Repetitions times
22
        int BeastType
23
        int DropChance
24
        int Rarity
25
        BeastType = Random(6) //Since we have 6 beasts in our array
26
        Rarity = aBeast(BeastType, BEAST_RARITY) //Get the rarity of items the beast should drop from the aBeast array
27
        DropChance = ceil(random(100)) //Picks a number between 0 and 100)
28
        Text.text = Text.text & loopindex & " _ " & aBeast(BeastType,BEAST_NAME) & "is slain"
29
        
30
        If DropChance > aBeast(BeastType,BEAST_DROPRATE)
31
            //The DropChance is bigger than the droprate for this beast
32
            Text.text = Text.text & "." & newline
33
            //We stop here, this beast is considered to not have dropped an item.
34
        
35
        If DropChance <= aBeast(BeastType,BEAST_DROPRATE)
36
           Text.text = Text.Text & " dropping " //We will put some text to display what item was dropped
37
           //On the other hand, DropChance is less or equal the droprate for this beast
38
            aTemp(0) //We clear/clean the aTemp array in which we will push entries to determine what item type to drop
39
                For a = 0 to aDrop.Width //We will loop through every elements of the aDrop array
40
                    aDrop(a,DROP_RATE) >= Rarity //When the item drop rate is greater or equal the expected Rarity
41
                        Push aTemp,a //We put the current a index in the temp array. We know that this index is a possible item type to drop
42
                int DropType
43
                DropType = random(aTemp.width) //The DropType is one of the indexes contained in the temporary array
44
                Text.text = Text.text & aDrop(DropType, DROP_NAME) & "." & newline //We display the item name that was dropped
45
                //We do some statistics
46
                aStats(DropType) = aStats(DropType) + 1
47
                BeastDrops = BeastDrops + 1
48
            TextStats.Text = BeastDrops & " beasts dropped items." & newline
49
            For a = 0 to aStats.width //Display each item amount that was dropped
50
            and aStats(a) > 0
51
                TextStats.Text = TextStats.Text & aStats(a) & " " & aDrop(a,DROP_NAME) & " "
52
    

First, the user action: clicking on the Slay 100 Beasts button. This button calls a function with a parameter of 100, just because 100 feels like a good number of enemies to slay. In a real game, it's more likely that you will slay beasts one by one, of course.

From this, the function SlainBeast is called. Its purpose is to display some text to give the user feedback on what happened. First, it cleans up the BeastDrops variable and aStats array ,which are used for the statistics. In a real game, it's unlikely you will need those. It cleans the Text as well, so that a new 100 lines will be displayed to see the results of this batch. In the function itself, three numeric variables are created: BeastType, DropChance, and Rarity.

BeastType will be the index we use to refer to a specific row in the aBeast array; it's basically the kind of beast that the player had to face and kill. Rarity is taken from the aBeast array as well; it's the rarity of the item this beast should drop, the value of the Item rarity field in the aBeast array.

Finally, DropChance is a number we randomly pick between 0 and 100. (Most coding languages will have a function to get a random number from a range, or at least to get a random number between 0 and 1, which you could then simply multiply by 100.)

At this point, we can display our first bit of information in the Text object: we already know what kind of beast spawned and was slain. So, we concatenate to the current value of Text.text the BEAST_NAME of the current BeastType we've randomly picked, out of the aBeast array.

Next, we have to determine whether an item shall be dropped. We do so by comparing the DropChance value to the BEAST_DROPRATE value from the aBeast array. If DropChance is less than or equal to this value, we drop an item.

(I decided to go for the "less than or equal to" approach, having been influenced by these live role players using the D&D King Arthur: Pendragon set of rules regarding dice rolls, but you could very well code the function the other way around, deciding that drops will only occur when "greater or equal". It's just a matter of numeric values and logic. However, do stay consistent all through your algorithm, and don't change the logic halfway—otherwise, you could end up with issues when trying to debug or maintain it.)

So, two lines determine whether an item is dropped or not. First:

1
DropChance > aBeast(BeastType,BEAST_DROPRATE)

Here, DropChance is greater than the DropRate, and we consider this to mean that no item is dropped. From there on, the only thing displayed is a closing "." (full stop) that ends the sentence, "[BeastType] was slain.", before moving on to the next enemy in our batch.

On the other hand:

1
DropChance <= aBeast(BeastType,BEAST_DROPRATE)

Here, DropChance is less than or equal to the DropRate for the current BeastType, and so we consider this to mean that an item is dropped. To do so, we will run a comparison between the Rarity of item that the current BeastType is "allowed" to drop, and the several rarity values we have set up in the aDrop table.

We loop through the aDrop table, checking each index to see whether its DROP_RATE is greater than or equal to Rarity. (Remember, counter-intuitively, the higher the Rarity value is, the more common the item is) For each index that matches the comparison, we push that index into a temporary array, aTemp

At the end of the loop, we should have at least one index in the aTemp array. (If not, we need to redesign our aDrop and aBeast tables!). We then make a new numeric variable DropType that randomly picks one of the indices from the aTemp array.; this will be the item we drop. 

We add the name of the item to our Text object, making the sentence to something like "BeastType was slain, dropping a DROP_NAME.". Then, for the sake of this example, we add some numbers to our various statistics (in the aStats array and in BeastDrops). 

Finally, after the 100 repetitions, we display those statistics, the number of beasts (out of 100) that dropped items, and the number of each item that was dropped.

Another Example: Dropping Items Visually

Let's consider another example:

Press Space to create a fireball that will kill the enemy.

As you can see, a random enemy (from a bestiary of 11) is created. The player character (on the left) can create a projectile attack. When the projectile hit the enemy, the enemy dies.

From there, a similar function to what we've seen in the previous example determines whether the enemy is dropping some item or not, and determine what the item is. This time, it also creates the visual representation of the item dropped, and updates the statistics at the bottom of the screen.

Here is an implementation in pseudocode :

1
CONSTANT ENEMY_NAME = 0
2
CONSTANT ENEMY_DROPRATE = 1
3
CONSTANT ENEMY_RARITY = 2
4
CONSTANT ENEMY_ANIM = 3
5
CONSTANT DROP_NAME = 0
6
CONSTANT DROP_RATE = 1
7
//Constants for the readability of the arrays

8
9
int EnemiesSpawned = 0
10
int EnemiesDrops = 0
11
12
array aEnemy(11,4)
13
array aDrop(17,2)
14
array aStats(17)
15
array aTemp(0)
16
17
On start of the project, we roll the data in aEnemy and aDrop
18
Start Timer "Spawn" for 0.2 second
19
20
Function "SpawnEnemy"
21
    int EnemyType = 0
22
    EnemyType = random(11) //We roll an enemy type out of the 11 available

23
    Create object Enemy //We create the visual object Enemy on screen

24
    Enemy.Animation = aEnemy(EnemyType, ENEMY_ANIM)
25
    EnemiesSpawned = EnemiesSpawned + 1
26
    txtEnemy.text = aEnemy(EnemyType, ENEMY_NAME) & " appeared"
27
    Enemy.Name = aEnemy(EnemyType, ENEMY_NAME)
28
    Enemy.Type = EnemyType
29
30
Keyboard Key "Space" pressed
31
    Create object Projectile from Char.Position
32
33
Projectile collides with Enemy
34
    Destroy Projectile
35
    Enemy start Fade
36
    txtEnemy.text = Enemy.Name & " has been vanquished."
37
38
Enemy Fade finished
39
    Start Timer "Spawn" for 2.5 seconds //Once the fade out is finished, we wait 2.5 seconds before spawning a new enemy at a random position on the screen

40
    Function "Drop" (Enemy.Type, Enemy.X, Enemy.Y, Enemy.Name)
41
42
Function Drop (EnemyType, EnemyX, EnemyY, EnemyName)
43
    int DropChance = 0
44
    int Rarity = 0
45
    DropChance = ceil(random(100))
46
    Rarity = aEnemy(EnemyType, ENEMY_RARITY)
47
    txtEnemy.text = EnemyName & " dropped "
48
    
49
    If DropChance > aEnemy(EnemyType, ENEMY_DROPRATE)
50
        txtEnemy.text = txtEnemy.text & " nothing."
51
        //Nothing was dropped

52
    If DropChance <= aEnemy(EnemyType, ENEMY_DROPRATE)
53
        aTemp.clear/set size to 0
54
        For a = 0 to aDrop.Width
55
        and aDrop(a, DROP_RATE) >= Rarity
56
            aTemp.Push(a) //We push the current index into the aTemp array as possible drop index

57
        
58
        int DropType = 0
59
        DropType = Random(aTemp.Width) //We pick what is the drop index amongst the indexes stored in aTemp

60
        aStats(DropType) = aStats(DropType) + 1
61
        EnemiesDrops = EnemiesDrops + 1
62
        Create Object Drop at EnemyX, EnemyY
63
        Drop.AnimationFrame = DropType
64
        txtEnemy.Text = txtEnemy.Text & aDrop.(DropType, DROP_NAME) & "." //We display the name of the drop

65
    txtStats.text = EnemiesDrops & " enemies on " & EnemiesSpawned & " dropped items." & newline
66
    For a = 0 to aStats.width
67
    and aStats(a) > 0
68
        txtStats.text = txtStats.Text & aStats(a) & " " & aDrop(a, DROP_NAME) & " "
69
        
70
Timer "Spawn"
71
    Call Function "SpawnEnemy"

Take a look at the contents of the aEnemy and aDrop tables, respectively:

Index (X)
Name (Y-0)
Drop rate (Y-1)
Item rarity (Y-2)
Animation name (Y-3)
0 Healer Female 100 100 Healer_F
1 Healer Male 75 75 Healer_M
2 Mage Female 65 55 Mage_F
3 Mage Male 45 100 Mage_M
4 Ninja Female 15 15 Ninja_F
5 Ninja Male 35 50 Ninja_M
6 Ranger Male 75 80 Ranger_M
7 Townfolk Female 75 15 Townfolk_F
8 Townfolk Male 95 95 Townfolk_M
9 Warrior Female 70 70 Warrior_F
10 Warrior Male 45 55 Warrior_M
Index (X)
Name (Y-0)
Item rarity (Y-1)
0 Apple 75
1 Banana 50
2 Carrot 95
3 Grape 85
4 Empty potion 80
5 Blue potion 75
6 Red potion 70
7 Green potion 60
8 Pink Heart 65
9 Blue pearl 15
10 Rock 100
11 Glove 25
12 Armor 30
13 Jewel 35
14 Mage Hat 65
15 Wood shield 85
16 Iron axe 65

Unlike the previous example, the array that contains the enemy data is named aEnemy and contains one more row of data, ENEMY_ANIM, which has the name of the enemy's animation. This way, when spawning the enemy, we can look this up and automate the graphical display.

In the same vein, aDrop now contains 16 elements, instead of six, and each index refers to the animation frame of the object—but I could have had several animation as well, as for the enemies, if the dropped items were to be animated.

This time, there are far more enemies and items than in the previous example. You can see, though, that the data regarding drop rates and rarity values is still there. One notable difference is that we have separated the spawning of the enemies from the function that calculates if there is a drop or not. This is because, in a real game, enemies would likely do more than just wait on screen to be slain!

So now we have a function SpawnEnemy and another function DropDrop is pretty similar to how we handled the "dice roll" of our item drops in the previous example, but takes several parameters this time: two of these are the X and Y coordinates of the enemy on screen, since that's the place where we will want to spawn the item when there is a drop; the other parameters are the EnemyType, so we can look up the name of the enemy in the aEnemy table, and the name of the character as a string, to make it quicker to write the feedback we want to give to the player.

The logic of the Drop function is otherwise similar to the previous example; what mostly changes is the way we display feedback. This time, instead of just displaying text, we also spawn an object on screen to give a visual representation to the player.

(Note: To spawn the enemies on several position on screen, I used an invisible object, Spawn, as reference, which continually moves left and right. Whenever the SpawnEnemy function is called, it creates the enemy at the current coordinates of the Spawn object, so that the enemies appear and a variety of horizontal locations.)

One last thing to discuss is when exactly the Drop function is called. I don't trigger it directly upon an enemy's death, but after the enemy has faded away (the enemy's death animation). You can of course call for the drop when the enemy is still visible on screen, if you prefer; once again, that is really down to your game design. 

Conclusion

On a design level, having enemies drop some loot gives an incentive to the player to confront and destroy them. The items dropped allow you to give power-ups, stats, or even goals to the player, whether in a direct or indirect way.

On an implementation level, dropping items is managed through a function that the coder decides when to call. The function does the job of checking the rarity of the items that should be dropped according to the type of enemy killed, and can also determine where to spawn it on screen if and when needed. The data for the items and enemies can be held in data structures like arrays, and looked up by the function.

The function uses random numbers to determine the frequency and type of the drops, and the coder has control over those random rolls, and the data it looks up, to adapt the feel of those drops in the game.

I hope you enjoyed this article and have a better understanding of how to make your monsters drop loots in your game. I'm looking forward to seeing your own games using that mechanic.

References

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.