diff --git a/actor.hpp b/actor.hpp index 3e61616..8737e7b 100644 --- a/actor.hpp +++ b/actor.hpp @@ -11,56 +11,101 @@ struct actor_t right, }; + enum collision { + impassable, + jump, + passable, + }; + world_t world; enum direction facing; bool moving; - bool collision; + enum collision collision; uint8_t frame; uint8_t cycle; + constexpr inline int32_t frame_pixels() + { + switch (collision) { + case jump: return frame; + default: return frame; + } + } + constexpr inline offset_t offset() { - if (collision) { + const int32_t pixels = frame_pixels(); + + switch (collision) { + default: return {0, 0}; - } else { + case jump: [[fallthrough]]; + case passable: switch (facing) { case left: - return {-frame, 0}; + return {-pixels, 0}; case right: - return {frame, 0}; + return {pixels, 0}; case up: - return {0, -frame}; + return {0, -pixels}; case down: - return {0, frame}; + return {0, pixels}; default: return {0, 0}; } } } + // engine/overworld/player_animations.asm + // $38, $36, $34, $32, $31, $30, $30, $30, $31, $32, $33, $34, $36, $38, $3C, $3C + // ^ these are in absolute position, subtract 60 from each to get the offset: + // an offset of -4 is the same as not jumping + static constexpr int8_t jump_screen_offset[16] = {-4, -8, -10, -12, -14, -15, -16, -16, -16, -15, -14, -13, -12, -10, -8, -4}; + + // in pixels + constexpr inline int32_t y_offset() + { + if (collision == jump) + return jump_screen_offset[frame >> 1 & 15]; + else + return -4; + } + + constexpr inline int32_t world_increment() + { + switch (collision) { + case jump: return 2; + default: return 1; + } + } + // call tick() before move() constexpr inline void tick() { if (!moving) return; frame = frame + 1; - if (frame == 16) { + if (frame == (16 * world_increment())) { cycle += 1; frame = 0; - if (!collision) { + int32_t inc = world_increment(); + switch (collision) { + default: break; + case jump: [[fallthrough]]; + case passable: switch (facing) { - case left: world.x--; break; - case right: world.x++; break; - case up: world.y--; break; - case down: world.y++; break; + case left: world.x -= inc; break; + case right: world.x += inc; break; + case up: world.y -= inc; break; + case down: world.y += inc; break; } } moving = false; } } - constexpr inline void move(enum direction dir, - bool collision) + constexpr inline void move(enum collision collision, + enum direction dir) { /* m 1 . 2 . 3 . 4 . @@ -68,9 +113,13 @@ struct actor_t */ - if (!(this->collision) && moving) return; - if (!collision) - frame = 0; + // don't change directions in the middle of a movement animation + if (this->collision != impassable && moving) return; + + // reset animation frame if there is no collision + // if there is a collision, keep animating + if (collision != impassable) frame = 0; + facing = dir; moving = true; this->collision = collision; diff --git a/main.cpp b/main.cpp index 4740e8c..b644d52 100644 --- a/main.cpp +++ b/main.cpp @@ -19,6 +19,7 @@ #include "coordinates.hpp" #include "render_map.hpp" #include "actor.hpp" +#include "ledge_tiles.hpp" constexpr inline uint16_t rgb15(int32_t r, int32_t g, int32_t b) { @@ -160,7 +161,8 @@ static inline uint32_t facing_inverted(const actor_t::direction facing, const ui void render_sprite(const uint32_t ix, const uint32_t sprite_id, const enum actor_t::direction facing, const uint32_t animation_frame, const uint32_t animation_cycle, - const screen_t& screen, const offset_t& offset) + const screen_t& screen, const offset_t& offset, + int32_t y_offset) { constexpr uint32_t color_address = 0; const uint32_t sprite_offset = facing_offset(facing) + animation_frame; @@ -180,7 +182,7 @@ void render_sprite(const uint32_t ix, const uint32_t sprite_id, vdp1.vram.cmd[ix].SRCA = character_address; vdp1.vram.cmd[ix].SIZE = SIZE__X(16) | SIZE__Y(16); 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; + vdp1.vram.cmd[ix].YA = (cell_offset::y * 8) + screen.y * 16 + y_offset - offset.y; } void render_sprites(const offset_t& offset) @@ -192,7 +194,8 @@ void render_sprites(const offset_t& offset) spritesheet_t::red, state.player.facing, animation_frame, state.player.cycle, {4, 4}, - {0, 0}); + {0, 0}, + state.player.y_offset()); ix++; const object_t& obj = map_objects[state.map]; @@ -203,7 +206,8 @@ void render_sprites(const offset_t& offset) event.sprite_id, actor_t::down, 0, 0, world.to_screen(state.player.world), - offset); + offset, + -4); ix++; } @@ -267,12 +271,10 @@ constexpr inline world_t direction_offset(const world_t& world, const enum actor } } -constexpr inline bool collision(const map_t& map, - const world_t& world, - enum actor_t::direction dir) +constexpr inline uint8_t get_tile_ix(const map_t& map, const world_t& coord) { - const world_t coord = direction_offset(world, dir); // keep this synchronized with render_screen() + const uint8_t block_ix = get_block(map, coord.to_block()); const tileset_t& tileset = tilesets[map.tileset]; const uint8_t * block_start = &(tileset.blockset.start)[block_ix * 4 * 4]; @@ -282,12 +284,42 @@ constexpr inline bool collision(const map_t& map, const int32_t block_row = 4 * (quadrant_y + 1); const uint8_t tile_ix = block_start[block_row + quadrant_x]; + return tile_ix; +} + +constexpr inline enum actor_t::collision collision(const map_t& map, + const world_t& world, + enum actor_t::direction direction) +{ + const world_t coord = direction_offset(world, direction); + uint8_t collision_tile_ix = get_tile_ix(map, coord); + + const tileset_t& tileset = tilesets[map.tileset]; for (uint32_t i = 0; i < tileset.collision.size; i++) { - if (tileset.collision.start[i] == tile_ix) - return false; + if (tileset.collision.start[i] == collision_tile_ix) + return actor_t::passable; } - return true; + // check ledge_tile pairs + + uint8_t actor_tile_ix = get_tile_ix(map, world); + + for (uint32_t i = 0; i < ledge_tiles_length; i++) { + const ledge_tile_t& lt = ledge_tiles[i]; + if (direction == lt.direction + && actor_tile_ix == lt.actor + && collision_tile_ix == lt.collision) { + return actor_t::jump; + } + } + + return actor_t::impassable; +} + +constexpr inline void collision_move(const map_t& map, actor_t::direction dir) +{ + const enum actor_t::collision c = collision(map, state.player.world, dir); + state.player.move(c, dir); } void change_maps() @@ -336,7 +368,7 @@ void change_maps() #undef _has } -constexpr bool is_outside(const enum map_t::map& map_id) +constexpr inline bool is_outside(const enum map_t::map& map_id) { const map_t& map = maps[map_id]; switch (map.tileset) { @@ -348,6 +380,30 @@ constexpr bool is_outside(const enum map_t::map& map_id) } } +struct collision_direction_t { + enum actor_t::collision collision; + enum actor_t::direction direction; +}; + +constexpr inline collision_direction_t find_direction(const map_t& map, const world_t& world, const enum actor_t::direction facing) +{ + // first, try `facing`, as this is typically correct in non- edge-cases + const enum actor_t::collision c_facing = collision(map, world, facing); + if (c_facing != actor_t::impassable) + return {c_facing, facing}; + + // otherwise try all other directions + // (this checks facing a second time for no reason) + constexpr enum actor_t::direction dirs[] = {actor_t::up, actor_t::down, actor_t::left, actor_t::right}; + for (const enum actor_t::direction& dir : dirs) { + const enum actor_t::collision c = collision(map, world, dir); + if (c != actor_t::impassable) + return {c, dir}; + } + + return {actor_t::impassable, facing}; +} + void update_warp() { if (state.player.moving) return; @@ -377,7 +433,13 @@ void update_warp() coord.x = dest.position.x; coord.y = dest.position.y; - state.player.move(state.player.facing, false); + // force the player to move off of the warp + + const collision_direction_t c_d = find_direction(maps[state.map], state.player.world, state.player.facing); + state.player.move(c_d.collision, c_d.direction); + + // must return: because map state.map changed, the rest of this + // loop is invalid return; } } @@ -389,21 +451,10 @@ void update() change_maps(); update_warp(); - const map_t& map = maps[state.map]; - - if (event::cursor_left()) { - const bool c = collision(map, state.player.world, actor_t::left); - state.player.move(actor_t::left, c); - } else if (event::cursor_right()) { - const bool c = collision(map, state.player.world, actor_t::right); - state.player.move(actor_t::right, c); - } else if (event::cursor_up()) { - const bool c = collision(map, state.player.world, actor_t::up); - state.player.move(actor_t::up, c); - } else if (event::cursor_down()) { - const bool c = collision(map, state.player.world, actor_t::down); - state.player.move(actor_t::down, c); - } + if (event::cursor_left() ) collision_move(maps[state.map], actor_t::left); + else if (event::cursor_right()) collision_move(maps[state.map], actor_t::right); + else if (event::cursor_up() ) collision_move(maps[state.map], actor_t::up); + else if (event::cursor_down() ) collision_move(maps[state.map], actor_t::down); } void render()