• 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!

GameMaker Query regarding ds_grids and passing info to a var

S

SLx

Guest
Hi guys, I'm finally taking the dive into game development and had some very limited experience with GML about a decade ago during the GML 5.1 era. I used to do some (pretty bad) sprite work for a friend back then so unfortunately I don't have a ton of experience with the GML side of things.

---

I'm working with GMS2 and am in the midst of trying to put together a straight forward turn-based combat game. I'm trying to figure out what the best way to handle a weapons system is, without having to rely on individual objects per weapon. Allow me to elaborate:

Say there are 5 weapons: Pistol, Shotgun, SMG, LMG, Sniper.

Each of these weapons will share properties but the values will differ. For example, the properties can be:
0 Name -> A string with the weapon's name.
1 Damage -> A number with the amount of damage the weapon does.
2 Ammo -> Another number; how much ammo the weapon can hold.
3 Accuracy -> How accurate the weapon is (All from 0-100%, we'll say the LMK might only be 60-70% given the spread)

What would be the best way to handle this without relying on individual objects storing these variables?

I presume a ds_grid would be the optimal way. So I'd be setting up a ds_grid in the Create event of a controller object, or alternatively a script?
wpns = ds_grid_create(5,4); // I presume that the width (5 weapons) will be the width in columns, so I then set the height (4 values/properties) to be the rows.

Would I be correct in setting up the rest of the values as follows:

ds_grid_add(wpns,0,0,"Pistol"); //Name of the first weapon, Pistol
ds_grid_add(wpns,0,1,4); //Damage the Pistol does
ds_grid_add(wpns,0,2,10); //Ammo the pistol can hold
ds_grid_add(wpns,0,3,90); // Accuracy of the pistol (truth be told I'll handle this value differently but I'll leave it for the sake of an example.

I'd then go on to add the next weapon, the Shotgun, using ds_grid_add(wpns,1,0,"Shotgun"); correct? (Iterating 0-3 through the Y).

---

Once set, would I be able to reference this list and the values with my player object with a variable called 'heldweapon'? As such it would be dynamic depending on what weapon is being used. How would I handle an attack (with the shotgun, for e.g)? I can't do something such as:

obj_enemy.HP -= heldweapon[1,1];

...as this would be a regular 2D array, and the value [1,1] is hardcoded. What would be the best way to pass the information from the ds_grid to the heldweapon variable?


Apologies for the long post, just wanted to be concise. Any help with this would be appreciated. Can speak in DMs or on Discord if necessary.

Thanks.
 

NightFrost

Member
A ds grid or an array is a typical way to hold item data such as yours. A further refinement is to use enumerators so you can do away with magic numbers in your code. In other words
Code:
// Define weapons
enum Weapon_Type {
    Pistol,
    Shotgun,
    SMG
    LMG,
    Sniper
}

// Define weapons data
enum Weapon_Data {
    Name,
    Damage,
    Ammo,
    Accuracy
}
Now when you create for example an array holding your weapons you can do:
Code:
// Define weapons
global.Weapons[Weapon_Type.Pistol, Weapon_Data.Name] = "Deagle";
global.Weapons[Weapon_Type.Pistol, Weapon_Data.Damage] = 10;
...
Player's Heldweapon simply holds a Weapon_Type enumerator value and can then be used to read data from the definitions
Code:
Heldweapon = Weapon_Type.SMG;
...
...
obj_enemy.HP -= global.Weapons[Heldweapon, Weapon_Data.Damage];
Or read it to a variable if you need to modify before applying to enemy.
 
  • Like
Reactions: SLx

Bart

WiseBart
A small addition to the above.
If you want to read from/write to a ds_grid using a similar syntax as for 2d arrays you can use the ds_grid accessor #.
Then you get this:
Code:
wpns[# xpos, ypos]    // In case wpns is a grid
Instead of this:
Code:
wpns[xpos, ypos]      // In case wpns is an array
 
  • Like
Reactions: SLx
S

SLx

Guest
A ds grid or an array is a typical way to hold item data such as yours. A further refinement is to use enumerators so you can do away with magic numbers in your code. In other words
Ah yes, enums are a part of GM now - I forgot about that! That's actually a great idea, thank you very much for the suggestion and the examples. Super helpful. :)

If you want to read from/write to a ds_grid using a similar syntax as for 2d arrays you can use the ds_grid accessor #.
Great point - I had seen the unique accessors for lists/maps/grids before but actually completely forgot about them. Appreciate the tip!
 

NightFrost

Member
A small addition to the above.
Good call, OP was talking about ds grids so I should have used grids in examples. One little trick that can be used on creating those data grids is to add a Length as the final entry to enumerators and use that to define grid size.
Code:
enum Weapon_Type {
   Pistol,
   Shotgun,
   SMG
   LMG,
   Sniper,
   Length
}
...
global.Weapons = ds_grid_create(Weapon_Type.Length, Weapon_Data.Length);
This'll always give the grid a proper amount of rows and columns no matter how much you change your enum definitions.

EDIT: a more general tip, if you start a project where you need to define lots of data, it saves some time to create a script to do the lifting, and it is less error prone as well. Using your weapons data as an example, instead of writing for dozens of weapons something like:
Code:
global.Weapons[Weapon_Type.Pistol, Weapon_Data.Name] = "Deagle";
global.Weapons[Weapon_Type.Pistol, Weapon_Data.Damage] = 10;
global.Weapons[Weapon_Type.Pistol, Weapon_Data.Ammo] = 8;
global.Weapons[Weapon_Type.Pistol, Weapon_Data.Accuracy] = 4;
you'd just call the script instead:
Code:
scr_weapon_add(Weapon_Type.Pistol, "Deagle", 10, 8, 4);
Well you also could use array-in-array formatting to lessen the amount of writing by doing
Code:
global.Weapons[Weapon_Type.Pistol] = ["Deagle", 10,, 8, 4];
but when your script is properly formatted you receive those tips at the bottom of the window so you always know what value you are writing, unlike when you write an array as above. You'll also avoid various mistakes in entering data, like above where a doube comma typo breaks the data. This would not happen if you used a script. And when you look at the data later and wonder, I wrote an eight there, what does that mean, you don't know until you cross-reference with your enums and count the position. With a script you just put the cursor above it and the tip immediately tells you it is in ammo position of the script call.
 
Last edited:
S

SLx

Guest
general tip, if you start a project where you need to define lots of data, it saves some time to create a script to do the lifting, and it is less error prone as well.
I decided to go ahead and do that as I didn't want to crowd up the create event. Instead the first line in my controller object the first line is scr_initWeapons(); which has all of the enums set and the appropriate values corresponding with one another. However, I actually went ahead and did it the suboptimal way. I wasn't sure if I could multi-define so I drove ahead and committed like 140 lines of declarations lmao.

Noted for future use all the same though!

If I could trouble you further, lets say the player finds a chest. In it is a random weapon that the player will automatically equip after interacting with the chest. Is it possible to pull up a random enum value? I thought this could be doable by perhaps adding an 'ID' to the Weapon_Data but I wasn't seeing any success with it. Some Googling on the matter didn't seem to return much results for GML also - some C++ and Java stuff that seemed mostly irrelevant given the different functions those languages use.

I know that GM uses a set seed on launch so I'd need to have randomize(); at the beginning of the game if I wanted to guarantee a random weapon every time.

Essentially interacting with the chest would immediately 'roll' a random value from the Weapon_Type enum (which would have its appropriate Weapon_data already set thanks to the Create event above), thus equipping whatever weapon was found in the chest.


Thanks again for the help Nightfrost - really do appreciate it.
 

NightFrost

Member
Yeah, that can be done. Short explanation: when you don't assign values to enumerator, GM assigns them integers from zero upwards. So the weapon types in the code fragment are simply integers from zero to four. Again it is best to go with the Length trick so you can later modify the enum without consequence.
Code:
Random_Weapon = irandom(Weapon_Type.Length - 1); // Minus one so that Length value itself will not be selected.
 
  • Like
Reactions: SLx
S

SLx

Guest
Short explanation: when you don't assign values to enumerator
Gotcha, was hoping it would be something along those lines. Thanks again NightFrost, really appreciate the responses and the help. :)

For the sake of not bumping without some additional content - I do have another question. Say I wanted to add random traits to weapons such as Knockback, Stun and Bleed. However, I want to put a certain weight value on the chances of what trait you get. Say 50% of the time it's Knockback, 20% of the time it's Stun and 5% of the time it's Bleed. The remaining/unaccounted for 25% is no trait - the weapon has no bonus or unique effect. What would be the most optimal way to add a weighted chance system like this?

I was trying it with enums (that contained the % chance for each trait) at one point last night but didn't have any success. I'll need to feed the % chance values into something bigger that will amalgamate the % of each trait and then randomly roll. Any input would be appreciated - thanks guys!
 

NightFrost

Member
I typically use arrays for weighted chances. Let's assume your traits are enumerated so you can communicate them efficiently in your code. You'd create a weight array for them:
Code:
// Traits enum
enum Trait {
   None,
   Knockback,
   Stun,
   Bleed,
   Length
}

Trait_Weights = array_create(Trait.Length);
Trait_Weight[Trait.None] = 25;
Trait_Weight[Trait.Knockback] = 50;
Trait_Weight[Trait.Stun] = 20;
Trait_Weight[Trait.Bleed] = 5;
Trait_Weight_Total = 100;
If the weights don't change, you can do that all in Create, possibly set up a loop to sum up that weight total if you think you'll alter the weights or add more entries later. If the entries and weights are dynamic then you sum up the total as you build the array. You can then select the trait by randomizing up to total weight.
Code:
var _Selected_Trait = Trait.None; // Set a default fallback value.
var _Selection = irandom_range(1, Trait_Weight_Total);
for(var i = 0; i < Trait.Length; i++){
    if(_Selection <= Trait_Weight[i]){
        _Selected_Trait = i;
        break;
    } else {
        _Selection -= Trait_Weight[i];
    }
}
I use this so often I have a generic script that receives an array and total weight, then returns the chosen value. The weights don't need to add up to 100 because the total is used to randomize the selection. The irandom_range starts with 1 so you can set weights to zero and then those particular entries will never be selected.
 
Top