Creating an item inventory array in GameMaker Studio 2 from a Google Spreadsheet exported as JSON

In this article: creating a Google spreadsheet of items for my RPG-style GameMaker game, exporting the spreadsheet as JSON, importing the JSON into GameMaker Studio 2, processing the item data into a usable format, and managing the player’s collection of “owned” items.

I’ve been building a hobby project in GameMaker Studio 2 for about a month now. I wanted to add the concept of “items” to my game – stuff like swords, armor, etc. – that grant stats and can be won via quests, managed in an inventory, and assigned to individual heroes. (Typical RPG stuff.)

The game itself might have hundreds or thousands of items (in theory), but the player is likely to have just a small selection of the available items in their inventory. This feature calls for two separate data structures: one to hold all of the game’s items, and one to hold the items the player has in their possession.

Note: I’m pretty new to GameMaker, and I’m documenting my findings as I go for future reference and ease of sharing with friends who are also using GameMaker. There’s quite possibly a better/easier way to do this than the method I’ve cobbled together here. 

Creating the items spreadsheet

For the items themselves, I created a spreadsheet in Google Sheets. The items are arranged in rows, and they have a collection of stats.

Some of them are restricted to certain classes, and Google Sheets lets you restrict choices in a particular field to a predefined list of options so you don’t make typos or introduce inconsistencies where you didn’t want them.

Exporting the data from the spreadsheet

I used Export Sheet Data to export my items as a JSON object. I checked these two settings:

  • Export sheet arrays
  • Export contents as array

Checking these two settings simplifies the structure of the JSON object I’ll be working with inside GameMaker.

Export sheet arrays

With “Export sheet arrays” checked:

[
  {
    "name": "Rusty Broadsword",
    "prestige": 0,

Without “Export sheet arrays” checked:

[
  {
    "Rusty Broadsword": {
      "name": "Rusty Broadsword",
      "prestige": 0,

As you can see, leaving this setting unchecked creates another layer of nesting. I found deeply nested JSON difficult to deal with inside GameMaker, so I wanted to minimize how many “layers” my data set had.

Export contents as array 

With “Export contents as array” checked:

[
  {
    "name": "Rusty Broadsword",
    "prestige": 0,

Without “Export contents as array” checked”:

{
  "items": [
    {
      "name": "Rusty Broadsword",
      "prestige": 0,

With this setting off, you still get all your items in an array but that array is itself a property and it’s contained within an object. This is just more “layers” to drill through in GameMaker and I prefer the simplified data structure.

The end result was this JSON object, which I’ve pasted into a gist:


[
{
"name": "Rusty Broadsword",
"prestige": 0,
"slot": "mainHand",
"rarity": "common",
"classRestriction": "warrior",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 3,
"strength": 0,
"armor": 0,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Rusty Knife",
"prestige": 0,
"slot": "mainHand",
"rarity": "common",
"classRestriction": "",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 1,
"strength": 0,
"armor": 0,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Rusty Old Shield",
"prestige": 0,
"slot": "mainHand",
"rarity": "common",
"classRestriction": "warrior",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 0,
"strength": 0,
"armor": 2,
"defense": 1,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Cracked Staff",
"prestige": 0,
"slot": "mainHand",
"rarity": "common",
"classRestriction": "wizard",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 2,
"DPS": 0,
"strength": 0,
"armor": 0,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Chainmail Vest",
"prestige": 0,
"slot": "chest",
"rarity": "common",
"classRestriction": "rogue",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 0,
"strength": 0,
"armor": 3,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Chainmail Leggings",
"prestige": 0,
"slot": "legs",
"rarity": "common",
"classRestriction": "rogue",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 0,
"strength": 0,
"armor": 3,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Chainmail Boots",
"prestige": 0,
"slot": "feet",
"rarity": "common",
"classRestriction": "rogue",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 0,
"strength": 0,
"armor": 3,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Chainmail Coif",
"prestige": 0,
"slot": "head",
"rarity": "common",
"classRestriction": "rogue",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 0,
"strength": 0,
"armor": 2,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Simple Ring",
"prestige": 0,
"slot": "jewelry",
"rarity": "common",
"classRestriction": "",
"noDrop": false,
"hpRaw": 2,
"manaRaw": 2,
"DPS": 0,
"strength": 0,
"armor": 0,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Simple Earring",
"prestige": 0,
"slot": "jewelry",
"rarity": "common",
"classRestriction": "",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 0,
"strength": 0,
"armor": 0,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 1,
"wisdom": 0
},
{
"name": "Basic Bow",
"prestige": 0,
"slot": "mainHand",
"rarity": "common",
"classRestriction": "ranger",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 3,
"strength": 0,
"armor": 0,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Cloth Shirt",
"prestige": 0,
"slot": "chest",
"rarity": "common",
"classRestriction": "",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 0,
"strength": 0,
"armor": 1,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Cloth Pants",
"prestige": 0,
"slot": "legs",
"rarity": "common",
"classRestriction": "",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 0,
"strength": 0,
"armor": 1,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Shabby Robe",
"prestige": 0,
"slot": "chest",
"rarity": "common",
"classRestriction": "wizard",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 5,
"DPS": 0,
"strength": 0,
"armor": 2,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 0,
"wisdom": 0
},
{
"name": "Simple Grey Robe",
"prestige": 0,
"slot": "chest",
"rarity": "common",
"classRestriction": "wizard",
"noDrop": false,
"hpRaw": 0,
"manaRaw": 0,
"DPS": 0,
"strength": 0,
"armor": 2,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 5,
"wisdom": 0
},
{
"name": "Blue Cotton Robe",
"prestige": 0,
"slot": "chest",
"rarity": "common",
"classRestriction": "wizard",
"noDrop": false,
"hpRaw": 5,
"manaRaw": 5,
"DPS": 0,
"strength": 0,
"armor": 3,
"defense": 0,
"accuracy": 0,
"stamina": 0,
"intelligence": 2,
"wisdom": 2
},
{
"name": "Sparkling Metallic Robe",
"prestige": 5,
"slot": "chest",
"rarity": "uncommon",
"classRestriction": "wizard",
"noDrop": true,
"hpRaw": 25,
"manaRaw": 50,
"DPS": 0,
"strength": 0,
"armor": 10,
"defense": 0,
"accuracy": 0,
"stamina": 2,
"intelligence": 10,
"wisdom": 10
},
{
"name": "Robe of Eternity",
"prestige": 10,
"slot": "chest",
"rarity": "epic",
"classRestriction": "wizard",
"noDrop": true,
"hpRaw": 50,
"manaRaw": 100,
"DPS": 0,
"strength": 0,
"armor": 15,
"defense": 5,
"accuracy": 5,
"stamina": 5,
"intelligence": 25,
"wisdom": 25
},
{
"name": "Tzip's Crooked Dagger",
"prestige": 5,
"slot": "mainHand",
"rarity": "rare",
"classRestriction": "rogue",
"noDrop": true,
"hpRaw": 25,
"manaRaw": 0,
"DPS": 15,
"strength": 5,
"armor": 0,
"defense": 3,
"accuracy": 3,
"stamina": 3,
"intelligence": 0,
"wisdom": 0
}
]

view raw

items.json

hosted with ❤ by GitHub

Bringing the item JSON data into GameMaker Studio 2

My main room has a controller object that I use to set up gameplay globals. In its Create script, I currently just use a flag to see if the init script has run yet (so it doesn’t run every time I re-enter the main room).

For loading the JSON data line by line, I used Jason Lee Elliott’s method he describes on his blog here.

if (!initGameDone) {
  //currency defaults
  global.gold = 100;
  global.diamonds = 10;

  //load all the game items from json
  //http://jasonleeelliott.com/using-json-data/
  var theJsonFile = file_text_open_read("items.json");
  var theData = "";
  while (!file_text_eof(theJsonFile)) {
    theData += file_text_read_string(theJsonFile);
    file_text_readln(theJsonFile);
  }
  file_text_close(theJsonFile);

  var decodedData = json_decode(theData);
  var defaultStuff = ds_map_find_value(decodedData, "default"); 
  //see "default" explanation below 

This is where it started to get weird. theData is an array of objects (makes sense). decodedData is a ds_map made from that array of objects (still making sense). Looking in the debugger, though, with a breakpoint on the line that creates defaultStuff, we can see there’s ds_list in here called “default”:

Weird, I didn’t put that there…  GameMaker did, and it’s documented here. From GameMaker’s documentation:

NOTE: When decoding arrays, there is a map with the key “default” ONLY when an array is the top level structure, and ONLY for that top-level array. Internal lists decode directly to ds_lists without being enclosed in a ds_map.

That’s why defaultStuff is made from finding the “default” value within decodedData.

The next I did was create a global variable to hold all the game’s possible items. This global variable is a ds_map. I wanted it to be a map so that each item in the game could be looked up by its name (I think “Rusty Broadsword” is more memorable and easier to work with than something like an ID number or a position in an array).

  global.allGameItems = ds_map_create();

  for (var i = 0; i < ds_list_size(defaultStuff); i++) {
    var rawItemMap = ds_list_find_value(defaultStuff, i);
    var newKeyName = ds_map_find_value(rawItemMap, "name");
    ds_map_add(global.allGameItems, newKeyName, rawItemMap);
  }

This is just a classic for-loop that looks at each item (represented as a map) in defaultStuff. It grabs the name property out of the map to serve as the key, and uses the item’s map as the value.

Putting items in the player’s inventory

The last step is to actually give a couple items to the player.

The player’s inventory is as ds_list (array) of ds_maps. In this code, item1 and item2 are actual items from the allGameItems ds_map that are found by their name.

  global.playerInventory = ds_list_create();
  var item1 = ds_map_find_value(global.allGameItems, "Rusty Broadsword"); 
  var item2 = ds_map_find_value(global.allGameItems, "Blue Cotton Robe");
  ds_list_add(global.playerInventory, item1);
  ds_list_add(global.playerInventory, item2);

  show_debug_message("player inventory size: " + string(ds_list_size(global.playerInventory)));

  //print what's in the player's inventory
  show_debug_message("PLAYER INVENTORY:");
  for (var i = 0; i < ds_list_size(global.playerInventory); i++) {
    var itemi = ds_list_find_value(global.playerInventory, i); 
    show_debug_message(ds_map_find_value(itemi, "name"));
  }

  initGameDone = true;
}

Since I haven’t yet implemented the player’s inventory screen, I wrote a for-loop to print the name of every item in the player’s inventory to the console. Hopefully this is self-explanatory: it just iterates through the player’s inventory and prints the name property of each item it finds in the inventory.

And there we have it: these items are managed in Google Sheets, exported as JSON, imported into GameMaker, and placed in the player’s inventory by name.