From e6cae1c2428bd76d3b26ace486f4857ff063f6d5 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Fri, 28 Jul 2023 05:46:36 +0000 Subject: [PATCH] coordinates: rework map rendering This also adds interactive player movement. I am much more satisfied with the coordinate space math in this commit compared to the previous commit. --- Makefile | 4 +- actor.hpp | 66 ++++++++++++++ coordinates.hpp | 67 ++++++++++++++ input.hpp | 10 ++- main.cpp | 197 +++++++++++++---------------------------- map_objects.hpp | 4 +- render_map.hpp | 75 ++++++++++++++++ tools/generate/maps.py | 6 +- 8 files changed, 282 insertions(+), 147 deletions(-) create mode 100644 actor.hpp create mode 100644 coordinates.hpp create mode 100644 render_map.hpp diff --git a/Makefile b/Makefile index 29bbbeb..debfed8 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CFLAGS = -Isaturn -OPT ?= -Og +OPT ?= -O3 LIB = ./saturn GEN_PYTHON_SOURCE = $(wildcard tools/source/*.py) $(wildcard tools/generate/*.py) @@ -84,7 +84,7 @@ res/%.bst: pokered/%.bst %.o: | $(GFX_TILESETS) -main.elf: $(OBJ) +main.elf: $(OBJ) $(LIBGCC) clean: clean-sh clean-sh: diff --git a/actor.hpp b/actor.hpp new file mode 100644 index 0000000..f40ab99 --- /dev/null +++ b/actor.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "coordinates.hpp" + +struct actor_t +{ + enum direction { + up, + down, + left, + right, + }; + + world_t world; + enum direction facing; + bool moving; + uint8_t frame; + + constexpr inline offset_t offset() + { + switch (facing) { + case left: + return {-frame, 0}; + case right: + return {frame, 0}; + case up: + return {0, -frame}; + case down: + return {0, frame}; + default: + return {0, 0}; + } + } + + // call tick() before move() + constexpr inline void tick() + { + if (!moving) return; + + if (++frame == 16) { + moving = false; + frame = 0; + switch (facing) { + case left: world.x--; break; + case right: world.x++; break; + case up: world.y--; break; + case down: world.y++; break; + } + } + } + + constexpr inline void move(enum direction dir) + { + /* + m 1 . 2 . 3 . 4 . + | - | - | - | - | + + */ + + if (moving) return; + + frame = 0; + facing = dir; + moving = true; + } +}; diff --git a/coordinates.hpp b/coordinates.hpp new file mode 100644 index 0000000..533116a --- /dev/null +++ b/coordinates.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +namespace screen_offset +{ + // the origin is at this offset in screen space + constexpr int32_t x = 4; + constexpr int32_t y = 4; +} + +struct offset_t { // in pixels + int32_t x; + int32_t y; +}; + +struct block_t { + int32_t x; + int32_t y; +}; + +struct world_t; + +struct screen_t { + int32_t x; + int32_t y; + + constexpr inline world_t to_world(const world_t& origin) const; +}; + +struct world_t { + int32_t x; + int32_t y; + + constexpr inline screen_t to_screen(const world_t& origin) const; + constexpr inline block_t to_block() const; +}; + +// screen_t + +constexpr inline world_t screen_t::to_world(const world_t& origin) const +{ + return { + (origin.x - screen_offset::x) + x, + (origin.y - screen_offset::y) + y, + }; +} + +// world_t + +constexpr inline screen_t world_t::to_screen(const world_t& origin) const +{ + return { + -origin.x + screen_offset::x + x, + -origin.y + screen_offset::y + y, + }; +} + +constexpr inline block_t world_t::to_block() const +{ + // division function must round towards negative infinity + // right shift provides that property + return { + x >> 1, + y >> 1, + }; +} diff --git a/input.hpp b/input.hpp index 99d1bc8..638c417 100644 --- a/input.hpp +++ b/input.hpp @@ -29,8 +29,8 @@ void digital_callback(uint8_t fsm_state, uint8_t data); extern input_t input; -constexpr int input_arr = 10; -constexpr int input_das = 20; +constexpr int input_arr = 1; +constexpr int input_das = 1; constexpr int input_debounce = 2; static constexpr inline int32_t @@ -52,6 +52,8 @@ input_flopped(count_flop_t& button) } struct event { - static inline bool cursor_left() { return input_flopped(input.a) == 1; } - static inline bool cursor_right() { return input_flopped(input.b) == 1; } + static inline bool cursor_left() { return input_flopped(input.left ) >= 1; } + static inline bool cursor_right() { return input_flopped(input.right) >= 1; } + static inline bool cursor_up() { return input_flopped(input.up ) >= 1; } + static inline bool cursor_down() { return input_flopped(input.down ) >= 1; } }; diff --git a/main.cpp b/main.cpp index 9e63ffa..7ce108a 100644 --- a/main.cpp +++ b/main.cpp @@ -16,6 +16,10 @@ #include "gen/sprites.hpp" #include "map_objects.hpp" +#include "coordinates.hpp" +#include "render_map.hpp" +#include "actor.hpp" + constexpr inline uint16_t rgb15(int32_t r, int32_t g, int32_t b) { return ((b & 31) << 10) | ((g & 31) << 5) | ((r & 31) << 0); @@ -75,28 +79,6 @@ uint32_t cell_data(const start_size_t& buf, const uint32_t top) return base_address; } -constexpr inline void render_block(const uint32_t base_pattern, - const tileset_t& tileset, - const int32_t map_x, - const int32_t map_y, - const uint8_t block) -{ - for (int32_t block_y = 0; block_y < 4; block_y++) { - for (int32_t block_x = 0; block_x < 4; block_x++) { - const int32_t block_ix = 4 * block_y + block_x; - const uint8_t tile_ix = tileset.blockset.start[block * 4 * 4 + block_ix]; - - const uint32_t cell_y = map_y * 4 + block_y; - const uint32_t cell_x = map_x * 4 + block_x; - // assumes NBG0 map plane_a is at offset 0 - vdp2.vram.u16[64 * (cell_y % 64) + (cell_x % 64)] = (base_pattern & 0xfff) + tile_ix; - //vdp2.vram.u32[64 * cell_y + cell_x] = base_pattern + tile_ix; - } - } -} - -constexpr int32_t last_map = map_t::wardens_house; - struct draw_t { struct { uint16_t tilesets[tileset_t::count]; // div 32 @@ -104,17 +86,10 @@ struct draw_t { } base_pattern; }; -struct player_t { - struct { - int32_t x; - int32_t y; - }; -}; - struct state_t { enum map_t::map map; draw_t draw; - player_t player; + actor_t player; }; static state_t state = { map_t::pallet_town, 0 }; @@ -157,17 +132,10 @@ void load_vram() vdp1_top = load_sprite(vdp1_top, static_cast(i)); } -// screen space -constexpr int32_t origin_px_x = ((320 - 160) / 2); -constexpr int32_t origin_px_y = ((240 - 144) / 2); -constexpr int32_t origin_cell_x = origin_px_x / 8; -constexpr int32_t origin_cell_y = origin_px_y / 8; - -void render_sprites() +void render_sprite(const uint32_t ix, const uint32_t sprite_id, const screen_t& screen, const offset_t& offset) { - uint32_t ix = 2; constexpr uint32_t color_address = 0; - const uint32_t character_address = (state.draw.base_pattern.spritesheets[spritesheet_t::oak] * 128) / 8; + const uint32_t character_address = (state.draw.base_pattern.spritesheets[sprite_id] * 128) / 8; vdp1.vram.cmd[ix].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE; vdp1.vram.cmd[ix].LINK = 0; @@ -181,11 +149,28 @@ void render_sprites() vdp1.vram.cmd[ix].COLR = color_address; // non-palettized (rgb15) color data vdp1.vram.cmd[ix].SRCA = character_address; vdp1.vram.cmd[ix].SIZE = SIZE__X(16) | SIZE__Y(16); - vdp1.vram.cmd[ix].XA = origin_px_x + 4 * 16; - vdp1.vram.cmd[ix].YA = origin_px_y + 4 * 16 - 4; + vdp1.vram.cmd[ix].XA = (cell_offset::x * 8) + screen.x * 16 - offset.x; + vdp1.vram.cmd[ix].YA = (cell_offset::y * 8) + screen.y * 16 - 4 - offset.y; +} +void render_sprites(const offset_t& offset) +{ + uint32_t ix = 2; + + render_sprite(ix, spritesheet_t::red, {4, 4}, {0, 0}); ix++; + const object_t& obj = map_objects[state.map]; + for (uint32_t i = 0; i < obj.object_length; i++) { + const object_event_t& event = obj.object_events[i]; + const world_t world = { event.position.x, event.position.y }; + render_sprite(ix, + event.sprite_id, + world.to_screen(state.player.world), + offset); + ix++; + } + constexpr uint16_t top_x = 80 - 1; constexpr uint16_t top_y = 48 - 1; constexpr uint16_t bot_x = 239 + 1; @@ -216,116 +201,54 @@ void render_sprites() vdp1.vram.cmd[ix].CTRL = CTRL__END; } -static uint16_t scroll = 0; - -/* - in block units: - - world screen - -----------|--------- - player 0,0 | map 4,4 - - scrolled 16,16 - player 1,1 | - - world | screen - --------------------------|--------- - top left = player x - 5 | (add 4) - top right = player x - 5 | (add 4) - - ---- - - screen scroll - - 0,0 -> screen 0,0 - 1,1 -> screen 16,16 - - */ - -// there are 16 pixels per block - -static inline uint8_t get_block(const map_t& map, int32_t block_x, int32_t block_y) -{ - const uint8_t border_block = map_objects[state.map].border_block; - - const bool x_lt = block_x < static_cast(map.width); - const bool y_lt = block_y < static_cast(map.height); - const bool x_gt = block_x >= 0; - const bool y_gt = block_y >= 0; - - const bool inside_map = x_lt && y_lt && x_gt && y_gt; - const bool north = x_lt && x_gt && !(y_gt); - const bool south = x_lt && x_gt && !(y_lt); - const bool west = y_lt && y_gt && !(x_gt); - const bool east = y_lt && y_gt && !(x_lt); - -#define _has(dir) (map.connections[map_t::connection_t::dir].map != map_t::unconnected) -#define _get(dir) (maps[map.connections[map_t::connection_t::dir].map]) -#define _offset(dir) (map.connections[map_t::connection_t::dir].offset) - - if (inside_map) { - return map.blocks.start[map.width * block_y + block_x]; - } else if (north && _has(north)) { - const map_t& north_map = _get(north); - const uint32_t north_y = north_map.height + block_y; - const uint32_t north_x = block_x - _offset(north); - return get_block(north_map, north_x, north_y); - } else { - return border_block; - } - -#undef _offset -#undef _get -#undef _has -} - void render_map() { - state.player.x = 8 * 16; - if (++scroll > 1) { - scroll = 0; - //state.player.x += 1; - state.player.y -= 1; - } - - vdp2.reg.SCXIN0 = state.player.x - 16; - vdp2.reg.SCYIN0 = state.player.y - 16; - /* - vdp2.reg.WPSX0 = 80 << 1; - vdp2.reg.WPSY0 = 48; - vdp2.reg.WPEX0 = 239 << 1; - vdp2.reg.WPEY0 = 191; - vdp2.reg.WCTLA = WCTLA__N0W0E | WCTLA__N0W0A__OUTSIDE; - */ - const map_t& map = maps[state.map]; const uint32_t base_pattern = state.draw.base_pattern.tilesets[map.tileset]; vdp2.reg.PNCN0 = PNCN0__N0PNB__1WORD | PNCN0__N0CNSM | PNCN0__N0SCN((base_pattern >> 10) & 0x1f); - int32_t origin_x = state.player.x / 32; - int32_t origin_y = state.player.y / 32; - - fill(vdp2.vram.u32, 0, 64 * 64 * 2); - - for (int32_t y = origin_y - 3; y <= (origin_y + 3); y++) { - for (int32_t x = origin_x - 3; x <= (origin_x + 3); x++) { - const uint8_t block = get_block(map, x, y); - - render_block(base_pattern, - tilesets[map.tileset], - x + 4, - y + 3, - block); + for (int32_t y = (0 - 1); y < (9 + 1); y++) { + for (int32_t x = (0 - 1); x < (10 + 1); x++) { + render_screen(base_pattern, + map, + state.player.world, + {x, y} + ); } } vdp2.reg.BGON = BGON__N0ON | BGON__N0TPON; } +void update() +{ + state.player.tick(); + + if (event::cursor_left()) { + state.player.move(actor_t::left); + //state.player.world.x--; + } else if (event::cursor_right()) { + state.player.move(actor_t::right); + //state.player.world.x++; + } else if (event::cursor_up()) { + state.player.move(actor_t::up); + //state.player.world.y--; + } else if (event::cursor_down()) { + state.player.move(actor_t::down); + //state.player.world.y++; + } +} + void render() { + const offset_t offset = state.player.offset(); + + render_sprites(offset); + + vdp2.reg.SCXIN0 = offset.x; + vdp2.reg.SCYIN0 = offset.y; + render_map(); - render_sprites(); } extern "C" @@ -339,6 +262,7 @@ void v_blank_in_int() sh2.reg.FRC.L = 0; sh2.reg.FTCSR = 0; // clear flags + update(); render(); // wait at least 300us, as specified in the SMPC manual. @@ -476,7 +400,6 @@ void init_vdp2() void main() { state.map = map_t::pallet_town; - state.player.y = 16 * 8; load_vram(); diff --git a/map_objects.hpp b/map_objects.hpp index 8d124f8..2604998 100644 --- a/map_objects.hpp +++ b/map_objects.hpp @@ -31,7 +31,7 @@ struct object_event_t { pokemon, }; - enum struct movement { + enum struct movement { // or 2-byte boulder movement? stay, walk, }; @@ -50,7 +50,7 @@ struct object_event_t { enum type type; position_t position; - enum spritesheet_t::spritesheet sprite_id; // fixme + enum spritesheet_t::spritesheet sprite_id; enum movement movement; enum range_or_direction range_or_direction; uint8_t text_id; // fixme diff --git a/render_map.hpp b/render_map.hpp new file mode 100644 index 0000000..0f8d3e4 --- /dev/null +++ b/render_map.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "coordinates.hpp" +#include "gen/maps.hpp" +#include "map_objects.hpp" +#include "vdp2.h" + +static inline uint8_t get_block(const map_t& map, block_t block) +{ + const uint8_t border_block = map_objects[map.id].border_block; + + const bool x_lt = block.x < static_cast(map.width); + const bool y_lt = block.y < static_cast(map.height); + const bool x_gt = block.x >= 0; + const bool y_gt = block.y >= 0; + + const bool inside_map = x_lt && y_lt && x_gt && y_gt; + const bool north = x_lt && x_gt && !(y_gt); + const bool south = x_lt && x_gt && !(y_lt); + const bool west = y_lt && y_gt && !(x_gt); + const bool east = y_lt && y_gt && !(x_lt); + +#define _has(_dir_) (map.connections[map_t::connection_t::_dir_].map != map_t::unconnected) +#define _get(_dir_) (maps[map.connections[map_t::connection_t::_dir_].map]) +#define _offset(_dir_) (map.connections[map_t::connection_t::_dir_].offset) + + if (inside_map) { + return map.blocks.start[map.width * block.y + block.x]; + } else if (north && _has(north)) { + map_t north_map = _get(north); + return get_block(north_map, { + .x = block.x - _offset(north), + .y = static_cast(north_map.height + block.y), + }); + } else { + return border_block; + } + +#undef _offset +#undef _get +#undef _has +} + +namespace cell_offset +{ + int32_t x = (320 - 160) / (2 * 8); + int32_t y = (240 - 144) / (2 * 8); +} + +static inline void render_screen(const uint32_t base_pattern, + const map_t& map, + const world_t& origin, + const screen_t& screen) +{ + const world_t world = screen.to_world(origin); + const block_t block = world.to_block(); + + const uint8_t block_ix = get_block(map, block); + const uint8_t * block_start = &(tilesets[map.tileset].blockset.start)[block_ix * 4 * 4]; + + const int32_t quadrant_x = 2 * (world.x & 1); + const int32_t quadrant_y = 2 * (world.y & 1); + + for (int32_t tile_y = quadrant_y; tile_y < quadrant_y + 2; tile_y++) { + const int32_t block_row = 4 * tile_y; + for (int32_t tile_x = quadrant_x; tile_x < quadrant_x + 2; tile_x++) { + const uint8_t tile_ix = block_start[block_row + tile_x]; + + const uint32_t cell_y = cell_offset::y + screen.y * 2 + tile_y - quadrant_y; + const uint32_t cell_x = cell_offset::x + screen.x * 2 + tile_x - quadrant_x; + + vdp2.vram.u16[64 * (cell_y % 64) + (cell_x % 64)] = (base_pattern & 0xfff) + tile_ix; + } + } +} diff --git a/tools/generate/maps.py b/tools/generate/maps.py index fd23974..86d1683 100644 --- a/tools/generate/maps.py +++ b/tools/generate/maps.py @@ -97,10 +97,11 @@ def struct_map_t(): *struct_connection_t(), "", "start_size_t blocks;", - "enum tileset_t::tileset tileset;", "uint32_t width;", "uint32_t height;", "connection_t connections[4];", + "enum tileset_t::tileset tileset;", + "enum map id;", "", f"static constexpr int32_t count = {len(_sorted_map_headers)};", "};", @@ -172,12 +173,13 @@ def map(map_header): ".blocks = {", *start_size_value(block_path), "},", - f".tileset = tileset_t::{map_header.tileset.lower()},", f".width = {map_constant.width},", f".height = {map_constant.height},", ".connections = {", *connections(map_header), "},", + f".tileset = tileset_t::{map_header.tileset.lower()},", + f".id = map_t::{map_header.name2.lower()},", "},", ]