engine-psx/tools/guide.org
2025-01-07 02:08:47 -03:00

9.8 KiB

Startup

Everything starts with generating 16x16 tiles. You are welcome to start with 128x128 tiles if you find it easier, but you'll have to control well your tiles so they don't add up much. Remember that VRAM is limited on the PlayStation, so your art is going to have to be cut into 8x8 pieces, and these pieces should fill a 256x256 texture at max, at the end of these steps, and shouldn't have too many colors, so let's say you're constrained to 1023 tiles of 8x8 pixels (tile 0 is always a blank tile).

Your first step is creating a 16x16.png file with your 16x16 tiles. If you started working with 128x128 you could probably cut them up into a single sprite sheet with the aid of Aseprite.

Generating Python's virtualenv

This is how you can create a virtualenv with all Python pendencies to run the tools, though you won't really need it in most cases, since there are no "weird" packages being used:

cd tools/
python -m venv ./venv
./venv/bin/pip install -r requirements.txt

To run any scripts with this venv, run ./tools/venv/bin/python ./tools/script.py.

Generating a character sprite 8x8 tileset and mappings

For characters, we need to use the Aseprite tool for building sprites.

Import the character's sprites as a sprite sheet and into an .aseprite file (see 'assets/sprites/CHARA/SONIC.aseprite').

The character's sprites must be manipulated into individual frames on a tileset layer, with 8x8 tiles. Plus, you'll have to organize the frames in such a way that frames of the same animation are coupled together.

Finally, group frames into animations by creating tags with animation names (again, see 'SONIC.aseprite' for valid animation names).

Finally, export the animation names and mappings using 'export_tilemap_psx.lua', then export the player tileset using 'export_tileset_psx.lua'.

This will create a 'SONIC.png' file and a 'SONIC.json' file.

Now, just pack the frame and tile data into a CHARA file, and generate a TIM image with correct CLUT and TPAGE info:

framepacker.py SONIC.json SONIC.CHARA
img2tim -usealpha -org 320 0 -plt 0 480 -bpp 8 -o SONIC.TIM SONIC.png

Notice that, different than other image tile data, character sprites rely on a PNG's alpha channel to generate transparency bits, instead of using the full black color as mask.

NOTE: If you need to refer to an animation directly by name, the animations are referred to by the engine by their Adler32 hash, so you might want to add a new definition for that on player.c.

The names used on tags are always converted to uppercase, with no spaces.

To calculate the Adler32 hash for a string, one may use Zlib through Python.

Here's a tool to generate hash definitions for some animations.

import zlib

names = [
    "STOPPED",
    "IDLE",
    "WALKING",
    "RUNNING",
    "ROLLING",
    "SKIDDING",
    "PEELOUT",
    "PUSHING",
    "CROUCHDOWN",
    "LOOKUP",
    "SPRING",
    "HURT",
    "DEATH",
    "DROWN",
    "GASP",
]

def get_hash(name):
    hash = zlib.adler32(str.encode(name))
    return f"0x{hash:08x}"

def print_hashes(names):
    for name in names:
        hash = get_hash(name)
        print(f"#define ANIM_{name:16} {hash}")

print_hashes(names)
#define ANIM_STOPPED          0x08cd0220
#define ANIM_IDLE             0x02d1011f
#define ANIM_WALKING          0x0854020e
#define ANIM_RUNNING          0x08bf0222
#define ANIM_ROLLING          0x08890218
#define ANIM_SKIDDING         0x0a85024e
#define ANIM_PEELOUT          0x0849021f
#define ANIM_PUSHING          0x08b2021f
#define ANIM_CROUCHDOWN       0x104802fd
#define ANIM_LOOKUP           0x067001db
#define ANIM_SPRING           0x068e01d4
#define ANIM_HURT             0x031b0144
#define ANIM_DEATH            0x04200167
#define ANIM_DROWN            0x048a018b
#define ANIM_GASP             0x02d9012c

Generating 8x8 tiles and their 16x16 mappings

The following steps will allow you to create intermediate files 'tiles.png', 'map16.json' and 'collision16.json'.

You will also be able to cook these files into PlayStation-only engine files 'TILES.TIM', 'MAP16.MAP' and 'MAP16.COL'. These are binary equivalents to the files above, with only relevant information.

Extra files such as 'tiles16.tsx' will also be generated.

  1. Create '16x16.png' tiles.
  2. Import '16x16.png' tiles into a 'tiles16.tsx'.
  3. Export 'tiles16.tsx' from Tiled as 'collision16.json'.
  4. Copy '16x16.png' to '8x8.png'.
  5. Open '8x8.png' (still 16x16 tiles) on Aseprite.
  6. File > Import > Import Sprite Sheet. The single image will be used as one. Make it a 16x16 grid.
  7. Right click layer > Convert to > tilemap. Make it a 8x8 grid.
  8. File > Scripts > export_tilemap_psx. This will create a '8x8.json' file. Rename it to 'map16.json'.
  9. File > Scripts > export_tileset_psx. Use a 8x8 grid. This will overwrite '8x8.png'. Rename it to 'tiles.png'.
  1. Open 'tiles.png' with your favorite editor and make sure that all transparent pixels are set to color `#000000` (black).
  2. Use TIMTOOL.EXE (preferably) from Psy-Q library to generate a .TIM for your tiles. This will generate a 'TILES.TIM' file on the same directory of the texture.

    • Make sure you un-mark the "Set for Black" option in Semi Transparent Information.
    • Make sure your tileset is at 448x0 and that the CLUT information is 4-bit depth and at 0x482. Notice that texture pages 8 and 24 are for level tiles and CLUT information, respectively.
    • NOTE: If you use another tool such as TIMEDIT, just make sure the black color is accurately picked as transparent color, and that no semi-transparency is enabled. Also ensure the positions for the texture and the CLUT on proper texture pages.
  3. Use the tool 'framepacker.py' to turn 'map16.json' into a 'MAP16.MAP' file:\ framepacker.py --tilemap map16.json MAP16.MAP
  4. Use the tool 'cookcollision.py' to turn 'collision16.json' into a 'MAP16.COL' file:\ cookcollision.py collision16.json MAP16.COL

Generating 128x128 tiles and mappings

The following steps will allow you to generate a 'MAP128.MAP' file from a 'tilemap128.tmx'. This 'tilemap128.tmx' tile is supposed to be a map comprised of 16x16 tiles, created from the same '16x16.png' file we addressed earlier. Each 128x128 tile is supposed to be equivalent to every eight rows and columns on the .tmx map.

Please make sure that the first tile is COMPLETELY BLANK and mind the tile sequence (tiles are counted first from left to right, then up to down).

  1. Create a 'tiles16.tsx' map from '16x16.png', if you haven't already.
  2. Create a 'tilemap128.tmx' map and use 'tiles16.tsx' as tileset. I recommend this map to start with 32x112 dimensions, and 16x16 tiles, of course.

    • Create layers called "none", "oneway" and "solid" (top to bottom), with those specific names.
  3. Create your tiles from left to right, and if you must, up to down. Be mindful of tile order, and make sure that the first tile (first eight rows and columns) are completely blank.
  4. Once you're done with your map (you may save your project for later manipulation), export your .tmx to a 'map128.csv'.
  5. Use the tool 'chunkgen.py' to turn 'map128.csv' into a 'MAP128.MAP' file:\ chunkgen.py map128.csv MAP128.MAP

Preparation for level map creation

Do this in preparation for creating your actual level map:

  1. Go back to your 'map128.tmx' and export it to an image called '128.png'.

    • Make sure you didn't mess up the tile mapping, and that the tile is properly aligned with the upper left corner of your frame. You'll see that by looking at the continuous line in your 128x128 infinite map.
    • Make sure you didn't mess up the map size also. Generally speaking, extra tiles on the right side are just as bad; use Map > Resize Map as needed to ensure that there are no extra tiles to the right.
  2. Create a '128x128.tsx' tileset and use image '128x128.png' as base.

    • If you already created this file, once you re-export '128x128.png', it should update with no extra effort needed, and so will your level maps that use this tileset.

Generating your level

The following steps will allow you to create level maps such as 'Z1.tmx' and 'Z2.tmx', and generate levels such as 'Z1.LVL' and 'Z1.LVL', in PlayStation format.

This will also create intermediate files such as 'Z1.psxlvl' and 'Z2.psxlvl'. This intermediate representation is necessary because Tiled is unable to export levels in binary format in one go, due to scripting limitations.

You'll need to have Python scripting enabled in Tiled, and you'll also need to have `lvlexporter.py` on your Tiled scripts directory (generally `~/.tiled` on Linux).

  1. Create a 'Z1.tmx' or 'Z2.tmx' file using '128x128.tsx' as tileset. The level must be exacly 255x31 blocks long; block size must be 128x128.
  2. Create a layer called 'LAYER0' and another one called 'LAYER1'. Make sure that 'LAYER1' is above 'LAYER0'; level layers are exported from bottom to top.
  3. Draw your tiles preferably on 'LAYER0' (this part is still unfinished, but this is the only layer where collision detection happens). Use 'LAYER1' to draw tiles that should go on front of your character (this part is also a work-in-progress).
  4. Once you're done with your map, go to File > Export as…, pick the "PlayStation proto map" format, and save it as 'Z1.psxlvl' or 'Z2.psxlvl'.
  5. Use the tool 'cooklvl.py' to turn 'Z1.json' or 'Z2.json' into 'Z1.LVL' or 'Z2.LVL':\ cooklvl.py Z1.psxlvl Z1.LVL