Tag Archives: tiles

Workflow: Diagonal movement across edges

The basic problem is that when moving diagonally, the edges are ignored because edges only exist at the sides of the square, and not at the corners.

Moving across diagonally travels across the corner of the logical square, which do not have any edges.

The solution to this was to query both the start tile (slg.PreTileUID) and the target tile (slg.TileUID) and from there, determine the GridMove/Board direction index that the current path was taking.

Using the principal direction (let’s assume it was a diagonal value of 6 (as demonstrated in the above image), the two other directions flanking the main one was also queried.

In the above image, the movement from the start tile is at direction 6. Direction 2 and 3 were derived from this by a lookup that defined two flanking direction for any given direction.

Then the target tile was also queried, but this time, the principal direction was the reverse of the start tile (computing for the reverse tile was made simpler by using a lookup, although a simple modulus would have done it, too). In other words, 4 is the direction. Using the same lookup, 0 and 1 were looked up as the flanking reverse directions.

Then a check is made against existing edges: are there any edges on the 2 and 3 directions of the start tile? If so, movement cost is BLOCKING. If not, then check the target tile’s 0 and 1 direction edges if they exist. And if they do block the movement.

The method used in determining the principal direction was simply comparing the logical X and Y positions of the start and end tiles.

 

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: Multiple Tilesets

Dealing with multiple tilesets

C2 Sprite are used as tilesets in conjunction with TMX Importer. One Tile Sprite is considered a single tileset.

This the solution I’ve come up so far.

Use of Family Object and Nickname

Tilesets are grouped into a Family called ts_f (stands for tileset family). They have the instance variables that mimick the previous ’tile’ instance.

tileset_family_variables2

The addition is the Nickname plugin and its corresponding behaviour. These are essential to both instantiate and pick the correct instance of the tileset.

It’s worth nothing that I have tried native C2 methods of generating and picking instances into the SOL. However, what I have observed is that while I am able to instantiate specific Family members (by compare-picking Family instance variables), these newly created Family members were not being picked up properly into the SOL, and thus configuring these new instances were not possible.

Nickname, however, is able to generate instances and then place into the Family again. But unlike C2’s own methods, Nickname somehow allows me to reference the newly-created object by accessing the Family.

This might be a worthwhile bug report.

Because of Nickname I was able to retain all my events save several modifications as to the placement of the Tile/Chess objects. Instead of using Action:Create chess/tile, I had to use the System’s Create object to generate the Sprite instance, position it using the Board’s convenience positioning expressions (eg Board.PXY2LXY), and then use Board’s Action:Add chess/tile, which adds the entity into the Board without additionally creating an instance of it.

In summary, I am loading in tilesets manually in C2 and assigning them the name that corresponds with the ones used in Tiled. When TMX is being loaded, during Tile Placement, the tmx.TileSetname is queried and stored. This is the same being used by Nickname to instantiate that tileset/spritesheet.

Just in case it doesn’t immediately become apparent to my future self, the reason why ts_f even exists is simply to conform all tileset’s instance variables. All tilesets are put into the family to inherit the variables.

 

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