FlashPunk Tutorial 09: The Inventory UI – Part 1

Posted by dolgion on Wednesday Feb 16, 2011 Under Flashpunk

Hello and welcome back once again as we delve deeper into the inner machinations of the FlashPunk JRPG Engine! Today’s topic is as annouced the implementation of the player’s inventory screen, which was a pain in the ass to code to be honest. I’ll explain in detail how the inventory screen works, how equipment modifies the player’s stats and also an explanation on how the first basic types of effect of consumables were coded. Blah blah blah full source code as blah is available blah here, and blah blah also play the latest build here.

So last time, we talked about how to put items into the game. We added treasure chests with their own inventory datastructure, and made it so that the player could walk up to a closed chest and open it, taking all the items that were in it and adding them to the player’s own inventory. But how can we create an interface for the player to interact with the items? How can we make it so that the player actually is able to equip that
sword, wear that pink leather suit and drink that healthy potion?

Well for that my dear reader, we’ll have to make a new inventory screen. Oh my god…this is going to be a very long one folks, I hope you can pull through with me ;) .

1. The Player’s Equipment

Before I talk about the inventory screen, I’ll explain to you the data structure used to represent the equipment. While the items array represents the player’s inventory, the equipment dictionary represents the weapon and armor currently equipped in all the body parts and weapon slots (primary and secondary). So for example, if I equip the primary weapon and it’s a Steel Sword, then it’ll be stored in the equipment dictionary like: equipment["WeaponEquipPrimary"] storing the instance of a Weapon for our steel sword. The keys for the dictionary are all stored as constants in the GC class.

2. Inventory UI functionality

You really should start up the demo if you haven’t, and pick up some items from the chest. Then, open the inventory by pressing the ‘i’ key. You’ll see that the inventory can be navigated with the arrow buttons, moving around a cursor (the hand in a glove from Final Fantasy) to the various item slots and items themselves. Upon moving the hand to an item or equipped item slot, the bottom part of the screen displays information and properties of the item in question. When you move towards a body part or one of the weapon equipment slots,
and press the “space” key, another cursor appears in the appropriate item column, while the first cursor stays at the item slot and greys out. You can now look for an item that is compatible with the selected slot (for example, Leather Gloves for the Hands slot). If you find one, you can press “space” after you moved the cursor to it and it will be equipped, the second cursor will disappear and the first cursor will be white and active again.
Also, the item slot will now display the item’s name. If you want to cancel while looking for an item, you can press “x” to cancel. It does the same as when equipping, only without equipping any item and therefore no item name will be displayed. You can also unequip and item from a slot if you move the cursor to the slot in question and press “x”. The item’s name will disappear from the slot. When you look for an item to equip, incompatible items in the column will be greyed out and you will not be able to equip item’s when they’re greyed out. When you move the cursor to the consumables item list, you can consume a selected item by pressing “space”. If you have more than one of the consumable item in you inventory, the quantity property will decrease. If you consume the last one, the item will disappear from the list and the consumables list will shrink by one item.

3. Cursor positions and keys

If we want to navigate the menu using a cursor entity FF-style, we need to define the different places that the cursor can move to, meaning we need to store the exact coordinates of every position. I created a class called CursorPosition.as. Take a look:

package utility
{
	import flash.geom.Point;

	/**
	 * ...
	 * @author dolgion1
	 */
	public class CursorPosition
	{
		public var x:int;
		public var y:int;
		public var valid:Boolean;
		public var key:String;

		public var upKey:String;
		public var downKey:String;
		public var leftKey:String;
		public var rightKey:String;

		public function CursorPosition() {}

		public function setKeys(_upKey:String,
								_downKey:String,
								_leftKey:String,
								_rightKey:String):void
		{
			if (_upKey != "null") upKey = _upKey;
			if (_downKey != "null") downKey = _downKey;
			if (_leftKey != "null") leftKey = _leftKey;
			if (_rightKey != "null") rightKey = _rightKey;
		}

		public function getPosition():Point
		{
			return new Point(x, y);
		}
	}
}

We have the x and y values, but also a flag called valid. If a cursor position is “valid”, it means that the cursor is allowed to go there. If it isn’t then it can’t move there. The key string will contain the name of the cursor position. All cursor positions are tied to a display text (but not all display texts are tied to a cursor position). I show the names of all the cursor positions in the inventory screen in this picture:

Inventory Screen UI Scheme

In the picture you can see the cursor positions in the green frames. The red frames represent the different display texts. In the information area, even if there is no text, we still have a display text there. There is just no text. The strings pointing at the display texts are the different keys for the display texts, and are the strings stored in the key member variable in the CursorPosition class. Next, we have upKey, downKey, leftKey, rightKey variables in the class. They are the keys for the neighboring cursor positions in the respective direction. The upKey of “ArmorEquipLegs” for example would be “ArmorEquipTorso”. With this key system, we’ll be able to build our UI logic around the cursor’s movements and more.

We store all the data for the cursor positions in the ui_inventory_data.xml in the assets/scripts
folder.

<?xml version="1.0" encoding="utf-8" ?>
<data>
	<cursorpositions>
		<!-->Equipment Area</!-->
		<cursorposition key="ArmorEquipHead" x="65" y="70" validity="true"
			up="null"
			down="ArmorEquipTorso"
			left="null"
			right="WeaponEquipPrimary" />

		<cursorposition key="ArmorEquipTorso" x="65" y="90" validity="true"
			up="ArmorEquipHead"
			down="ArmorEquipLegs"
			left="null"
			right="WeaponEquipSecondary" />

		<cursorposition key="ArmorEquipLegs" x="65" y="110" validity="true"
			up="ArmorEquipTorso"
			down="ArmorEquipHands"
			left="null"
			right="null" />

		<cursorposition key="ArmorEquipHands" x="65" y="130" validity="true"
			up="ArmorEquipLegs"
			down="ArmorEquipFeet"
			left="null"
			right="null" />

		<cursorposition key="ArmorEquipFeet" x="65" y="150" validity="true"
			up="ArmorEquipHands"
			down="WeaponItem1"
			left="null"
			right="null" />

		<cursorposition key="WeaponEquipPrimary" x="265" y="70" validity="true"
			up="null"
			down="WeaponEquipSecondary"
			left="ArmorEquipHead"
			right="null" />

		<cursorposition key="WeaponEquipSecondary" x="265" y="90" validity="true"
			up="WeaponEquipPrimary"
			down="ArmorEquipLegs"
			left="ArmorEquipTorso"
			right="null" />

		<!-->Item Area</!-->
		<cursorposition key="WeaponItem1" x="65" y="210" validity="false"
			up="ArmorEquipFeet"
			down="WeaponItem2"
			left="null"
			right="ArmorItem1" />

		<cursorposition key="WeaponItem2" x="65" y="230" validity="false"
			up="WeaponItem1"
			down="WeaponItem3"
			left="null"
			right="ArmorItem2" />

		<cursorposition key="WeaponItem3" x="65" y="250" validity="false"
			up="WeaponItem2"
			down="WeaponItem4"
			left="null"
			right="ArmorItem3" />

		<cursorposition key="WeaponItem4" x="65" y="270" validity="false"
			up="WeaponItem3"
			down="WeaponItem5"
			left="null"
			right="ArmorItem4" />

		<cursorposition key="WeaponItem5" x="65" y="290" validity="false"
			up="WeaponItem4"
			down="WeaponItem6"
			left="null"
			right="ArmorItem5" />

		<cursorposition key="WeaponItem6" x="65" y="310" validity="false"
			up="WeaponItem5"
			down="null"
			left="null"
			right="ArmorItem6" />

		<cursorposition key="ArmorItem1" x="215" y="210" validity="false"
			up="ArmorEquipFeet"
			down="ArmorItem2"
			left="WeaponItem1"
			right="ConsumableItem1" />

		<cursorposition key="ArmorItem2" x="215" y="230" validity="false"
			up="ArmorItem1"
			down="ArmorItem3"
			left="WeaponItem2"
			right="ConsumableItem2" />

		<cursorposition key="ArmorItem3" x="215" y="250" validity="false"
			up="ArmorItem2"
			down="ArmorItem4"
			left="WeaponItem3"
			right="ConsumableItem3" />

		<cursorposition key="ArmorItem4" x="215" y="270" validity="false"
			up="ArmorItem3"
			down="ArmorItem5"
			left="WeaponItem4"
			right="ConsumableItem4" />

		<cursorposition key="ArmorItem5" x="215" y="290" validity="false"
			up="ArmorItem4"
			down="ArmorItem6"
			left="WeaponItem5"
			right="ConsumableItem5" />

		<cursorposition key="ArmorItem6" x="215" y="310" validity="false"
			up="ArmorItem5"
			down="null"
			left="WeaponItem6"
			right="ConsumableItem6" />

		<cursorposition key="ConsumableItem1" x="365" y="210" validity="false"
			up="ArmorEquipFeet"
			down="ConsumableItem2"
			left="ArmorItem1"
			right="null" />

		<cursorposition key="ConsumableItem2" x="365" y="230" validity="false"
			up="ConsumableItem1"
			down="ConsumableItem3"
			left="ArmorItem2"
			right="null" />

		<cursorposition key="ConsumableItem3" x="365" y="250" validity="false"
			up="ConsumableItem2"
			down="ConsumableItem4"
			left="ArmorItem3"
			right="null" />

		<cursorposition key="ConsumableItem4" x="365" y="270" validity="false"
			up="ConsumableItem3"
			down="ConsumableItem5"
			left="ArmorItem4"
			right="null" />

		<cursorposition key="ConsumableItem5" x="365" y="290" validity="false"
			up="ConsumableItem4"
			down="ConsumableItem6"
			left="ArmorItem5"
			right="null" />

		<cursorposition key="ConsumableItem6" x="365" y="310" validity="false"
			up="ConsumableItem5"
			down="null"
			left="ArmorItem6"
			right="null" />

	</cursorpositions>

	<columnkeys>
		<columnkey column="0" index="0" key="WeaponItem1" />
		<columnkey column="0" index="1" key="WeaponItem2" />
		<columnkey column="0" index="2" key="WeaponItem3" />
		<columnkey column="0" index="3" key="WeaponItem4" />
		<columnkey column="0" index="4" key="WeaponItem5" />
		<columnkey column="0" index="5" key="WeaponItem6" />

		<columnkey column="1" index="0" key="ArmorItem1" />
		<columnkey column="1" index="1" key="ArmorItem2" />
		<columnkey column="1" index="2" key="ArmorItem3" />
		<columnkey column="1" index="3" key="ArmorItem4" />
		<columnkey column="1" index="4" key="ArmorItem5" />
		<columnkey column="1" index="5" key="ArmorItem6" />

		<columnkey column="2" index="0" key="ConsumableItem1" />
		<columnkey column="2" index="1" key="ConsumableItem2" />
		<columnkey column="2" index="2" key="ConsumableItem3" />
		<columnkey column="2" index="3" key="ConsumableItem4" />
		<columnkey column="2" index="4" key="ConsumableItem5" />
		<columnkey column="2" index="5" key="ConsumableItem6" />
	</columnkeys>

	<displaytexts>
		<displaytext text="Equipment" x="50" y="30" size="14" />
		<displaytext text="Items" x="50" y="170" size="14" />
		<displaytext text="Information" x="50" y="320" size="14" />
		<displaytext text="Head: " x="100" y="60" size="12" />
		<displaytext text="Torso: " x="100" y="80" size="12" />
		<displaytext text="Legs: " x="100" y="100" size="12" />
		<displaytext text="Hands: " x="100" y="120" size="12" />
		<displaytext text="Feet: " x="100" y="140" size="12" />
		<displaytext text="Primary Weapon: " x="300" y="60" size="12" />
		<displaytext text="Secondary Weapon: " x="300" y="80" size="12" />

		<displaytext text="" x="150" y="340" size="12" />
		<displaytext text="" x="150" y="360" size="12" />
		<displaytext text="" x="150" y="380" size="12" />
		<displaytext text="" x="150" y="400" size="12" />
		<displaytext text="" x="340" y="340" size="12" />
		<displaytext text="" x="340" y="360" size="12" />
		<displaytext text="" x="340" y="380" size="12" />
		<displaytext text="" x="340" y="400" size="12" />
	</displaytexts>
</data>

Apart from the data for cursor positions, we also store the keys for the item columns separately connecting the key strings with their row and column. So for example, “ArmorItem5″ will be in column 1 and row/index 4. Remember, everything starts at 0, not 1. Then, we also declare data for all display texts used in the inventory screen. All of them, except for the item column ones. They will be created dynamically in the InventoryScreen class.

4. Loading UI data in the Dataloader

When we first create an instance of the InventoryScreen, the first thing we do is give it the ui data structures, read from the xml by the data loader, as parameter. A dictionary of CursorPosition instances is created. The key for each element is the cursor position key it itself stores. So I could get the CursorPosition object for “WeaponEquipPrimary” by doing cursorPositions["WeaponEquipPrimary"] in the InventoryScreen class.
So in the data loader, this cursorPositions dictionary is created by reading in the data from the xml, then we load in the item column keys and store them in a two dimensional array, essentially modelling the item columns after a 2 dimensional matrix (lol I make it sound so complicated). And in the end, we create all the DisplayText instances (except for the item column ones) and store them as well. In the end, all 3 data structures are returned in one array.

public function setupInventoryUIData():Array
{
	var inventoryUIDataArray:ByteArray = new inventoryUIData;
	var inventoryUIDataXML:XML = new XML(inventoryUIDataArray.readUTFBytes(inventoryUIDataArray.length));
	var i:XML;
	var cursorPositions:Dictionary = new Dictionary();
	var columnKeys:Array = new Array();

	columnKeys[0] = new Array();
	columnKeys[1] = new Array();
	columnKeys[2] = new Array();

	var displayTexts:Array = new Array();

	for each (i in inventoryUIDataXML.cursorpositions.cursorposition)
	{
		var cursorPosition:CursorPosition = new CursorPosition();
		cursorPosition.x = int(i.@x);
		cursorPosition.y = int(i.@y);
		cursorPosition.key = i.@key;

		if (i.@validity == "true")
		{
			cursorPosition.valid = true;
		}
		else
		{
			cursorPosition.valid = false;
		}

		cursorPosition.setKeys(i.@up, i.@down, i.@left, i.@right);
		cursorPositions["" + i.@key] = cursorPosition;
	}

	for each (i in inventoryUIDataXML.columnkeys.columnkey)
	{
		columnKeys[i.@column][i.@index] = new String(i.@key);
	}

	for each (i in inventoryUIDataXML.displaytexts.displaytext)
	{
		displayTexts.push(new DisplayText("" + i.@text, i.@x, i.@y, "default", i.@size, 0xFFFFFF, 500));
	}

	return new Array(cursorPositions, columnKeys, displayTexts);
}

5. In the constructor of InventoryScreen

I’m going to walk you through the process of all that happens in the constructor here. The constructor immediately calls initUIDatastructures() using the parameter passed to it.

public function InventoryScreen(_uiDatastructures:Array)
{
	initUIDatastructures(_uiDatastructures);
	initItemColumnDisplayTexts();

	background = new TextBox(GC.INVENTORY_OFFSET_X, GC.INVENTORY_OFFSET_Y, GC.INVENTORY_SCALE_X, GC.INVENTORY_SCALE_Y);
	cursor = new Cursor(0, 0);
	cursorEquip = new CursorEquip(0, 0);
	cursorEquip.visible = false;
}
public function initUIDatastructures(_uiDatastructures:Array):void
{
	cursorPositions = _uiDatastructures[0];
	columnKeys = _uiDatastructures[1];
	displayTexts = _uiDatastructures[2];
}

This function is nothing special. It only allocates the 3 data structures to the equivalent members. Then in the constructor, we call initItemColumnDisplayTexts().

public function initItemColumnDisplayTexts():void
{
	for (var i:int = 0; i < GC.INVENTORY_MAX_ITEM_COLUMNS; i++)
	{
		itemColumns[i] = new Array();
		for (var j:int = 0; j < GC.INVENTORY_MAX_ITEM_ROWS; j++)
		{
			itemColumns[i].push(new DisplayText("", 100 + (i * 150), 200 + (j * 20), "default", GC.INVENTORY_DEFAULT_FONT_SIZE, 0xFFFFFF, 500));
			displayTexts.push(itemColumns[i][j]);
		}
	}
}

Here, we use the constants that indicate how many rows and columns there are in the item column area, and using the constants, we create their DisplayText instances, pushing them also into the displayTexts array. After that, the background entity and the cursor entities are initialized. The cursorEquip is set to invisible though, because we only want to display the greyed-out cursor in equip mode. And that’s it for the constructor.

6. Initialization when opening the inventory

The inventory is opened when the player presses the “i” key during the game’s normal mode. It calls this function:

public function openInventoryScreen():void
{
	gameMode = INVENTORY_SCREEN_MODE;
	inventoryScreen.visible = true;
	inventoryScreen.initialize(player);
}

Easy: we switch the game mode to INVENTOR_SCREEN_MODE, set the visible setter of the inventory screen (which works EXACTLY like the one for the status screen) and then call the inventory screen’s initialize() method, with the player as the parameter.

public function initialize(_player:Player):void
{
	player = _player;

	currentMode = GC.INVENTORY_NORMAL_MODE;
	currentCursorPositionKey = GC.INVENTORY_KEY_ARMOR_EQUIP_HEAD;
	currentCursorColumn = GC.INVENTORY_ARMOR_EQUIP_COLUMN;
	currentEquipmentKey = "";
	cursor.setPosition(cursorPositions[currentCursorPositionKey].getPosition());

	populateEquipmentColumns();
	populateItemColumns();
}

So here we are. We immediately set the inventory screen’s Player instance. One thing I’ve realized is that when you pass an instance of a class as parameter, ActionScript3 handles it in a pass-by-pointer way, like in C/C++ when pointers are passed as parameters, rather than copies of the variable. This has the effect that if you, in the InventoryScreen, change something in the player.items or any of the other player properties, it affects the original player instance in the game world. I think technically you access the same memory addresses in the InventoryScreen‘s player as the Game world’s player. Anyway, to get back to the function: we set the current inventory mode to NORMAL_MODE, we set the current cursor position key to “ArmorEquipHead”. This way, the cursor will point in the beginning at the head’s armor slot, as is shown in the scheme above. We set the currentCursorColumn to ARMOR_EQUIP_COLUMN. The currentCursorColumn is a variable that always stores the general area that the cursor is in. The areas are ARMOR_EQUIP_COLUMN, WEAPON_EQUIP_COLUMN, ARMOR_ITEM_COLUMN, WEAPON_ITEM_COLUMN and CONSUMABLE_ITEM_COLUMN. I’ll show you how this is useful later on. The currentEquipmentKey is set to be an empty string. Equipment keys are the same as the cursor position keys. The equipment, as stated in the previous tutorial is a dictionary that stores instances of Weapon, Armor or Consumable. To make things simpler, we use the same keys as for the cursorPosition dictionary here. So if we equip a new pair of pants, its instance will be stored in equipment["ArmorEquipLegs"]. As we are not currently in equip mode, we don’t give the currentEquipmentKey any value. After that, we set the cursor entity’s coords by calling the getPosition() function of the current cursor position. You see here the interplay of the data structure and state variables in action. And in the end, we populate the equipment columns, meaning we look through the player’s equipment and set the body part slots’ texts accordingly.

public function populateEquipmentColumns():void
{
	if (player.equipment[GC.INVENTORY_KEY_ARMOR_EQUIP_HEAD] != null) displayTexts[GC.INVENTORY_ARMOR_EQUIP_HEAD_DISPLAY_TEXT].displayText.text = GC.BODY_PART_HEAD_STRING + ": " + player.equipment["ArmorEquipHead"].name;
	else displayTexts[GC.INVENTORY_ARMOR_EQUIP_HEAD_DISPLAY_TEXT].displayText.text = GC.BODY_PART_HEAD_STRING + ": ";

	if (player.equipment[GC.INVENTORY_KEY_ARMOR_EQUIP_TORSO] != null) displayTexts[GC.INVENTORY_ARMOR_EQUIP_TORSO_DISPLAY_TEXT].displayText.text = GC.BODY_PART_TORSO_STRING + ": " + player.equipment["ArmorEquipTorso"].name;
	else displayTexts[GC.INVENTORY_ARMOR_EQUIP_TORSO_DISPLAY_TEXT].displayText.text = GC.BODY_PART_TORSO_STRING + ": ";

	if (player.equipment[GC.INVENTORY_KEY_ARMOR_EQUIP_LEGS] != null) displayTexts[GC.INVENTORY_ARMOR_EQUIP_LEGS_DISPLAY_TEXT].displayText.text = GC.BODY_PART_LEGS_STRING + ": " + player.equipment["ArmorEquipLegs"].name;
	else displayTexts[GC.INVENTORY_ARMOR_EQUIP_LEGS_DISPLAY_TEXT].displayText.text = GC.BODY_PART_LEGS_STRING + ": ";

	if (player.equipment[GC.INVENTORY_KEY_ARMOR_EQUIP_HANDS] != null) displayTexts[GC.INVENTORY_ARMOR_EQUIP_HANDS_DISPLAY_TEXT].displayText.text = GC.BODY_PART_HANDS_STRING + ": " + player.equipment["ArmorEquipHands"].name;
	else displayTexts[GC.INVENTORY_ARMOR_EQUIP_HANDS_DISPLAY_TEXT].displayText.text = GC.BODY_PART_HANDS_STRING + ": ";

	if (player.equipment[GC.INVENTORY_KEY_ARMOR_EQUIP_FEET] != null) displayTexts[GC.INVENTORY_ARMOR_EQUIP_FEET_DISPLAY_TEXT].displayText.text = GC.BODY_PART_FEET_STRING + ": " + player.equipment["ArmorEquipFeet"].name;
	else displayTexts[GC.INVENTORY_ARMOR_EQUIP_FEET_DISPLAY_TEXT].displayText.text = GC.BODY_PART_FEET_STRING + ": ";

	if (player.equipment[GC.INVENTORY_KEY_WEAPON_EQUIP_PRIMARY] != null) displayTexts[GC.INVENTORY_WEAPON_EQUIP_PRIMARY_DISPLAY_TEXT].displayText.text = GC.PRIMARY_WEAPON_STRING + ": " + player.equipment["WeaponEquipPrimary"].name;
	else displayTexts[GC.INVENTORY_WEAPON_EQUIP_PRIMARY_DISPLAY_TEXT].displayText.text = GC.PRIMARY_WEAPON_STRING + ": ";

	if (player.equipment[GC.INVENTORY_KEY_WEAPON_EQUIP_SECONDARY] != null) displayTexts[GC.INVENTORY_WEAPON_EQUIP_SECONDARY_DISPLAY_TEXT].displayText.text = GC.SECONDARY_WEAPON_STRING + ": " + player.equipment["WeaponEquipSecondary"].name;
	else displayTexts[GC.INVENTORY_WEAPON_EQUIP_SECONDARY_DISPLAY_TEXT].displayText.text = GC.SECONDARY_WEAPON_STRING + ": ";
}

That’s simple and straight forward code. After that we populate the item columns:

public function populateItemColumns():void
{
	resetItemColumnDisplayTexts();
	itemsStartIndex[GC.INVENTORY_WEAPON_ITEM_COLUMN] = 0;
	itemsStartIndex[GC.INVENTORY_ARMOR_ITEM_COLUMN] = 0;
	itemsStartIndex[GC.INVENTORY_CONSUMABLE_ITEM_COLUMN] = 0;

	// set the text for every display list of each column
	var i:int;
	var j:int;
	for (i = 0; i < GC.INVENTORY_MAX_ITEM_COLUMNS; i++)
	{
		if (player.items.length == i) break;

		// set the end index of the items[i] subset
		// if it happens to be less than the max rows
		if (player.items[i].length < GC.INVENTORY_MAX_ITEM_ROWS)
		{
			itemsEndIndex[i] = player.items[i].length;
		}
		else itemsEndIndex[i] = GC.INVENTORY_MAX_ITEM_ROWS;

		for (j = 0; j < itemsEndIndex[i]; j++)
		{
			itemColumns[i][j].displayText.text = player.items[i][j].item[i].name;
			cursorPositions[columnKeys[i][j]].valid = true;
		}
	}
}

In this function, the goal is essentially to set the texts of the item column display texts. In order to do that, we have a double loop, one going through the columns, the other through the rows. Some explanation: before we do anything, we want to make sure that all the display texts in itemColumns are without text and that their text color is white. So we call resetItemColumnDisplayTexts(). After that, we initialize the itemsStartIndex array. It’s an array of 3 elements, one for each item column. The thing is, sometimes, “ConsumableItem3″ does not necessarily show the consumable item in items[2][2]. If the list was scrolled down by one, it would actually be showing items[2][3]. In order to keep track of how big the scroll offset in the list is, we store the “start index”. In the current example, the itemsStartIndex[2] would be 1. If the list scrolled down once more, it would be 2, and so forth. Also, sometimes there maybe less items than there is space in the list.
If there are only 3 weapons to display but there 6 display texts available to display them in, we want to store the first index in the items[0] array where there is no item in it. This would mean that itemsEndIndex[0] would be 3. There is no items[0][3], because [0][2] was the last one. So it turns out that itemsStartIndex and itemsEndIndex are arrays which are used to keep track of the amount of scrolling in the list, as well as where the list might end. If we didn’t do this, our inventory UI wouldn’t work properly. Having explained the two arrays, you can now see that we only set texts of display texts that correspond to an actual item in the inventory, setting the name and also validate them for cursor movement. This means that only display texts which show an item can be accessed by the cursor. If there is no item, the cursor can’t even go there.

I’m going to cut off here. In Part 2 of the Inventory tutorial, I’ll explain the code for cursor movement
and how equipping items is implemented.

PS: I would like to know if the recent tutorials are actually useful and understandable to you.
Are there things that I should pay more attention too? Is the level of detail in my writing too high or too low?
I hope that with some feedback I’ll be able to improve upon the series. Thanks!

Share Share

Leave a Reply