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.

Adapting an extension written for GameMaker Studio 1.4 for use in GameMaker Studio 2.0

In this article: how I adapted an extension written for GameMaker Studio 1.4 (the free version) so that it worked in GameMaker Studio 2 (the new/paid license version).

I wanted to use dicksonlaw583‘s JSOnion extension in GameMaker Studio 2, but the extension was written for GameMaker Studio 1.4. Compiling with the extension in use kept giving me a vague “invalid token” error in 4 different places (luckily, line numbers were included) but no clues as to what token, exactly, was invalid.

It took a lot of trial and error and digging on Google to figure it all out, so I’ve documented what I needed to do here in case it helps someone else.

Importing the Extension

First thing I got confused on was how, exactly, to bring the extension code into GameMaker 2.

The JSOnion instructions say “drag JSOnion.gml into your open project”, but that doesn’t work (or at least it didn’t for me, no matter where I tried to drop).

What I actually had to do is go to the Resources panel and right click on Extensions. Select Create Extension.

Escaping characters

Next, I tried to run my game but the build failed due to a number of “invalid token” errors.

Each error looks like this:

Error : gml_Script__jso_decode_map(84) : invalid token

I was pretty lost here, and the GameMaker docs provided little help.  Searching for “invalid token” comes up with nothing and this list of GML compiler errors offers only a vague hint:

You have an invalid character in your game code, which can happen with foreign language characters or Unicode

Hmm. The extension code looked okay to me…at least, it wasn’t full of bizarre characters and nothing looked weird at the line numbers GameMaker was complaining about.

This particular forum post gave me an important clue: single quotes are considered invalid characters in GML 2! Okay, that was something to go with. I went through the JSOnion.gml code and replaced single quotes with double quotes and, in order to do things like a set of double quotes within double quotes, escaped the interior set of double quotes with a backslash.

I also discovered (through more trial and error) that backslashes used for purposes other than escaping things are also considered invalid characters, so I had to escape those, too.

In summary, to adapt an extension meant for 1.4 to work with 2.0, you have to go into the .gml file and: 

  • Remove usages of single quotes
  • Escape \ and ” characters
    • escaped slash looks like this: “\\”
    • escaped double quotes looks like this: “\””

Note: each time I wanted to test a change I made to JSOnion.gml, I had to go into the JSOnion extension, delete the reference to the file, and re-add the file to get the changes.

Adding macros (constants)

One more thing to do: add the macros.

JSOnion came with a file, constants.txt, that I wasn’t sure how to use. The repo instructions gave a clue: “import constants.txt as macros”, but I think there might’ve been some UI changes since GML 1.4, because I couldn’t figure out how to import this file in the version I’m using.

What I ended up doing was adding the constants manually in the “Macros” section of the extension properties. (See screenshot below).

(These are indeed required, too – if you don’t add the macros and you try to use JSOnion methods, you’ll get errors when trying to read a variable that wasn’t set.)

With the extension code cleaned of “invalid tokens” and the macros in place I can now build.

Was this the right way to do it? I don’t know – I’ve only worked in GameMaker for a few weeks and this was my first attempt at bringing in an extension, so I am far from an “expert” at this point.

Also, I haven’t had a chance to actually use the extension code yet (that’s a subject for another post), but I did get it building so I wanted to share the process in case anyone else is also trying to adapt an old/free extension for the newer/paid version of GameMaker.