GML Inventory System Design

D

DarthTenebris

Guest
Hello everybody,

I'm working on an inventory system for my game, and would like some ideas. I currently have an inventory for the player which is a ds_map, with 22 slots each also a ds_map. I plan on having several items, and am wondering what is the best way to approach it. The items are to be somewhat customizable, for example a piece of paper will have the same item name and texture, but it might contain different text (I'm planning on using the papers as the unofficial tutorial, to let the player explore the game by themselves and find clues along the way). Another example is a replica of Minecraft's recent addition of "suspicious stew", which is an item that gives the player a random (hidden) effect, depending on the ingredients the stew was made from. I would like to have a food item that heals a hidden amount of health. Basically I'm looking for the equivalent of Minecraft's NBT system it seems. Any idea on how I can do this?

EDIT: Item.none doesn't exist, it's just a placeholder for an empty slot so the diagram doesnt look weird. An empty ds_map will not have a texture key for it, and thus my code defaults to drawing ItemTexture.none, an empty sprite (white box with alpha = 0).
upload_2019-12-22_16-5-1.png
This system feels tiring to use, as I have to create an item fresh from the start, writing down the id, texture, etc. I could write a script to automate it, but I think there might be a more elegant solution, worth exploring the problem more.
upload_2019-12-22_15-59-37.png
Doing it this way however will not allow items such as the paper item to be flexible. If I am to follow this path, I would need something similar to Minecraft's NBT system.

Thank you for your time.
 

Simon Gust

Member
I would like to recommend not using ds_maps inside ds_maps, or the same kind of data structure stacked in general.
You're going to have a hard time with displaying your inventory using ds maps, they are not loop-friendly.
Also, it's better to have slots hold single values for item ids or index to other data structures. If you start storing information in inventory slots, you will be facing problems.

Currently, I'm using objects (as items) + arrays or ds_list as the inventory to hold all the instance ids for the items.

The reason I use instances for items is that objects are versatile and fit every aspect of an item state, such as being inside an invetory, chest, on the ground or in your hand.
Instances can have their own variables and therefore editable in every way.
The array is used to list all these instances. This way I can loop through it for displaying the inventory, stacking items or finding items in general for crafting purposes.

A 1D array or ds_list should suffice, you should not use a 2D array or ds_grid even if your inventory does look like it does in Minecraft.
You can wrap around the inventory spaces to make it look like a grid using mod and div.

Inside every slot of your inventory you can store, instead of an index, an id for an instance representing the item.
items have both constant and variable values that need to be given:
by default (constant, name given by default for example)
and
by you (variable, different colour due to enchantment for example).

On item creation you can distribute these constant values to the instance based on which item it is.
Code:
/// scr_create_item(item_index, item_amount)
var item_index = argument0; // item identifier
var item_amount = argument1; // how many should be generated

var item = instance_create(x, y, obj_item);
    item.index = item_index;
    item.amount = item_amount;
    item.name = *name of the item based on item_index*;
    item.desc = *describtion of the item based on item_index*
    item.damage = *damage of the item based on item_index*
    ...
    item.sprite_index = asset_get_index("spr_" + asset_get_index(item.name)); // distribute sprite to item based on name inside resource tree
   
return (item);
All of the variables given to the item are based of on the item index and can be changed at will inside this item instance and it will not affect any other item that has the same index.
Only do this however if your item isn't stackable or you'll get into trouble.

This script I'm using returns the instance id back to where the script was called from, which is usually for a slot in the inventory or the overworld.
Code:
global.inventory[5] = scr_create_item(items.wooden_sword, 1);
Now global.inventory[5] will refer to that instance.
 

DukeSoft

Member
I can recommend using ds_grids for an inventory with arrays for storage per item - using an enum as a key-based index for items. If you'd swap out instances for arrays with enum'ed keys in Simon's answer, I could agree too (e.g. a 1D array or list with array items - abeit I feel like grids are much easier to navigate in comparison to 1d arrays / lists).

The reason I disagree with using instances, is that each instance will generate quite the performance impact. I once was working on a 3D voxel world where I'd use instances for variable storage as well (no events, just variable storage) - but at 50k instances my game ran at 400 fps vs 3000 fps without the "empty" instances; https://forum.yoyogames.com/index.php?threads/vertex_submit-creating-massive-unknown-overhead.37034/

Please note that this opinion might change 180 degrees once 2.3 comes along and we have lightweight instances :)

EDIT: changed the link to the proper topic :)
 
R

robproctor83

Guest
Ya instances are quite performance heavy especially compared to something like an data structure. However it's all relative so if you only had say a couple dozen items on screen at a time you probably wouldn't notice a difference. In my opinion I would go with a data structure as it seems more appropriate, but that also depends on what exactly you need. Anyway my solution to a similar problem was to save everything into a grid, and the grid stores a list and then each item in the list has behavior scripts which run at different times. There is a on pickup sript, an on equip script, on hit, on attack and so on. Then when I pickup that item I see if it has a custom behavior and if so I execute it. This way I can add health on pickup, etc.
 

Simon Gust

Member
I don't understand why people say objects are performance heavy. They really aren't if you don't do collision checks with them.
The reason I disagree with using instances, is that each instance will generate quite the performance impact. I once was working on a 3D voxel world where I'd use instances for variable storage as well (no events, just variable storage) - but at 50k instances my game ran at 400 fps vs 3000 fps without the "empty" instances; https://forum.yoyogames.com/index.php?threads/vertex_submit-creating-massive-unknown-overhead.37034/
Ah yes, the good old inventory with 50k slots in it. But seriously, if you code objects right, they really don't affect performance. And why would they be "empty instances". If you're using instances to just hold data then you're a fool.
In my post I said that I use the instances for every state an item can be in: in the inventory (where most of the step event is halted), on the overworld (where physics are applied to that item and can be picked up) and when using the item (just an animation with collision checking with enemies around). This is exactly what objects are for. I assume your player is an object, not a ds_list right?

I can recommend using ds_grids for an inventory /..../ - abeit I feel like grids are much easier to navigate in comparison to 1d arrays / lists).
I don't see how 2D structures are easier to navigate, just because your inventory looks like a grid doesn't mean you should implement it as such.
You loop through it the same way using a for loop (or 2 for a 2D structure) to display them, use them for crafting, check on them etc.
The only thing that's easier with a 2D array is displaying the slots, where in the 1D array you have to utilize mod and div
2D
Code:
for (var i = 0; i < wdt; i++) {
    var xx = 32 * i;
for (var j = 0; j < hgt; j++) {
    var yy = 32 * j;
}}
1D
Code:
for (var i = 0; i < len; i++) 
{
    var xx = 32 * (i mod inv_wdt);
    var yy = 32 * (i div inv_wdt);
}
 

GMWolf

aka fel666
I don't understand why people say objects are performance heavy. They really aren't if you don't do collision checks with them.
Objects no, but instances to incur a significant memory and performance hit.
Possibly not a big deal on desktop but you really don't want to be paying that cost on mobile.
 

Simon Gust

Member
Objects no, but instances to incur a significant memory and performance hit.
Possibly not a big deal on desktop but you really don't want to be paying that cost on mobile.
You bring this up as if a single instance will cut fps by half and double the memory. I think it isn't that bad.
 

Simon Gust

Member
Oh yeah I misunderstood. One instance per system is fine. Just don't have an instance per item or something.
No no, I was picking on you. It is one object (obj_item) and for every item (for every stack of dirt, or every wooden sword) one instance.
But still, if you bundle them together correctly, you won't have more than 30 on the ground at once and another 50 (without step code) inside the inventory, depending how big the inventory is.

Same thing if you have 100 enemies on screen at once except the items are very short of code. Most of the functions the item has comes externally from every other object (player, enemy) using it or being used upon it.
And clearly I have less instances of obj_player than instances of obj_item.
 
D

DarthTenebris

Guest
I know having an instance for every item is probably bad, but what if I deactivate the instance, so it doesn't do anything? I can still access and modify its variables, even if it's deactivated. Also, my inventory is not that large, at most 20 player slots and 5 chest slots, plus another "temporary" mouse slot used when the player moves items between slots.

What about an array of instances for each inventory?
Code:
obj_player:
for (var i = 0; i < 5; i++) {
     for (var j = 0; j < 5; j++) {
          if (((i != 0) || (i != 2) || (i != 4)) && (j != 5)) { // the way my inventory is laid out means I don't need slots at those coordinates
               playerInventory[i, j] = undefined;
          }
     }
}
mouseInventory = undefined;

Adding items to an inventory:
playerInventory[2, 3] = instance_create_layer(0, 0, "Instances", obj_ItemPaper);
instance_deactivate(playerInventory[2, 3]);
playerInventory[2, 3].text = "Some random text";

obj_ItemPaper Create:
itemId = Item.paper;
texture = ItemTexture.paper;
text = "";
As I'm typing this reply I realized all I needed to do was to create a script that does everything for me. I'm dumb. I'll see where that gets me.

Thank you for your time.
 

Simon Gust

Member
I know having an instance for every item is probably bad
I really don't know where this comes from. Thousands of people use instances for players, npcs, projectiles and it isn't bad. I just don't understand.

but what if I deactivate the instance, so it doesn't do anything? I can still access and modify its variables, even if it's deactivated.
You can do that. Just be aware that mass-deactivation / -activation can cause a performance hit. So for example, if your player drops every item on death, you may get a hickup in your game, probably not a issue with an inventory size of ~20.
 

TailBit

Member
btw, for your minecraft approach you could just have the default values for all the items stored in a list ..
Code:
// your Item.paper position in the item database ds_list
{
    texture:0
    text:""
}
so for a blank paper all you need in the inventory is the id .. when reading the item data it will first check if the position in your inventory have a value to that should overwrite the base value, if not then just read the base value .. so if you have the text:"this is a test" in the inventory then it will be read instead of text:""

now .. the minecraft crafting is interesting, you can place items on a 3x3 slot, but when looking for a match then it will shrink your array based on how much of the area you used before comparing to the recipe list arrays
let's take a bed
Code:
{
    recipe: "aaa
                 bcd"
    a{ item:wool }
    b{ item:plank }
    c{ item:plank }
    d{ item:plank }
    output: bed
}
each plank is its own letter here, which means the planks don't have to match, but the wool is the same, so they do..

you then have the bed item search the items used to make it, for variables that it shares (that is not among the basic data like name and texture), and it will carry over the color variable from the wool

the bed can have an array of textures to use which color is the index for

something like that should work for your soup
 

DukeSoft

Member
I really don't know where this comes from. Thousands of people use instances for players, npcs, projectiles and it isn't bad. I just don't understand.
That doesn't mean its good practice :+) Most of my bigger projects use very few instances. Particles / projectiles / walls (or items that need basic collision checking), text notifications / boxes, sound players usually all exist out of data structures and not instances. (Also doesn't mean that is good practice either)

You can do that. Just be aware that mass-deactivation / -activation can cause a performance hit. So for example, if your player drops every item on death, you may get a hickup in your game, probably not a issue with an inventory size of ~20.
I see where you're coming from though, yet I wouldn't go with the setup.

If you just look at it plain from requirements - dropped items that need to be interacted with can be instances. I agree. Easy to use and manage. Can be something else than instances if you'd have thousands laying around, but you probably wouldn't. Take the advantage of the engine and use instances.

Storing inventories and items? I assume you'd have lootboxes with inventories, a player, maybe NPC's with inventories. Why nest a bunch of active instances (or deactivated) into that? You'll never need "collisions" or "draw" events for items (or even step) in your inventory. That could differ on implementation ofcourse, but I'd do inventory drawing / handling based on a simple datastructure somewhere in memory (be it array, 2D array, grid, ds_list, maybe even buffers or whatever), instead of instances with states.

For a test setup you could do;
{start time measure}
Create 100 instances
Assign values to each
Assign 50% of them to an inventory (i guess a datastructure within an object?)
Disable them all
List the contents of the items and print them out
Enable them all
Destroy them all
{end time measure}

{start time measure}
Create 100 arrays
Assign values to each array (can be on creation)
Assign 50% of them to an inventory (i guess also a datastructure within an object?)
List the contents of the items and print them out
Destroy all the arrays
{end time measure}

Maybe don't even destroy them and look at memory usage / overall performance. I'm quite sure the latter setup is at least 50 times faster.

You wouldn't notice it on a small scale - true - but its still CPU usage that you could otherwise use for something else.

EDIT: Also, with 50k instances it went from 5000fps to 50.
 
Last edited:

Alice

Darts addict
Forum Staff
Moderator
Personally, I'd pick a combination of ds_maps/ds_list over other structures/instances. Not because of performance, but because it makes saving/loading inventory a no-brainer (mostly) - just use json_encode to prepare a string to save and json_encode to turn loaded string into in-memory structures. You didn't specify you plan to *save* the structures, but if a game has an inventory system - especially elaborate enough to involve separate player and chest inventories - there's a good chance a save system is needed at some point.

For that reason alone I'd use data structures for storage of inventory information. At the same time, having one instance per each inventory item might be appropriate for presentation/manipulation of UI information (e.g. serve as convenient handles for hovering or drag-and-drop operations).

----------

Compared to the setup you posted initially - you were onto something with a map containing player_inventory and chest_inventory. However, instead of *naming* each slot I'd rather have them represented as list items. So e.g. a 8x5 inventory grid will turn into 40-item list. UI layer can handle the mapping between grid coordinates behind the scenes based on these rules:
  • inventory column is equal to list_index mod inventory_width
  • inventory row is equal to list_index div inventory_width
  • list index is equal to inventory_column * inventory_width + inventory_row

----------

As for the internal structure of such an inventory:
  • at top level we would have ds_map of inventories (player, chest, maybe also mouse - assuming "mouse inventory" is something that is persisted between game sessions)
  • each inventory would consist of either ds_list of inventory items - if the inventory has multiple slots (player inventory, chest inventory) - or single inventory item - if the inventory has one slot only (mouse inventory, single equipment slot like weapon/armor/etc.)
  • each inventory item would be either ds_map of item properties or undefined to stand for a blank item (instead of that repetitive "none" item); you might want to double-check that "undefined" array/map property is stored as "null" when json_encode is used - otherwise go for other blank-item value like -1
Now, to avoid repetition while still allowing some flexibility:
  • define a dictionary of predefined inventory items - whether stored as included file with JSON map, parsed from a custom format or declared at startup with GML
  • in each non-blank inventory item store a property like "predefined_item" or "base_item" or something like that - it's a reference to corresponding predefined inventory item
  • when accessing properties from an inventory item, first look at its own properties, and if no such property is given, check the corresponding property in the base item
An example:
Given predefined items like this:
Code:
{
    "paper": {
        "type": "info",
        "icon": "paper",
        "title": "Piece of paper",
        "description": "[insert description here]"
    },
    "sword": {
        "type": "weapon",
        "weapon_type": "sword",
        "icon": "sword",
        "title": "Sword",
        "description": "A regular sword.",
        "atk": 10
    }
}
And an inventory like that:
Code:
[
    {
        "base_item": "paper",
        "description": "Use 'I' key to open inventory... oh wait, you just did that!"
    },
    {
        "base_item": "paper",
        "title": "Mage's Journal",
        "description": "Day 45: I just realised I forgot to write the journal for day 44."
    },
    null,
    null,
    {
        "base_item": "sword",
        "icon": "cooler_sword",
        "title": "Excalibur",
        "description": "It rocks!",
        "atk": 999
    }
]
You would get items equivalent to:
Code:
[
    {
        "type": "info",
        "icon": "paper",
        "title": "Piece of paper",
        "description": "Use I to open inventory... oh wait, you just did that!"
    },
    {
        "type": "info",
        "icon": "paper",
        "title": "Mage's Journal",
        "description": "Day 45: I just realised I forgot to write the journal for day 44."
    },
    null,
    null,
    {
        "type": "weapon",
        "weapon_type": "sword",
        "icon": "cooler_sword",
        "title": "Excalibur",
        "description": "It rocks!",
        "atk": 999
    }
]

----------

Finally, when it comes to the code structures, I'd arrange it into the following modules:
  • game data storage module - responsible for keeping all game data - the map the player is currently at, the player stats, quests progress and such - and of course the inventories state; the storage module is just responsible for retrieving, writing, saving and loading the game data, without going too much into purpose of specific game state components
  • inventory management module - responsible for managing the state of inventories behind the scenes - accessing the inventories data, adding/moving/removing inventory items within the stored state and so on
  • inventory UI module - responsible for displaying inventory information and allowing the player to manipulate it; for example, an inventory item that displays inventory item when hovered, removes inventory item when dragged and returns/moves inventory item when dropped would belong to inventory UI module; note that operations like removing or adding inventory item should be simple calls to inventory management module logic
Hope this helps. ^^
 
H

Homunculus

Guest
That’s exactly how I structure my inventories @Alice . The best thing you can do if you want a well structured inventory is keeping those three modules as independent as you possibly can (database, inventory data, UI).

For the UI, I go as fat as setting up an event system based on the observer pattern where instances can subscribe to “inventory events” in order to get notified whenever something changes (items are added or removed, the inventory gets destroyed or resized, ...). This means that the inventory system doesn’t need to know anything about the UI, it just fires events and forgets.
 
Last edited by a moderator:

Simon Gust

Member
That doesn't mean its good practice :+) Most of my bigger projects use very few instances. Particles / projectiles / walls (or items that need basic collision checking), text notifications / boxes, sound players usually all exist out of data structures and not instances. (Also doesn't mean that is good practice either)



I see where you're coming from though, yet I wouldn't go with the setup.

If you just look at it plain from requirements - dropped items that need to be interacted with can be instances. I agree. Easy to use and manage. Can be something else than instances if you'd have thousands laying around, but you probably wouldn't. Take the advantage of the engine and use instances.

Storing inventories and items? I assume you'd have lootboxes with inventories, a player, maybe NPC's with inventories. Why nest a bunch of active instances (or deactivated) into that? You'll never need "collisions" or "draw" events for items (or even step) in your inventory. That could differ on implementation ofcourse, but I'd do inventory drawing / handling based on a simple datastructure somewhere in memory (be it array, 2D array, grid, ds_list, maybe even buffers or whatever), instead of instances with states.

For a test setup you could do;
{start time measure}
Create 100 instances
Assign values to each
Assign 50% of them to an inventory (i guess a datastructure within an object?)
Disable them all
List the contents of the items and print them out
Enable them all
Destroy them all
{end time measure}

{start time measure}
Create 100 arrays
Assign values to each array (can be on creation)
Assign 50% of them to an inventory (i guess also a datastructure within an object?)
List the contents of the items and print them out
Destroy all the arrays
{end time measure}

Maybe don't even destroy them and look at memory usage / overall performance. I'm quite sure the latter setup is at least 50 times faster.

You wouldn't notice it on a small scale - true - but its still CPU usage that you could otherwise use for something else.

EDIT: Also, with 50k instances it went from 5000fps to 50.
It is true that the items inside the inventory don't do anything except for a single state check (if !in_inventory) and I could destroy them and transfer the data to a map or array.
I just want to have the same situation for reading and writing item values, it's not worth the extra nesting of your code just so your game is 1-2ms faster.
You have 16.67ms avaliable, use them. I see that it's a problem with 50k instances. But why would you lay out that many items?
You say that you'd rather use cpu usage somewhere else. On what then?
 

DukeSoft

Member
...
I just want to have the same situation for reading and writing item values, it's not worth the extra nesting of your code just so your game is 1-2ms faster.
You have 16.67ms avaliable, use them. I see that it's a problem with 50k instances. But why would you lay out that many items?
You say that you'd rather use cpu usage somewhere else. On what then?
That might be a bit opinionated and we're probably not going to find a middle ground if that's your opinion, but here's mine:

Please don't take this as a personal attack, but this is my view:

1) You're physically wasting power and wearing hardware of your users if you don't care about the 1 or 2 ms. If your game runs at a solid 120fps uncapped (so 60fps solid), those 2ms will account for 20% extra CPU time - effectively draining a battery, wearing fans, increasing your users' electricty bill, wasting power.
2) If you want your game to perform smooth, an uncapped 500fps vs an uncapped 5000 fps is a very big difference. In the end, if you cap it on 60, they both run at 60, but you have so much more room for extra's, you provide better support for older hardware, and allow for people to run multiple things at the same time.
3) I'd like to have people play my game on 144hz or even 240hz monitors, too. In that case, 2ms is a big deal.
4) Once you hit that threshold (where your game starts to have framedrops), you'll need to make room for new features by refactoring. If you'd kept efficiency in mind intially, you wouldn't have had to refactor bits that use 20% of your available processing power. (Allthough - don't micro-optimize early! Look at a proper balance between the work you put in vs. the result vs. how much it will take you to refactor). In this case, I think you'd spend as much time on either solution, yet one of those will have to be refactored and will take a lot of time.
5) In some cases it actually costs you (the developer) money - I'm running dedicated game servers on Amazon, where you pay for CPU usage. That 1 or 2 ms difference can be the difference between 1 VM running 2 game servers, or 1 VM running 5.

Now, I might be a bit biased (I even tend to use ~~uint instead of floor(uint) because its slightly faster) as I'm "intruiged" by efficiency and speed in programming, but I still think its a tradeoff you shouldn't make as a software developer.

EDIT:
TL;DR: my opinion is not "you have 16.6ms available, why not use it?", but "you have limited resources, choose wisely and use as little as you need."
 

Simon Gust

Member
That might be a bit opinionated and we're probably not going to find a middle ground if that's your opinion, but here's mine:

Please don't take this as a personal attack, but this is my view:

1) You're physically wasting power and wearing hardware of your users if you don't care about the 1 or 2 ms. If your game runs at a solid 120fps uncapped (so 60fps solid), those 2ms will account for 20% extra CPU time - effectively draining a battery, wearing fans, increasing your users' electricty bill, wasting power.
2) If you want your game to perform smooth, an uncapped 500fps vs an uncapped 5000 fps is a very big difference. In the end, if you cap it on 60, they both run at 60, but you have so much more room for extra's, you provide better support for older hardware, and allow for people to run multiple things at the same time.
3) I'd like to have people play my game on 144hz or even 240hz monitors, too. In that case, 2ms is a big deal.
4) Once you hit that threshold (where your game starts to have framedrops), you'll need to make room for new features by refactoring. If you'd kept efficiency in mind intially, you wouldn't have had to refactor bits that use 20% of your available processing power. (Allthough - don't micro-optimize early! Look at a proper balance between the work you put in vs. the result vs. how much it will take you to refactor). In this case, I think you'd spend as much time on either solution, yet one of those will have to be refactored and will take a lot of time.
5) In some cases it actually costs you (the developer) money - I'm running dedicated game servers on Amazon, where you pay for CPU usage. That 1 or 2 ms difference can be the difference between 1 VM running 2 game servers, or 1 VM running 5.

Now, I might be a bit biased (I even tend to use ~~uint instead of floor(uint) because its slightly faster) as I'm "intruiged" by efficiency and speed in programming, but I still think its a tradeoff you shouldn't make as a software developer.

EDIT:
TL;DR: my opinion is not "you have 16.6ms available, why not use it?", but "you have limited resources, choose wisely and use as little as you need."
I see you. I didn't exactly mean throw ms out of the window, more like, decide whether optimizing is worth based on how much can be saved and how much more complex the code gets.
I did spend a lot of time optimizing, primarily my rendering which used to be like 4ms and is now 0.5ms on average. And it took a lot, but enough for me to do it.
 

breakmt

Member
I use this "instance per item" approach too. Moreover I don't use arrays to hold inventory. I just put all items in special layer on map. I don't much care about optimisation, cause there won't be 50k items per map and also it is turnbased roguelike game.
I do it cause I don't want to put all variables in array and then put them back again. It is logical for me that item is instance. Also, it gives me more agile and common way for things like events per turn, cause I need system where something can happen with item even if it is in inventory.
 

NightFrost

Member
The best thing you can do if you want a well structured inventory is keeping those three modules as independent as you possibly can (database, inventory data, UI).
This, I'll just hop in to point out, is a very good idea in general. There's a reason why in web design the MVC frameworks (model-view-controller) and their relatives are popular. They provide a clean separation between elements, and interfaces to access the functionality, which makes the flow of data from DB to UI and back easy to build and manage. And in GMS it gives you portable code. If your other project also needs an inventory system that works in same manner, you just import the pieces into it and tweak the UI part (having kept the code modular, so it runs without errors in a void where only the relevant object(s) and their helper scripts are available).
 
Top