Browse Source

Begin work on map loading

master
Forest Belton 2 years ago
parent
commit
28f207cbe2
8 changed files with 343 additions and 3 deletions
  1. +4
    -2
      .vscode/settings.json
  2. BIN
      level/intro.png
  3. BIN
      level/intro_coll.png
  4. +174
    -0
      scripts/generate_map.py
  5. +21
    -1
      src/game.h
  6. +8
    -0
      src/level.c
  7. +95
    -0
      src/map.c
  8. +41
    -0
      src/map.h

+ 4
- 2
.vscode/settings.json View File

@ -1,6 +1,8 @@
{
"files.associations": {
"hardware.h": "c",
"util.h": "c"
"util.h": "c",
"video.h": "c",
"vram.h": "c"
}
}
}

BIN
level/intro.png View File

Before After
Width: 256  |  Height: 256  |  Size: 2.4 KiB

BIN
level/intro_coll.png View File

Before After
Width: 256  |  Height: 256  |  Size: 723 B

+ 174
- 0
scripts/generate_map.py View File

@ -0,0 +1,174 @@
import argparse
import os
import pathlib
import re
import subprocess
import sys
from typing import NoReturn, Tuple
from PIL import Image
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
def abort(msg: str) -> NoReturn:
print(msg, file=sys.stderr)
sys.exit(1)
Point = Tuple[int, int]
def generate_coll_map(
in_path: pathlib.Path, width: int, height: int, compress: bool = False
) -> Tuple[str, Point, Point]:
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 = []
spawn = None
camera = (0, 0)
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 = 0
elif pixel == GREEN:
bit = 1
elif pixel == BLUE:
bit = 1
spawn = (x, y)
elif pixel == YELLOW:
bit = 1
camera = (x, y)
else:
abort(f"unsupported pixel in collision map: {pixel}")
if compress:
bits.append(bit)
if len(bits) == 8:
byte = sum([bit << i for i, bit in enumerate(bits)])
out_bytes.append(byte)
bits = []
else:
out_bytes.append(bit)
png.close()
if spawn is None:
abort(f"no spawn point located")
return format_bytes(out_bytes, width=width), spawn, camera
def format_bytes(data: bytes, width: int = 16) -> str:
lines = []
for line_no in range(0, len(data), width):
line = data[line_no : line_no + width]
lines.append(" " + ", ".join(["0x%02X" % b for b in line]))
return ",\n".join(lines)
def generate_map(pngfile: str, compress: bool = False) -> None:
pngpath = pathlib.Path(pngfile).resolve()
section = re.sub(r"[^a-z]", "_", pngpath.name.replace(".png", "").lower())
tilepath = pngpath.parent / f"{section}.tiles"
mappath = pngpath.parent / f"{section}.tilemap"
incpath = pngpath.parent / pngpath.name.replace(".png", ".h")
spath = pngpath.parent / pngpath.name.replace(".png", ".c")
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
if width > 127 or height > 127:
abort(
f"file '{pngfile}' has invalid dimensions (width/height greater than 127 tiles)"
)
png.close()
subprocess.run(
[
"rgbgfx",
"-u",
"-t",
tilepath,
"-o",
mappath,
pngfile,
]
)
collpath = pngpath.parent / pngpath.name.replace(".png", "_coll.png")
coll_map, spawn, camera = generate_coll_map(
collpath, width, height, compress=compress
)
tiles_size = os.path.getsize(tilepath)
with open(incpath, "w") as outf:
outf.write(
f"""
#ifndef IS_MAP_{section}_H_
#define IS_MAP_{section}_H_
extern map_t map_{section};
#endif
"""
)
with open(spath, "w") as outf:
outf.write(
f"""
#include "map.h"
MAP_ASSET({section}_tiles, "{section}.tiles");
MAP_ASSET({section}_map, "{section}.tilemap");
//MAP_ASSET({section}_collision, "{section}.collision");
const uint8_t {section}_collision[] = {{
{coll_map}
}};
const map_t map_{section} = {{
(uint16_t)&{section}_tiles[0],
{tiles_size} / 16,
(uint16_t)&{section}_map[0],
(uint16_t)&{section}_collision[0],
{width},
{height},
{spawn[0]},
{spawn[1]},
{camera[0]},
{camera[1]}
}};
"""
)
def main() -> None:
parser = argparse.ArgumentParser("generate_map")
parser.add_argument("-c", "--compress", default=False)
parser.add_argument("png")
args = parser.parse_args()
generate_map(args.png, compress=(not not args.compress))
if __name__ == "__main__":
main()

+ 21
- 1
src/game.h View File

@ -16,11 +16,31 @@
#define SCRN_VX 256
#define SCRN_VY 256
#define SCRN_VX_B 32
#define SCRN_VX_Y 32
#define SCRN_VY_B 32
#define SCREEN_HEIGHT_TILES 18
#define SCREEN_WIDTH_TILES 20
#define ALLOC_SIZE_FONT 39
#define ALLOC_SIZE_PLAYER 52
#define ALLOC_SIZE_BACKGROUND 32
#define ALLOC_SIZE_ITEMS 20
#define ALLOC_SIZE_MONSTERS 108
#define TILE_INDEX_FONT 0
#define TILE_INDEX_PLAYER ALLOC_SIZE_FONT
#define TILE_INDEX_BACKGROUND (ALLOC_SIZE_FONT + ALLOC_SIZE_PLAYER)
#define TILE_INDEX_ITEMS \
(ALLOC_SIZE_FONT + ALLOC_SIZE_PLAYER + ALLOC_SIZE_BACKGROUND)
#define TILE_INDEX_MONSTERS \
(ALLOC_SIZE_FONT + ALLOC_SIZE_PLAYER + ALLOC_SIZE_BACKGROUND + \
ALLOC_SIZE_ITEMS)
#define TILE_SIZE 16 //!< Size of 1 tile in bytes
#define VRAM_TILE_PTR(idx) \
((void*)(_VRAM + \
(idx)*TILE_SIZE)) //!< Compute VRAM pointer for tile section
typedef enum {
GAME_STATE_TITLE,
GAME_STATE_LEVEL,

+ 8
- 0
src/level.c View File

@ -1,8 +1,16 @@
#include "game.h"
#include "map.h"
#include "sdk/hardware.h"
#include "sdk/joypad.h"
#include "sdk/video.h"
void level(void) {
lcd_off();
// map_load();
rLCDC |= LCDC_ON;
while (1) {
joypad_update();

+ 95
- 0
src/map.c View File

@ -0,0 +1,95 @@
#include "map.h"
#include <string.h>
#include "game.h"
#include "sdk/hardware.h"
#include "vram.h"
#define INIT_SCX ((SCRN_VX - SCRN_X) / 2)
#define INIT_SCY ((SCRN_VY - SCRN_Y) / 2)
map_t MAP;
uint8_t pending_row[SCRN_VX_B];
uint16_t pending_row_dest;
uint8_t pending_col[SCRN_VY_B];
uint16_t pending_col_dest;
static void map_write_row(uint16_t map_ptr, int8_t x0, int8_t y0);
static void map_write_col(uint16_t map_ptr, int8_t x0, int8_t y0);
void map_load(map_t *map) {
MAP = *map;
rSCX = INIT_SCX;
rSCY = INIT_SCY;
// TODO: Update player state
memcpy(VRAM_TILE_PTR(TILE_INDEX_BACKGROUND), (uint8_t *)MAP.tile_ptr,
MAP.tile_count);
int8_t x0 = MAP.camera_x - INIT_SCX;
int8_t y0 = MAP.camera_y - INIT_SCY;
uint16_t map_ptr = _SCRN0;
for (uint8_t i = 0; i < SCRN_VY_B; ++i) {
map_write_row(map_ptr, x0, y0);
map_ptr += SCRN_VX_B;
y0++;
}
}
void map_update(void) {
if (pending_row_dest != 0) {
vram_enqueue_mem_xfer((uint8_t *)pending_row_dest, &pending_row[0],
sizeof pending_row);
pending_row_dest = 0;
}
// TODO: Make it work
/* if (pending_col_dest != 0) {
vram_enqueue_mem_xfer(pending_col_dest, &pending_col[0],
sizeof pending_col);
pending_col_dest = 0;
} */
}
static void map_enqueue_row(uint16_t vram_ptr, int8_t x0, int8_t y0) {
pending_row_dest = vram_ptr;
if (y0 < 0 || y0 > MAP.map_height) {
memset(&pending_row[0], 0, sizeof pending_row);
return;
}
uint8_t *map_ptr = (uint8_t *)(MAP.map_ptr + y0 * MAP.map_width);
uint8_t left = sizeof pending_row;
uint8_t *row = &pending_row[0];
while (left > 0 && x0 < 0) {
*row++ = TILE_INDEX_BACKGROUND;
left--;
}
while (left > 0 && x0 < MAP.map_height) {
*row++ = *map_ptr++;
left--;
}
while (left > 0) {
*row++ = TILE_INDEX_BACKGROUND;
left--;
}
}
static void map_write_row(uint16_t vram_ptr, int8_t x0, int8_t y0) {
map_enqueue_row(vram_ptr, x0, y0);
memcpy((uint8_t *)pending_row_dest, &pending_row[0], sizeof pending_row);
pending_row_dest = 0;
}
static void map_write_col(uint16_t map_ptr, int8_t x0, int8_t y0) {}

+ 41
- 0
src/map.h View File

@ -0,0 +1,41 @@
#ifndef IS_MAP_H_
#define IS_MAP_H_
#include <stdint.h>
#include "sdk/assets.h"
typedef struct {
uint16_t tile_ptr;
uint8_t tile_count;
uint16_t map_ptr;
uint16_t collision_ptr;
int8_t map_width;
int8_t map_height;
uint8_t spawn_x;
uint8_t spawn_y;
uint8_t camera_x;
uint8_t camera_y;
} map_t;
extern map_t MAP; //!< The current game map
#define MAP_ASSET(var_name, filename) \
void __##var_name##__() __naked { \
__asm__("_" #var_name "::"); \
__asm__(".incbin \"_build/level/" filename "\""); \
__asm__("_" #var_name "_end::"); \
} \
EXTERN_ASSET(var_name)
void map_load(map_t *map);
void map_scroll_up(void);
void map_scroll_down(void);
void map_scroll_right(void);
void map_scroll_left(void);
#endif

Loading…
Cancel
Save