diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c8dd7d..f0c2b7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ set(SOURCES src/geometry.c src/main.c src/map.c + src/menu.c src/pathfinding.c src/player.c src/render.c @@ -44,6 +45,9 @@ set(ASSETS assets-cg/levels/lv1.txt assets-cg/levels/lv2.tmx assets-cg/levels/lv2.txt + # Menu + assets-cg/menu_title.png + assets-cg/menu_arrows.png # HUD assets-cg/hud.png assets-cg/hud_life.png diff --git a/assets-cg/fxconv-metadata.txt b/assets-cg/fxconv-metadata.txt index f7d009b..1883b2f 100644 --- a/assets-cg/fxconv-metadata.txt +++ b/assets-cg/fxconv-metadata.txt @@ -1,15 +1,11 @@ *.png: type: bopti-image name_regex: (.*)\.png img_\1 - profile: p8 - -hud*.png: profile: p4 hud_xp.ase: custom-type: aseprite-anim name: frames_hud_xp - profile: p4 center: 6, 8 next: Idle=Idle, Shine=Idle, Explode=Idle diff --git a/assets-cg/menu_arrows.png b/assets-cg/menu_arrows.png new file mode 100644 index 0000000..370a817 Binary files /dev/null and b/assets-cg/menu_arrows.png differ diff --git a/assets-cg/menu_title.png b/assets-cg/menu_title.png new file mode 100644 index 0000000..be04450 Binary files /dev/null and b/assets-cg/menu_title.png differ diff --git a/src/main.c b/src/main.c index 72111f1..0509e9b 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,7 @@ #include "geometry.h" #include "level.h" #include "map.h" +#include "menu.h" #include "pathfinding.h" #include "player.h" #include "render.h" @@ -33,22 +34,6 @@ #include -int main_menu(void) -{ - while(1) { - dclear(C_BLACK); - dprint(1, 1, C_WHITE, "Rogue Life"); - dprint(1, 15, C_WHITE, "[1] Cavern"); - dprint(1, 29, C_WHITE, "[2] Lab"); - dupdate(); - - int key = getkey().key; - if(key == KEY_EXIT) return -1; - if(key == KEY_1) return 1; - if(key == KEY_2) return 2; - } -} - int main(void) { /* Enable %f etc. in printf()-like functions */ @@ -63,12 +48,12 @@ int main(void) usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL }; usb_open(interfaces, GINT_CALL_NULL); - int lv = main_menu(); + int lv = menu_level_select(0); level_t const *level = NULL; if(lv == -1) return 1; - if(lv == 1) level = &level_lv1; - if(lv == 2) level = &level_lv2; + if(lv == 0) level = &level_lv1; + if(lv == 1) level = &level_lv2; game_t game = { 0 }; camera_t *c = &game.camera; diff --git a/src/menu.c b/src/menu.c new file mode 100644 index 0000000..7f9ba42 --- /dev/null +++ b/src/menu.c @@ -0,0 +1,131 @@ +#include "map.h" +#include "level.h" +#include "render.h" +#include +#include +#include +#include +#include + +typedef struct { + /* Same mechanism as in game_t */ + map_t const *map; + uint16_t *map_anim; + camera_t camera; + +} menu_game_t; + +static menu_game_t *menu_load_game(level_t const *level) +{ + menu_game_t *mg = malloc(sizeof *mg); + mg->map = level->map; + mg->map_anim = malloc(mg->map->width * mg->map->height * + sizeof *mg->map_anim); + + for(int i = 0; i < mg->map->width * mg->map->height; i++) + mg->map_anim[i] = rand() & 4095; + + camera_init(&mg->camera, mg->map); + + return mg; +} + +static void menu_destroy_game(menu_game_t *mg) +{ + free(mg->map_anim); + free(mg); +} + +static void menu_render_game(menu_game_t *mg) +{ + render_map_layer(mg->map, &mg->camera, 0, 0, HORIZONTAL, mg->map_anim, + DIMAGE_NONE); + render_map_layer(mg->map, &mg->camera, 0, 0, VERTICAL, mg->map_anim, + DIMAGE_NONE); + render_map_layer(mg->map, &mg->camera, 0, 0, CEILING, mg->map_anim, + DIMAGE_NONE); +} + +static void menu_update_animations(menu_game_t *mg, fixed_t dt) +{ + for(int i = 0; i < mg->map->width * mg->map->height; i++) + mg->map_anim[i] += fround(dt * 1000); +} + +int menu_level_select(int start) +{ + extern bopti_image_t img_menu_title, img_menu_arrows; + + menu_game_t *options[2]; + #define OPTION_COUNT ((int)(sizeof options / sizeof options[0])) + + options[0] = menu_load_game(&level_lv1); + options[1] = menu_load_game(&level_lv2); + + int selection = (start >= 0 && start < OPTION_COUNT) ? start : 0; + int target_x=0, x=0; + + int volatile frame_tick = 1; + int t = timer_configure(TIMER_ANY, 1000000 / FRAME_RATE, + GINT_CALL_SET(&frame_tick)); + timer_start(t); + + while(1) { + while(!frame_tick) + sleep(); + fixed_t dt = fix(1) / FRAME_RATE; + + if(x != target_x) { + int dx = (target_x - x) / 6; + if(x < target_x && dx < 6) + dx = min(target_x-x, 6); + if(x > target_x && dx > -6) + dx = max(target_x-x, -6); + x += dx; + } + + dclear(C_BLACK); + + for(int i = 0; i < OPTION_COUNT; i++) { + if(-x <= (i-1)*DWIDTH) + continue; + if(-x >= (i+1)*DWIDTH) + continue; + + int local_offset = x + i*DWIDTH; + fixed_t map_center = fix(options[i]->map->width) / 2; + options[i]->camera.x = map_center - fix(local_offset) / TILE_WIDTH; + menu_render_game(options[i]); + } + + dimage(148, 9, &img_menu_title); + if(selection > 0) + dsubimage(16, 93, &img_menu_arrows, 0, 0, 35, 42, DIMAGE_NOCLIP); + if(selection < OPTION_COUNT - 1) + dsubimage(345, 93, &img_menu_arrows, 35, 0, 35, 42, DIMAGE_NOCLIP); + + dupdate(); + + for(int i = 0; i < OPTION_COUNT; i++) + menu_update_animations(options[i], dt); + + int key = getkey_opt(GETKEY_MENU, &frame_tick).key; + if(key == KEY_LEFT && selection > 0) { + selection--; + target_x += DWIDTH; + } + if(key == KEY_RIGHT && selection < OPTION_COUNT - 1) { + selection++; + target_x -= DWIDTH; + } + if(key == KEY_EXE || key == KEY_SHIFT) + return selection; + } + + timer_stop(t); + + for(size_t i = 0; i < OPTION_COUNT; i++) + menu_destroy_game(options[i]); + + return 0; +} diff --git a/src/menu.h b/src/menu.h new file mode 100644 index 0000000..5317351 --- /dev/null +++ b/src/menu.h @@ -0,0 +1,9 @@ +//--- +// menu: Main menu +//--- + +#pragma once + +/* Run the main menu and return the selected entry. start is the entry shown at + the start; default 0. */ +int menu_level_select(int start); diff --git a/src/render.c b/src/render.c index 2544242..5b36e4a 100644 --- a/src/render.c +++ b/src/render.c @@ -123,7 +123,7 @@ fixed_t camera_ppu(camera_t const *c) //--- static inline void render_tile(int x, int y, tileset_t const *tileset, - int tile_id, int time_ms) + int tile_id, int time_ms, int flags) { /* If the tile is animated, find the position in the cycle */ if(tileset->tiles[tile_id].anim_length > 0) { @@ -147,14 +147,12 @@ static inline void render_tile(int x, int y, tileset_t const *tileset, dsubimage(x, y, tileset->sheet, TILE_WIDTH * (tile_id % tileset->width), TILE_HEIGHT * (tile_id / tileset->width), - TILE_WIDTH, TILE_HEIGHT, DIMAGE_NOCLIP); + TILE_WIDTH, TILE_HEIGHT, flags); } -static void render_map_layer(game_t const *game, camera_t const *c, int ss_x, - int ss_y, int layer) +void render_map_layer(map_t const *map, camera_t const *c, int ss_x, int ss_y, + int layer, uint16_t *map_anim, int flags) { - map_t const *map = game->map; - /* Render floor and walls */ for(int row = -2; row < map->height + 2; row++) for(int col = -1; col < map->width + 1; col++) { @@ -170,12 +168,12 @@ static void render_map_layer(game_t const *game, camera_t const *c, int ss_x, continue; } - int time_ms = game->map_anim[map->width * row + col]; + int time_ms = map_anim ? map_anim[map->width * row + col] : 0; if(map->tileset->tiles[cell->base].plane == layer) - render_tile(p.x, p.y, map->tileset, cell->base, time_ms); + render_tile(p.x, p.y, map->tileset, cell->base, time_ms, flags); if(cell->decor && map->tileset->tiles[cell->decor].plane == layer) - render_tile(p.x, p.y, map->tileset, cell->decor, time_ms); + render_tile(p.x, p.y, map->tileset, cell->decor, time_ms, flags); } } @@ -397,16 +395,19 @@ void render_game(game_t const *g, bool show_hitboxes) prof_enter(ctx); /* Render map floor and floor entities */ - render_map_layer(g, camera, ss_x, ss_y, HORIZONTAL); + render_map_layer(g->map, camera, ss_x, ss_y, HORIZONTAL, g->map_anim, + DIMAGE_NOCLIP); render_entities(g, camera, floor_depth_measure, ss_x, ss_y, show_hitboxes); /* Render map walls and vertical entities TODO ECS: Sort walls and wall entities together for proper ordering!*/ - render_map_layer(g, camera, ss_x, ss_y, VERTICAL); + render_map_layer(g->map, camera, ss_x, ss_y, VERTICAL, g->map_anim, + DIMAGE_NOCLIP); render_entities(g, camera, wall_depth_measure, ss_x, ss_y, show_hitboxes); /* Render ceiling tiles (including out of bounds) and ceiling entities */ - render_map_layer(g, camera, ss_x, ss_y, CEILING); + render_map_layer(g->map, camera, ss_x, ss_y, CEILING, g->map_anim, + DIMAGE_NOCLIP); render_entities(g, camera, ceiling_depth_measure, ss_x,ss_y,show_hitboxes); prof_leave(ctx); diff --git a/src/render.h b/src/render.h index 20d9faa..a7847a4 100644 --- a/src/render.h +++ b/src/render.h @@ -59,6 +59,14 @@ fixed_t camera_ppu(camera_t const *c); /* Render window overlay. */ void render_window(int x, int y, int w, int h); +/* Render a single layer of the map, with animated tiles. + ss_x and ss_y are additional displacement for screenshake; default 0. + layer is HORIZONTAL (floor), VERTICAL (wall), or CEILING. + map_anim is the indivual tiles' animation time, can be NULL. + flags is the flags for dsubimage() for the tiles. */ +void render_map_layer(map_t const *map, camera_t const *c, int ss_x, int ss_y, + int layer, uint16_t *map_anim, int flags); + /* Render game full-screen. */ struct game; void render_game(struct game const *g, bool show_hitboxes);