Category Archives: Coding

Animation sheet commands

With the ongoing development in Animation Sheets, I’ve also formed a command syntax to allow flexibility in how actions are sequentially executed, while also being aware of sequence animation.

The flow in C2 is a bit convoluted so I want to document this for future reference.


This is the variable that contains a sequence of ‘intended actions’ An example intend_list looks like this:




An action_prefix can be ‘a’, ‘p’, ‘w’, ‘s’. This corresponds with ‘action’, ‘pose’, ‘weapon’, and ‘speed’, respectively. I’ll refer to this as SWAP for simplicity.

Actions can be move, wepi, wepo, shoot, idle.

Pose can be up, dn.

Weapon can be u, as, am, al (this is not yet implemented as of writing)

Speed can be walk, run.

C2 logic flow

Like I said, the flow jumps around.

The first, we start with a given animation. An animation is defined by SWAP, and SWAP are actually something like states. The SWAP is referred to in order to make the right decision which animation is to be played.

So even at the beginning, the SWAP is initialised manually and then PlayerSetAnimation, which is the function that does much of the decision-making is run, and in constantly run.

How it works

At the beginning of PlayerSetAnimation, the first item ([0]) in intend_list is read and then popped. (Since it is a string, it is implicitly tokenised; when I say ‘popped’ I mean that the first token is popped, since it is not really an array, though it is treated as such.)

The popped data is called command. The command is queried if it is an Action. If is not an Action, then the state variables of the SWP is set. For example, if the command is s:run, then the intend_speed variable, which is the internal C2 state variable for controlling actual speed, is set to "run".

One important note is that the intend_list commands continue to be popped until an Action is encountered (so that SWP are processed as state variable changes only).

However, if it is an Action, then it starts querying the SWP variables, then forming an appropriate lookup to the animation map list.

If it is Speed or Pose change then after setting the state variables, it will determine a lookup but uses the nominal animation with changed Pose or Speed variables.

The complicated bit

The complication lay in the combination of the implicit ‘next animation’ of one animation. Some important things to remember:

  • An animation, whether it has a sequence or not, will default to looping itself.
  • In the animation map, a ‘next animation’ can be specified. This makes one animation inherently connected to another.

I’ll demonstrate different intend_list examples and how they are processed, and what goes on beyond the PlayerSetAnimation function.

Example 1

Given an intend_list:


This is used when switching from a small weapon to a medium weapon, and only applies when a small weapon is equipped.


When a:wepi is popped, ‘wepi’ is the intend_anim, which is the keyword. SWP variables are queried (although in this case only the pose and weapon are relevant). Then the animation lookup is set, in this case ‘am upwo’ (medium weapon, standing weapon out).

Remember that the intend_list is popped, so it now looks like this.


Then the lookup is used to get the seq_start/seq_end, and other variables needed to play the animation. The next_anim and is_seq variables are also populated.


Then PlayerPlayAnimation is called. What this function does is simply play the animation, and starts the playhead at the appropriate place. It doesn’t check anything; it just makes the animation run.


Then there is a On frame change C2 trigger, which calls the PlayerCheckAnimation function. This function checks to see if the sequence as ended. If the sequence’s parameters have been satisfied, it calls PlayerAdvanceSequence.


PlayerAdvanceSequence is reponsible for determining what to do next. If there is a sequence of animation to be played (ie segmentations of the animation to be played discretely), then this function increments the sequence index. This is important, because as long as there is a sequence to be played, the system will continue to play the sequence.

If however, the sequence has been played out PlayerAdvanceSequence decides several things. First, does the current animation have a ‘next_anim’ name? In this example, the ‘wepi’ animation has ‘idle’ for its ‘next_anim’.

So if there is a ‘next_anim’ name, the next thing is to determine if there are any other Actions in the recently popped intend_list. In our case, yes. Remember our intend_list:


‘a:wepo’ is still present. So here, nothing special happens. The above intend_list is retained, and PlayerSetAnimation is called again.

Back to PlayerSetAnimation

When it is called again it sees ‘w:am’, and pops that. But we already know that PlayerSetAnimation will always keep on popping non-Action commands (setting variables as commanded by SWP). So after popping ‘w:am’, it sees the final command ‘a:wepo’ and pops that.

When the last is popped, intend_list is blank. But the whole process is repeated: the lookup is made, and then run. At some point, we end up in PlayerAdvanceSequence again, and then from here we ask the same questions. Does it have a ‘next_anim’ variable? Yes: ‘idle’.

Does it have any other Action in intend_list? No, it’s blank. When that happens, the default ‘next_anim’ is prepended to the intend_list (although because it’s blank, it doesn’t matter). Then the intend_list looks like:


Then PlayerSetAnimation is called with that intend_list, in which is goes back to idle.

Example 2

Let’s try another intend_list going through the same process:


‘a:wepi’ is processed and popped first in PlayerSetAnimation. Because it’s an Action, a lookup is immediately processed for it. Then the intend_list looks like:


After the lookup is processed, it is played and then checked like Example 1.


Eventually we arrive at PlayerAdvanceSequence again when the ‘weapon in’ animation sequence is finished. The ‘weapon in’ animation has ‘idle’ for its ‘next_anim’.

PlayerAdvanceSequence queries if there are still any Action commands in the intend_list. There are none (ie ‘w:u’). So what happens is that the ‘next_anim’ is appended to the intend_list so that the PlayerSetAnimation knows it is the right time to use the ‘next_anim’. The intend_list now looks like this:


So when PlayerSetAnimation is called with that intend_list, it processes and pops ‘w:u’,  which switches the weapon to ‘unarmed’, and then processes/pops the Action ‘idle’. This completes the command sequence for this example.

Example 3

Given the this intend_list:


These are just Pose and Speed changes, so they are considered separately.


Pose and Speed commands change the intend_pose, and intend_speed variables. But a lookup must also be made for them in the same way Actions are. But the main difference is that in Pose and Speed, they are popped and processed immediately at the beginning with the setting of the variables. In our example, before a lookup is created, the intend_list would be empty, though the variables have been set (ie intend_pose=”dn”, intend_speed=”run”)

The lookup is made in the same place where Actions are. The main difference is that Speed and Pose uses the state variables to create the lookup. And just as importantly, it relies on the current PLAYER.anim variable to know what the current animation is.


PLAYER.anim is an instance variable in the sprite which contains the ‘base’ animation which specifies only the Pose and the Action. Some examples:

  • If player is idle, unarmed, is standing, PLAYER.anim=”up idle”.
  • If player is idle, unarmed, is crouched, PLAYER.anim=”dn idle”.
  • If player is idle, small weapon drawn, crouched, PLAYER.anim=”dn idle”
  • If player is walking, standing up, weapon drawn, PLAYER.anim=”up move w”
  • If player is walking, crouching, unarmed, PLAYER.anim=”dn move w”
  • If player is intending to run, crouching, unarmed, PLAYER.anim=”dn move w”

Note that PLAYER.anim doesn’t express whether a weapon is drawn or not.

Using PLAYER.anim, we can get the nominal state of movement. Then is a Pose change is required, then the Pose aspect of the PLAYER.anim is modified and PlayerPlayAnimation is called.

The rest goes through the same process again.


The main ideas are

  • intend_list is the the entry point for all animation changes
  • intend_list is popped and data is put into PLAYER.intend_anim
  • intend_anim is used to process what command this is
  • If a command is an Action, is processed normally
    • An animation lookup is constructed from the command itself
    • In addition, the other SWP variables are used
  • When a sequence has played out a check is made if there are still an Action command in the intend_list, to know whether the ‘next_anim’ animation needs to be played out.
    • If there are still Action commands, then the intend_list is fed back in PlayerSetAnimation.
    • If there are no other Action commands, the current animation’s ‘next_anim’ name is appended to intend_list and then is fed back to PlayerSetAnimation.
  • If a Pose or Speed change is made, the intend_pose and intend_speed variables are changed. Then the command is made a lookup using the nominal animation that is currently playing. The nominal animation is found out by referencing the PLAYER.anim variable.


Animation ‘sheets’

In the test RND project, there were a fair amount of sprites used for the player character which featured variations that depicted a separate facing direction from the move direction.

In CITIZEN there are additions such as drawn weapon of a certain size, pose changes (standing, crouched), and speed. All of these combine into a huge animation sprite ‘sheet’.

I use ‘sheet’ to mean a series of sprites meant to be organised together. Much of the work has been how to organise the sheets in such a way as to easily reference them.


The animation hierarchy looks something like this.

  • For every direction (8 directions)
    • (For every direction) A weapon state: unarmed, armed small, armed medium
      • (For every weapon state) A pose: stand, crouch.
        • (For every pose) An intended movement: idle, walk, run, weapon in, weapon out, shoot.

Due to the resulting number of images, I had decided against implementing a separate facing and moving direction, since this would somewhat triple the amount of frames.

In C2, the interface to manipulate sprites is not production-friendly, so the lesser number of Animation folders used the better. Therefore, I decided to arrange the animation by the last element in the hierarchy, which is the intended movement. This grouping was also closest to how it was being rendered in 3d, so the transfer of the sprites to C2 was simpler.

Animations grouped by ‘intended action’.

For the record, ‘wf‘ is ‘walk forward’, ‘rf‘ is ‘run forward’, ‘cf‘ is ‘crouch forward’, ‘wepio‘ is a combination of ‘weapon in’ and ‘weapon out’, and the rest are self-explanatory. ‘wepio‘ has been put together in one group because the frames used in ‘weapon in’ is the reverse of ‘weapon out’, and also the limited frame meant that another animation folder was unnecessary.

Inside any of these folders are the large number of sprites which depict the intended action (eg wf) in stand and crouch, and for each of that, in unarmed, armed small, and armed medium variations, and for each of those variations, all 8 directions.

It is organised as the hierarchy above indicates. Eg in the folder ‘wf’, the first number cycle of frames depict Direction 0, Unarmed, Stand, Walk forward. The second depicts Direction 1, Unarmed, Stand, Walk foward, etc.

Animation map

In order to to retrieve the sprite frame or sequence to play, an animation map had to be created in order for the C2 events to locate the frame and folder.

The animation map consisted of simple directives. Here’s an example:

# IDLE --------------------------------------
u upi anim:idle
u upi is_seq:0-0@5,0-3@3,4-5@3,6-7@2,7-5@2,5-0@2
u upi num_frames:8
u upi prev_frames:0

as upi anim:idle
as upi is_seq:0-0@5,0-3@2,3-6@2,3-0@2
as upi num_frames:7
as upi prev_frames:8

am upi anim:idle
am upi is_seq:0-0@5,0-3@.15,3-0@2,4-7@2,7-4@10
am upi num_frames:8
am upi prev_frames:15

Left of the colon ‘:’ is the directive, and at the right is the value.

The directive is in this template: <wep> <pose&action><key>.

<wep> can be ‘u‘, ‘as‘, ‘am‘.

<pose&action> is a combination of the pose and intended action. ‘pose‘ can either be ‘up‘ or ‘dn‘. ‘action’ can be ‘i‘ (idle), ‘wf‘ (walk forward), ‘rf‘ (run forward), ‘cf‘ (crouch forward), ‘s‘ (shoot), ‘wepi‘ (weapon in), and ‘wepo‘ (weapon out).

Then back in C2, a lookup string is constructed based on the state variables, and this is used to find the directive name, eg ‘u dni’, which means unarmed, crouched, idle.

The ‘key’ is the attribute of a particular animation. ‘anim‘ refers to the C2 animation folder that this animation will use. ‘is_seq‘ is a string that tells the system to play a sequence in a particular order, including a wait time. The syntax for is_seq is as follows.


Note that the startframe and endframe tokens are relative to the sequence being looked up.

The ‘num_frames‘ key specifies the number of animation frames this sequence is supposed to have. This is used to find the proper offsets.

The ‘prev_frames‘ key specifies the number of total offset frames from the beginning of this animation folder. Note in the example above, notice how the next ‘prev_frames‘ value is the sum of the ‘num_frames‘ and ‘prev_frames’ of the previous entry. It is definitely easier to create the animation map in the order in which they are currently arranged in the 3d renders, so that the specification of offsets in the file is just a matter of adding on top of the previous.

(There are also two other keys that bear mentioning. One is ‘next_anim‘ and ‘endsignal‘. ‘next_anim‘ is used so that a specific animation be explicitly told to move to another animation after the sequence is finished. Currently, this is used to automatically move to the ‘idle’ animation after drawing or hiding the weapon. ‘endsignal’ is a text that is specifically used to trigger some action/event in C2. For example, this is used to signal that a weapon has completed being drawn or holstered. So in the YML file, the endsignal of wepin, is ‘wepin’, and in C2, an “AnimationEndSignal” function is used to look for that specific signal string. When it encounters it, it changes the weapon status variable.)

In relation to sorting renders out predictably, 3d renders are appended with a prefix that enables them to be alphabetically sorted in a predictable way. So, for example, if the ‘wf’ (walk forward) animation folder, the ‘u’ sequences go first, followed by ‘as’, then ‘am’. That sorting is enforced by prefixing 3d renders of ‘u’ by ‘a_’, ‘as’ by ‘b_’, and ‘am’ by ‘c_’. In addition to this, the direction angle (eg 0, 45, 90, 180, etc) are written with 3-digit padding (eg 000, 045, 090, 180). With these two modifications to the 3d render filename, this will always yield an correct sort. These naming conventions are incorporated into Janus configurations so there’s no need to do them manually.

The point is: renders need to be sorted right from the time they are rendered, so that when cropped and copied over, C2SpriteMan can rename them in the proper order they will appear finally in the animation sheet.

Primer on animation workflow

Now that the animation aspects being developed in C2 is becoming closer to how renders are named, the following information bears putting down.

First there are several applications involved in making character animation in C2 in CITIZEN: Maya (using Sandline for cache export), LightWave3D, Imagemagick, and C2SpriteMan. The following information is the workflow of data and formats leading up to the final graphics put into C2.

Maya animation

The animation workflow starts assuming that all characters have been modelled; texturing is done separately from animation. In Maya, the characters are rigged.

The organisation of the Maya scenes are such that only one scene file is used for all animation.

Because the Maya workflow uses Sandline, which uses namespaces in order to organise the cache files in the file system, multiple references of the character are brought in. (While I admit that this is an overhead, it is negligible because of the minimalist assets used in the game). These multiple references refer to the animation that the rig is intended to be. For example, the walk animation will have the PWALK (Player Walk) namespace, and the run animation is called PRUN. These referenced rigs will be animated as such, and then they are exported via Sandline, the cache folders they will reside in will be according to their namespace: /PWALK, and /PRUN. This makes it easier to identify the animation in the file system.

Furthermore, the animation timeline in Maya starts at frame 1. Each referenced rig, each with their own assigned animation, starts at frame 1 and animate for as long as necessary, as long it is no more than 100 frames long, which is the defined limit for this workflow.

Orientation of the character is +Z. Animation is only for one direction, too, since the multiple directions are rendered automatically using Janus in LW.

Once the cache files are exported it goes into LW to be set up.

LightWave rendering

There are too many steps that describe this workflow, but the main idea, is that the relationship between the animation done in Maya is related back to LW in several methods.

Animation ranges

It must also be noted that while in Maya, all animations started in frame 1, in LW, the animation is blocked out in discrete 100-frame chunks. For example, the walk animation (eg /PWALK) is assigned frames 1-100, the run animation (/PRUN) frames 101-200, the crouch (/PCROUCH) animation frames 201-300; the idle animation (PIDLE) is assigned frames 901-1000, etc.

A nodal network in LW is created that assigns each cache file to the appropriate geometry. It also offsets each cache (which starts at frame 1) to their corresponding mapped chunk as described above.


Once the cache files are assigned, the geometries are parented to nulls that enable it to rotate to 8 directions. Then Janus comes in where it is responsible for breaking out the master scene file into the varied animations.

There are two ‘map’ files configuring Janus to do this.

Timeframe definition

Actually called ‘CDEMO_timeframe_definition.txt’, this text file maps out the the entire duration for all animations.

For example, it defines where in the timeline you can find the ‘unarmed walk forward’. Basically, Janus uses this file to set the start and end frames (and frame step parameter) depending on the intended breakout.

The intended breakout, on the other hand, is defined in Janus’s FORFILEs.


Without getting into too specific with Janus, FORFILEs is a Janus capability to iterate through a text file and populate aspects of the breakout by the contents of the file.

In this usage, the FORFILE lists all the breakouts that need to happen. For example, the FORFILE lists that for every 8-direction angle, a ‘unarmed walk forward’ animation is exported. In Janus, it reads the tokens and then through its configuration it reads the timeframe definition (described above) and looks up the appropriate time frames to render.

It’s also in this FORFILE that all the specific naming conventions are applied to the final image output.

Workflow: Using AnimationLoader, dynamic tilesets

As usual, Rex has done it again. Or rather, he’d already done it.

The look development of the game is what I’ve always been concerned about. I’m creating tiles, binning them, replacing them. The tile management in Tiled is a bit too simple for my needs.

First came the issue of volatile tile IDs. Then the inability of re-arranging the tiles. So what I’ve done now is to offload all the actual organisation of data to 3d and C2 workflows. What this means is that I no longer have to use tile IDs to reference.

This is an outline of what I’m thinking of designing (and have implemented it for the most part at the end).


3d renders, or wherever the tile renders may come from are rendered directly to the C2 folder’s /Files directory. They have the following name convention.


This collapses the tilset name and tile id into one name. These are then put into a flat hierarchy in the /Files directory.

C2 animation name

Back in C2, I create a single tile sprite. The content of this sprite can be empty because the idea here is that the image will be dynamically loaded in.

This is the gtiles sprite (GBoard tiles), and contains (I think) all of the tilesets used in the game. Now the tilesets are stored in C2’s animation blocks. So in the gtiles sprite, I create all the tileset names that should exist:


This refers to the naming convention of the .png described above.

C2 animation frames

After the animation blocks/tilesets have been created, then I note down how many frames each tileset should have. I create the exact number of frames for them though the frames are blank. If I don’t I will get errors in the Browser console, but it won’t stop the execution of the game.


Rex’s AnimationLoader handles the loading of the renders in the /Files folder by going through every animation block in the gtiles sprite, and then through every frame that it has. I’ve written events that concats it properly, adds the underscores to conform with the convention, and it loads the images into those frames.


This is the important bit.

In Tiled, when I load in the tiles into the tileset, I must reference the png files in the /Files folder, because when the TMX is read, the image source from the tile is also read. This image source TMX attribute is the string that is parsed in order to determine which tileset this belongs to, and what the tile ID is. So, in effect, by the naming convention alone, regardless of which tileset the tiles belong to in Tiled, the fact that the file contains the tileset and id, that’s enough to make a connection to C2. In this way, it doesn’t matter how I arrange my tiles in Tiled because I only use the name of the png for the correspondence.


Collisions are often defined in the C2 image editor, and the information is stored in the caproj. However, when dynamically loading sprites, the corresponding collisions must also be dynamically loaded.

I’m currently implementing a solution to this which involves writing out .col files (collision files) that is partnered with their respective images. This much in the same vein as .imagepoint files.

Collision polygons are defined in Photoshop using paths. A ‘Work Path’ must be created, and a jsx to save that path is run. This outputs a .col file alongside the .png file that is being edited.

Back in C2, I’m currently modifying Rex’s AnimationLoader to take in this .col file implicitly, by reading the file, and then injecting the information directly into the poly_pnts array of the Sprite object.


Workflow: Test project RnD 2017 03 25

I’ve been testing a lot of concepts (some old, some new) with a test project and this post is about what I’ve learned, and what else needs to be explored.

CSVToDictionary and AJAX

It’s easier to maintain a separate text file for populating lookup dicts. Use the AJAX object to read the text and then CSVToDictionary to populate the dict. Remove the double-quote marks when using a text file. This makes it easier to read.

Newlines in text files

When extracting text using AJAX, newlines might be necessary, but escape characters do not seem to be recognised. Therefore, I ended up using escape characters, but had to process it (using search-replace) during extraction.


Containers have been extremely useful especially in terms of debugging messages. For every object I need to debug, a debug Text object is created, and querying the instance of the object will always point to the same objects of the container. No additional picking is necessary. This is probably the most important aspect of my testing.


There are no real enumerations in C2, but simply assigning a constant number to a recognisable variable name is good enough. For example, in the case where the z-layer of a logical position needs to be identified by keyword, I use Z_TILE=0, Z_WALLS=1, etc.


Although a topic unto itself, the main takeaway from doing AI, is how triggers are setup in Tiled and how it’s set up in C2 to respond to triggers.

There are area triggers, which are set up in Tiled. These are positional, and in the test project, they included a ‘facing’ property, which meant that the trigger is fired only when the player is facing a certain direction. The trigger’s name is the string that will end up being called by C2. I opted to use the ‘name’ attribute in Tiled instead of the relegating it to a property because it’s clearer to see the object name in the Tiled viewport.

Some triggers are set up in C2, especially other kinds of interactions. For example, talking to an NPC will yield a trigger specific to the interaction.

The C2 trigger itself is tied to a particular entity, whether it is another NPC, or some other object. That object is responsible for keeping track of the global trigger calls, and what is relevant to itself. For example, if a certain trigger is called 3 times, the object must keep track that it has heard those 3 triggers, and act accordingly. As such, two variables are meant to store AI-specific data: scriptmem, scriptmem_float. The scriptmem variable is meant for strings, and the scriptmem_float is for float value. For example, scriptmem_float was used a generic timer (for waiting). On the other hand, scriptmem was used to store how many times the AI has heard a specific trigger by checking and appending keywords onto the string.

Another important thing about AI is the switch between scripted AI and ‘nominal’ AI. Nominal AI is one that is already pre-programmed in C2 where if there are no scripts directing the AI, it will follow a certain logic (which also depends on the type of AI it is). Two things needed to happen. First the AI needed to know which AI it was allowed to switch to, and this was put in a C2 variable called ai. For example, one agent was assigned ai=script,see. This allowed the agent to switch to ‘script’ mode, but also allow state changes to occur when the C2 ‘see player’ trigger was fired. There was a ‘hear player’ trigger that existed, but because this was not included in the variable, the agent did not respond to hearing, only seeing, and only triggers involving scripts. This ai variable assignment is first done in Tiled, and then propagated to the agent during TMX load.

In addition to the ai variable, the agent had to be put into an FSM state called “script” when it is in scripted AI mode, which allows the system to distinguish which part of the AI sequence it is in. The C2 events which constitute the AI for that agent must consider other FSM states, like “idle”, which is often the ending state after a move.

AI is a bigger topic and I will delve into it more when needed.


I find, more and more, that groups are quite useful not only in organising and commenting, but allows simpler conditional actions to be done in-game. The only example I have is the deactivation of user input if a particular state is on-going. This makes it trivial to block input rather than having check conditions of state all through the user input event.

SLG movement cost

SLG movement cost functions can actually be quite simple. At first I thought it needed to accommodate many aspects, but in the end, despite the relatively complex requirement of the test AI, pathfinding, at most, needed only to query the LOS status of a tile. Impassability was bypassed by excluding impassable tiles from the MBoard, making pathfinding simpler and, I think, faster.

It’s also probably best to name SLG movement cost functions with the following convention: <char> <purpose> path. Eg: “npc evade path”, or “npc attack path”, whereby in the “npc evade path” the NPC avoids LOS, and “npc attack path” does not avoid LOS at all.

Orthogonal and Isometric measurements

I’ve researched and learned a lot of about how to transfer orthogonal measurements to isometric values. The positional values were simple enough, but the real progress was in computing angles.

Here is a list of important considerations when dealing with isometric stuff:

  • Converting a C2 object angle (orthogonal space) to an isometric angle (OrthoAngle2IsoAngle). This is used to draw a line in isometric view if that line’s angle was the same as in orthogonal view.
  • Converting an angle depicted in isometric view to orthogonal space (IsoAngle2OrthoAngle). This is used to determine what an angle would like when viewed from top-down. So when you measure the angle between two points, it’s not truly the angle when viewed in orthogonal space because the isometric view is skewing things. IsoAngle2OrthoAngle allows the reverse computation so that, for example, LOS could be determined for a particular point.
  • Converting orthogonal XY positions to logical XY (OXY2LXY). There is no Board function that allows this mainly because this is a peculiarity of the way Tiled positions objects of the Board. The positioning of objects is written in orthgonal space, but when Rex’s SquareTx projection is set to isometric, then all measurements become isometric. Thus this function is needed for this lack.
  • Movement Board and Graphics Board versions of OXY2LXY. This is required because SquareTx for each Board is different, and thus the logical positions will yield a different location.
  • Computing isometric distance to orthogonal distance. This measures two points in isometric space and gives out the distance as though you were looking from above. This is useful in determining the distance between foreground and background objects. This uses a SquareTx as a point of reference for the width/height ratio, but can use the MBoard or GBoard, because they are assumed to have the same ratio.
  • Computing snap angle (Angle2SnapeAngle). Looks at the object’s angle, and finds the nearest angle to snap to (assuming 8 directions). This is required so that the proper animation is set.
  • Converting MBoard logical positions to GBoard logical positions and vice-versa (MLXY2GLXY, GLXY2MLXY). This is very important as it is able to relate the MBoard to the GBoard. Because the MBoard has smaller cell sizes, querying the logical positions of the MBoard using bigger GBoard logical positions will always yield the top-left cell of the MBoard.
  • Convert GridMove direction to C2 angle (GetGridMoveDirection). The GridMove values are quite different. This function converts it for use with other things, like animation, or other function related to facing, which use the C2 angle, or snap angle.


The last part of this post is about LOS. I’ve already wrote about some aspects of this. But the main path of the research lay in the following:

  • A Line-of-sight behaviour is applied to the player.
  • The player’s facing angle is taken as orthogonal.
  • An LOS field-of-view is defined (eg 90 degrees) for the player.
  • At a given angle (facing_direction), left-side and right-side fov lines are drawn based on the defined fov (90 degrees). Note that these lines are virtually drawn orthogonally.
  • The left and right lines are then converted to isometric angles.
  • Because the left and right lines have been transformed, the difference between these two angles have changed. This new difference is the new LOS field-of-view.
  • The center between these two lines is the LOS center line. The player’s LOS is rotated towards the center.
  • With the new LOS field-of-view, and a new center, this corresponds to an isometric LOS based off a 90-degree LOS when viewed orthogonally.
  • The facing_direction mentioned above bears special mention. When a player clicks on tile in-game, he is actually picking with a view that he is viewing it in isometric view. Therefore, the facing_direction is an isometric angle, which must be converted to an orthogonal angle. It is only then that the left and right fov lines can be properly oriented, because they, in their turn, will be converted back to isometric after the computation is done.

What needs to be explored

ZSorting seems to take a lot of cpu time (~50%), and I’m wondering whether there is a way I can optimise this. So far, the best solution I’ve come up with is to use On GridMove as a condition for sorting. But I think the most ideal way is to localise the sorting around the areas where movement is taking place.


Converting angles to isometric angles

I don’t think ‘isometric angles’ is even a term, but I’m referring to how an angle should appear like in isometric view.

The image below describes the situation.


Given an  angle (75deg and 45deg depicted in red arrows above), what is computed angle if the circle was compressed by half (ie an isometric view). The small ellipse depict a circle in isometric view.

The yellow arrows depict the scaling effect of the point on the big white circle and is projected to touch the isometric circle.

The green arrows represent the vectors whose angles I am computing.


I must apologise to any potential researcher that this solution is entirely homemade and there might be more elegant solutions out there.

  • With a given angle, determine the normalised length of the Adjacent side A. This is done by cos(angle).
  • The length of the A enables the measurement of the Opposite side O.
  • O is computed by multiply A with the tan() ratio of the angle: O = tan(angle)*A
  • Now that O has been found out (the yellow arrow line depicted above), scale the length down as per the isometric projection. For simplicity, I’m scaling it by half (eg for 256×125 tile): O2=O/2
  • Then recompute the ratio of the known A and new O2: ratio=O2/A
  • The new ratio can be used to determine angle using atan(): new_angle=atan(ratio)
  • If angle reaches 90, then A has no length, and this corresponds with the nominal orthographic angle.
  • If angle reaches 180, then O has no length, and so the same thing.
  • If angle > 90 and < 180 the result of A=cos(angle) will be negative.  The same with tan(angle).
  • If angle > 180 and < 270 then A=cos(angle) will be negative still, but tan(angle) will be positive.
  • If  angle > 270 and < 360 then A=cos(angle) will be positive, and tan(angle) will be negative.
  • When result angle is reached, modify the value based on the quadrant of the original angle:
    • If cos(angle) is negative, and tan(angle) is negative, add 180 degrees. (Quadrant 2)
    • If cos(angle) is negative, and tan(angle) is positive, add 180 degrees. (Quadrant 3)
    • If cos(angle) is positive, and tan(angle) is negative, add 360 degrees. (Quadrant 4)
    • If cos(angle) is positive, and tan(angle) is positive, then keep result. (Quadrant 1)

C2 angle

The images above show 45 degrees as pointing NE. In C2, however, it’s pointing SE. But this orientation allowed me to understand what was going on.

C2 Implementation


Working rather well.

To reverse the operation, to get the orthogonal angle from an isometric angle, reverse tile’s width/height in O2: O2=O/(SquareTx.Height/SquareTx.Width)


Workflow: Tiled’s tile IDs not to be referenced in C2

Tiled’s tile IDs are quite volatile. Ordinarily, each tileset’s first tile should be 0-index, but unfortunately, this doesn’t seem to be guaranteed and I’m not sure if it should.

In this forum post Bjorn himself suggested not to use tile IDs as a reference, and instead use tile properties to make a harder connection, which is what I’ve done: created an ‘id’ parameter and and just retrieved it from there.

The only issue here is that each tile ID must be manually entered, but this better than figuring out how to make tile IDs behave.

To make it solid, write a tool that reads the C2 caproj, reads the Sprite object, stores the image source for each frame of a Sprite: a text lookup to associate a filename with an ID.

For example, the lookup can read:

proto_walls walls_block.0000.png 0
proto_walls walls_block_cross.0000.png 1
proto_walls walls_block_ne.0000.png 2

Where the first token is the tileset name, followed by the image filename, followed by the animation frame, which is the ID.

Then the tool is run again on the Tiled-side. Going through each line of the lookup, searches for the tileset name, locates the filename (maybe ignores the relative path), then sets the ‘id’ property to the stored value.

C2 Sprite Manager



The C2 Sprite Manager (C2SpriteMan) was written by me, Lernie, and is a Python (exe wrapped) that views and modifies image files and their corresponding data as it relates in a .caproj (C2 project) file.

This project was originally done for LW (called LWC2), and for speed and flexibility, I migrated the code to Python and turned it to a Windows-based utility.

This tool revolves around a specific pipeline which involves mass creation of sprite assets from LW (or any 3D prog) and then conforms those renders to something that can be plugged into C2.

The tools works upon existing Sprites as they are defined in the .caproj file. This is due to the fact that ‘sid’ attributes in C2 might be better of be maintained by the host prog, so new objects are created only within C2, not externally.

The tool organises sprite graphics into ‘units’ (left panel) From ‘units,’ the individual sprites are displayed (right panel) where the name, its version number, frame duration, image-point file and crop-file and crop-image-point file validation, speed, looping and such parameters as found in C2 itself.

First, the frame number is determined by the actual number of frames found in the unit’s folder, and is used to debug if more or less frames were rendered than expected. There is an option to preview the unit animation using djv (hard-coded, so it djv must be installed). The frame rate is fed as an argument in djv so it mimicks what you would expect to see in C2.

The image-point, crop, crop-image-point files are another thing. First, the image-point is a special file generated in LW which contain normalised X and Y coordinates of the hotspot, and/or an image-point for a particular frame of the animation. It is formatted thus:

_IP_hotspot 0.1 0.2
_IP_gunpoint 0.5 0.12

Again, each frame of animation has a corresponding image-point file, and are named in a specific convention that makes their relationship apparent.

NULL.0001.png # rendered frame
NULL.0001.png.imagepoint # ascii imagepoint file

Note the use of the ‘NULL’ name. This is a constant name. Remember that these files are contained under the unit animation folder, so they are unique; there is no need to make them unique from other units as C2SpriteMan identifies the image based on the folder they are in, very much the same way C2 does it.

The next step is to crop the sprite, and ImageMagick (IM) is used as the tool for it. In this process, IM outputs a file that defines the cropping operation that it has done on the image. Since any number of sprites will most likely have a different cropping result from each other, this information is important because I can use it to modify the image-point file, which changes based on how much was cropped from its partner NULL file.

These cropped files are named as such:


These CROP files are used by C2SpriteMan as the actual usable sprites that is meant to be used in C2,

After cropping, the sprites are copied over to the C2 folder, which is defined as the /c2 subdirectory under the overall project folder. (Since I use many applications, I keep a main project trunk with major areas of delineation, depending on how much relative dependencies are needed for a particular use. For instance, LW, Maya, and C2 all reference relatively. But LW and Maya reference the same things, such as texture maps, cache, other shareable data. C2 references its own assets and files. So I kept the 3D side separate from C2)

The copy procedure transfer all renders to the C2 folder in strict accordance with how C2 expects them to be: file names are stripped down, and become the ###.png format, and are put in their own sprite folder. They were managed in 3D in the same way as C2 to make this process easier.

Once copied, all data can be ‘committed’ to the .caproj file. What ‘committing’ means is that any parameter change made to the sprites (eg changing Speed) will be updated in the .caproj file. This is a write operation on the .caproj, and a backup is made every time it is done. There is a drop-down menu on the lower-right that allows for rolling back to a previous version.

From the top procedure

I’ve just explained the process, but not in the sequence that it would otherwise be done. So this could be confusing reading for my future self. So this is how it’s done from the top-down.

Note that .caproj (folder-based projects) is necessary to use, for obvious reasons.

C2: Create placeholder Sprites

In C2, Sprite objects must be create beforehand. It is important to name the Sprites accordingly because this is essentially the unit name that need to correspond with the renders in 3D.

For purposes of illustration, let’s call our unit ‘hunter’.

Save the .caproj so that these modifications are reflected in the file.

3D: Rendering to expected locations

In 3D, it’s important to render to the following location


First note the /units subfolder under the /renders folder. This is mandatory token.

Then <unit> represents the name of the sprite unit (‘hunter’).

<animation> represents that particular animation name (‘walk’)

Though there is a <version> token, this doesn’t have to be used, though it is much better to use it because you can juggle around variations quickly.

When placed properly, you should get something like this:


In the Sprite list, note ‘Fr’ is 0. This means there are no frames there. In order for any frame to register as proper frames, they need to be named thus:


Note ‘IP’ is also 0. This means that there either have been no image-points generated. The number of IP should be the same as the number of Fr.

Note ‘Crop’ is 0, which means no cropped images have been detected.. ‘CropIP’ is the same deal.

Lastly, note that the C2-related parameters like ‘Spd’, ‘Loop’, etc, are -1. -1 means that no change should be done when you hit ‘Commit caproj’. If this value is larger than -1, then that value will be set in the .caproj as your desired value.

Let’s say now that some frames were rendered from the 3D application, and move on.

C2SpriteMan: Previewing, settings attributes

This step can be done at any point even after cropping.

You can preview the animation using djv. It takes the ‘Spd’ parameter and uses that as the framerate. If ‘Spd’ is -1, then djv will play it back with whatever default it selects.

On the far-right side, an Attributes panel shows the fields which you can adjust parameters of selected sprites. Hitting the blue button ‘Apply’ will apply your changes onto the sprites. These changes are stored in their respective unit folder, so that these settings are portable and will carry over as long as you keep those files in the folders they are meant to be in.

3D: Generate imagepoint files

Generating imagepoint files in LW means getting the projected camera-view space coordinates of a particular 3D location. I’ve done this in LW using LScript but this is a 3D-specific solution. I’ve not it in Maya, and I reckon it would be a different solution.

Either way, the generation of imagepoint files must follow the rules outlined in the first section. Once the imagepoint files are done, C2SpriteMan should reflect a corresponding value in the ‘IP’ column.

C2SpriteMan: Croppping

After generating imagepoint files, the next step is to crop the sprites. This not only crops the sprites but it also ‘crops’ the imagepoint files accordingly, taking the values there and trimming them so that the cropped imagepoint files will have values that register with their corresponding cropped spirtes.

C2SpriteMan: Copying

The next step is to immediately copy the sprites over to the C2 folder.

C2SpriteMan: Commit

After copying, you can then commit.

First, select the unit animations you want to effect. Then hit ‘Commit caproj’. This function looks at all the settings of the selected animations and goes through the .caproj, parses it, and applies the XML settings.

Hotspots and image-point attributes are derived from the .imagepoint files, while the frame-related settings are derived from the other individual settings inside the 3D unit folder.

Frame duration is applied based on the number of CROP sprites detected in the 3D unit folder.

C2: Check sprites

Open up C2, and confirm the settings.