summaryrefslogtreecommitdiff
path: root/gfx-iso/asset/mkasset.py
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-07-08 14:37:29 -0700
committer3gg <3gg@shellblade.net>2023-07-08 14:37:29 -0700
commit21a0d0c1c424f7db90c3282aad4bf6ad4ef809b7 (patch)
treea6ae8a8cb4108cd33713178e67d3b482fc1fd5ee /gfx-iso/asset/mkasset.py
parent303f5dc58dd8e8266df3c62fc84d9799db8047b9 (diff)
Load tile maps and tile sets from files.
Diffstat (limited to 'gfx-iso/asset/mkasset.py')
-rw-r--r--gfx-iso/asset/mkasset.py155
1 files changed, 155 insertions, 0 deletions
diff --git a/gfx-iso/asset/mkasset.py b/gfx-iso/asset/mkasset.py
new file mode 100644
index 0000000..15f7912
--- /dev/null
+++ b/gfx-iso/asset/mkasset.py
@@ -0,0 +1,155 @@
1# Converts tile sets and tile maps to binary formats (.TS, .TM) for the engine.
2#
3# Currently handles Tiled's .tsx and .tmx file formats.
4#
5# The output is a binary tile set file (.TS) or a binary tile map file (.TM).
6import argparse
7import ctypes
8from PIL import Image
9import sys
10from xml.etree import ElementTree
11
12# Maximum length of path strings in .TS and .TM files.
13MAX_PATH_LENGTH = 128
14
15
16def drop_extension(filepath):
17 return filepath[:filepath.rfind('.')]
18
19
20def to_char_array(string, length):
21 """Convert a string to a fixed-length ASCII char array.
22
23 The length of str must be at most length-1 so that the resulting string can
24 be null-terminated.
25 """
26 assert (len(string) < length)
27 chars = string.encode("ascii")
28 nulls = ("\0" * (length - len(string))).encode("ascii")
29 return chars + nulls
30
31
32def convert_tsx(input_filepath, output_filepath):
33 """Converts a Tiled .tsx tileset file to a .TS tile set file."""
34 xml = ElementTree.parse(input_filepath)
35 root = xml.getroot()
36
37 tile_count = int(root.attrib["tilecount"])
38 max_tile_width = int(root.attrib["tilewidth"])
39 max_tile_height = int(root.attrib["tileheight"])
40
41 print(f"Tile count: {tile_count}")
42 print(f"Max width: {max_tile_width}")
43 print(f"Max height: {max_tile_height}")
44
45 with open(output_filepath, 'bw') as output:
46 output.write(ctypes.c_uint16(tile_count))
47 output.write(ctypes.c_uint16(max_tile_width))
48 output.write(ctypes.c_uint16(max_tile_height))
49
50 num_tile = 0
51 for tile in root:
52 # Skip the "grid" and other non-tile elements.
53 if not tile.tag == "tile":
54 continue
55
56 # Assuming tiles are numbered 0..N.
57 tile_id = int(tile.attrib["id"])
58 assert (tile_id == num_tile)
59 num_tile += 1
60
61 image = tile[0]
62 tile_width = int(image.attrib["width"])
63 tile_height = int(image.attrib["height"])
64 tile_path = image.attrib["source"]
65
66 output.write(ctypes.c_uint16(tile_width))
67 output.write(ctypes.c_uint16(tile_height))
68
69 with Image.open(tile_path) as im:
70 bytes = im.convert('RGBA').tobytes()
71 output.write(bytes)
72
73
74def convert_tmx(input_filepath, output_filepath):
75 """Converts a Tiled .tmx file to a .TM tile map file."""
76 xml = ElementTree.parse(input_filepath)
77 root = xml.getroot()
78
79 map_width = int(root.attrib["width"])
80 map_height = int(root.attrib["height"])
81 base_tile_width = int(root.attrib["tilewidth"])
82 base_tile_height = int(root.attrib["tileheight"])
83 num_layers = 1
84
85 print(f"Map width: {map_width}")
86 print(f"Map height: {map_height}")
87 print(f"Tile width: {base_tile_width}")
88 print(f"Tile height: {base_tile_height}")
89
90 with open(output_filepath, 'bw') as output:
91 output.write(ctypes.c_uint16(map_width))
92 output.write(ctypes.c_uint16(map_height))
93 output.write(ctypes.c_uint16(base_tile_width))
94 output.write(ctypes.c_uint16(base_tile_height))
95 output.write(ctypes.c_uint16(num_layers))
96
97 tileset_path = None
98
99 for child in root:
100 if child.tag == "tileset":
101 tileset = child
102 tileset_path = tileset.attrib["source"]
103
104 print(f"Tile set: {tileset_path}")
105
106 tileset_path = tileset_path.replace("tsx", "ts")
107 elif child.tag == "layer":
108 layer = child
109 layer_id = int(layer.attrib["id"])
110 layer_width = int(layer.attrib["width"])
111 layer_height = int(layer.attrib["height"])
112
113 print(f"Layer: {layer_id}")
114 print(f"Width: {layer_width}")
115 print(f"Height: {layer_height}")
116
117 assert (tileset_path)
118 output.write(to_char_array(tileset_path, MAX_PATH_LENGTH))
119
120 # Assume the layer's dimensions matches the map's.
121 assert (layer_width == map_width)
122 assert (layer_height == map_height)
123
124 data = layer[0]
125 # Handle other encodings later.
126 assert (data.attrib["encoding"] == "csv")
127
128 csv = data.text.strip()
129 rows = csv.split('\n')
130 for row in rows:
131 tile_ids = [x.strip() for x in row.split(',') if x]
132 for tile_id in tile_ids:
133 output.write(ctypes.c_uint16(int(tile_id)))
134
135
136def main():
137 parser = argparse.ArgumentParser()
138 parser.add_argument("input", help="Input file (.tsx, .tmx)")
139 args = parser.parse_args()
140
141 output_filepath_no_ext = drop_extension(args.input)
142 if ".tsx" in args.input:
143 output_filepath = output_filepath_no_ext + ".ts"
144 convert_tsx(args.input, output_filepath)
145 elif ".tmx" in args.input:
146 output_filepath = output_filepath_no_ext + ".tm"
147 convert_tmx(args.input, output_filepath)
148 else:
149 print(f"Unhandled file format: {args.input}")
150
151 return 0
152
153
154if __name__ == '__main__':
155 sys.exit(main())