New versions of exINV are released for Game Maker: Studio 2 only. For the GMS 1 version, please refer to the legacy documentation
exINV is a Game Maker: Studio 2 asset that can be added to any project requiring a comprehensive and efficient slot based inventory system. The asset provides ready to use, general purpose modules that can be used to implement a full featured inventory system around your specific requirements.
The asset has been structured in a way that should allow you to program the behaviour of your inventories without having to change the provided scripts, your focus should be on using them to your advantage in your objects.
exINV is composed by a set of core modules that communicate with each other in order to produce the final graphical inventory output.
Database system
Stores and provides access to the data of every possible inventory item, along with its properties,
that can be added an inventory.
Inventory manager
Allows creating and organizing multiple concurrent inventories and perform all the required operations
to manage the inventory items. Inventories are handled essentially like data structures, as they are always
referenced by a numeric id and are global in scope.
User interface (UI)
The user interface uses the inventory manager data to display the inventories to the user, and
is in charge of reacting to the user input like mouse clicks or keyboard actions.
It is important to note that the UI and the inventory manager are totally decoupled.
You can throw away all the UI objects and implement your own without ever touching the inventory manager scripts.
Notification system
exINV uses a notification system based on the observer pattern in order to let the inventory manager and the UI
communicate with each other while keeping them decoupled.
You can however hook your objects to the notification system as well, allowing them to be notified whenever an
inventory update is fired (imagine for example the player object being notified when its weapon or equipment changes).
Before integrating exINV into an existing project, you are strongly encouraged to test it out by importing everything into a blank project and running the demo to get a feel of what you can do and expect beforehand.
Integrating and using exINV into your project requires a few steps:
While you may not need everything included in the asset package, it is strongly suggested to work in a subtractive way, by adding all the "ex" folders from the package resource tree to your game, and edit / remove what you don't need afterwards.
All the assets in those folders are namespaced so you should not find any name clash with your current resources.
The few assets outside of those folders are part of the demo. You are encouraged look at them since they essentially show a working example of how to implement the steps that follow, but are not meant to be imported directly into your project.
The first thing you have to do is initialize the inventory system.
You should only do that once and only once, ideally at game start.
In the demo, this is done in obj_game_controller.
In your project you should add this code in your game initialization routine.
Note that you need to provide the full sandbox path to your CSV files.
//initialize the system
ex_init();
//load the item information from the CSV files. Alternatively, you can create your own routine
ex_db_load("ex/inv_armor.csv","ex/inv_food.csv","ex/inv_potions.csv","ex/inv_weapons.csv");
//(optional) initialize the crafting system
ex_craft_init();
To create an inventory just call ex_inv_create specifying the size (number of slots). The script will return the unique inventory id to be used to reference that specific inventory in subsequent calls.
Inventories are data essentially structures. They will not be deleted on room change, nor when setting their variable to another value. Remember to destroy them with ex_inv_destroy when not needed anymore, or you'll run into memory leaks.
global.inventory = ex_inv_create(30);
All items are identified by a unique item key (as string), and have a maximum stack size. Other item properties can be defined in the item database files loaded in step 2.
ex_item_add(global.inventory,"weapon_fire_arrow",32);
ex_item_add(global.inventory,"food_cheese",8);
So far we loaded the item database, created an inventory, and inserted some items. All of the above generates no visual output though, since we have not yet involved the UI. This is because inventory managament and UI are decoupled, you are therefore not required to display inventories at all while still being able to interact with them.
In order to display an inventory, you need a panel object that determines how it will look and how the user is able to interact with its items. The asset provides a few sample panels for the most common use cases:
ex_ui_panel_open(global.inv_player, obj_inv_panel_backpack, 32, 32, layer);
You are strongly encouraged to look at the provided demo and go through every event of obj_inv_controller, since this is where most of the above (and a lot more) is defined, and use that to build your own.
The next section answers some common questions about customizing the defaults.
Documentation for older versions of the asset can be found here:
Crafting is a broad term referring to in game item creation, generally by consuming other items. There is no single crafting system nor implementation that fits every game, therefore the one included in the asset package is a simple implementation showing how a crafting system can be integrated with exINV.
Crafting recipes are defined at game start by the script ex_craft_init(), generating a recipe list holding the required items and the resulting item (as well as its quantity).
The panel obj_inv_panel_craft is in charge of checking the recipes, and consuming the recipe items once the user removes the resulting item(s).
obj_inv_panel_craft defines 4 slots that are dedicated to ingredients, and a special slot where the resulting items are generated. The panel also defines a very specific logic for the result slot, prohibiting anything from being inserted there and consuming ingredients when the player actually gets something out of it.
Keep in mind that this approach is relatively simple, but also limited in scope. It does not consider, in contrast for example with games like minecraft, the number of ingredient items in the same slot, nor the position in the crafting grid
Adds a crafting recipe to the crafting system
Given a ds_list of ingredient keys, looks for a recipe and returns it as a ds_map (or -1 if not found)
initializes the crafting system, and generates all the available recipes
callback script on the crafting panel when the crafting inventory changes (called automatically, see "events" section)
The database stores and provides access to the data of every possible inventory item, along with its properties, that can be added an inventory. It is worth noting that database data should be considered read-only. Individual items attributes can be added and changed on run time using tags (see related section).
exINV uses files in CSV format in order to load the item properties into the game. While you are not required to use this format, it is a very convenient way to manage and organize your item data in a way that is easily edited by using excel or a simple text editor.
The CSV files have to be formatted in a specific way though in order to work properly:
Adds a new item into the database. You can optionally specify a group the item belongs to, so that you can later retireve a list of items keys using ex_db_get() belonging to a specific group.
var _item = ds_map_create();
_item[? "key"] = "weapon_sword";
_item[? "stack_size"] = 1;
ex_db_add(_item, "weapons");
Gets the item ds_map having the specified key in the database
var _item = ex_db_get("weapon_sword");
Returns a ds_list containing the keys of every database item (or group)
Loads items into the database from one or more CSV files. A group is created for every file, using the filename as group name
Events are used to notify it "subscribed instances" of an inventory that something has been updated.
Any instance can "subscribe" to an inventory, using ex_ev_subscribe(), in order to be notified whenever something in the inventory has been changed or updated.
Important! You should remember to also unsubscribe your instances using ex_ev_unsubscribe() whenever they get destroyed or removed from the game. The cleanup event is a good place for this.
ex_ev_subscribe lets you specify a script that should be run when the instance get notified. This script gets called with the instance scope and has to specify 3 parameters. The following is a template script you can use:
///@desc script description
///@arg {number} inv - id of the inventory that got updated
///@arg {number} event - event that generated the call (as in EX_EVENTS enum)
///@arg {ds_list} updated_slots - ds_list of affected slots if inv_updated event (-1 in the other events)
var _inv = argument0;
var _event = argument1;
var _updated_slots = argument2;
switch(_event) {
case EX_EVENTS.inv_updated:
//fired when the contents of the inventory have been changed
break;
case EX_EVENTS.inv_destroyed:
//fired when the inventory gets destroyed (as a result as ex_inv_destroy)
break;
case EX_EVENTS.inv_resized:
//fired when the inventory gets resized (as a result as ex_inv_resize)
break;
}
Fires an event notification to all subscribers (internal use only! Do not call manually)
Subscribe the specified instance to the inventory events. Whenever an event for the inventory is fired, the specified script is run.
ex_ev_subscribe(global.inventory, id, update_player_equipment)
Unsubscribes the specified instance from the inventory events
Initializes the inventory system. To be called once and only once at game start, even in the case you use game_restart(), you have to be sure not to call this again.
An inventory is a special data structure holding a predefined number of slots. Like regular data structures, they are kept in memory as long as the program is running or the inventory explicitly destroyed with ex_inv_destroy();
While inventories are meant to be displayed and interact with the player input, in exINV they are not necessarily tied to a visual representation, since that's the UI role. You can create and perform all kind of operations on inventories without ever displaying them.
clears all slots in an inventory
returns the amount of items having the specified key in the inventory (considering stack amount as well)
creates a new inventory, returning the inventory id to be used in subsequent calls
destroys the inventory, freeing the memory
returns the slot index of the first item matching the specified key, or -1 if not found
returns the total number of slots (including empty slots)
creates an inventory from a JSON string generated by ex_inv_write, and returns its id
resizes an inventory by adding or removing slots according to the new size. Items in extra slots are removed
returns the amount of non-empty slots
sorts the inventory items base on a sort attribute
//sorts based on the amount inside each slot, from lowest to highest
ex_inv_sort(global.inventory, EX_COLS.amount, true);
//sorts based on the "name" attribute of the items
ex_inv_sort(global.inventory, "name", true);
returns a JSON encoded string of the specified inventory. Can be reverted to an actualy inventory using ex_inv_read
An item inside an inventory slot has 6 properties you can access:
You can access those properties at any time using the ex_item_get_{property} functions.
It is important to note that every item of the same type (that is, having the same key), references the same data in the database. You should not change the item data in any way, as this would change the database directly. If you want to give an item some extra attributes, you should use tags.
inserts an item into an inventory in the first available slot(s), or in the specified slot.
Important! The ds_map passed to the "tags" argument is copied to the inventory. This means that you are still in charge of destroying it after the call.
//adds two pieces of cheese to the inventory in the first available slot
ex_item_add(global.inventory, "food_cheese", 2);
clears the contents of a slot
inserts an item into an inventory by copying it from a slot in another (or same) inventory
//copies the first item in the toolbar to the first available slot in the player inventory
ex_item_copy(global.toolbar, 0, global.inventory, -1);
returns the amount at the given slot index
returns the item data (as ds_map) at the given slot index, or -1 if no item is present
returns the key of the item at the given slot index, or an empty string if the slot is empty
returns the stack_id at the given slot index
returns the tags ds_map at the given slot index, or -1 if no tags are set in the slot.
Important! The ds_map you get from this function is a copy of the one held in the inventory. This means that changing it won't affect the item tags, but more importantly, you are in charge of destroying it when not needed anymore.
moves an item from one slot to another, in the same inventory or a different one
//moves the first item in the toolbar to the first available slot in the player inventory
ex_item_copy(global.toolbar, 0, global.inventory, -1);
removes an item from the inventory in general, or from a specific slot
//removes 8 units of cheese from the inventory (if present)
ex_item_remove(global.inventory, "food_cheese", 8);
sets the contents of a slot, replacing the current item (if any)
Important! The ds_map passed to the "tags" argument is copied to the inventory. This means that you are still in charge of destroying it after the call.
//adds one unit of cheese to the first slot in the inventory, replacing any item already in there
ex_item_copy(global.inventory, "food_cheese", 1, 0);
replaces the tags of an items with a new set of tags.
Important! The ds_map passed to the "tags" argument is copied to the inventory. This means that you are still in charge of destroying it after the call.
//sets the tags for the first item in the toolbar, by marking the "favorite" tag to true.
var _tags = ds_map_create();
_tags[? "favorite"] = true;
ex_item_set_tags(global.toolbar, 0, _tags);
ds_map_destroy(_tags);
switches the contents of the two specified slots, either in the same or different inventories.
simulates inserting an item into an inventory (as in ex_item_add), but doesn't actually insert the item.
Important! The ds_map passed to the "tags" argument is used to determine item stacking. It does not get automatically destroyed under any circumstance, so it's up to you to destroy it afterwards.
simulates inserting an item into an inventory by copying it from a slot in another (or same) inventory, without actually inserting it
As mentioned in the previous sections, item properties in the database are to be considered immutable. An item having a certain key shares the same attributes as all the other items having that same key. It is common though for specific items to have some unique and independent properties that need to be set at runtime: that's where tags come into play.
Tags can be assigned to any item or stack in a specific slot and allow defining any number of attributes specific to that item or stack.
Tags can also be mass assigned directly using a few ex_item functions. Specifically ex_item_set_tags, ex_item_add and ex_item_set. Please refer to those functions for details.
In exINV, two items having the same key are generally able to stack, as long as the maximum stack size has not been reached. This does not apply though if the items have a different set of tags. In short: items in exINV will stack only if their key and their tags are equal.
It may be worth knowing how the above works in practice, since comparing ds_maps is a slow process and you may be concerned about performance. Whenever you set or change the tags of an item using the tag scripts, the contents of the ds_map are serialized in a string, hashed using MD5 and the result is stored in the stack_id property of the inventory slot. This property is then used to tell when to stack or not stack items when moving them around.
removes a tag from an item
gets the value of a tag for the item in the specified inventory slot
sets the value of a tag for the item in the specified slot
The user interface determines how inventories are displayed to the user, but also contain all the logic that handles how the inventory behaves when the user interacts with them.
As such, while the inventory system and database system are generally project agnostic, the UI (meaning, the objects that compose the UI) is instead an opinionated resource, which can differ greatly from game to game not just in appearance but in functionality too. exINV provides a flexible base for you to work on top, with some common inventories like crafting, player inventory, toolbar and equipment, but it's up to you to refine or rewrite this part based on your needs.
As a general rule, you are supposed to change the UI objects, but not the scripts. You can do that of course if you feel the need to, but exINV has been structured in a way that everything can be extended without touching the ex_* scripts at all.
The UI is composed of 2 main objects: panels and slots.
Panels are container objects that handle the general logic of a specific inventory and its appearance, and are in charge of keeping a list of slot instances representing the inventory slots.
obj_inv_panel defines the shared default logic of ALL of your inventory panels. In general, you do not want create instances of obj_inv_panel directly; instead, every inventory should have its own panel object, as a child of obj_inv_panel, that overrides / defines its specific behavior.
Knowing how inheritance works in Game Maker is fundamental to customize and create panels.
The Mouse inventory (obj_inv_mouse) is a special panel that is in charge of storing items temporarily as the user moves them from slot to slot. You can disable mouse input by simply not opening / closing this panel if you wish.
Slot instances (obj_inv_slot) are in charge of representing the contents of a specific slot in an inventory. Every panel should create its slots in the "on create" user event using one of the functions below.
Slot instances are automatically updated and drawn by the panel itself, and as such the act simply as containers. Every instance of a slot defines the following instance variables:
New panel objects have to be a child obj_inv_panel. Be sure to right click the parent event and select Inherit event in the dropdown menu for every event you need to define. This ensures that the event contains a event_inherited(); call.
Panel objects are also required to inherit and extend the "on create" event (event user 0). In this event you have to create the slot instances.
The folder ex/panels/ in the objects resource provides a few useful examples you can use to build your own, the most basic one being obj_inv_panel_backpack, that simply implements the create slots event and nothing else.
closes the specified panel instance. If a panel object is passed instead of a specific instance, all instances of that object will be closed.
opens and displays a panel for the specified inventory, returning the newly created panel instance
called whenever an inventory assigned to a panel changes (see notification system in the docs)
Returns true if the item in both slot instances is the same (including tags)
Creates and returns a slot instance attached to a panel
Generates a grid of slot instances for the specified panel based on the inventory size
Returns true if the specified slot instance is empty (has no item)
For any kind of question or issue relates to this asset, please contact me:
Developed with by Homunculus.