How to Code Monster Loot Drops
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
|Index (X)||Name (Y-0)||Drop rate (Y-1)||Item rarity (Y-2)|
And here's the
|Index (X)||Name (Y-0)||Item rarity (Y-1)|
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
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:
CONSTANT BEAST_NAME = 0 CONSTANT BEAST_DROPRATE = 1 CONSTANT BEAST_RARITY = 2 CONSTANT DROP_NAME = 0 CONSTANT DROP_RATE = 1 //Those constants are used for a better readability of the arrays On start of the project, fill the arrays with the correct values array aBeast(6,3) //The array that contains the values for each beast array aDrop(6,2) //The array that contains the values for each item array aTemp(0) //A temporary array that will allow us what item type to drop array aStats(6) //The array that will contain the amount of each item dropped On button clicked Call function "SlainBeast(100)" Function SlainBest (Repetitions) int BeastDrops = 0 //The variable that will keep the count of how many beasts did drop item Text.text = "" aStats().clear //Resets all the values contained in this array to make new statistics for the current batch Repeat Repetitions times int BeastType int DropChance int Rarity BeastType = Random(6) //Since we have 6 beasts in our array Rarity = aBeast(BeastType, BEAST_RARITY) //Get the rarity of items the beast should drop from the aBeast array DropChance = ceil(random(100)) //Picks a number between 0 and 100) Text.text = Text.text & loopindex & " _ " & aBeast(BeastType,BEAST_NAME) & "is slain" If DropChance > aBeast(BeastType,BEAST_DROPRATE) //The DropChance is bigger than the droprate for this beast Text.text = Text.text & "." & newline //We stop here, this beast is considered to not have dropped an item. If DropChance <= aBeast(BeastType,BEAST_DROPRATE) Text.text = Text.Text & " dropping " //We will put some text to display what item was dropped //On the other hand, DropChance is less or equal the droprate for this beast aTemp(0) //We clear/clean the aTemp array in which we will push entries to determine what item type to drop For a = 0 to aDrop.Width //We will loop through every elements of the aDrop array aDrop(a,DROP_RATE) >= Rarity //When the item drop rate is greater or equal the expected Rarity 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 int DropType DropType = random(aTemp.width) //The DropType is one of the indexes contained in the temporary array Text.text = Text.text & aDrop(DropType, DROP_NAME) & "." & newline //We display the item name that was dropped //We do some statistics aStats(DropType) = aStats(DropType) + 1 BeastDrops = BeastDrops + 1 TextStats.Text = BeastDrops & " beasts dropped items." & newline For a = 0 to aStats.width //Display each item amount that was dropped and aStats(a) > 0 TextStats.Text = TextStats.Text & aStats(a) & " " & aDrop(a,DROP_NAME) & " "
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 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
DropChance is a number we randomly pick between
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
1, which you could then simply multiply by
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
BEAST_NAME of the current
BeastType we've randomly picked, out of the
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:
DropChance > aBeast(BeastType,BEAST_DROPRATE)
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:
DropChance <= aBeast(BeastType,BEAST_DROPRATE)
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
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,
At the end of the loop, we should have at least one index in the
aTemp array. (If not, we need to redesign our
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
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 :
CONSTANT ENEMY_NAME = 0 CONSTANT ENEMY_DROPRATE = 1 CONSTANT ENEMY_RARITY = 2 CONSTANT ENEMY_ANIM = 3 CONSTANT DROP_NAME = 0 CONSTANT DROP_RATE = 1 //Constants for the readability of the arrays int EnemiesSpawned = 0 int EnemiesDrops = 0 array aEnemy(11,4) array aDrop(17,2) array aStats(17) array aTemp(0) On start of the project, we roll the data in aEnemy and aDrop Start Timer "Spawn" for 0.2 second Function "SpawnEnemy" int EnemyType = 0 EnemyType = random(11) //We roll an enemy type out of the 11 available Create object Enemy //We create the visual object Enemy on screen Enemy.Animation = aEnemy(EnemyType, ENEMY_ANIM) EnemiesSpawned = EnemiesSpawned + 1 txtEnemy.text = aEnemy(EnemyType, ENEMY_NAME) & " appeared" Enemy.Name = aEnemy(EnemyType, ENEMY_NAME) Enemy.Type = EnemyType Keyboard Key "Space" pressed Create object Projectile from Char.Position Projectile collides with Enemy Destroy Projectile Enemy start Fade txtEnemy.text = Enemy.Name & " has been vanquished." Enemy Fade finished 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 Function "Drop" (Enemy.Type, Enemy.X, Enemy.Y, Enemy.Name) Function Drop (EnemyType, EnemyX, EnemyY, EnemyName) int DropChance = 0 int Rarity = 0 DropChance = ceil(random(100)) Rarity = aEnemy(EnemyType, ENEMY_RARITY) txtEnemy.text = EnemyName & " dropped " If DropChance > aEnemy(EnemyType, ENEMY_DROPRATE) txtEnemy.text = txtEnemy.text & " nothing." //Nothing was dropped If DropChance <= aEnemy(EnemyType, ENEMY_DROPRATE) aTemp.clear/set size to 0 For a = 0 to aDrop.Width and aDrop(a, DROP_RATE) >= Rarity aTemp.Push(a) //We push the current index into the aTemp array as possible drop index int DropType = 0 DropType = Random(aTemp.Width) //We pick what is the drop index amongst the indexes stored in aTemp aStats(DropType) = aStats(DropType) + 1 EnemiesDrops = EnemiesDrops + 1 Create Object Drop at EnemyX, EnemyY Drop.AnimationFrame = DropType txtEnemy.Text = txtEnemy.Text & aDrop.(DropType, DROP_NAME) & "." //We display the name of the drop txtStats.text = EnemiesDrops & " enemies on " & EnemiesSpawned & " dropped items." & newline For a = 0 to aStats.width and aStats(a) > 0 txtStats.text = txtStats.Text & aStats(a) & " " & aDrop(a, DROP_NAME) & " " Timer "Spawn" Call Function "SpawnEnemy"
Take a look at the contents of the
aDrop tables, respectively:
|Index (X)||Name (Y-0)||Drop rate (Y-1)||Item rarity (Y-2)||Animation name (Y-3)|
|Index (X)||Name (Y-0) ||Item rarity (Y-1)|
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.
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
Drop 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.
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.