Tag Archives: tmx

Prototype sampler # 1

So, finally, I’ve completed my last major developmental milestone and I think that deserves a post. ūüôā

The above video simply shows the playthrough of¬†some of the game mechanics. The quest has not been fully written yet, and though all of the game mechanics are working, they’re not readily apparent without some introductions. The video is mainly to see how adventure and combat are blending as one piece.


I’ve slaved away on numerous aspects to accomplish all the major milestones I had set out to do. There were a few additions to these, but they were minor changes, and all in part of the iterative process of figuring out the closest gameplay mechanics I wanted to implement in Unity.

Though my work isn’t done yet — there are still UI issues I need to sort out — and there are still some niggling bugs present in the prototype, it is largely playable. By ‘playable’ that means you can run around, talk to people, and shoot Robots, and get shot back. You can plant a bomb, blow it up, and you can blow yourself up in the process as well. You can ‘pox’ a powerlet to get energy, you can buy and use meds to heal yourself. Frankly, a few months ago I didn’t think I could end up saying all this in one paragraph.

Most of the joy, and fear, of this prototype has been the implementation of a bespoke AI graph framework. It’s a joy because it actually works; it’s a fear because it sometimes feels too deep for me to always grasp its innards when some things don’t go right.

I’ve gone through mounds of halved/quarter-A4 to-do sheets with heaps of orange highlighter marks signifying all the big and small tasks or goals I needed done. There are so many disparate systems working that that if I didn’t have a calendar tracking my progress, I wouldn’t be able to grasp what I myself had accomplished.

For example, here’s a quick run-down of the aspects.

  • Asset creation.
    • I’ve heavily used Janus to break out animated sequences.¬† Using FORFILEs, a Janus looping construct that iterates through the lines of a file, creating an animated character, such as the Player character, was simple as I needed only to set up one angle and let Janus break out all the other 15 directions. Variable frame ranges for a particular animation were also taken care of using the same principle.
    • Janus was an important cog in the making of the prototype because of the amount of iterations for the scenes. An element would sometimes become designated as an interactable element, which had to be split from the main scene and rendered separately.
    • NPC/Robot portraits had a separate animation and render, and specifically had to go through post-processing.
  • Tiled¬†was used in making the maps, and Rex’s TMX Importer was used to carry that information in C2. I had to do some modifications to the TMX Importer to enable the retrieval of the Tiles and Objects image source. Tiled enabled me to experiment and implement concepts by introducing certain datatypes for the engine’s use, which informs me of how I may implement the maps in Unity.
    • This had to be balanced with Game Data Documents which are comprised of text-based files of varying structures. These Data Documents are the immutable attributes used by the systems. In the beginning, the data would come from different sources; one would be defined in the TMX, while others defined in a CSV table. As I progressed, I refined the categorisation of data.
  • The in-game Inventory system was one hell of an undertaking, The Inventory system is connected to the Trade system, which is further split into two variants: the Container system, and the Merchant system; the former simulates the ability to store items in ‘containers’, and the latter simulates buy/sell transactions with NPCs. Merchant data, like price, buy/sell limitations, and price adjusters are tied to tables and the NPC entity as defined in the TMX.
  • While the code related to the movement was entirely specific to C2, I had to nevertheless overcome these issues to get a working prototype. Pathfinding needed some optimisations, behaviours related to physicality of entities needed to be coded in relation to the established movement behaviours. This aspect will largely be replaced by Unity’s navmesh, in addition to a target grid overlay that I may custom-build myself.
  • The Action Strip (a.k.a. Astrip) system — the method for interacting with elements in the game — was developed to be authored using text files (like most systems in the game).It serves as the hub for all ‘adventure’ interactions. It was also designed to be generic so that the display of interaction results can be be tweaked directly from the text file. For example, a ‘look’ action,¬† at an object may initiate a display of a description, or the narrative box, or initiate a dialogue, or anything else that has been allowed in the engine.
  • The Convo system was another early development. Some additional Python code was necessary to convert the authored .graphml files (using yEd) into a Markdown format (for readability in a text editor). However, the development of the AI graph framework proved that the Convo system was inferior, though both used node graphs. Although the Convo system has not yet been upgraded to use the same (or similar) framework of AI, this would eventually be done when the port to Unity is made.
    • The Convo system could be called by the Astrip system.
    • The Convo system also allows implicit trade of items. For example, if through speaking with an NPC, it gives you an item to be used. The Convo system communicates to the Inventory system and places the item in the Players inventory.
  • The AI system used the TGF format to represent a nodal graph. Then an in-game parser and callback/event handler framework handled the execution of the AI graph on a per-Robot basis.
    • The AI system is connected to other systems, such as the Inventory, the Trade, Convo (dialogue system) and of course, NPCs/Robots themselves.
    • Using the AI system, a Robot can accost you to do a contraband check, which was one of the first implementations of the AI (even before combat).
    • The AI can contextualise its own dialogue with the Player, changing it from a contraband check to an arrest, for example.
  • Lookups for gameplay values, such as hit-chance, effect of skills on gameplay, were done using a non-linear interpolation that was accomplished by using Open Office Calc’s cell formulas. This allowed me to tweak lookup values utilising functions as opposed to doing it individually, per cell! This application was conveniently placed to export to CSV directly, so no other intermediate process was needed to get it to C2.
  • The Combat system is closely tied with the AI and is comprised of many factors, a few of which include:
    • Alert level behaviour of Robots; certain actions at a certain alert level means differently for Robots. For example, running or crouching is OK when Alert Level is 0. But when the Alert Level is 1, running or crouching is interpreted as¬†suspicious and Player will be fired upon.
    • Behaviour of Robots differ from one another. Some guard, some patrol, some check for contraband.
    • Offensive component
      • Player accuracy skill
      • Weapon attributes such as range, max_range, weapon dropoff (weapon damage and chance to hit is affected)
      • Rate of fire
      • Dual-wielding of weapons
      • Crouching increases accuracy
      • Bomb placement and detonation
      • Shock effect; certain weapon may stun a Robot for a period of time.
      • GMAC system, which is a modifier on top of a typical random number generator.
    • Defensive component
      • Use of cover for defence
      • Crouching reduces profile, increases Player defence against being hit
      • Running increases Player defence against hit but only if running perpendicular to Robot.
    • Stealth component
      • Crouching behind low obstacles for stealth
      • Noise level when running; Robot hears you!
      • Glitters is Electronic Counter-Measures and makes the Player invisible for a short period of time.
    • Hacking¬†powerlets to get more energy, and the associated success rates, and the penalties for failure
    • And others that are too lengthy to include, but you get the idea…

Normally, a prototype is small, whose gameplay represents the root of what the game is about. Sometimes, a prototype is created to determine if a gameplay works or not, or if people like it enough.

But I built the prototype as a technical reconnoitre¬† of what I’m going to come up against. You can say I was also trying to form a beachhead at the same time. I don’t know if people would like it, but I can’t be dissuaded either way; I’ve gone this far solely on the excitement of taking a childhood game to my present.

But a prototype is also made to present the gameplay as clear as possible, that if the prototype is¬†fun to play, then the real thing would be as fun, if not more fun to play. The problem I have with Citizen is that it is an adventure as much as it is a shoot-em-up game. The fun in 2400 AD, Fallout, or Shadowrun, for example, is the fact that it is an adventure. But I find it difficult to express the full adventure by doing a half-adventure. I think that’s due to my lack of experience writing for games. At the same time, I think that I’ve been focused so much on the technical aspects that I’ve not really dug as deep as I should into the potential of the narrative. I’ve been working on the framework in which I hope to base an adventure story (of which I have a first draft already), and I think that this prototype, as it stands, should be just seen as the prototype for the framework.

More to come.

 

Advertisements

Thoughts on displaying adventure text

Adventure text can be long, and should be variable as much as possible.

In the RND test, I’ve formulated an initial design that has the following aspects.

Terms (TextTerms)

The use of terms mean that a keyword is used as a topic. So if I used the term ‘missionsuccess’, then I could define that the displayed text is ‘Mission successful! Congratulations!’

Furthermore, I can use a TMX entity, for example an npc with the name ‘letigus’, and use that as my term. And thus define ‘letigus’ as ‘Hi, my name is Letigus. What can I do you for?’

Also, through the use of terms, I can encapsulate a variations into one term. This is the syntax which I use to implement this. This is written in a separate text file which read by the AJAX object and then put into a Dictionary using the CSV2Dictionary plugin by Rex.

Each term is defined by at least 2 lines. One line is a declaration of how many variations there are. And the succeeding lines are the content of the term.

<term>|<default index>/<maximum index>
<term>.<index>.<text type>.<duration>|<text>

The first line’s <term> is the declaration of the term’s name. The default index refers to which index it should start with. The maximum index is expected maximum variations of the term.

The second line’s <term> signifies this is the term being used. The <index> is defining this particular term’s index. <text type> can be anything that identifies the kind of text it is supposed to be. For example, the text could refer to a person speaking, or it could refer to a ‘narrator’, or it could refer to a description of something. This <text type> is used to colour the text according.y The <duration> is the number of seconds that the text will hold. If 0, it will hold indefinitely, which is useful in cases, for example, if a player is killed, and a ‘defeat’ term is used. Then there is a bar ‘|’ and the <text> follows afterwards.

Note that in the text file, there are no newlines, so the use of ‘\n’ is just a marker for a C2 text replacement for an actual C2¬†newline character.

To recap, here’s an example:

look_r1|1/2
look_r1.1.desc.0|The place is a smelly hole.\nAnd a sight for sore eyes.
look_r1.2.desc.0|You don't know how anyone could live here.

So at the start, ‘look_r1’ is defined as having 2 variations, and it starts at the first index.

Then look_r1’s two texts are defined, each specifying their own index, and type, and duration, and finally, the text.

Displaying the text

The display of the text is then offloaded to another function (DisplayText) which sets the duration and colour based on the data it receives from the Term itself.

For example, if the type is an NPC speaking, the DisplayText function will wrap the text in quotation marks, and maybe give it a different colour.

External text file, map-specific

As I said before, the AJAX and CSV2Dictionary plugins are used to transfer an external text file’s contents to a dictionary, and following the format above, this is relatively trivial.

CSV2Dictionary requires a pair separation by a specified delimiter. I chose the default (comma) and used the ¬†bar ‘|’ as a separator within the tokens.

In the RND test, there was no distinction between map-centric adventure text and more generic/global text. However, this should be implemented as it makes most sense.

Thoughts on accomps

Accomps is ‘accomplishments’ and I’ve separated map-related accomps (m_accomps) with global accomps (g_accomps). Global accomps persist, while m_accomps are re-populated ¬†when a map loads.

This separation may not be necessary, but for the meantime, there it is.

accomps have the key as the accomplishment, and the value is either 0 or 1. 0 if not yet accomplished (default value), and 1 if it is.

In the RND test, the accomp related to exiting an area was triggered 1 every time the player was on top of the designated area (ie current_trigger variable). But when  the current_trigger="", it triggered (using TOWT) a reset to 0. So as long as the player was not on the trigger, the accomps will always revert to 0.

On the map logic script, the ‘success’ of a map is checked whether all (or some) of the accomps have been set to 1. Because it is map specific, anything can be written here.

In the TMX side, there are some inputs that need to be done. First, the map’s accomps are written in the Map’s custom properties called ‘accomps’. Eg, accomps=talk:npc1,kill:guard1,remain:exit

It is done in the TMX because it is only relevant for this map. There are other data that could be offloaded here.

In this case, when player talks to an NPC, this interaction is marked as ‘talk’, and thus the trigger is sent as ‘talk’. If player talks to ‘npc1′ then this is sent to npc1. Now in the TOWT trigger for f_trigger_receiver.f_name==’npc1’, it is responsible for writing the proper key to the accomps. The syntax is free-form so care must be taken.

In the RND test, the killing of an enemy was not broadcast, but this is a good example of implementing such a broadcast (eg BroadcastTrigger(“killed”,f_trigger_receiver.uid)) within the main logic.

Then in the main logic, a trigger receiver is actually waiting to receive the news of death. When it does, it takes care of its own key in the accomps: accomps["kill:guard1"] = 1.

 

Workflow: Setting up the Board for movement

Synopsis

This post is going to try to be the most definitive guide to setting up the Board system from scratch for the purposes of movement. This means it will hit the following requirements.

Requirements

  1. Maps using Tiled/TMX via TMX Importer V2
  2. Board setup based on TMX reading
  3. SLG Movement for pathfinding

Mandatory objects

  1. AJAX, for reading TMX files
  2. Board
  3. Browser, for logging
  4. Function2M (or any function plugin)
  5. Keyboard, escaping movement
  6. InstanceGroup, for storing path information from SLG
  7. SLG Movement, for tile pathfinding
  8. SquareTX
  9. TMX Importer V2
  10. TMX-XML Parser

Mandatory behaviours

  1. GridMove, for the movers on the grid.

Setup

tmx_1

  1. AJAX calls the tmx to be loaded, which gets it as a string.Then the trigger AJAX:On completed is called.
  2. SquareTx’s position offset is set to (16,16); ie the position offset is the physical coordinates of LogicXY (0,0). In an orthographic setup (which this event sheet screencap is based), the value of 16 refers to the offset so that the center of the tile would be moved inside the layout, and the top-left corner of the upper-left most tile will be aligned squarely at the layout’s (0,0) coordinates.
    1. In an isometric position the map height plays a part. 
  3. SLG is configured to use the Board as its Board, and an InstanceGroup (ig).
  4. The mover’s GridMove behaviour is configured to use a particular InstanceGroup for its data.
  5. Make sure that any object that is to be instantiated using the TMX process is destroyed. This makes sure that during the instantiation the proper object is being referenced.
  6. When AJAX completes reading of the tmx, it will trigger its On completed event.
  7. Use the TMX Importer V2 (tmx) to import AJAX.LastData using the XML parser. This populates the tmx object.
  8. Then set the SquareTX’s cell width and height to correspond with the tmx.
  9. Set the Board’s width and height (logical entries) to correspond to the size of the map in the tmx.
  10. Then initiate the tile retrieval.

TMX Data Retrieval

I’ve not yet documented the timings of Objects vs Tile retrieval, so I’m not making any dependence on timings.

Tiles are retrieved first before Objects.

Movable areas

When creating areas for movement, I prefer to create a Tiled layer for movable areas and leave tiles blank where it’s not possible to move on, rather than tagging tiles impassable, so I don’t need to check this during the¬†cost function.

On each tile cell

  1. Use Board:Create tile to instantiate the tiles and place them on the board.
    1. Board: Add chess could also be used, but this is more confusing because it only places a logical ‘marker’, and does not instantiate it.
  2. Configure the frame of the tile (ie id of the tileset).
    1. Note that some it’s not always the case that you can configure the tiles after they’ve been created, for example, re-positioning tiles after instantiation didn’t seem to be reliable or possible. But it seems that instance variables are ok.
    2. TMX tile properties are set as necessary

On each object

Same sort of thing as On each tile cell.

  1. Create using Board:Create chess.
  2. Note: pay attention to the Board z-index as well as the C2 layer.
  3. Configure non-Board related stuff as needed.
  4. Note OXY2LXY, which is the ‘Orthogonal to Logical coordinate function’. To repeat a past post, the TMX Object’s position is recorded in orthogonal coordinates, and the Board or SquareTX object have no convenience features to translate those values to isometric. The OXY2LXY function is this translation:

This completes the TMX retrieval.


Initiating the move

The first thing to consider is the first call to move.

In this case it is a LMB on a tile.

  • Note ig:Clean group. This removes all previous path entries (in this case it is “path” referring to the waypoint nodes)
  • The main command is: slg: Get moving path start from <mover> to tile/chess <tile> with moving points to slg.INFINITY and cost to <cost_function> then put result to group¬†<instance_group_name>
    • <mover> refers to the chess that is already on the Board.
    • <tile> refers to the tiles on Z=0 on the Board, which is the basis for moving.
      • Presumably (haven’t checked), if <tile> is at a specific Z index on the Board, then SLG will consider that Z index and pathfind on that level only. But what if the <tile> is at Z=2, for example?
    • <cost_function> is the cost function of SLG which determines the resulting path. We can also call this a¬†path function.
    • <instance_group_name> is the group name inside the InstanceGroup object which stores the UIDs of the pathfinding nodes.
  • Then on the condition that the GridMove is not moving the mover, we pop the first waypoint, which is the first waypointand this is SOL’d as the tile object.
  • With this SOL, direct GridMove to move to that tile.
  • This the initial movement phase.

Cost function (moving path function)

Before dealing with the continuation of the move, we must define the cost function of SLG on the mouse click.

The cost function, also called moving path function, is called by SLG when a moving path is required.

  • The basic definition of a cost function to make set the the return¬†cost to 1.
  • Returning a cost of SLG.BLOCKING will make this tile impassable
  • Use slg.TileUID as the reference to the tile being queried for pathfinding.
  • If the map was generated with blank areas, then there’s no need to check against those, as they won’t be even be considered for pathfinding.

Continuing the move

Once the move has been initiated, then continue to move as long as there are nodes in the InstanceGroup for paths.

  • The continuation of the move is on the GridMove:On reach target trigger, which is triggered when GridMove moves on top of each tile as stored in the InstanceGroup.
  • Use the condition InstanceGroup:Pop one instance <tiles> from group <instance_group_name> in order to determine if it has popped the last one. If it has then GridMove is bypassed.

Movable area function

The movable area function may or may not be used in Citizen 2401, but this is a good time to document this function.

SLG has 2 ‘cost’ functions. One is the movable path, and the other is the movable area.

Focus on the SLG call.

Just like movable path, movable area’s search pattern is to move out from a logical coordinate.

What the events above are trying to do is to generate a list of tiles which the AI can move to that are not LOS’d by the player. The LOS of the tile is determined by another function which switches the¬†los instance variable accordingly, so that only this variable is checked.

The movable area cost function itself (‘p evade area’) does not check for the LOS state, and the reason is described here. Simply put, because of the movable area search ‘creep’ may get blocked by LOS’d tiles, only the tiles are tested on a distance basis (ie movement cost as defined in the SLG:Get moving area)

Then, a filter function is applied on top of¬†the results of the movable area function in order to get rid of those tiles that is LOS’d.


Stopping, changing paths

To stop, simply clear the InstanceGroup path group. This will give GridMove no waypoints to go when GridMove:On reach target is triggered.

When trying to LMB on a tile while still moving, simply clean the Instance path group.

This has the effect of generating a new path while making sure the GridMove still goes to the last assigned waypoint.

Workflow: Animated Tiles

Dedicated animated tiles

The syntax to activate animated tiles is in the ‘vars’ (variant) variable of each tile. Again, to recap, the Tiles’ ‘vars’ property denote the any number of states that the Tile can be in. For every state specified in the ‘vars’ property, there is a corresponding data related to the graphic that will be shown when the Tile (of that Layer) reaches that state.

Example:

vars=open*:a,close:a:15

Note * and ‘a’ and ’15’.

The * denotes that it is the default state of the Tile.

The ‘a’ denotes that instead of using another Tile ID for this state, use an animated variant.

The ’15’ denotes an alternate Tile ID which is an extra level of control in order to find the animated Sprite

Ok, so how does it evaluate which animated Sprite to use?

Through the use of Nicknames to reliably instantiate the tileset Family (ts_f) each animated Tile variant is given a unique name. The syntax template is:

<tilesheet_name>_a_<tile_id>_<state>

What this means is this: let’s say we have the above ‘vars’ property in the Tile from the ‘objects’ tilesheet, it’s Tile ID is 10. For a give state, let’s say ‘open’, the system will look for a nickname called:

objects_a_10_open

If this ts_f exists then it will be picked. This ts_f is a dedicated animated sprite for that tilesheet/tile ID/state combination.

Now, as another example, if the Layer’s Tile state becomes ‘close’, the ‘vars’ property above specifies:

close:a:15

In this case, it will look for a nickname called:

objects_a_15_close

No ‘nominal’ state

As an aside topic, this is about whether tile states (ie TcoDict) should have a ‘nominal’ (or default) state. This was apparently not a good idea, since it is logically ambiguous. A default of any state make it impossible to know what is the converse value of it.

At any rate, I’ve made all TcoDict unambiguously have a state, and this is inherited in the C2 import of the TMX.

The behaviour before¬†was that for each TCO, it reads the ‘layers’ property and there initialises the state. This could be tedious in the future. And since it is possible to indicate the state of a Sprite/Tile by visually looking at it (eg open door, closed box), I will do it by reading the Tile’s ‘vars’ and looking for the -1 directive. Whatever state it is, it will be written as the default state of the Tile in the TcoDict.

Workflow: Changing Tiles

Overview and purpose

I’ve worked out a method of changing tiles at run-time using the same kind of Object ‘tagging’ system I’ve outlined in previous posts (eg Storage) using the same reasoning: the need to identify entities uniquely.

The purpose of changing tiles is to indicate a state of a Tile/Chess, and visually represent the change.

For example, a door’s opened or closed state, or a floor’s damaged or otherwise modified state.

Implementation

tco_explainer

Three elements are needed for this implementation that I call Tile Change Object (TCO).

  1. TCO Object (defined in the TMX)
  2. TCO Sprite (created in C2 through TMX loading)
  3. TCO Dictionary (created in C2 in conjunction with TCO Sprite)
  4. Tiles ‘vars’ property
A TCO Object in the TMX is defined by a 'tco' type. Additionally, it needs a custom property called 'layers' to define the extent of its control over which Tile is on which Layer.
A TCO Object in the TMX is defined by a ‘tco’ type. Additionally, it needs a custom property called ‘layers’ to define the extent of its control over which Tile is on which Layer.

TCO Object

The TCO Object defined in the TMX is any Object with type ‘tco’. It requires a custom property called ‘layers’ which is a comma-separated string defining a list of Layers that is meant to be changed. by this TCO. For example:

layers=Walls,Floor

Again, the TCO Object only marks the tile as changeable. The configuration of how it changes is defined in the Tile itself.

TCO Sprite

The TCO Sprite is a Chess object that resides in its own z-layer in the Board (“Tco”). The Sprite is the effective marker of the TCO in the Board.

TCO Dictionary (TcoDict)

When a TCO Sprite is created on the Board, it creates a TCO Dictionary (TcoDict). This TcoDict’s keys is thus added using the TCO Object’s ‘layers’ property values:

TcoDict["Floor"] = ""
TcoDict["Walls"] = ""

Note that the value is empty, and this means it is in its nominal state.

Tiles ‘vars’ Property

The 'vars' property define the possible states that the Tile could be in, and the Tile ID to use if in that state. A value of -1 means to use the 'self' Tile ID, and implicitly means that the nominal state of the Tile is as defined.
The ‘vars’ property define the possible states that the Tile could be in, and the Tile ID to use if in that state. A value of -1 means to use the ‘self’ Tile ID, and implicitly means that the nominal state of the Tile is as defined.

(Edit: the ‘vars’ property syntax is always in a state of flux so this part has been edited a few times)

The Tiles in the TMX which are expected to change need a property called ‘vars’. This ‘vars’ property define a state, and the Tile ID it’s supposed to use when the Tile and Layer is switched to that state. Example of a ‘vars’ value:

vars=open:42,closed*:-1,broken:5,burning:a,burninglow:a:2

The ‘vars’ line is tokenised by commas ‘,’, which separate it into what we’ll call ‘subvars’

open:42
closed*:-1
broken:5
burning:a
burninglow:a:2

Then it is further tokenised by colon ‘:’.

The first token is the possible state that this Tile could be in. If that state has a *, it is considered to be the default state of the Tile. This is important in the initial creation of the map, to initialise the state of each Tile.

The second token is the Tile ID (of the same Tilesheet) that represents that state. If -1 is specified, it means to use the original Tile ID that the Tile originally began with.

If ‘a’ is specified in the second token, then that means that an animated Sprite is intended.

If ‘a’ was specified in the second token, then the third token can also be specified to denote to use another Tile’s animated Sprite.

For reference this is the syntax template for a subvar:

<state>[*]:<Tile_ID>|a[:<Alt_Tile_ID>]

Refer to the Animated Tiles post on how this is used.

Workflow: Persistent data using a custom Storage entity

Storage

As a continuation from the post¬†Workflow: Room loading with TMX, I’ve implemented one persistent data type called Storage to be able to demonstrate a workflow that will keep data between TMX loads.

Storage is an entity placed in Tiled (an Object). It is named s#. When the TMX is read, it instantiates a ‘storage’ Sprite onto the Board on its own ‘Storage’ z-layer.

It is then initialised: the Tiled property ‘name’ is copied to the C2 instance, and the room (my term for the currently processing TMX file) is also populated thus. (Unrelated, there is also a property called ‘content’ which is the initialising value for the Storage’s contents.)

After the Storage Sprite is initialised, it is effectively a TMX representation in C2. Now, I associate a Dictionary (named ‘StorageDict’) with this Storage Sprite. StorageDict has a ‘name’ and ‘room’ instance variable which are the two mandatory associations to match itself up with the Storage Sprite.

When a Storage Sprite is initialised it searches for its StorageDict partner using ‘name’ and ‘room’ instance variables. If it find it, then nothing needs to happen further — just making sure that there is actually a container for that storage. If it doesn’t find it, then it makes one. The only connection between a Storage Sprite and StorageDict is the ‘name’ and ‘room’ variables/properties.

When the TMX is unloaded and another reloaded, the StorageDict remain in memory. Loading and saving C2 will also keep the data. In other words, the association is ‘soft’.

EDIT: I’ve decided to directly relate the storage Sprite by adding an instance variable. This connection is always made every scene load, but it does save me from having to re-evaluate which StorageDict a storage Sprite is referring to.

Where to from here?

This demonstrate the use of a Dictionary to maintain C2 instance data. It is in itself the data and up this point there is no requirement to visually represent changes (eg tile id change based on a new value).

But I suppose this is where the next challenge is: changes in tile usage depending on saved data.