>> X4FSED <<
When creating the layouts for the rooms in the game, I started by hand-positioning objects by entering pixel-positions in a header file. This worked but… was not practical once I got the spritesheet for the room backgrounds. I was going to need to place hundreds of sprites.
Rather than do that, I decided to spend more time, writing my own editor.
I didn't just jump in and decide to write brand new tooling for everything, of course not. I did take a look at what was available. There are tools like Tiled which provide a lot of functionality out of the box, I would have just had to write a converter to put the data into the header format I was using.
The problem is, Tiled does a lot of things that I don't need, and on top of that, it does a lot of things I don't want. The latter is more problematic. One reason is that it is, as its name implies, a tile editor. My sprites (objects) aren't nicely aligned to a tile grid. I could not find a clean way to use Tiled in such a way that would make this possible. This is what I have found in a number of situations - sometimes when you have a very specific problem, using a generic tool can cause you to spend more time, than just writing a very specific solution yourself.
Is my editor perfect? No of course not. THe user input is awkward and esoteric, just me sticking functions on any free keys at the time (there is… sort of… a reason for this) but it doesn't matter. It is functional.
The code is hideous. It is not something I wish to share with anyone, but that's fine, it needs to do this one task.
It is a modal editor, where you can choose between creating collision objects (green boxes) trigger volumes (the same, but blue) or place objects in the scene.
It loads the spritesheet information from a json file
{ "source": "data/peregrine_spritesheet.tga", "objects": [ { "name": "OBJ_SCREEN", "animate" : true, "frames" : [ { "x": 66, "y": 1, "w": 24, "h": 19, "t": 30 }, { "x": 99, "y": 1, "w": 24, "h": 19, "t": 40 }, { "x": 2, "y": 35, "w": 24, "h": 19, "t": 50 } ] }, { "name": "OBJ_CONTROLPANEL", "x": 297, "y": 34, "w": 48, "h": 27 }, { "name": "OBJ_CHAIR", "x": 322, "y": 2, "w": 20, "h": 30 }, { "name": "OBJ_DOORWAY_TOP", "x": 298, "y": 256, "w": 24, "h": 32 }, { "name": "OBJ_DOORWAY_BOTTOM", "x": 234, "y": 288, "w": 24, "h": 32 }, { "name": "OBJ_CEILING_1", "x": 231, "y": 241, "w": 20, "h": 15 } ] }
It can use any of these object types to position in the room anywhere. It also uses json for its own save state
{ "sprites": [{ "type": "OBJ_SCREEN", "x": 89, "y": 122 }, { "type": "OBJ_SCREEN", "x": 135, "y": 122 }, { "type": "OBJ_SCREEN", "x": 112, "y": 111 }, { "type": "OBJ_CHAIR", "x": 208, "y": 162 }, { "type": "OBJ_CONTROLPANEL", "x": 101, "y": 132 }, { "type": "OBJ_DOORWAY_TOP", "x": -5, "y": 132 }, { "type": "OBJ_DOORWAY_BOTTOM", "x": -5, "y": 164 }], "collision": [{ "x": 158, "y": 204, "w": 318, "h": 10 }, { "x": 311, "y": 132, "w": 12, "h": 147 }], "transition": [{ "x": 2, "y": 150, "w": 4, "h": 100, "id": 0 }] }
But will output the header file in the format I previously specified alongside the json file
#ifndef __peregrine_bridge_H__ #define __peregrine_bridge_H__ extern phrase peregrine_bridge_gfx asm("peregrine_bridge_gfx"); objectdef peregrine_bridge_objects[] = { {OBJ_SCREEN, 101, 143, 8, 0}, {OBJ_SCREEN, 147, 143, 8, 0}, {OBJ_SCREEN, 124, 154, 8, 0}, {OBJ_CHAIR, 218, 92, 8, 0}, {OBJ_CONTROLPANEL, 125, 125, 8, 0}, {OBJ_DOORWAY_TOP, 7, 120, 8, 0}, {OBJ_DOORWAY_BOTTOM, 7, 88, 8, 0}, {OBJ_INVISIBLE, 158, 75, 8, 591}, {OBJ_INVISIBLE, 311, 79, 8, 9219}, }; transitiondef peregrine_bridge_transitions[] = { {TRANSITION_LEFT, ROOM_PEREGRINE_CREWQUARTERS, 2, 134, 4, 100}, }; roomdef peregrine_bridge = { .background_gfx = &peregrine_bridge_gfx, .w = 372, .h = 288, .numobjs = 9, .objects = peregrine_bridge_objects, .numtransitions = 1, .transitions = peregrine_bridge_transitions, }; #endif/*__peregrine_bridge_H__*/
At this time, some of the fields are hardcoded for the bridge.
Additional to that, another tool was created, x4ssc, which was just a quick spritesheet cutter. It was a wrapper around tga2cry, but read in the same spritesheet json file as above, and used it to generate the cry formatted images, as well as game headers.
#ifndef __PEREGRINE_SPRITESHEET_H__ #define __PEREGRINE_SPRITESHEET_H__ #define PEREGRINE_SPRITESHEET \ OBJECT(SCREEN, 24, 19, screen_1_gfx, 3, anim_screen) \ OBJECT(CONTROLPANEL, 48, 27, controlpanel_gfx, 1, 0) \ OBJECT(CHAIR, 20, 30, chair_gfx, 1, 0) \ OBJECT(DOORWAY_TOP, 24, 32, doorway_top_gfx, 1, 0) \ OBJECT(DOORWAY_BOTTOM, 24, 32, doorway_bottom_gfx, 1, 0) \ OBJECT(CEILING_1, 20, 15, ceiling_1_gfx, 1, 0) #ifndef EDITOR extern phrase screen_1_gfx asm("screen_1"); extern phrase screen_2_gfx asm("screen_2"); extern phrase screen_3_gfx asm("screen_3"); extern phrase controlpanel_gfx asm("controlpanel"); extern phrase chair_gfx asm("chair"); extern phrase doorway_top_gfx asm("doorway_top"); extern phrase doorway_bottom_gfx asm("doorway_bottom"); extern phrase ceiling_1_gfx asm("ceiling_1"); animation_chunk anim_screen[4] = { {.data = &screen_1_gfx, .speed = 30}, {.data = &screen_2_gfx, .speed = 40}, {.data = &screen_3_gfx, .speed = 50}, {.data = 0} }; #endif/*EDITOR*/ #endif/*__PEREGRINE_SPRITESHEET_H__*/
This could have been done several ways, including just manually hardcoding the headers, rather than using json, which isn't period appropriate, but this felt like the easiest way to get the data I wanted.
In game, I could debug the positioning of these, by implementing a 'debug mode' which would render a pink box over each object. This can be enabled while holding down the 3 key on the controller. More functionality needs to be added to the tooling surrounding this game's development, but hopefully this can be added slowly over time. It should be enough to start doing some proper layout now.