dynamic tiles, tile animations, door logic, spawn and victory

This commit is contained in:
Lephenixnoir 2020-12-23 00:00:58 +01:00
parent 0a9a3a9fae
commit c2178fd2b0
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
11 changed files with 503 additions and 96 deletions

View File

@ -11,9 +11,9 @@ find_package(Gint 2.1 REQUIRED)
set(SOURCES
src/animation.c
src/main.c
src/engine.c
# ...
src/game.c
src/main.c
)
# Shared assets, fx-9860G-only assets and fx-CG-50-only assets
set(ASSETS
@ -22,6 +22,7 @@ set(ASSETS
set(ASSETS_fx
assets-fx/levels.png
assets-fx/lv1.png
assets-fx/tilesheet.png
assets-fx/title.png
assets-fx/spritesheet.png
assets-fx/font_mystere.png

Binary file not shown.

View File

@ -1,10 +1,10 @@
##### #####
# ##### #
# a a #
##A### ###A##
# a a #
# b a #
## ### ### ##
# a b #
# ~ ##### @ #
##### #####
a: #.
A: #.
b: .#

BIN
assets-fx/tilesheet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

View File

@ -24,6 +24,13 @@ struct sheet const anim_player = {
.frame_h = 16,
};
extern bopti_image_t img_tilesheet;
struct sheet const anim_tiles = {
.img = &img_tilesheet,
.frame_w = 10,
.frame_h = 20,
};
/* anim_frame(): Get a frame from a sheet */
static struct anim_frame anim_frame(struct sheet const *sheet, int col,int row)
{
@ -44,7 +51,7 @@ void dframe(int x, int y, struct anim_frame const frame)
}
//---
// Animation functions
// Player animation functions
//---
int anim_player_idle(struct anim_data *data, int init)
@ -101,3 +108,75 @@ int anim_player_walking(struct anim_data *data, int init)
data->img = anim_frame(&anim_player, data->dir + 4, data->frame / 2);
return 1;
}
//---
// Tile animation functions
//---
int anim_tile_start(struct anim_data *data, int init)
{
data->function = anim_tile_start;
data->frame = init ? 0 : (data->frame + 1) % 5;
data->duration = (data->frame == 0 ? 1000 : 150);
data->img = anim_frame(&anim_tiles, data->frame, 0);
return 0;
}
int anim_tile_end(struct anim_data *data, int init)
{
data->function = anim_tile_end;
data->frame = init ? 0 : (data->frame + 1) % 5;
data->duration = (data->frame == 0 ? 1000 : 150);
data->img = anim_frame(&anim_tiles, data->frame, 1);
return 0;
}
int anim_door_closed(struct anim_data *data, GUNUSED int init)
{
/* Init takes [data->dir] as input */
data->function = anim_door_closed;
data->frame = 0 + 16 * (data->dir == DIR_LEFT);
data->duration = 1000;
data->img = anim_frame(&anim_tiles, data->frame & 15,
2 + (data->frame >= 16));
return 0;
}
int anim_door_opening(struct anim_data *data, int init)
{
data->function = anim_door_opening;
data->frame = init ? 1 + 16 * (data->dir == DIR_LEFT): data->frame + 1;
if((data->frame & 15) == 6)
return anim_door_open(data, 1);
data->duration = 75;
data->img = anim_frame(&anim_tiles, data->frame & 15,
2 + (data->frame >= 16));
return 1;
}
int anim_door_open(struct anim_data *data, GUNUSED int init)
{
data->function = anim_door_open;
data->frame = 6 + 16 * (data->dir == DIR_LEFT);
data->duration = 1000;
data->img = anim_frame(&anim_tiles, data->frame & 15,
2 + (data->frame >= 16));
return 0;
}
int anim_door_closing(struct anim_data *data, int init)
{
data->function = anim_door_closing;
data->frame = init ? 7 + 16 * (data->dir == DIR_LEFT): data->frame + 1;
if((data->frame & 15) == 11)
return anim_door_closed(data, 1);
data->duration = 75;
data->img = anim_frame(&anim_tiles, data->frame & 15,
2 + (data->frame >= 16));
return 1;
}

View File

@ -7,9 +7,17 @@ struct anim_data;
/* anim_function_t: Update function for each animation */
typedef int (anim_function_t)(struct anim_data *data, int init);
anim_function_t anim_player_idle;
anim_function_t anim_player_walking;
anim_function_t anim_tile_start;
anim_function_t anim_tile_end;
anim_function_t anim_door_closed;
anim_function_t anim_door_opening;
anim_function_t anim_door_open;
anim_function_t anim_door_closing;
/* struct anim_frame: Subrectangle of an animation sheet */
struct anim_frame
{

View File

@ -1,29 +1,201 @@
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/std/string.h>
#include "engine.h"
#define CELL_X(x) (-2 + 10 * (x))
#define CELL_Y(y) (-3 + 10 * (y))
//---
// Utilities
//---
/* Determine the cycle pattern for a certain door */
static char const *map_door_cycle(struct map const *map, int x, int y)
{
int door = map->tiles[y * map->w + x];
int cycle_id = -1;
if(door >= TILE_VDOOR && door < TILE_VDOOR + 8)
cycle_id = door - TILE_VDOOR;
else if(door >= TILE_HDOOR && door < TILE_HDOOR + 8)
cycle_id = door - TILE_HDOOR + 8;
else
return NULL;
return map->door_cycle + map->door_cycle_index[cycle_id];
}
/* Check whether a cell of the map is walkable */
static bool map_walkable(struct game const *game, int x, int y)
{
/* Refuse to walk on walls */
int tile = game->map->tiles[y * game->map->w + x];
if(tile == TILE_WALL) return false;
/* Refuse to walk on closed doors */
for(int t = 0; t < game->dynamic_tile_count; t++)
{
struct dynamic_tile const *dt = &game->dynamic_tiles[t];
if(dt->x != x || dt->y != y) continue;
char const *cycle = map_door_cycle(game->map, x, y);
if(!dt->state2 && cycle[dt->state] == '#')
return false;
}
return true;
}
//---
// Map loading
//---
void engine_load(struct game *game, struct map const *map)
{
game->map = map;
game->time = 0;
for(int p = 0; p < PLAYER_COUNT + 1; p++)
game->players[p] = NULL;
memset(&game->dynamic_tiles, 0,
MAP_DYNAMIC_TILES * sizeof(struct dynamic_tile));
/* Create dynamic tiles for suitable tiles in the map */
int t = 0;
for(int y = 0; y < map->h && t < MAP_DYNAMIC_TILES; y++)
for(int x = 0; x < map->w && t < MAP_DYNAMIC_TILES; x++)
{
int tile = map->tiles[y*map->w + x];
struct dynamic_tile *dt = &game->dynamic_tiles[t];
dt->y = y;
dt->x = x;
dt->state = 0;
int is_dynamic = 1;
if(tile == TILE_START)
{
dt->idle = !anim_tile_start(&dt->anim, 1);
}
else if(tile == TILE_END)
{
dt->idle = !anim_tile_end(&dt->anim, 1);
}
else if(tile >= TILE_KEY && tile < TILE_KEY + 8)
{
/* TODO: Initial keys' dynamic tile information */
}
else if((tile >= TILE_VDOOR && tile < TILE_VDOOR + 8) ||
(tile >= TILE_HDOOR && tile < TILE_HDOOR + 8))
{
dt->anim.dir = (tile < TILE_HDOOR) ? DIR_UP : DIR_LEFT;
char const *cycle = map_door_cycle(map, x, y);
if(cycle[dt->state] == '#')
dt->idle = !anim_door_closed(&dt->anim, 1);
else
dt->idle = !anim_door_open(&dt->anim, 1);
}
else is_dynamic = 0;
t += is_dynamic;
}
game->dynamic_tile_count = t;
}
void engine_spawn(struct game *game)
{
struct map const *map = game->map;
int p = 0;
for(int y = 0; y < map->h && game->players[p]; y++)
for(int x = 0; x < map->w && game->players[p]; x++)
{
/* Try to spawn a player at (x,y) */
if(map->tiles[y*map->w + x] == TILE_START)
{
struct player *player = game->players[p];
player->y = y;
player->x = x;
player->dir = DIR_DOWN;
player->anim.dir = DIR_DOWN;
player->idle = !anim_player_idle(&player->anim, 1);
p++;
}
}
}
bool engine_all_players_idle(struct game *game)
{
for(int p = 0; game->players[p]; p++)
{
if(!game->players[p]->idle) return false;
}
return true;
}
bool engine_all_dynamic_tiles_idle(struct game *game)
{
for(int t = 0; t < game->dynamic_tile_count; t++)
{
if(!game->dynamic_tiles[t].idle) return false;
}
return true;
}
bool engine_wins(struct game *game, struct player *player)
{
/* First check that the player is participating in this game */
bool found = false;
for(int p = 0; game->players[p] && !found; p++)
{
if(game->players[p] == player) found = true;
}
if(!found) return false;
int tile = game->map->tiles[player->y * game->map->w + player->x];
return (tile == TILE_END);
}
//---
// Animations
//---
void engine_tick(struct game *game, int dt)
void engine_tick(struct game *game, int delta_time)
{
game->time += dt;
game->time += delta_time;
/* Update the animations for every player */
for(int p = 0; game->players[p]; p++)
{
struct player *player = game->players[p];
player->anim.duration -= dt;
player->anim.duration -= delta_time;
if(player->anim.duration > 0) continue;
/* Call the animation function to generate the next frame */
player->idle = !player->anim.function(&player->anim, 0);
}
/* Update animations for dynamic tiles */
for(int t = 0; t < game->dynamic_tile_count; t++)
{
struct dynamic_tile *dt = &game->dynamic_tiles[t];
dt->anim.duration -= delta_time;
if(dt->anim.duration > 0) continue;
dt->idle = !dt->anim.function(&dt->anim, 0);
}
}
//---
@ -37,11 +209,24 @@ static void engine_draw_player(struct player const *player)
player->anim.img);
}
static void engine_draw_dynamic_tile(struct dynamic_tile const *dt)
{
/* The -10 is hardcoded to suit the format of the tilesheet */
dframe(CELL_X(dt->x) + dt->anim.dx,
CELL_Y(dt->y) + dt->anim.dy - 10,
dt->anim.img);
}
void engine_draw(struct game const *game)
{
dclear(C_WHITE);
dimage(0, 0, game->map->img);
for(int t = 0; t < game->dynamic_tile_count; t++)
{
engine_draw_dynamic_tile(&game->dynamic_tiles[t]);
}
for(int p = 0; game->players[p]; p++)
{
engine_draw_player(game->players[p]);
@ -52,13 +237,6 @@ void engine_draw(struct game const *game)
// Physics
//---
/* Check whether a cell of the map is walkable */
static int map_walkable(struct map const *map, int x, int y)
{
int tile = map->tiles[y * map->w + x];
return (tile != TILE_WALL);
}
int engine_move(struct game *game, struct player *player, int dir)
{
int dx = (dir == DIR_RIGHT) - (dir == DIR_LEFT);
@ -73,7 +251,7 @@ int engine_move(struct game *game, struct player *player, int dir)
player->anim.dir = dir;
/* Only move the player if the destination is walkable */
if(!map_walkable(game->map, player->x + dx, player->y + dy))
if(!map_walkable(game, player->x + dx, player->y + dy))
{
/* If not, set the new idle animation */
if(dir != olddir)
@ -91,3 +269,48 @@ int engine_move(struct game *game, struct player *player, int dir)
return 1;
}
void engine_finish_turn(struct game *game)
{
/* TODO: Have players pick up keys and open corresponding doors */
/* Update door cycles */
for(int t = 0; t < game->dynamic_tile_count; t++)
{
struct dynamic_tile *dt = &game->dynamic_tiles[t];
char const *cycle = map_door_cycle(game->map, dt->x, dt->y);
if(!cycle) continue;
/* Determine current open/closed status */
int current_status = dt->state2 ? '.' : cycle[dt->state];
/* Move to next cycle state */
dt->state++;
if(cycle[dt->state] == ' ' || cycle[dt->state] == 0)
dt->state = 0;
dt->state2 = 0;
/* Determine next open/closed status (this might be different
from the cycle state if a player standing in the door
prevents if from closing) */
int new_status = cycle[dt->state];
for(int p = 0; game->players[p]; p++)
{
struct player *player = game->players[p];
if(player->x == dt->x && player->y == dt->y)
{
new_status = '.';
dt->state2 = 1;
}
}
if(new_status == current_status) continue;
/* Set animations when changing status */
if(new_status == '#')
dt->idle = !anim_door_closing(&dt->anim, 1);
else if(new_status == '.')
dt->idle = !anim_door_opening(&dt->anim, 1);
}
}

View File

@ -2,12 +2,16 @@
#define _MYSTNB_ENGINE_H
#include <stdint.h>
#include <stdbool.h>
#include <gint/display.h>
#include "animation.h"
/* Maximum number of players */
#define PLAYER_COUNT 1
/* Maximum number of dynamic tiles on the map */
#define MAP_DYNAMIC_TILES 32
/* Time per engine tick (ms) */
#define ENGINE_TICK 25
@ -24,7 +28,7 @@ struct player
int x, y;
/* Direction currently facing */
int dir;
/* Whether playing is currently idle (ie. can move) */
/* Whether player is currently idle (ie. can move) */
int idle;
/* Current animation function and data */
struct anim_data anim;
@ -63,17 +67,50 @@ enum map_tile
TILE_HDOOR = 32,
};
/* struct dynamic_tile: Dynamic tile information */
struct dynamic_tile
{
/* Position */
int x, y;
/* Current state */
int state;
int state2;
/* Whether animation is idle */
int idle;
/* Current animation */
struct anim_data anim;
};
/* struct game: A running game with a map and some players */
struct game
{
/* Current map */
struct map *map;
struct map const *map;
/* Players */
struct player *players[PLAYER_COUNT + 1];
/* Current game time (ms) */
int time;
/* Dynamic tiles */
struct dynamic_tile dynamic_tiles[MAP_DYNAMIC_TILES];
/* Number of dynamic tiles */
int dynamic_tile_count;
};
/* Load map with no players */
void engine_load(struct game *game, struct map const *map);
/* Spawn all the players on the map */
void engine_spawn(struct game *game);
/* Check whether all players are idle */
bool engine_all_players_idle(struct game *game);
/* Check whether all dynamic tiles are idle */
bool engine_all_dynamic_tiles_idle(struct game *game);
/* Check whether a player stands on the exit */
bool engine_wins(struct game *game, struct player *player);
/* Update animations */
void engine_tick(struct game *game, int dt);
@ -84,4 +121,7 @@ void engine_draw(struct game const *game);
0 otherwise (collisions or out-of-bounds moves) */
int engine_move(struct game *game, struct player *player, int direction);
/* Process automatic actions at the end of a turn */
void engine_finish_turn(struct game *game);
#endif /* _MYSTNB_ENGINE_H */

116
src/game.c Normal file
View File

@ -0,0 +1,116 @@
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/timer.h>
#include <gint/clock.h>
#include "game.h"
#include "engine.h"
/* All maps */
extern struct map map_lv1;
struct map *maps[] = {
&map_lv1,
};
/* Phases of a turn in the game */
enum turn_phase
{
PHASE_IDLE = 0,
PHASE_MOVE = 1,
PHASE_UPDATE = 2,
};
/* Returns a direction to move in */
static int get_inputs(void)
{
int opt = GETKEY_DEFAULT & ~GETKEY_REP_ARROWS;
int timeout = 1;
while(1)
{
key_event_t ev = getkey_opt(opt, &timeout);
if(ev.type == KEYEV_NONE) return -1;
int key = ev.key;
if(key == KEY_DOWN) return DIR_DOWN;
if(key == KEY_RIGHT) return DIR_RIGHT;
if(key == KEY_UP) return DIR_UP;
if(key == KEY_LEFT) return DIR_LEFT;
}
}
/* Notify play_level() that the next frame is due soon */
static int callback_tick(volatile int *tick)
{
*tick = 1;
return TIMER_CONTINUE;
}
bool play_level(int level)
{
if(level < 1 || level > 8) return false;
/* Load the map */
struct game game;
engine_load(&game, maps[level-1]);
/* Create and spawn a solo player */
struct player singleplayer;
game.players[0] = &singleplayer;
engine_spawn(&game);
/* Start the frame timer */
static volatile int tick = 1;
int t = timer_configure(TIMER_ANY, ENGINE_TICK*1000,
GINT_CALL(callback_tick, &tick));
if(t < 0) return false;
timer_start(t);
enum turn_phase phase = PHASE_IDLE;
bool succeeded = false;
while(1)
{
while(!tick) sleep();
tick = 0;
engine_draw(&game);
dupdate();
int dir = get_inputs();
/* If the player inputs a control during IDLE, move them */
if(phase == PHASE_IDLE && dir >= 0)
{
if(engine_move(&game, &singleplayer, dir))
phase = PHASE_MOVE;
}
/* When the move is finished, perform a map update */
else if(phase == PHASE_MOVE && engine_all_players_idle(&game))
{
/* TODO: Winning condition */
if(engine_wins(&game, &singleplayer))
{
succeeded = true;
break;
}
phase = PHASE_UPDATE;
engine_finish_turn(&game);
}
/* When update finishes, start another turn */
else if(phase == PHASE_UPDATE &&
engine_all_dynamic_tiles_idle(&game))
{
phase = PHASE_IDLE;
}
engine_tick(&game, ENGINE_TICK);
}
timer_stop(t);
return succeeded;
}

9
src/game.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef _MYSTNB_GAME_H
#define _MYSTNB_GAME_H
#include <stdbool.h>
/* Play the level, returns whether player completed the level */
bool play_level(int level);
#endif /* _MYSTNB_GAME_H */

View File

@ -1,8 +1,8 @@
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/timer.h>
#include <gint/clock.h>
#include <gint/gint.h>
#include "game.h"
#include "engine.h"
static void draw_menu(int selected)
@ -54,88 +54,19 @@ static int main_menu(void)
selected--;
if(key == KEY_RIGHT && selected < 8)
selected++;
if(key == KEY_EXIT)
gint_osmenu();
}
return selected;
}
/* Returns a direction to move in */
static int get_inputs(void)
{
int opt = GETKEY_DEFAULT & ~GETKEY_REP_ARROWS;
int timeout = 1;
while(1)
{
key_event_t ev = getkey_opt(opt, &timeout);
if(ev.type == KEYEV_NONE) return -1;
int key = ev.key;
if(key == KEY_DOWN) return DIR_DOWN;
if(key == KEY_RIGHT) return DIR_RIGHT;
if(key == KEY_UP) return DIR_UP;
if(key == KEY_LEFT) return DIR_LEFT;
}
}
static int callback_tick(volatile int *tick)
{
*tick = 1;
return TIMER_CONTINUE;
}
int main(void)
{
GUNUSED int level = main_menu();
struct player singleplayer = {
.x = 2,
.y = 3,
.dir = DIR_DOWN,
.anim.function = anim_player_idle,
.anim.dir = DIR_DOWN,
};
singleplayer.idle = !anim_player_idle(&singleplayer.anim, 1);
extern struct map map_lv1;
struct game game = {
.map = &map_lv1,
.players = { &singleplayer, NULL },
.time = 0,
};
/* Global tick clock */
static volatile int tick = 1;
int t = timer_configure(TIMER_ANY, ENGINE_TICK*1000,
GINT_CALL(callback_tick, &tick));
if(t >= 0) timer_start(t);
int level_finished = 0;
while(!level_finished)
while(1)
{
while(!tick) sleep();
tick = 0;
engine_draw(&game);
dupdate();
int dir = get_inputs();
int turn_finished = 0;
if(dir >= 0)
{
turn_finished = engine_move(&game, &singleplayer, dir);
}
if(turn_finished)
{
/* Update doors, etc */
}
engine_tick(&game, ENGINE_TICK);
GUNUSED int level = main_menu();
play_level(1);
}
if(t >= 0) timer_stop(t);
return 1;
}