added smooth game element movements for native BD engine
authorHolger Schemel <info@artsoft.org>
Sat, 17 Feb 2024 11:38:18 +0000 (12:38 +0100)
committerHolger Schemel <info@artsoft.org>
Sun, 18 Feb 2024 15:17:00 +0000 (16:17 +0100)
src/game_bd/bd_caveengine.c
src/game_bd/bd_elements.h
src/game_bd/bd_gameplay.c
src/game_bd/bd_gameplay.h
src/game_bd/bd_graphics.c
src/game_bd/main_bd.c

index bfb7dfbc004ea74428ac2494752c4beed588a341..3019aeca80dea04636e5379ab4978c992781eb56 100644 (file)
@@ -526,6 +526,19 @@ static inline boolean is_space_dir(const GdCave *cave, const int x, const int y,
   return (e == O_SPACE || e == O_LAVA);
 }
 
+static inline void store_dir_buffer(GdCave *cave, const int x, const int y, const GdDirection dir)
+{
+  /* raw values without range correction */
+  int raw_x = x + gd_dx[dir];
+  int raw_y = y + gd_dy[dir];
+  /* final values with range correction */
+  int new_x = getx(cave, raw_x, raw_y);
+  int new_y = gety(cave, raw_x, raw_y);
+  int new_dir = (dir > GD_MV_TWICE ? dir - GD_MV_TWICE : dir);
+
+  game_bd.game->dir_buffer[new_y][new_x] = new_dir;
+}
+
 /* store an element at the given position */
 static inline void store(GdCave *cave, const int x, const int y, const GdElement element)
 {
@@ -551,6 +564,7 @@ static inline void store_sc(GdCave *cave, const int x, const int y, const GdElem
 static inline void store_dir(GdCave *cave, const int x, const int y,
                             const GdDirection dir, const GdElement element)
 {
+  store_dir_buffer(cave, x, y, dir);
   store(cave, x + gd_dx[dir], y + gd_dy[dir], element | SCANNED);
 }
 
@@ -558,6 +572,7 @@ static inline void store_dir(GdCave *cave, const int x, const int y,
 static inline void store_dir_no_scanned(GdCave *cave, const int x, const int y,
                                        const GdDirection dir, const GdElement element)
 {
+  store_dir_buffer(cave, x, y, dir);
   store(cave, x + gd_dx[dir], y + gd_dy[dir], element);
 }
 
index 875841707e0d69bfe501b78f0cd8fa7f91f7b275..34a0615ae1af5a534c535f81253b6652a55b1c1b 100644 (file)
@@ -308,9 +308,10 @@ typedef enum _element
 
   SCANNED = 0x100,
   COVERED = 0x200,
+  SKIPPED = 0x400,
 
   /* binary AND this to elements to get rid of properties above. */
-  O_MASK = ~(SCANNED | COVERED)
+  O_MASK = ~(SCANNED | COVERED | SKIPPED)
 } GdElement;
 
 typedef enum _sound
index e367d9fb34e55d8b1bc735c3bdad58ef0e7f40e0..ffedad2cb4e093e492191c397ffa65011ab2cdd3 100644 (file)
@@ -31,6 +31,10 @@ void gd_game_free(GdGame *game)
 
   if (game->element_buffer)
     gd_cave_map_free(game->element_buffer);
+  if (game->last_element_buffer)
+    gd_cave_map_free(game->last_element_buffer);
+  if (game->dir_buffer)
+    gd_cave_map_free(game->dir_buffer);
   if (game->gfx_buffer)
     gd_cave_map_free(game->gfx_buffer);
 
@@ -140,6 +144,16 @@ static void load_cave(GdGame *game)
     gd_cave_map_free(game->element_buffer);
   game->element_buffer = NULL;
 
+  /* delete last element buffer */
+  if (game->last_element_buffer)
+    gd_cave_map_free(game->last_element_buffer);
+  game->last_element_buffer = NULL;
+
+  /* delete direction buffer */
+  if (game->dir_buffer)
+    gd_cave_map_free(game->dir_buffer);
+  game->dir_buffer = NULL;
+
   /* delete gfx buffer */
   if (game->gfx_buffer)
     gd_cave_map_free(game->gfx_buffer);
@@ -171,6 +185,20 @@ static void load_cave(GdGame *game)
     for (x = 0; x < game->cave->w; x++)
       game->element_buffer[y][x] = O_NONE;
 
+  /* create new last element buffer */
+  game->last_element_buffer = gd_cave_map_new(game->cave, int);
+
+  for (y = 0; y < game->cave->h; y++)
+    for (x = 0; x < game->cave->w; x++)
+      game->last_element_buffer[y][x] = O_NONE;
+
+  /* create new direction buffer */
+  game->dir_buffer = gd_cave_map_new(game->cave, int);
+
+  for (y = 0; y < game->cave->h; y++)
+    for (x = 0; x < game->cave->w; x++)
+      game->dir_buffer[y][x] = GD_MV_STILL;
+
   /* create new gfx buffer */
   game->gfx_buffer = gd_cave_map_new(game->cave, int);
 
@@ -455,6 +483,9 @@ static GdGameState gd_game_main_int(GdGame *game, boolean allow_iterate, boolean
     if (allow_iterate)
       game->milliseconds_game += millisecs_elapsed;
 
+    /* increment cycle (frame) counter for the current cave iteration */
+    game->itercycle++;
+
     if (game->milliseconds_game >= cavespeed)
     {
       GdPlayerState pl;
@@ -462,6 +493,25 @@ static GdGameState gd_game_main_int(GdGame *game, boolean allow_iterate, boolean
       game->milliseconds_game -= cavespeed;
       pl = game->cave->player_state;
 
+      /* initialize buffers for last cave element and direction for next iteration */
+      for (y = 0; y < game->cave->h; y++)
+      {
+       for (x = 0; x < game->cave->w; x++)
+       {
+         game->last_element_buffer[y][x] = game->element_buffer[y][x];
+         game->dir_buffer[y][x] = GD_MV_STILL;
+       }
+      }
+
+      /* store last maximum number of cycles (to force redraw if changed) */
+      game->itermax_last = game->itermax;
+
+      /* update maximum number of cycles (frame) per cave iteration */
+      game->itermax = game->itercycle;
+
+      /* reset cycle (frame) counter for the next cave iteration */
+      game->itercycle = 0;
+
       iterate_cave(game, game->player_move, game->player_fire);
 
       if (game->player_move == GD_MV_STILL)
index 4fb6ece48f342c0e3edd5ee17b00568f34938fbf..6cd016ec51308cb1c10126f37cb0c9d777ff4731 100644 (file)
@@ -92,7 +92,12 @@ typedef struct _gd_game
 
   int state_counter;        /* counter used to control the game flow, rendering of caves */
   int **element_buffer;
+  int **last_element_buffer;
+  int **dir_buffer;
   int **gfx_buffer;         /* contains the indexes to the cells; created by *start_level, deleted by *stop_game */
+  int itercycle;
+  int itermax;
+  int itermax_last;
   int animcycle;
   int milliseconds_game;
   int milliseconds_anim;
index 2c0e1335ad05573c81313f83b919d223762abb08..34b34b39631d1ed37a0f0b8f7f2cef06d197a891 100644 (file)
@@ -301,6 +301,10 @@ int gd_drawcave(Bitmap *dest, GdGame *game, boolean force_redraw)
   int scroll_y_aligned = scroll_y;
   int x, y, xd, yd;
 
+  /* force redraw if maximum number of cycles has changed (to redraw moving elements) */
+  if (game->itermax != game->itermax_last)
+    redraw_all = TRUE;
+
   if (!game->cave->gate_open_flash)
   {
     show_flash_count = 0;
@@ -327,8 +331,15 @@ int gd_drawcave(Bitmap *dest, GdGame *game, boolean force_redraw)
   {
     for (x = game->cave->x1, xd = 0; x <= game->cave->x2; x++, xd++)
     {
-      if (redraw_all || game->gfx_buffer[y][x] & GD_REDRAW)
+      /* potential movement direction of game element */
+      int dir = game->dir_buffer[y][x];
+
+      if (redraw_all || game->gfx_buffer[y][x] & GD_REDRAW || dir != GD_MV_STILL)
       {
+       /* skip redrawing already drawn element with movement */
+       if (game->element_buffer[y][x] & SKIPPED)
+         continue;
+
        /* if it needs to be redrawn */
        SDL_Rect offset;
 
@@ -345,6 +356,57 @@ int gd_drawcave(Bitmap *dest, GdGame *game, boolean force_redraw)
        struct GraphicInfo_BD *g = &graphic_info_bd_object[tile][frame];
        int width  = g->width  * TILESIZE_VAR / TILESIZE;
        int height = g->height * TILESIZE_VAR / TILESIZE;
+       boolean use_smooth_movements = TRUE;
+
+       /* if game element is just moving, draw movement animation between two tiles */
+       if (use_smooth_movements && dir != GD_MV_STILL)
+       {
+         if (!(game->last_element_buffer[y][x] & SKIPPED))
+         {
+           /* redraw previous game element on the cave field the new element is moving to */
+           int tile_old = game->last_element_buffer[y][x] & ~SKIPPED;
+           struct GraphicInfo_BD *g_old = &graphic_info_bd_object[tile_old][frame];
+
+           blit_bitmap(g_old->bitmap, dest, g_old->src_x, g_old->src_y, width, height,
+                       offset.x, offset.y);
+         }
+
+         /* get cave field position the game element is moving from */
+         int dx = (dir == GD_MV_LEFT ? +1 : dir == GD_MV_RIGHT ? -1 : 0);
+         int dy = (dir == GD_MV_UP   ? +1 : dir == GD_MV_DOWN  ? -1 : 0);
+         int old_x = game->cave->getx(game->cave, x + dx, y + dy);
+         int old_y = game->cave->gety(game->cave, x + dx, y + dy);
+
+         if (old_x >= game->cave->x1 &&
+             old_x <= game->cave->x2 &&
+             old_y >= game->cave->y1 &&
+             old_y <= game->cave->y2)
+         {
+           if (game->dir_buffer[old_y][old_x] == GD_MV_STILL)
+           {
+             /* redraw game element on the cave field the element is moving from */
+             int tile_from = game->element_buffer[old_y][old_x];
+             struct GraphicInfo_BD *g_from = &graphic_info_bd_object[tile_from][0];
+
+             blit_bitmap(g_from->bitmap, dest, g_from->src_x, g_from->src_y, width, height,
+                         offset.x + dx * cell_size, offset.y + dy * cell_size);
+
+             game->element_buffer[old_y][old_x] |= SKIPPED;
+           }
+           else
+           {
+             /* if old tile also moving (like pushing player), do not redraw it again */
+             game->last_element_buffer[old_y][old_x] |= SKIPPED;
+           }
+         }
+
+         /* get shifted position between cave fields the game element is moving from/to */
+         int itercycle = MIN(MAX(0, game->itermax - game->itercycle - 1), game->itermax);
+         int shift = cell_size * itercycle / game->itermax;
+
+         offset.x += dx * shift;
+         offset.y += dy * shift;
+       }
 
        blit_bitmap(g->bitmap, dest, g->src_x, g->src_y, width, height, offset.x, offset.y);
 
index 933505233746c064d3e445d5290acadece34b594..d637acc85fc0348b2b89fcaa2e3317628a22b9dc 100644 (file)
@@ -269,6 +269,10 @@ void InitGameEngine_BD(void)
 
   game_bd.game = gd_game_new(native_bd_level.cave_nr, native_bd_level.level_nr);
 
+  game_bd.game->itercycle = 0;
+  game_bd.game->itermax = 8;   // default; dynamically changed at runtime
+  game_bd.game->itermax_last = game_bd.game->itermax;
+
   // default: start with completely covered playfield
   int next_state = GAME_INT_START_UNCOVER + 1;