Compare commits
3 Commits
1f80d22e72
...
a443d09b3d
Author | SHA1 | Date | |
---|---|---|---|
a443d09b3d | |||
d12e49aa16 | |||
fde161dabf |
4
Makefile
4
Makefile
@ -54,11 +54,11 @@ gen/%.cpp: gen/%.hpp $(GEN_PYTHON_SOURCE)
|
||||
|
||||
res/%.2bpp: pokered/%.png
|
||||
@mkdir -p $(dir $@)
|
||||
python tools/png_to_nbpp.py $< 2 $@
|
||||
python tools/png_to_nbpp.py 2 $@ $<
|
||||
|
||||
res/gfx/sprites/%.2bpp: pokered/gfx/sprites/%.png
|
||||
@mkdir -p $(dir $@)
|
||||
python tools/png_to_nbpp_sprite.py $< 2 $@
|
||||
python tools/png_to_nbpp_sprite.py 2 $@ $<
|
||||
|
||||
%.2bpp.h:
|
||||
$(BUILD_BINARY_H)
|
||||
|
85
actor.hpp
85
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;
|
||||
|
35
ledge_tiles.hpp
Normal file
35
ledge_tiles.hpp
Normal 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));
|
107
main.cpp
107
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()
|
||||
|
@ -6,13 +6,13 @@ from PIL import Image
|
||||
from palette import intensity_to_index
|
||||
|
||||
def convert(image, bpp):
|
||||
assert bpp in {8, 4, 2}, bpp
|
||||
assert bpp in {8, 4, 2, 1}, bpp
|
||||
px_per_byte = 8 // bpp
|
||||
px_per_row = 8
|
||||
bits_per_byte = 8
|
||||
bytes_per_row = (px_per_row // (bits_per_byte // bpp))
|
||||
|
||||
assert image.mode == 'L', image.mode
|
||||
assert image.mode in {'L', '1'}, image.mode
|
||||
width, height = image.size
|
||||
|
||||
buf = bytearray(width * height // px_per_byte)
|
||||
@ -21,14 +21,17 @@ def convert(image, bpp):
|
||||
for cell_x in range(width//8):
|
||||
for y in range(8):
|
||||
for x in range(8):
|
||||
px = im.getpixel((cell_x * 8 + x, cell_y * 8 + y))
|
||||
index = intensity_to_index(px)
|
||||
px = image.getpixel((cell_x * 8 + x, cell_y * 8 + y))
|
||||
if image.mode == 'L':
|
||||
index = intensity_to_index(px)
|
||||
elif image.mode == '1':
|
||||
index = int(px != 0)
|
||||
buf_ix = x//px_per_byte + (bytes_per_row * (cell_x * 8 + (cell_y * width) + y))
|
||||
buf[buf_ix] |= (index << bpp * ((px_per_byte - 1) - (x % px_per_byte)))
|
||||
return buf
|
||||
|
||||
def debug(buf, bpp):
|
||||
assert bpp in {8, 4, 2}, bpp
|
||||
assert bpp in {8, 4, 2, 1}, bpp
|
||||
px_per_byte = 8 // bpp
|
||||
px_per_row = 8
|
||||
bits_per_byte = 8
|
||||
@ -44,13 +47,14 @@ def debug(buf, bpp):
|
||||
if (row % 8 == 7):
|
||||
print()
|
||||
|
||||
in_path = sys.argv[1]
|
||||
bpp = int(sys.argv[2])
|
||||
out_path = sys.argv[3]
|
||||
bpp = int(sys.argv[1])
|
||||
out_path = sys.argv[2]
|
||||
assert len(sys.argv) >= 4, sys.argv
|
||||
|
||||
im = Image.open(in_path)
|
||||
buf = convert(im, bpp)
|
||||
if 'NBPP_DEBUG' in os.environ:
|
||||
debug(buf, bpp)
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(buf)
|
||||
for in_path in sys.argv[3:]:
|
||||
im = Image.open(in_path)
|
||||
buf = convert(im, bpp)
|
||||
if 'NBPP_DEBUG' in os.environ:
|
||||
debug(buf, bpp)
|
||||
f.write(buf)
|
||||
|
@ -56,13 +56,14 @@ def debug(buf, bpp):
|
||||
if (row % cell_height == (cell_height - 1)):
|
||||
print()
|
||||
|
||||
in_path = sys.argv[1]
|
||||
bpp = int(sys.argv[2])
|
||||
out_path = sys.argv[3]
|
||||
bpp = int(sys.argv[1])
|
||||
out_path = sys.argv[2]
|
||||
assert len(sys.argv) >= 4, sys.argv
|
||||
|
||||
im = Image.open(in_path)
|
||||
buf = convert(im, bpp)
|
||||
if 'NBPP_DEBUG' in os.environ:
|
||||
debug(buf, bpp)
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(buf)
|
||||
for in_path in sys.argv[3:]:
|
||||
im = Image.open(in_path)
|
||||
buf = convert(im, bpp)
|
||||
if 'NBPP_DEBUG' in os.environ:
|
||||
debug(buf, bpp)
|
||||
f.write(buf)
|
||||
|
Loading…
x
Reference in New Issue
Block a user