• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Game Mechanics Reward Levels and Rarity System

samspade

Member
In my game, players get upgrades at the end of a level. Currently, I put all of those upgrades into a list, shuffle the list, and then choose some at random. I'd like to implement a rarity system of some kind. My specific questions are:
  • What are some good ways to implement a rarity system?
  • How do have control the percentages and so on?
The two ideas I currently have are:
  1. Put upgrades into pools and then pick a pool based on the rarity of that pool.
  2. Give each upgrade an individual rarity and pick based on the individual rarity.
The first one I could code, but it doesn't seem to offer a lot of flexibility. I'd prefer to do the second one, as it would allow me to do things like control the distribution curve, but I don't know how I'd go about it. Are there other good ways of doing putting in a rarity system?

Mostly looking for ideas and general structure, but if you have code lying around I'd appreciate that as well. It's been a long time since I've had a math class, so I'd also appreciate links, explanations, of the math side of this as well.
 

NightFrost

Member
I've done a weighted random selection before in the following manner. Let's assume we have four items with following rarity values, with higher number meaning more common:
Code:
Item1: 10
Item2: 5
Item3: 20
Item4: 15
I would place references into those items into a 2d array, along with a running count of rarity. So the complete array would look something like this:
Code:
(reference to item1) (10) // 0 + 10
(reference to item2) (15) // 10 + 5
(reference to item3) (35) // 15 + 20
(reference to item4) (50) // 35 + 15
Then I irandom_range between 1 and the final sum (50 in this case) and run through the array again. If the random number is lower than or equal to the recorded running total, I choose the item on that row. For example if I rolled 24, item3 would be selected.
 

samspade

Member
I've done a weighted random selection before in the following manner. Let's assume we have four items with following rarity values, with higher number meaning more common:
Code:
Item1: 10
Item2: 5
Item3: 20
Item4: 15
I would place references into those items into a 2d array, along with a running count of rarity. So the complete array would look something like this:
Code:
(reference to item1) (10) // 0 + 10
(reference to item2) (15) // 10 + 5
(reference to item3) (35) // 15 + 20
(reference to item4) (50) // 35 + 15
Then I irandom_range between 1 and the final sum (50 in this case) and run through the array again. If the random number is lower than or equal to the recorded running total, I choose the item on that row. For example if I rolled 24, item3 would be selected.
I'm honestly not entirely sure I understand what you're saying, but I think what you're saying is the same as this method which I found on The Coding Train:


I converted it to GML and created a test project. Test project has one room (no changes made) one object, and one script. Object is in the room.

Code:
/// @description Create Event

//enums and macros
#macro NAME 0
#macro RARITY 1
#macro COUNT 2

enum item_name {
    melon,
    apple,
    banana,
    cherry,
    mango,
    kiwi,
    height,
}

//create item grid
item_grid = ds_grid_create(3, item_name.height);
ds_grid_set_region(item_grid, COUNT, 0, COUNT, item_name.height, 0);

item_grid[# NAME, item_name.melon] = "MELON";
item_grid[# RARITY, item_name.melon] = 12;

item_grid[# NAME, item_name.apple] = "APPLE";
item_grid[# RARITY, item_name.apple] = 4;

item_grid[# NAME, item_name.banana] = "BANANA";
item_grid[# RARITY, item_name.banana] = 5;

item_grid[# NAME, item_name.cherry] = "CHERRY";
item_grid[# RARITY, item_name.cherry] = 10;

item_grid[# NAME, item_name.mango] = "MANGO";
item_grid[# RARITY, item_name.mango] = 2;

item_grid[# NAME, item_name.kiwi] = "KIWI";
item_grid[# RARITY, item_name.kiwi] = 15;
Code:
/// @description Step Event
if (keyboard_check_pressed(vk_space)) {
    repeat (100) {
        scr_pick_item(); //for testing purposes I'm not using the item, just increasing the count in the script
    }
}

if (keyboard_check_pressed(ord("R"))) {
    game_restart();
}

if (keyboard_check_pressed(ord("Q"))) {
    game_end();
}
Code:
/// @description Draw

var _x_start = 0;
var _y_start = room_height - room_height/8;
var _spacing = room_width/item_name.height

for (var i = 0; i < item_name.height; i += 1) {
    var _height = item_grid[# COUNT, i];  
    draw_set_color(c_red);
    draw_rectangle(_x_start + (i * _spacing), _y_start, _x_start + (i * _spacing) + (_spacing - 5), _y_start - (_height * 3), false);
    draw_text(_x_start + (i * _spacing), _y_start + 10, item_grid[# NAME, i] + " COUNT = " + string(item_grid[# COUNT, i]));
}
Code:
/// @description Clean Up

//destroy data structures
ds_grid_destroy(item_grid);



Code:
/// @description Pick Item Based on Rarity

//get total a sum of all rarities
var _count = 0;
for (var i = 0; i < item_name.height; i += 1) {
    _count += item_grid[# RARITY, i];
}

//pick a random number
var _roll = irandom(_count - 1);

//go through array again
for (var i = 0; i < item_name.height; i += 1) {
    var _rarity = item_grid[# RARITY, i];
    if (_roll < _rarity) {
        item_grid[# COUNT, i] += 1; //this line is just for the drawing purposes
        return item_grid[# NAME, i]
    } else {
        _roll -= _rarity;
    }
}

Anyway, this seems to work.

Follow up question: How do I distribute rarity along a bell curve? Say I have 100 items and I don't want to manually calculate their rarity because I'm constantly adding and removing items and because I don't want to have to manually figure out the math each time.
 

NightFrost

Member
I'm honestly not entirely sure I understand what you're saying, but I think what you're saying is the same as this method which I found on The Coding Train:
Yeah he spends 20 minutes explaining it, but the same idea. He kind of needlessly wastes time normalizing the thing just so he can random(1) instead of against the sum though. The idea of substracting is an interesting alternate take but requires an inelegant fixing of the index value after the loop. But there's no major differences between mine and the video's solutions, both of these require two loops, first one to understand the sum of the ratios and a second one to figure out which item the random selection points at.

As for bell curves, the thing that springs to mind are the old D&D encounter tables with a 3d6 roll. Because you roll and add together multiple dice the results will distribute on a bell curve. Encounters at values 10 and 11 will be the most common for being the most common results of the roll. So for that type of selection I guess you'd need to code a system that builds a list with most common results at the center, then creates a random number in bell curve manner.
 

samspade

Member
Yeah he spends 20 minutes explaining it, but the same idea. He kind of needlessly wastes time normalizing the thing just so he can random(1) instead of against the sum though. The idea of substracting is an interesting alternate take but requires an inelegant fixing of the index value after the loop. But there's no major differences between mine and the video's solutions, both of these require two loops, first one to understand the sum of the ratios and a second one to figure out which item the random selection points at.

As for bell curves, the thing that springs to mind are the old D&D encounter tables with a 3d6 roll. Because you roll and add together multiple dice the results will distribute on a bell curve. Encounters at values 10 and 11 will be the most common for being the most common results of the roll. So for that type of selection I guess you'd need to code a system that builds a list with most common results at the center, then creates a random number in bell curve manner.
It's true that a lot of his examples tend towards over coding, I assume because it is easier to understand.

Another followup question, how would you increase the selection of rares with this method? If the array was a list and it was sorted rarest numbers lowest, I think you could just add a number to the roll which would increase your chance of landing farther, and thus rarer, down the list. Is this the best solution?
 

NightFrost

Member
Yeah controlling the random range could be used to make sure only certain items come up from the selection. The most difficult part would be setting the appropriate range.

I also thought of a little easier way to create that bell curve selection. You'll put the items into the list on order from rarest to most common, then generate a random value that is twice the length of the list minus one, switching to count from end to start if it goes over the length. Let's assume you have ten items, with rarest at list position one and most common at ten. You create a random number on bell curve and it comes up fifteen, so you start at position ten and take five steps ending up at position five.
 

samspade

Member
Just as a note for completions sake, an easy method I found for adjusting the rarity using the above method is with exponential decay. Since high numbers are more common, the below formula (which can be messed with to provide a variety of different values) returns high numbers for low input and low numbers for high input. You can add this number to the initial rarity. So lower numbers, more rare, will get a higher number added to them while higher numbers, more common, will get a lower number. By tweaking the values, you can easily increase rares with a great degree of control.


Code:
///scr_return_exp_decay_num(input)

return 6 * power(0.9, _argument0);
 
Top