My dream project is an action RPG style game, in the vein of 8- and 16-bit games like The Legend of Zelda, The Faery Tale Adventure, Beyond Oasis, and others.
In such a game, enemies drop items when defeated, and those items generally aren’t fixed. There may be a random chance for an item to drop, or if every enemy has an item, the item may be selected randomly. The enemy may have multiple items. There may be drops common to every enemy, and others specific to one individual or group. And certain items may be exceedingly common, while others are exceptionally rare.

In order to deal with this, I decided to prototype up a system where you’d be able to put items on a weighted table. But to make it as flexible as possible, I made it so that each unit could have multiple tables attached, and each would have its own unique rules. Perhaps an enemy would always have exactly one weapon; the game would roll on a table that drops the item with a 100% drop rate.

For coins, it might roll 50 times with a 10% chance each, and for each that succeeds, there’s a chance for it to be copper, silver or gold, with decreasing chances for each. And for random drops, it may do something like rolling three times with a low chance each, and for each success it rolls on a large table with any number of common goods.
The method is relatively simple. Working from the ground up, each item is a standard Game Object, saved as a prefab.
The next step was to create the drop tables. I created a Scriptable Object, and added a struct that holds two items: A Game Object reference to one of those prefabs, and an int for the item’s drop weight. The higher the weight, the more likely it is for that item to drop. By setting the weight to zero, the item remains in the list, but has no chance to drop. Setting the weight to a negative value would also cause the item to be unable to drop, but would also mess with the range of rolls, reducing the chances of things near the end of the list, or maybe even making it impossible to roll that high. In a final version, I would definitely add some sort of safeguard to prevent negative values.
Because the table can have an unpredictable total weight, I created a function that simply iterates through all the items on the table, adds together all the weights, and returns the total. This is where the negative values would potentially cause a problem. The simplest solution would be to add Mathf.Max(0, itemWeight) rather than just adding the weight. That way, negative values are treated the same as zeroes.
The next function selects an item off the list. It generates a random number from 0 to the value returned by the previous function. It then loops through the entire list, checking to see if the rolled number is less than the current item’s weight. If it is, it returns that item. If not, it subtracts the current item’s weight from the roll, and progresses to the next item.
For example, if your table had two items, each with a weight of 50, it would roll a number from 0 to 99. If the roll was less than 50, it would return the first item. If the roll was 50 to 99, it would subtract 50, getting a number between 0 and 49, and then move on to the next one. Since the roll would now be under the second item’s weight of 50, it would return that, and exit the function.
The code should always find a valid result before it finishes the loop. As a safety precaution, after the loop are two lines that push an error message to the console, and return an empty drop result.
The final function on the drop table is Public, and it creates an empty list of Game Objects, and enters a loop that runs a number of times equal to the maximum number of drops allowed. For each loop, if checks if a random number from 0 to 1 is less than the drop chance, and if it is, it runs the item selection function above. If the return value isn’t null (either because of the error check, or because the drop table contains an empty entry), it adds that item to the list, and once it’s finished, it returns the contents of that list.
The units each have a list of Drop Tables, and when clicked, they run a function that calls the public function on each of their Drop Tables, and Instantiates each of the items returned before destroying itself and waiting to be respawned. In this demo, each drop simply spawns in at the location of the unit that created it, and gets launched in a random direction before fading out a few seconds later.
