Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

How to Code Monster Loot Drops

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

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)
0Boar100100
1Goblin7575
2Squire6555
3ZogZog45100
4Owl1515
5Mastodon3550

And here's the drops array:

Index (X)
Name (Y-0)
Item rarity (Y-1)
0Lollipop75
1Gold50
2Rocks95
3Jewel25
4Incense35
5Equipment15

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:

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, 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:

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:

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 :

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 aEnemy and aDrop tables, respectively:

Index (X)
Name (Y-0)
Drop rate (Y-1)
Item rarity (Y-2)
Animation name (Y-3)
0Healer Female100100Healer_F
1Healer Male7575Healer_M
2Mage Female6555Mage_F
3Mage Male45100Mage_M
4Ninja Female1515Ninja_F
5Ninja Male3550Ninja_M
6Ranger Male7580Ranger_M
7Townfolk Female7515Townfolk_F
8Townfolk Male9595Townfolk_M
9Warrior Female7070Warrior_F
10Warrior Male4555Warrior_M
Index (X)
Name (Y-0)
Item rarity (Y-1)
0Apple75
1Banana50
2Carrot95
3Grape85
4Empty potion80
5Blue potion75
6Red potion70
7Green potion60
8Pink Heart65
9Blue pearl15
10Rock100
11Glove25
12Armor30
13Jewel35
14Mage Hat65
15Wood shield85
16Iron axe65

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