Workflow: Persistent data using a custom Storage entity


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.


Workflow: Room loading with TMX


The call to load another TMX is easy because I’ve designed the events to be generic. However, all C2 instances generated by the TMX, which have been placed on the board, need to be deleted before another one could take its place.

I’ve used InstGroup in this way: I store all the different types of Object Instances in the InstGroup, such as Edges, Tiles, Chess, Portals, etc, into a InstGroup group called ‘board’.

Then there is a nice convenience feature in InstGroup that simply says ‘Destroy instances’ and will allow you to pick which group. This is super-clean, though I noticed that the UID numbers of destroyed instances (eg Tiles) do not get re-used, so the numbers keep on going up.

Portals and player_start

In Tiled, I’ve used Object Layers to define portals and start positions. The player_start entity is only defined by its name ‘player_start’.  It may be a good idea to make this more generic, but I’ll leave that for later.

Portals, on the other hand, I expect to be placed a lot more, so this object has the type property called ‘portal’. The name of the portal refers to its identifier in the context of the room. For now, the syntax goes like p#, where # is simply the number.

In Tiled, the Portal entity has one custom property called ‘destination’, and the syntax for that is r#.p#. The r# refers to the room number, which is the same as the TMX file (eg r1.tmx). Later, in C2, this ‘destination’ property is tokenised to give out the TMX file to be opened, and the portal to go to.

In C2, the Portal exists in a z-layer called ‘Portal’.  To clarify, a z-layer refers the Board’s z-index. During TMX loading, when a Portal Object is encountered, it places a Portal Sprite into that ‘Portal’ z-layer and is tested later when there is an attempt to use the Portal (Cell is occupied)

I’ve used a global variable to track the intended Portal, since it doesn’t seem that there are any advantages to doing this through some parameter in the TMX loading.

So, the TMX is loaded by the tokenised ‘destination’, the portal, also tokenised, is stored as a global variable; then the TMX goes through its processing, and when processing Object Layers, sees the portal that the player must start in, and places the player there.


Some thoughts re save/load functionality: saving and loading the TMX object will yield only the data that had already been previously read, but the TMX data itself could not be changed by C2 because the TMX Importer doesn’t have methods to do that.

So what’s happening here is that the TMX is loaded, and I transfer pertinent information from TMX into C2 through positioning, instance variables, etc. So what I have is a functionally static TMX as the basis for a scene/room, but loading/saving of this TMX file means nothing in this context.

So how to go about persistence?

If we load the TMX every time there is a portal movement, instances are deleted, and then recreated and applied the changes post-load, very much like Maya’s referenceEdits.

Or, could we use multiple C2 Layouts and jump between them? However, C2 destroys objects when moving to another Layout unless that object is flagged as global.

So, do we store state data for specific things before room transfer and then apply them back on when loading? The issue here is knowing which entity held the data to begin with. For example, if a Chess sits as a container (of data), like a chest, then when it is recreated again by room loading, how do we identify that Chess uniquely again? I consider tile ids to be volatile.

I think the only way to uniquely identify something is by using Objects.

TMX Importer v2 – TMX and external tilesheets

Unfortunately, external tilesheets are not supported, since they are referenced in the TMX as external, and TMX Importer v2, understandably, does not open up the reference.

However, it is possible to copy-and-paste changes made to the external tilesheet into the TMX. Perhaps a tool could be made to replace the TMX’s tilesheet with another .tsx file.

SLG Movement – Picking Tiles/Chess with collision polygons

This is an obvious one; Mouse/Touch inputs use the Sprite’s (frame’s) collision polygon to determine whether the Sprite has been clicked/touched.

Because an isometric tilesheet has spaces in between, the collision polygon will vary greatly between tiles. For the moment I’m doing this by hand.

However, I wonder if there was some way (by way of tools, perhaps) to procedural generate vertices so that it can be written back to the CAPROJ.


EDIT: There is a Tile Collision Editor in Tiled, which writes its information in the TMX file. It would be just a matter of working out the coordinates, and applying those settings in the CAPROJ. Of course, it would be better if a smart algorithm could take care of this. 🙂

SLG Movement – Pathfinding impassable Tiles and Chess

This is how I’m dealing with SLG Movement’s pathfinding and the way impassable Tiles and Chess affect it.

The goal is summarised thus: When I click on an impassable Tile, my intention is to find a path to the Tile even though I may not finally reach the final Tile. 

The problem is that SLG Movement’s cost function, which is the function evaluating the path, will always include the destination as part of its results to InstGroup. It’s no use trying to define a BLOCKING cost for it: SLG Movement will always include the destination Tile.

I tried poppping instances from InstGroup, but the methods in InstGroup did not easily allow me (as far as I could see, anyway) to pop the tail end of the InstGroup array.

What I ended up doing was querying whether Tile being moved on to was impassable or not. If not, GridMove is stopped from moving..

SLG Movement – No path generated due to creating two Chess in same Layout

This title is pretty lengthy due to the specificity of the problem.

The issue stems from SLG Movement’s failure to generate a path. This is rooted in the wrong Board placement of the Chess (which, in this case, is meant to refer to the mover), which, specifically, is having two or more instances of the Chess in the same Layout.

This happens when the Board’s Action:Create chess is executed on the same Layout where the Chess mover exists. This instantiates another copy of Chess. I think this fine, but when SLGMovement’s Action:Get moving path is called, and if the Chess is not unambiguously picked (SOL), then it will use the first Chess, which is not really assigned to the Board.

I noticed this particularly in Rex’s example capx where he puts the ‘resources’ — eg exemplar Sprites — into a separate Layout.

I think having a ‘resources’ Layout is good practice, however, this should only be used for Sprites intended for instancing, as those directly manipulated will not be visible in the current Layout if they are manually set in the ‘resources’ one.

Workflow: Tiled to C2 – TMX to Board

Creating tiles on the Board from TMX Importer

Basic stuff. The main idea is to create the Tiles as the TMX is being imported. Rex explains it in the Scirra forums.

Rex’s example for putting tiles into Board based on TMX data.

Layering up Tiles and Chess

Tiles are the elements used for determining the Board’s logical positions. Chess, on the other hand, are elements above the Tiles. As mentioned in Rex’s docs, Tiles are those residing in Z=0, while Chess are those that reside in Z>0. The distinction cropped up because I wanted to generate Edges based on a custom property of a tile.

But what was happening was this: I was generating Tiles on the board using Board’s Action:Create tile. This not only instantiates the Sprite, but registers its presence as a Tile on Board. Now, TMX Importer was generating other Tiles from other layers, so it was overwriting some of the Tiles that I had just placed. When it came to query the Tiles in order to determine where an Edge should appear, it was always referring to the latest Tile that was put in that logical position, and some of those Tiles had no edge requirement.

The solution was to put the TMX tiles as Chess entities on top of each other on the Board in order to distinguish them from one another. It was a matter, then, to decide which TMX tiles would be Board Tiles, and which would be Chess. I decided that the base Floor layer — for now, at any rate — should be the Tiles, and every other TMX tile would be Chess.

As part of my solution, I thought it would be intuitive if the Board’s z-ordering matches the layer ordering in Tiled (rather than C2’s layers matching tiled, since C2’s layers have a different function from Tiled layers anyway). So what I did was to initialise a C2 Dictionary to contain the TMX layer names along with their layer indices (eg tmx_layer[“Floor”] = 0). Then when a Tile is being generated, it knowing what TMX layer it belongs to (eg tmx.LayerName), that is looked up against the Dictionary, and placed in the appropriate z-axis.

So Tiles can be some generic Sprite. But to make it more efficient, the Tiles should represent all ground areas, even those not necessarily impassible, but at the least the areas which are of programmatic interest.

Tiled’s Objects and Board’s Logical Positions

Using Rex's Function2M to convert Tiled orthogonal-type Object parameters to Logical positions.
Using Rex’s Function2M to convert Tiled orthogonal-type Object parameters to Logical positions.

Here are some tricky bits regarding TMX’s Objects (note that Objects, capitalised, pertains to the Object entity in the Tiled prog). In the first place, querying a TMX’s Object’s ObjectX/Y parameters inside Condition:On each object, will give you the coordinates as it is written down in the TMX file (observable in the Tiled Editor). The problem with this is that that the coordinates is not a Logical position, nor is it a screen-space (aka ‘Physical position’) position. It is actually a position in orthogonal-space!

There are no Expressions in Rex’s Board plugin that computes this, because the computation is dependent on the projection, which is reasonable. Instead of maintaining another Board for a trivial lookup, I just implemented my own using functions.

There are some interesting points here. First, in Tiled Objects, the X/Y values use pixel values, and they use the Map’s Tile Height as the normalising factor. Because this is an isometric map, the width and height of the tiles are not the same and because the tiles have indeed been transformed, you have to ask what is the resulting pixel X/Y value that defines the end of one tile and the beginning of another. It turns out that the tiles use the tile height:

At (0,0), the ‘upper-left’ corner of the object is aligned with the top-left corner of the grid.
With the grid/tile width/height to be 256/149, moving the Object X=+149 pixels brings it up neatly to just the end of that tile. At +150 pixels, it will lie on the adjacent tile.