graphic: add functions to draw stats screen borders

This also draws pokemon sprites on the screen, sequentially.
This commit is contained in:
Zack Buhman 2023-08-03 22:19:01 +00:00
parent f821ac9e6d
commit 3d8110ca1c
9 changed files with 218 additions and 30 deletions

View File

@ -5,6 +5,17 @@ LIB = ./saturn
GEN_PYTHON_SOURCE = $(wildcard tools/source/*.py) $(wildcard tools/generate/*.py)
GEN_HEADERS =
GEN_HEADERS += gen/maps.hpp
GEN_HEADERS += gen/tilesets.hpp
GEN_HEADERS += gen/sprites.hpp
GEN_HEADERS += gen/collision_tile_ids.hpp
GEN_HEADERS += gen/text.hpp
GEN_HEADERS += gen/text_pointers.hpp
GEN_HEADERS += gen/pokemon/moves.hpp
GEN_HEADERS += gen/pokemon/types.hpp
GEN_HEADERS += gen/pokemon/pokemon_enum.inc.hpp
GEN_SRC =
GEN_SRC += gen/maps.cpp
GEN_SRC += gen/map_objects.cpp
@ -13,6 +24,8 @@ GEN_SRC += gen/tilesets.cpp
GEN_SRC += gen/collision_tile_ids.cpp
GEN_SRC += gen/text.cpp
GEN_SRC += gen/text_pointers.cpp
GEN_SRC += gen/pokemon/moves.cpp
GEN_SRC += gen/pokemon/pokemon.cpp
SRC =
SRC += $(GEN_SRC)
@ -35,9 +48,10 @@ GFX_POKEMON = $(call res_png,2bpp,pokered/gfx/pokemon/front/) $(call res_png,2bp
MAPS_BLOCKS = $(call res,blk,pokered/maps/)
FONTS = res/font.2bpp.o
GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(GFX_POKEMON) $(MAPS_BLOCKS) $(GEN_SRC) $(FONTS)
GENERATED_SRC = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(GFX_POKEMON) $(MAPS_BLOCKS) $(GEN_SRC) $(FONTS)
GENERATED = $(GENERATED_SRC) $(GEN_HEADERS)
OBJ = $(patsubst %.cpp,%.o,$(SRC) $(GENERATED))
OBJ = $(patsubst %.cpp,%.o,$(SRC) $(GENERATED_SRC))
all: main.cue
@ -57,7 +71,7 @@ endef
gen/%.hpp: $(GEN_PYTHON_SOURCE)
$(RUN_GENERATE) $@
gen/%.cpp: gen/%.hpp $(GEN_PYTHON_SOURCE)
gen/%.cpp: $(GEN_PYTHON_SOURCE)
$(RUN_GENERATE) $@
define PNG_TO_2BPP

View File

@ -22,8 +22,8 @@ struct block_t {
};
struct screen_cell_t {
uint32_t x;
uint32_t y;
int32_t x;
int32_t y;
};
struct screen_t {

View File

@ -9,7 +9,7 @@
struct dialog_border {
static constexpr uint8_t corner_top_left = 105;
static constexpr uint8_t corner_top_right = 106;
static constexpr uint8_t corner_bot_left = 107;
static constexpr uint8_t corner_bottom_left = 107;
static constexpr uint8_t corner_bottom_right = 108;
static constexpr uint8_t vertical_end_cap = 109;
static constexpr uint8_t vertical = 110;
@ -21,18 +21,34 @@ struct battle_border {
static constexpr uint8_t corner_bottom_right = 97;
static constexpr uint8_t arrow_right = 98;
static constexpr uint8_t vertical = 99;
static constexpr uint8_t corner_bot_left = 100;
static constexpr uint8_t corner_bottom_left = 100;
static constexpr uint8_t three_dots = 101;
static constexpr uint8_t vertical_end_cap = 102;
static constexpr uint8_t level = 103;
static constexpr uint8_t arrow_left = 104;
};
struct hp_bar {
static constexpr uint8_t hp = 0x50;
static constexpr uint8_t start_cap = 0x51;
static constexpr uint8_t seg0 = 0x52;
static constexpr uint8_t seg1 = 0x53;
static constexpr uint8_t seg2 = 0x54;
static constexpr uint8_t seg3 = 0x55;
static constexpr uint8_t seg4 = 0x56;
static constexpr uint8_t seg5 = 0x57;
static constexpr uint8_t seg6 = 0x58;
static constexpr uint8_t seg7 = 0x59;
static constexpr uint8_t seg8 = 0x5a;
static constexpr uint8_t end_cap_nobar = 0x5b;
static constexpr uint8_t end_cap_bar = 0x66;
};
#define S reinterpret_cast<const uint8_t *>
void draw_text(const uint32_t base_pattern,
const uint8_t * text,
const uint32_t x, const uint32_t y)
const int32_t x, const int32_t y)
{
uint8_t i = 0, x_o = 0;
uint8_t c;
@ -80,9 +96,9 @@ void draw_box_border(const uint32_t base_pattern,
put_char(base_pattern, top_left.x, top_left.y, dialog_border::corner_top_left);
put_char(base_pattern, bottom_right.x, top_left.y, dialog_border::corner_top_right);
put_char(base_pattern, bottom_right.x, bottom_right.y, dialog_border::corner_bottom_right);
put_char(base_pattern, top_left.x, bottom_right.y, dialog_border::corner_bot_left);
put_char(base_pattern, top_left.x, bottom_right.y, dialog_border::corner_bottom_left);
for (uint32_t y = top_left.y + 1; y < bottom_right.y; y++) {
for (int32_t y = top_left.y + 1; y < bottom_right.y; y++) {
// left vertical bar
put_char(base_pattern, top_left.x, y, dialog_border::vertical);
// right vertical bar
@ -90,7 +106,7 @@ void draw_box_border(const uint32_t base_pattern,
}
for (uint32_t x = top_left.x + 1; x < bottom_right.x; x++) {
for (int32_t x = top_left.x + 1; x < bottom_right.x; x++) {
// top horizontal bar
put_char(base_pattern, x, top_left.y, dialog_border::horizontal);
@ -102,13 +118,91 @@ void draw_box_border(const uint32_t base_pattern,
void draw_box_background(const uint32_t base_pattern,
const screen_cell_t& top_left, const screen_cell_t& bottom_right)
{
for (uint32_t x = top_left.x + 1; x < bottom_right.x; x++) {
for (uint32_t y = top_left.y + 1; y < bottom_right.y; y++) {
for (int32_t x = top_left.x + 1; x < bottom_right.x; x++) {
for (int32_t y = top_left.y + 1; y < bottom_right.y; y++) {
put_char(base_pattern, x, y, ascii_to_font(' '));
}
}
}
void draw_battle_border(const uint32_t base_pattern,
const screen_cell_t& top_corner, const screen_cell_t& bottom_corner)
{
// draw the vertical line
for (int32_t y = top_corner.y; y < bottom_corner.y; y++)
put_char(base_pattern, top_corner.x, y, battle_border::vertical);
// decide which direction we are drawing
const int32_t increment = bottom_corner.x <= top_corner.x ? 1 : -1;
// draw the horizontal line
for (int32_t x = bottom_corner.x + increment; x != top_corner.x; x += increment)
put_char(base_pattern, x, bottom_corner.y, battle_border::horizontal);
// draw the end cap and corner piece
const uint8_t end_cap = increment == 1 ? battle_border::arrow_left
: battle_border::arrow_right;
const uint8_t corner = increment == 1 ? battle_border::corner_bottom_right
: battle_border::corner_bottom_left;
put_char(base_pattern, bottom_corner.x, bottom_corner.y, end_cap);
put_char(base_pattern, top_corner.x , bottom_corner.y, corner );
}
constexpr inline uint8_t hp_seg(const int32_t hp_48, const int32_t ix)
{
const int32_t hp_ix = hp_48 - (ix * 8);
if (hp_ix < 0) return hp_bar::seg0;
switch (hp_ix) {
default: [[fallthrough]];
case 8: return hp_bar::seg8;
case 7: return hp_bar::seg7;
case 6: return hp_bar::seg6;
case 5: return hp_bar::seg5;
case 4: return hp_bar::seg4;
case 3: return hp_bar::seg3;
case 2: return hp_bar::seg2;
case 1: return hp_bar::seg1;
case 0: return hp_bar::seg0;
}
}
void draw_hp_bar(const uint32_t base_pattern,
const screen_cell_t& top_left,
const uint8_t end_cap,
const int32_t hp_48)
{
put_char(base_pattern, top_left.x + 0, top_left.y, hp_bar::hp);
put_char(base_pattern, top_left.x + 1, top_left.y, hp_bar::start_cap);
for (int32_t ix = 0; ix < 6; ix++) {
const uint8_t seg = hp_seg(hp_48, ix);
put_char(base_pattern, top_left.x + 2 + ix, top_left.y, seg);
}
put_char(base_pattern, top_left.x + 8, top_left.y, end_cap);
}
static uint32_t hp = 0;
void draw_stats1(const uint32_t base_pattern)
{
// white out the entire screen
draw_box_background(base_pattern, {-1, -1}, {20, 18});
// top status battle box
draw_battle_border(base_pattern, {19, 1}, {8, 7});
draw_hp_bar(base_pattern, {11, 4}, hp_bar::end_cap_bar, (hp >> 3) % 48);
hp++;
// bottom border
draw_battle_border(base_pattern, {19, 9}, {12, 17});
// bottom left dialog box
draw_box_border(base_pattern, {0, 8}, {9, 17});
}
void dialog_t::draw(const uint32_t base_pattern,
const start_size_t& text)
{
@ -116,8 +210,8 @@ void dialog_t::draw(const uint32_t base_pattern,
draw_box_background(base_pattern, top_left, bottom_right);
uint32_t ix = 0;
uint32_t x = top_left.x + 1;
uint32_t y = top_left.y + 2;
int32_t x = top_left.x + 1;
int32_t y = top_left.y + 2;
// ignore C-string null terminator
while (ix < (text.size - 1)) {
const uint8_t c = text.start[ix];

View File

@ -16,7 +16,7 @@ static inline void put_char(const uint32_t base_pattern,
void draw_text(const uint32_t base_pattern,
const uint8_t * text,
const uint32_t x, const uint32_t y);
const int32_t x, const int32_t y);
void draw_box_border(const uint32_t base_pattern,
const screen_cell_t& top_left, const screen_cell_t& bottom_right);
@ -24,6 +24,11 @@ void draw_box_border(const uint32_t base_pattern,
void draw_box_background(const uint32_t base_pattern,
const screen_cell_t& top_left, const screen_cell_t& bottom_right);
void draw_battle_border(const uint32_t base_pattern,
const screen_cell_t& top_corner, const screen_cell_t& bottom_corner);
void draw_stats1(const uint32_t base_pattern);
struct dialog_t
{
static constexpr screen_cell_t top_left = { 0, 12};

View File

@ -29,8 +29,8 @@ void digital_callback(uint8_t fsm_state, uint8_t data);
extern input_t input;
constexpr int input_arr = 1;
constexpr int input_das = 1;
constexpr int input_arr = 10;
constexpr int input_das = 20;
constexpr int input_debounce = 2;
static constexpr inline int32_t

View File

@ -26,11 +26,19 @@
#include "graphic.hpp"
#include "menu.hpp"
#include "pokemon.hpp"
static int32_t pokemon_raw_index = 0;
struct draw_t {
struct {
uint16_t font; // div 32
uint16_t tilesets[tileset_t::count]; // div 32
uint16_t spritesheets[spritesheet_t::count]; // div 128
struct {
uint16_t front;
uint16_t back;
} pokemon[pokemon_t::count]; // div 16
} base_pattern;
};
@ -45,21 +53,31 @@ static state_t state = { map_t::pallet_town, map_t::last_map, 0 };
uint32_t load_tileset(uint32_t top, enum tileset_t::tileset tileset)
{
uint32_t base_address = top = cell_data(tilesets[tileset].tileset, top);
const uint32_t base_address = top = cell_data(tilesets[tileset].tileset, top);
state.draw.base_pattern.tilesets[tileset] = base_address / 32;
return top;
}
uint32_t load_sprite(uint32_t top, enum spritesheet_t::spritesheet spritesheet)
uint32_t load_spritesheet(uint32_t top, enum spritesheet_t::spritesheet spritesheet)
{
const spritesheet_t& s = spritesheets[spritesheet];
uint32_t base_address = top = character_pattern_table(s.spritesheet, top);
const uint32_t base_address = top = character_pattern_table(s.spritesheet, top);
state.draw.base_pattern.spritesheets[spritesheet] = base_address / 128;
return top;
}
uint32_t load_pokemon(uint32_t top, enum pokemon_t::pokemon _pokemon)
{
const pokemon_t& p = pokemon[_pokemon];
const uint32_t base_address_front = top = character_pattern_table(p.pic.front, top);
const uint32_t base_address_back = top = character_pattern_table(p.pic.back, top);
state.draw.base_pattern.pokemon[_pokemon].front = base_address_front / 16;
state.draw.base_pattern.pokemon[_pokemon].back = base_address_back / 16;
return top;
}
void load_vram()
{
vdp2.reg.CYCA0 = 0xeeee'eeee;
@ -81,7 +99,10 @@ void load_vram()
uint32_t vdp1_top = (sizeof (union vdp1_vram));
for (uint32_t i = 0; i < spritesheet_t::count; i++)
vdp1_top = load_sprite(vdp1_top, static_cast<enum spritesheet_t::spritesheet>(i));
vdp1_top = load_spritesheet(vdp1_top, static_cast<enum spritesheet_t::spritesheet>(i));
for (uint32_t i = 0; i < pokemon_t::count; i++)
vdp1_top = load_pokemon(vdp1_top, static_cast<enum pokemon_t::pokemon>(i));
}
static inline uint32_t facing_offset(const actor_t::direction facing)
@ -109,7 +130,7 @@ 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,
void render_sprite(const uint32_t ix, const enum spritesheet_t::spritesheet 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,
int32_t y_offset)
@ -135,6 +156,41 @@ void render_sprite(const uint32_t ix, const uint32_t sprite_id,
vdp1.vram.cmd[ix].YA = (cell_offset::y * 8) + screen.y * 16 + y_offset - offset.y;
}
uint32_t pokemon_sprite_dimension(const uint32_t size)
{
switch (size) {
default: [[fallthrough]];
case 256: return 32;
case 400: return 40;
case 576: return 48;
case 784: return 56;
}
}
void render_pokemon(const uint32_t ix, const enum pokemon_t::pokemon pokemon_id,
const screen_cell_t& screen_cell)
{
constexpr uint32_t color_address = 0;
const uint32_t base_pattern = state.draw.base_pattern.pokemon[pokemon_id].front;
const uint32_t character_address = (base_pattern * 16) / 8;
const uint32_t dimension = pokemon_sprite_dimension(pokemon[pokemon_id].pic.front.size);
vdp1.vram.cmd[ix].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE;
vdp1.vram.cmd[ix].LINK = 0;
// The "end code" is 0xf, which is being used in the mai sprite palette. If
// both transparency and end codes are enabled, it seems there are only 14
// usable colors in the 4-bit color mode.
vdp1.vram.cmd[ix].PMOD = PMOD__ECD | PMOD__COLOR_MODE__COLOR_BANK_16;
// It appears Kronos does not correctly calculate the color address in the
// VDP1 debugger. Kronos will report FFFC when the actual color table address
// in this example is 7FFE0.
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(dimension) | SIZE__Y(dimension);
vdp1.vram.cmd[ix].XA = (cell_offset::x * 8) + screen_cell.x * 8;
vdp1.vram.cmd[ix].YA = (cell_offset::y * 8) + screen_cell.y * 8;
}
void render_sprites(const offset_t& offset)
{
uint32_t ix = 2;
@ -148,6 +204,10 @@ void render_sprites(const offset_t& offset)
state.player.y_offset());
ix++;
render_pokemon(ix, static_cast<enum pokemon_t::pokemon>(pokemon_raw_index),
{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];
@ -417,10 +477,18 @@ void update()
change_maps();
update_warp();
/*
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);
*/
if (event::cursor_left() ) pokemon_raw_index = (pokemon_raw_index - 1);
else if (event::cursor_right()) pokemon_raw_index = (pokemon_raw_index + 1);
if (pokemon_raw_index < 0) pokemon_raw_index = pokemon_t::count - 1;
else if (pokemon_raw_index >= pokemon_t::count) pokemon_raw_index = 0;
//else if (event::button_a() ) check_sign();
}
@ -442,6 +510,8 @@ void render()
draw_menu(state.draw.base_pattern.font, fight_menu);
cursor_t cursor = { 1, 1 };
draw_menu_cursor(state.draw.base_pattern.font, fight_menu, cursor);
draw_stats1(state.draw.base_pattern.font);
}
extern "C"
@ -529,9 +599,9 @@ void init_vdp1()
void init_vdp2()
{
vdp2.reg.PRISA = PRISA__S0PRIN(6); // Sprite register 0 PRIority Number
vdp2.reg.PRISA = PRISA__S0PRIN(7); // Sprite register 0 PRIority Number
vdp2.reg.PRINA = PRINA__N0PRIN(5)
| PRINA__N1PRIN(7);
| PRINA__N1PRIN(6);
// DISP: Please make sure to change this bit from 0 to 1 during V blank.
vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE

View File

@ -127,11 +127,14 @@ def dex_constant_to_enum_name(dex_constant):
return name.lower()
def enum_inc():
dex_constants = parse.pokemon_dex_constants_list()
yield "enum pokemon {"
for dex_constant in parse.pokemon_dex_constants_list():
for dex_constant in dex_constants:
name = dex_constant_to_enum_name(dex_constant)
yield f"{name.lower()},"
yield "};"
yield ""
yield f"static constexpr int32_t count = {len(dex_constants)};"
def generate_enum_inc():
render, out = renderer()

View File

@ -5,8 +5,8 @@ from PIL import Image
from palette import intensity_to_index
cell_width = 16
cell_height = 16
cell_width = None
cell_height = None
def convert(image, bpp):
assert bpp in {8, 4, 2}, bpp
@ -64,6 +64,8 @@ assert len(sys.argv) >= 4, sys.argv
with open(out_path, 'wb') as f:
for in_path in in_paths:
im = Image.open(in_path)
cell_width, cell_height = im.size
assert cell_width in {16, 32, 40, 48, 56}, cell_width
buf = convert(im, bpp)
if 'NBPP_DEBUG' in os.environ:
debug(buf, bpp)

View File

@ -40,8 +40,8 @@ static void _2bpp_4bpp_vram_copy(uint32_t * vram, const start_size_t& buf)
uint32_t character_pattern_table(const start_size_t& buf, const uint32_t top)
{
// round to nearest multiple of 32
const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20);
// round to nearest multiple of 16
const uint32_t table_size = ((buf.size * 2) + 0x10 - 1) & (-0x10);
const uint32_t base_address = top - table_size;
uint32_t * vram = &vdp1.vram.u32[(base_address / 4)];