diff --git a/.gitignore b/.gitignore index 7534c9b..a00b467 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *.2bpp is.gb is.gb.sym -png/map/*.s \ No newline at end of file +png/map/*.s +png/map/*.inc diff --git a/Makefile b/Makefile index a9affb9..a905c66 100644 --- a/Makefile +++ b/Makefile @@ -1,33 +1,35 @@ # Graphics 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_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 SFILES := $(shell find src -type f -name '*.s') -OFILES := $(SFILES:%.s=%.o) $(MAP_ASM) +OFILES := $(SFILES:%.s=%.o) $(MAP_S:%.s=%.o) .PHONY: clean -is.gb is.gb.sym:$(OFILES) $(ALL_2BPP) +is.gb is.gb.sym: $(OFILES) rgblink -o $@ -n $@.sym $(OFILES) rgbfix -v $@ -$(OFILES): $(ALL_2BPP) +$(OFILES): $(MAP_INC) +$(OFILES): $(SPRITE_2BPP) %.o: %.s 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 rgbgfx -o $@ $< clean: - rm -f is.gb is.gb.sym $(ALL_2BPP) $(OFILES) $(MAP_ASM) \ No newline at end of file + rm -f is.gb is.gb.sym $(OFILES) $(MAP_S) $(MAP_INC) $(SPRITE_2BPP) \ No newline at end of file diff --git a/scripts/generate_coll_map.py b/scripts/generate_coll_map.py deleted file mode 100644 index e1a801d..0000000 --- a/scripts/generate_coll_map.py +++ /dev/null @@ -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() diff --git a/scripts/generate_map.py b/scripts/generate_map.py new file mode 100644 index 0000000..29f8799 --- /dev/null +++ b/scripts/generate_map.py @@ -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() diff --git a/src/bg.s b/src/bg.s index 6dd8fba..7ee1323 100644 --- a/src/bg.s +++ b/src/bg.s @@ -1,43 +1,19 @@ INCLUDE "hardware.inc" - -; SCENE.Z80 -; -; Map Source File. -; -; Info: -; Section : ROM0 -; Bank : 0 -; Map size : 20 x 18 -; Tile set : C:\Users\case\Downloads\woods.gbr -; Plane count : 1 plane (8 bits) -; Plane order : Tiles are continues -; Tile offset : 0 -; Split data : No -; -; This file was generated by GBMB v1.8 - -sceneWidth EQU 20 -sceneHeight EQU 18 +INCLUDE "png/map/intro.inc" SECTION "BG0", ROM0 -bg: - INCBIN "png/map/intro.map.2bpp" -bg_tiles: - INCBIN "png/map/intro.tiles.2bpp" -bg_tiles_end: - BG_Init:: ; copy map - ld e, 18 + ld e, intro_HEIGHT ld bc, _SCRN0 - ld hl, bg + ld hl, intro_MAP .copy_map_row: - ld d, 20 + ld d, intro_WIDTH call memcpy dec e jr z, .done - ld d, 32 - 20 + ld d, 32 - intro_WIDTH .skip: ; skip over trailing part in vram inc c @@ -50,9 +26,9 @@ BG_Init:: .done: ; copy tiles - ld hl, bg_tiles + ld hl, intro_TILES ld bc, _VRAM - ld d, bg_tiles_end - bg_tiles + ld d, intro_NUM_TILES call memcpy ret diff --git a/src/main.s b/src/main.s index a13045e..3715d36 100644 --- a/src/main.s +++ b/src/main.s @@ -46,7 +46,7 @@ start: call Keys_Update call Player_Update - call waitForVblank + call wait_for_vblank ld a, HIGH(_OAM) call DMA_Start diff --git a/src/util.s b/src/util.s index 12f858c..ee4e838 100644 --- a/src/util.s +++ b/src/util.s @@ -1,17 +1,18 @@ SECTION "Utilities", ROM0 ; Busy-wait until vertical blank occurs -waitForVblank:: +wait_for_vblank:: ld a, [$ff41] and 3 cp 1 - jr nz, waitForVblank + jr nz, wait_for_vblank ret -; Copies data between two regions +; Copy data between two regions ; @param bc Pointer to the destination region ; @param hl Pointer to the source region ; @param d Size (in bytes) to copy. Must be >0 +; @destroy a, b, c, d, h, l memcpy:: ld a, [hli] ld [bc], a @@ -20,19 +21,21 @@ memcpy:: jr nz, memcpy ret -; Fills a memory region with a value +; Fill a memory region with a value ; @param hl Pointer to the destination region ; @param a Byte to fill with ; @param c Number of bytes to fill. Must be >0 +; @destroy a, c, hl memset:: ld [hli], a dec c jr nz, memset ret -; Divides a value by 10 (http://homepage.divms.uiowa.edu/~jones/bcd/decimal.html#division) +; Divide a value by 10 (http://homepage.divms.uiowa.edu/~jones/bcd/decimal.html#division) ; @param b Dividend ; @return a Quotient +; @destroy b div10:: ld a, b srl a