main: add ledge jumps

This commit is contained in:
Zack Buhman 2023-07-29 03:41:51 +00:00
parent 62988e73ac
commit fde161dabf
3 changed files with 181 additions and 46 deletions

View File

@ -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;

35
ledge_tiles.hpp Normal file
View File

@ -0,0 +1,35 @@
/*
data/tilesets/ledge_tiles.asm
; player direction, tile player standing on, ledge tile, input required
db SPRITE_FACING_DOWN, $2C, $37, D_DOWN
db SPRITE_FACING_DOWN, $39, $36, D_DOWN
db SPRITE_FACING_DOWN, $39, $37, D_DOWN
db SPRITE_FACING_LEFT, $2C, $27, D_LEFT
db SPRITE_FACING_LEFT, $39, $27, D_LEFT
db SPRITE_FACING_RIGHT, $2C, $0D, D_RIGHT
db SPRITE_FACING_RIGHT, $2C, $1D, D_RIGHT
db SPRITE_FACING_RIGHT, $39, $0D, D_RIGHT
*/
#pragma once
#include "actor.hpp"
struct ledge_tile_t {
enum actor_t::direction direction;
uint8_t actor;
uint8_t collision;
};
static const ledge_tile_t ledge_tiles[] = {
{actor_t::down, 0x2c, 0x37},
{actor_t::down, 0x39, 0x36},
{actor_t::down, 0x39, 0x37},
{actor_t::left, 0x2c, 0x27},
{actor_t::left, 0x39, 0x27},
{actor_t::right, 0x2c, 0x0d},
{actor_t::right, 0x2c, 0x1d},
{actor_t::right, 0x39, 0x0d},
};
constexpr uint8_t ledge_tiles_length = (sizeof (ledge_tiles)) / (sizeof (ledge_tile_t));

109
main.cpp
View File

@ -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];
for (uint32_t i = 0; i < tileset.collision.size; i++) {
if (tileset.collision.start[i] == tile_ix)
return false;
return tile_ix;
}
return true;
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] == collision_tile_ix)
return actor_t::passable;
}
// 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()