@ -1,33 +1,35 @@ | |||||
# Graphics | # Graphics | ||||
SPRITE_PNG := $(shell find png/sprite -type f -name '*.png') | SPRITE_PNG := $(shell find png/sprite -type f -name '*.png') | ||||
MAP_PNG := $(shell find png/map -type f -name '*.png' -not -name '*_coll.png') | MAP_PNG := $(shell find png/map -type f -name '*.png' -not -name '*_coll.png') | ||||
MAP_COLL := $(shell find png/map -type f -name '*_coll.png') | MAP_COLL := $(shell find png/map -type f -name '*_coll.png') | ||||
MAP_ASM := $(MAP_PNG:%.png:%.s) | |||||
ALL_2BPP := $(SPRITE_PNG:%.png=%.2bpp) $(MAP_PNG:%.png=%.map.2bpp) $(MAP_PNG:%.png=%.tiles.2bpp) | |||||
# Graphics (generated) | |||||
MAP_S := $(MAP_PNG:%.png=%.s) | |||||
MAP_INC := $(MAP_PNG:%.png=%.inc) | |||||
SPRITE_2BPP := $(SPRITE_PNG:%.png=%.2bpp) | |||||
# Code | # Code | ||||
SFILES := $(shell find src -type f -name '*.s') | SFILES := $(shell find src -type f -name '*.s') | ||||
OFILES := $(SFILES:%.s=%.o) $(MAP_ASM) | |||||
OFILES := $(SFILES:%.s=%.o) $(MAP_S:%.s=%.o) | |||||
.PHONY: clean | .PHONY: clean | ||||
is.gb is.gb.sym:$(OFILES) $(ALL_2BPP) | |||||
is.gb is.gb.sym: $(OFILES) | |||||
rgblink -o $@ -n $@.sym $(OFILES) | rgblink -o $@ -n $@.sym $(OFILES) | ||||
rgbfix -v $@ | rgbfix -v $@ | ||||
$(OFILES): $(ALL_2BPP) | |||||
$(OFILES): $(MAP_INC) | |||||
$(OFILES): $(SPRITE_2BPP) | |||||
%.o: %.s | %.o: %.s | ||||
rgbasm -i inc -o $@ $< | rgbasm -i inc -o $@ $< | ||||
png/map/%.tiles.2bpp png/map/%.map.2bpp: png/map/%.png | |||||
rgbgfx -u -t $(<:%.png=%.map.2bpp) -o $(<:%.png=%.tiles.2bpp) $< | |||||
png/map/%.s: png/map/%_coll.png | |||||
python scripts/generate_coll_map.py -o $@ $< | |||||
png/map/%.s png/map/%.inc: png/map/%.png png/map/%_coll.png | |||||
python scripts/generate_map.py $< | |||||
png/sprite/%.2bpp: png/sprite/%.png | png/sprite/%.2bpp: png/sprite/%.png | ||||
rgbgfx -o $@ $< | rgbgfx -o $@ $< | ||||
clean: | clean: | ||||
rm -f is.gb is.gb.sym $(ALL_2BPP) $(OFILES) $(MAP_ASM) | |||||
rm -f is.gb is.gb.sym $(OFILES) $(MAP_S) $(MAP_INC) $(SPRITE_2BPP) |
@ -1,71 +0,0 @@ | |||||
import argparse | |||||
import pathlib | |||||
import sys | |||||
from PIL import Image | |||||
GREEN = (0, 255, 0) | |||||
RED = (255, 0, 0) | |||||
def generate_coll_map(in_path: str, out_path: str) -> None: | |||||
png = Image.open(in_path).convert("RGB") | |||||
assert png.height % 8 == 0 and png.width % 8 == 0 | |||||
out_bytes = [] | |||||
bits = [] | |||||
for y in range(png.height // 8): | |||||
for x in range(png.width // 8): | |||||
pixel = png.getpixel((x * 8, y * 8)) | |||||
bit = None | |||||
if pixel == RED: | |||||
bit = 1 | |||||
elif pixel == GREEN: | |||||
bit = 0 | |||||
else: | |||||
print(f"unsupported pixel in collision map {pixel}", file=sys.stderr) | |||||
return | |||||
bits.append(bit) | |||||
if len(bits) == 8: | |||||
byte = sum([bit << i for i, bit in enumerate(bits)]) | |||||
out_bytes.append(byte) | |||||
bits = [] | |||||
lines = [] | |||||
for line_no in range(0, len(out_bytes), 16): | |||||
line = out_bytes[line_no : line_no + 16] | |||||
lines.append(" DB " + ", ".join(["$%02X" % b for b in line])) | |||||
section = pathlib.Path(in_path).name.replace("_coll.png", "") | |||||
all_lines = "\n".join(lines) | |||||
with open(out_path, "wb") as outf: | |||||
outf.write( | |||||
f"""SECTION "{section.upper()} MAP", ROM0 | |||||
; Values are provided in tiles | |||||
DEF {section}_width EQU {png.width // 8} | |||||
DEF {section}_height EQU {png.height // 8} | |||||
{section}_collision_map:: | |||||
{all_lines} | |||||
""".encode( | |||||
"utf-8" | |||||
) | |||||
) | |||||
def main() -> None: | |||||
parser = argparse.ArgumentParser("generate_coll_map") | |||||
parser.add_argument("-o", "--output", required=True) | |||||
parser.add_argument("png") | |||||
args = parser.parse_args() | |||||
generate_coll_map(args.png, args.output) | |||||
if __name__ == "__main__": | |||||
main() |
@ -0,0 +1,140 @@ | |||||
import argparse | |||||
import pathlib | |||||
import subprocess | |||||
import sys | |||||
import tempfile | |||||
from typing import NoReturn | |||||
from PIL import Image | |||||
GREEN = (0, 255, 0) | |||||
RED = (255, 0, 0) | |||||
def abort(msg: str) -> NoReturn: | |||||
print(msg, file=sys.stderr) | |||||
sys.exit(1) | |||||
def generate_coll_map(in_path: pathlib.Path, width: int, height: int) -> str: | |||||
png = Image.open(in_path).convert("RGB") | |||||
if png.width % 8 != 0 or png.height % 8 != 0: | |||||
abort(f"file '{in_path}' has invalid dimensions (should be multiple of 8)") | |||||
if png.width // 8 != width or png.height // 8 != height: | |||||
abort(f"file '{in_path}' has different size from map") | |||||
out_bytes = [] | |||||
bits = [] | |||||
for y in range(png.height // 8): | |||||
for x in range(png.width // 8): | |||||
pixel = png.getpixel((x * 8, y * 8)) | |||||
bit = None | |||||
if pixel == RED: | |||||
bit = 1 | |||||
elif pixel == GREEN: | |||||
bit = 0 | |||||
else: | |||||
abort(f"unsupported pixel in collision map: {pixel}") | |||||
bits.append(bit) | |||||
if len(bits) == 8: | |||||
byte = sum([bit << i for i, bit in enumerate(bits)]) | |||||
out_bytes.append(byte) | |||||
bits = [] | |||||
png.close() | |||||
lines = [] | |||||
for line_no in range(0, len(out_bytes), 16): | |||||
line = out_bytes[line_no : line_no + 16] | |||||
lines.append(" DB " + ", ".join(["$%02X" % b for b in line])) | |||||
return "\n".join(lines) | |||||
def format_bytes(data: bytes) -> str: | |||||
lines = [] | |||||
for line_no in range(0, len(data), 16): | |||||
line = data[line_no : line_no + 16] | |||||
lines.append(" DB " + ", ".join(["$%02X" % b for b in line])) | |||||
return "\n".join(lines) | |||||
def generate_map(pngfile: str) -> None: | |||||
pngpath = pathlib.Path(pngfile).resolve() | |||||
incpath = pngpath.parent / pngpath.name.replace(".png", ".inc") | |||||
spath = pngpath.parent / pngpath.name.replace(".png", ".s") | |||||
png = Image.open(pngpath) | |||||
if png.width % 8 != 0 or png.height % 8 != 0: | |||||
abort(f"file '{pngfile}' has invalid dimensions (should be multiple of 8)") | |||||
width = png.width // 8 | |||||
height = png.height // 8 | |||||
png.close() | |||||
with tempfile.NamedTemporaryFile() as tilef, tempfile.NamedTemporaryFile() as mapf: | |||||
subprocess.run( | |||||
[ | |||||
"rgbgfx", | |||||
"-u", | |||||
"-t", | |||||
tilef.name, | |||||
"-o", | |||||
mapf.name, | |||||
pngfile, | |||||
] | |||||
) | |||||
map_data = format_bytes(tilef.read()) | |||||
tile_data = format_bytes(mapf.read()) | |||||
section = pngpath.name.replace(".png", "") | |||||
collpath = pngpath.parent / pngpath.name.replace(".png", "_coll.png") | |||||
coll_map = generate_coll_map(collpath, width, height) | |||||
with open(incpath, "w") as outf: | |||||
outf.write( | |||||
f"""DEF {section}_WIDTH EQU {width} | |||||
DEF {section}_HEIGHT EQU {height} | |||||
DEF {section}_NUM_TILES EQUS "({section}_TILES_end - {section}_TILES)" | |||||
DEF {section}_MAP_SIZE EQUS "({section}_MAP_end - {section}_MAP)" | |||||
ASSERT {section}_MAP_SIZE == {section}_WIDTH * {section}_HEIGHT | |||||
""" | |||||
) | |||||
with open(spath, "w") as outf: | |||||
outf.write( | |||||
f"""SECTION "MAP - {section}", ROMX | |||||
{section}_MAP:: | |||||
{map_data} | |||||
{section}_MAP_end:: | |||||
{section}_TILES:: | |||||
{tile_data} | |||||
{section}_TILES_end:: | |||||
{section}_COLLISION:: | |||||
{coll_map} | |||||
{section}_COLLISION_end:: | |||||
""" | |||||
) | |||||
def main() -> None: | |||||
parser = argparse.ArgumentParser("generate_map") | |||||
parser.add_argument("png") | |||||
args = parser.parse_args() | |||||
generate_map(args.png) | |||||
if __name__ == "__main__": | |||||
main() |