From 819c7899b3452a405bac6300fe44460ca9e5dcd6 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sun, 7 Sep 2025 10:55:19 -0700 Subject: Add support for multiple layers. --- src/gfx2d.c | 36 ++++++++++++++++++++++++------------ tools/mkasset.py | 56 +++++++++++++++++++++++++++++++++----------------------- 2 files changed, 57 insertions(+), 35 deletions(-) diff --git a/src/gfx2d.c b/src/gfx2d.c index 1c8b06f..eaed2b8 100644 --- a/src/gfx2d.c +++ b/src/gfx2d.c @@ -26,6 +26,9 @@ /// Take the minimum of two values. #define min(a, b) ((a) < (b) ? (a) : (b)) +/// The 0-tile denotes "no tile". The renderer skips drawing 0-tiles. +static const Tile NoTile = 0; + typedef struct ivec2 { int x, y; } ivec2; @@ -606,6 +609,10 @@ static void draw_tile_ortho(Gfx2d* gfx, Tile tile, int x, int y) { assert(x < gfx->map->world_width); assert(y < gfx->map->world_height); + if (tile == NoTile) { + return; + } + const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile); const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile); @@ -626,6 +633,10 @@ static void draw_tile_iso(Gfx2d* gfx, Tile tile, int iso_x, int iso_y) { assert(iso_x < gfx->map->world_width); assert(iso_y < gfx->map->world_height); + if (tile == NoTile) { + return; + } + const Ts_Tile* pTile = ts_tileset_get_tile(gfx->tileset, tile); const Pixel* pixels = ts_tileset_get_tile_pixels(gfx->tileset, tile); @@ -677,18 +688,19 @@ static void draw_map_iso(Gfx2d* gfx) { assert(gfx); assert(gfx->map); - // TODO: Support for multiple layers. - const Tm_Layer* layer = tm_map_get_layer(gfx->map, 0); - - // TODO: Culling. - // Ex: map the screen corners to tile space to cull. - // Ex: walk in screen space and fetch the tile. - // The tile-centric approach might be more cache-friendly since the - // screen-centric approach would juggle multiple tiles throughout the scan. - for (int wy = 0; wy < gfx->map->world_height; ++wy) { - for (int wx = 0; wx < gfx->map->world_width; ++wx) { - const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy); - draw_tile_iso(gfx, tile, wx, wy); + for (uint16_t l = 0; l < gfx->map->num_layers; ++l) { + const Tm_Layer* layer = tm_map_get_layer(gfx->map, l); + + // TODO: Culling. + // Ex: map the screen corners to tile space to cull. + // Ex: walk in screen space and fetch the tile. + // The tile-centric approach might be more cache-friendly since the + // screen-centric approach would juggle multiple tiles throughout the scan. + for (int wy = 0; wy < gfx->map->world_height; ++wy) { + for (int wx = 0; wx < gfx->map->world_width; ++wx) { + const Tile tile = tm_layer_get_tile(gfx->map, layer, wx, wy); + draw_tile_iso(gfx, tile, wx, wy); + } } } } diff --git a/tools/mkasset.py b/tools/mkasset.py index a402e3c..1be0573 100644 --- a/tools/mkasset.py +++ b/tools/mkasset.py @@ -12,6 +12,7 @@ # import argparse import ctypes +import struct import sys from enum import IntEnum from typing import Generator @@ -80,7 +81,8 @@ def convert_tsx(input_filepath, output_filepath): assert (tileset.tag == "tileset") # Header. - tileset_tile_count = int(tileset.attrib["tilecount"]) + # +1 for the 0-tile. + tileset_tile_count = int(tileset.attrib["tilecount"]) + 1 tileset_tile_width = int(tileset.attrib["tilewidth"]) tileset_tile_height = int(tileset.attrib["tileheight"]) @@ -88,17 +90,14 @@ def convert_tsx(input_filepath, output_filepath): print(f"Tile width: {tileset_tile_width}") print(f"Tile height: {tileset_tile_height}") - pixels = [] # List of byte arrays - pixels_offset = 0 + pixels = bytearray() def output_tile(output, tile_width, tile_height, tile_image_bytes): # Expecting RGBA pixels. assert (len(tile_image_bytes) == (tile_width * tile_height * 4)) - nonlocal pixels_offset - tile_pixels_offset = pixels_offset - pixels.append(tile_image_bytes) - pixels_offset += len(tile_image_bytes) + tile_pixels_offset = len(pixels) + pixels.extend(tile_image_bytes) output.write(ctypes.c_uint16(tile_width)) output.write(ctypes.c_uint16(tile_height)) @@ -111,6 +110,10 @@ def convert_tsx(input_filepath, output_filepath): output.write(ctypes.c_uint16(tileset_tile_height)) output.write(ctypes.c_uint16(0)) # Pad. + # Write the 0-tile, which denotes "no tile", at index 0. + zero_pixels = bytes(tileset_tile_width * tileset_tile_height * 4) + output_tile(output, tileset_tile_width, tileset_tile_height, zero_pixels) + # A tileset made up of multiple images contains various 'tile' children, # each with their own 'image': # @@ -174,8 +177,7 @@ def convert_tsx(input_filepath, output_filepath): output_tile(output, tile_width, tile_height, image_bytes) # Write the pixel data. - for bytes in pixels: - output.write(bytes) + output.write(pixels) def convert_tmx(input_filepath, output_filepath): @@ -187,8 +189,10 @@ def convert_tmx(input_filepath, output_filepath): map_height = int(root.attrib["height"]) base_tile_width = int(root.attrib["tilewidth"]) base_tile_height = int(root.attrib["tileheight"]) - num_layers = 1 flags = Orientation.Isometric if (root.attrib["orientation"] == "isometric") else Orientation.Orthogonal + firstgid = 1 + layers = [] + tileset_path = "" print(f"Map width: {map_width}") print(f"Map height: {map_height}") @@ -196,8 +200,6 @@ def convert_tmx(input_filepath, output_filepath): print(f"Tile height: {base_tile_height}") print(f"Orientation: {flags}") - tileset_path = None - with open(output_filepath, 'bw') as output: for child in root: if child.tag == "tileset": @@ -205,19 +207,12 @@ def convert_tmx(input_filepath, output_filepath): tileset = child tileset_path = tileset.attrib["source"] + firstgid = int(tileset.attrib["firstgid"]) print(f"Tile set: {tileset_path}") tileset_path = tileset_path.replace("tsx", "ts") - # Write the header. - output.write(to_char_array(tileset_path, MAX_PATH_LENGTH)) - output.write(ctypes.c_uint16(map_width)) - output.write(ctypes.c_uint16(map_height)) - output.write(ctypes.c_uint16(base_tile_width)) - output.write(ctypes.c_uint16(base_tile_height)) - output.write(ctypes.c_uint16(num_layers)) - output.write(ctypes.c_uint16(flags)) elif child.tag == "layer": layer = child layer_id = int(layer.attrib["id"]) @@ -238,12 +233,27 @@ def convert_tmx(input_filepath, output_filepath): csv = data.text.strip() rows = csv.split('\n') + tiles = bytearray() for row in rows: tile_ids = [x.strip() for x in row.split(',') if x] for tile_id in tile_ids: - # TODO: We need to handle 'firsgid' in TMX files. - # For now, assume it's 1 and do -1 to make the tiles 0-based. - output.write(ctypes.c_uint16(int(tile_id) - 1)) + # Subtract firstgid and then add 1 to make tiles 1-based. + tile_id = int(tile_id) - firstgid + 1 + tiles.extend(struct.pack("=h", tile_id)) + layers.append(tiles) + + # Write the header. + output.write(to_char_array(tileset_path, MAX_PATH_LENGTH)) + output.write(ctypes.c_uint16(map_width)) + output.write(ctypes.c_uint16(map_height)) + output.write(ctypes.c_uint16(base_tile_width)) + output.write(ctypes.c_uint16(base_tile_height)) + output.write(ctypes.c_uint16(len(layers))) + output.write(ctypes.c_uint16(flags)) + + # Write the layers. + for layer in layers: + output.write(layer) def get_num_cols(image, sprite_width): -- cgit v1.2.3