Workflow: Room loading with TMX

InstGroup

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.

Persistence

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:

tiled_object_layer_positioning1
At (0,0), the ‘upper-left’ corner of the object is aligned with the top-left corner of the grid.
tiled_object_layer_positioning2
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.

Workflow: Tiled to C2 – Tilemaps

No C2 support for isometric Tiled maps

Using C2 Tile Object is limited to orthogonal, both in terms of doing the design in the C2 layout, or importing TMX files through the Tilebar functionality in C2. In other words, native C2 is a no-go for isometric levels.

Rex’s TMX Importer V2 Workflows

I turned to TMX Importer v2.x (tmxiv2) by Rex, which is a runtime importer and supports a much wider aspect of Tiled’s functionality. By ‘runtime’ this means that the map is not loaded into the C2 editor, and can’t be seen until the level is being previewed. This a bit of disadvantage for me, as I am used to editing things in C2. But I have been getting predictable results and I think, with enough practice, the workflow will be seamless.

The TMX Importer uses Sprite as though it were a tilesheet. It uses the animation frame of the C2 Sprite as a tile id.

Get TMX via AJAX

The standard way to retrieve text (ASCII) files from external source is to use the C2 AJAX object. In Rex’s own TMX examples, he uses pasted strings into the input. Either way, the workflow to do this is as follows:

tmx_ajax_call
On start of layout, AJAX object requests the ‘iso.tmx’ file. It is necessary to import this file into the project (under the project’s /File folder) in because the AJAX object assumes this to be relative root. This request is tagged with a name (eg ‘tmx’) and when the reading of the file is completed, Trigger:On completed is called, whereby we assume that AJAX.LastData contains the text file string data.

Using TMX Importer v2.x

Once the AJAX object reads the TMX file, it will run Trigger:On completed. Here we take AJAX.LastData as the content of the TMX file, and feed that into tmxiv2. There are two main methods of importing: creating tiles immediately, or retrieving a tile array.

Creating tiles (Action:Create tiles) is simpler, but it obeys a pre-defined behaviour which requires you to create C2 layers to match the TMX layers. This becomes a hindrance when you consider sorting Sprites. As I will touch on later about Rex’s ZSorter plugin, we need many of the Sprites in one layer. And so Action:Create tiles will not do that for us.

So the other way of doing it is to use Action:Retrieve tile array, which kicks off a procedure that starts getting the tile information. Note that the TMXXMLParser is used because a TMX file is an XML.

tmx_import_retrieve_tile_array
When Action:Retrieve tile array is called, it goes into procedure locating every tile. For every tile it encounters in the TMX file, it goes to Trigger:On each cell tile.

As each tile is being retrieved, it triggers Trigger:On each cell tile, which is the place to configure the tile as they are being placed. There is a caveat however. Although a Sprite instance is being created in Trigger:On each cell tile, there seems to be a problem accessing at least some instance information and thus some actions are not possible in that trigger. In the picture above, I had to reposition the tiles after they were placed by tmxiv2. I had tried to do it in Trigger:On each cell tile but the reference to tiles.ImagePointX/Y were not being read at all. While this may be a bug, I decided to use a trigger variable that is switched when the retrieving is finished in order to act upon the tiles instances from a different scope.

ZSorter and ImagePoints

In order to incorporate z-sorting, which is the sorting of Sprites based on their Y-position, the Sprites must reside in the same layer as the player character, and those moving elements expected to move in front or behind of other Sprites.

The first obstacle to this is the ZSorter’s use of the Origin imagepoint as a basis for determining the Y-position. This is not customisable, so I developed a workaround.

  • Place the Origin imagepoint in respect to how ZSorter references this position for sorting.
    Place the 'Origin' imagepoint for ZSorter.
  • Create a new imagepoint called ‘center’. This is the imagepoint used by TMX Importer for placement. This image point resides at the point where the center of the logical position is.

    center_imagepoint

So what happens is that when TMX Importer first imports the tiles, it will default to using the Origin imagepoint, which will likely cause many of the tiles to be offset. We remedy this by using the vector difference between the Origin imagepoint and the desired center imagepoint and applying that difference to the tile

tile_offset_by_imagepoints