game.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. #include "game.h"
  2. /****** Entities: Player ******/
  3. static Level *get_next_level(GameManager *manager)
  4. {
  5. Level *current_level = game_manager_current_level_get(manager);
  6. GameContext *game_context = game_manager_game_context_get(manager);
  7. for (int i = 0; i < game_context->level_count; i++)
  8. {
  9. if (game_context->levels[i] == current_level)
  10. {
  11. // check if i+1 is out of bounds, if so, return the first level
  12. game_context->current_level = (i + 1) % game_context->level_count;
  13. return game_context->levels[(i + 1) % game_context->level_count] ? game_context->levels[(i + 1) % game_context->level_count] : game_context->levels[0];
  14. }
  15. }
  16. game_context->current_level = 0;
  17. return game_context->levels[0] ? game_context->levels[0] : game_manager_add_level(manager, generic_level("town_world", 0));
  18. }
  19. void player_spawn(Level *level, GameManager *manager)
  20. {
  21. GameContext *game_context = game_manager_game_context_get(manager);
  22. game_context->players[0] = level_add_entity(level, &player_desc);
  23. // Set player position.
  24. // Depends on your game logic, it can be done in start entity function, but also can be done here.
  25. entity_pos_set(game_context->players[0], (Vector){WORLD_WIDTH / 2, WORLD_HEIGHT / 2});
  26. // Add collision box to player entity
  27. // Box is centered in player x and y, and it's size is 10x10
  28. entity_collider_add_rect(game_context->players[0], 10 + PLAYER_COLLISION_HORIZONTAL, 10 + PLAYER_COLLISION_VERTICAL);
  29. // Get player context
  30. PlayerContext *player_context = entity_context_get(game_context->players[0]);
  31. // Load player sprite
  32. player_context->sprite_right = game_manager_sprite_load(manager, "player_right.fxbm");
  33. player_context->sprite_left = game_manager_sprite_load(manager, "player_left.fxbm");
  34. player_context->direction = PLAYER_RIGHT; // default direction
  35. player_context->health = 100;
  36. player_context->strength = 10;
  37. player_context->level = 1;
  38. player_context->xp = 0;
  39. player_context->start_position = entity_pos_get(game_context->players[0]);
  40. player_context->attack_timer = 0.5f;
  41. player_context->elapsed_attack_timer = player_context->attack_timer;
  42. // Set player username
  43. if (!load_char("Flip-Social-Username", player_context->username, 32))
  44. {
  45. snprintf(player_context->username, 32, "Player");
  46. }
  47. game_context->player_context = player_context;
  48. }
  49. // Modify player_update to track direction
  50. static void player_update(Entity *self, GameManager *manager, void *context)
  51. {
  52. PlayerContext *player = (PlayerContext *)context;
  53. InputState input = game_manager_input_get(manager);
  54. Vector pos = entity_pos_get(self);
  55. GameContext *game_context = game_manager_game_context_get(manager);
  56. // Increment the elapsed_attack_timer for the player
  57. player->elapsed_attack_timer += 1.0f / game_context->fps;
  58. // Store previous direction
  59. int prev_dx = player->dx;
  60. int prev_dy = player->dy;
  61. // Reset movement deltas each frame
  62. player->dx = 0;
  63. player->dy = 0;
  64. // Handle movement input
  65. if (input.held & GameKeyUp)
  66. {
  67. pos.y -= 2;
  68. player->dy = -1;
  69. player->direction = PLAYER_UP;
  70. game_context->user_input = GameKeyUp;
  71. }
  72. if (input.held & GameKeyDown)
  73. {
  74. pos.y += 2;
  75. player->dy = 1;
  76. player->direction = PLAYER_DOWN;
  77. game_context->user_input = GameKeyDown;
  78. }
  79. if (input.held & GameKeyLeft)
  80. {
  81. pos.x -= 2;
  82. player->dx = -1;
  83. player->direction = PLAYER_LEFT;
  84. game_context->user_input = GameKeyLeft;
  85. }
  86. if (input.held & GameKeyRight)
  87. {
  88. pos.x += 2;
  89. player->dx = 1;
  90. player->direction = PLAYER_RIGHT;
  91. game_context->user_input = GameKeyRight;
  92. }
  93. // Clamp the player's position to stay within world bounds
  94. pos.x = CLAMP(pos.x, WORLD_WIDTH - 5, 5);
  95. pos.y = CLAMP(pos.y, WORLD_HEIGHT - 5, 5);
  96. // Update player position
  97. entity_pos_set(self, pos);
  98. // switch levels if holding OK
  99. if (input.held & GameKeyOk)
  100. {
  101. // if all enemies are dead, allow the "OK" button to switch levels
  102. // otherwise the "OK" button will be used to attack
  103. if (game_context->enemy_count == 0)
  104. {
  105. game_manager_next_level_set(manager, get_next_level(manager));
  106. furi_delay_ms(500);
  107. }
  108. else
  109. {
  110. game_context->user_input = GameKeyOk;
  111. FURI_LOG_I("Game", "Player is attacking");
  112. furi_delay_ms(100);
  113. }
  114. return;
  115. }
  116. // If the player is not moving, retain the last movement direction
  117. if (player->dx == 0 && player->dy == 0)
  118. {
  119. player->dx = prev_dx;
  120. player->dy = prev_dy;
  121. player->state = PLAYER_IDLE;
  122. game_context->user_input = -1; // reset user input
  123. }
  124. else
  125. {
  126. player->state = PLAYER_MOVING;
  127. }
  128. // Handle back button to stop the game
  129. if (input.pressed & GameKeyBack)
  130. {
  131. game_manager_game_stop(manager);
  132. }
  133. }
  134. static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
  135. {
  136. // Get player context
  137. UNUSED(manager);
  138. PlayerContext *player = context;
  139. // Get player position
  140. Vector pos = entity_pos_get(self);
  141. // Draw background (updates camera_x and camera_y)
  142. draw_background(canvas, pos);
  143. // Draw player sprite relative to camera, centered on the player's position
  144. canvas_draw_sprite(
  145. canvas,
  146. player->direction == PLAYER_RIGHT ? player->sprite_right : player->sprite_left,
  147. pos.x - camera_x - 5, // Center the sprite horizontally
  148. pos.y - camera_y - 5 // Center the sprite vertically
  149. );
  150. // draw username over player's head
  151. canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
  152. canvas_draw_str(canvas, pos.x - camera_x - (strlen(player->username) * 2), pos.y - camera_y - 7, player->username);
  153. }
  154. const EntityDescription player_desc = {
  155. .start = NULL, // called when entity is added to the level
  156. .stop = NULL, // called when entity is removed from the level
  157. .update = player_update, // called every frame
  158. .render = player_render, // called every frame, after update
  159. .collision = NULL, // called when entity collides with another entity
  160. .event = NULL, // called when entity receives an event
  161. .context_size = sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
  162. };
  163. /****** Game ******/
  164. /*
  165. Write here the start code for your game, for example: creating a level and so on.
  166. Game context is allocated (game.context_size) and passed to this function, you can use it to store your game data.
  167. */
  168. static void game_start(GameManager *game_manager, void *ctx)
  169. {
  170. // Do some initialization here, for example you can load score from storage.
  171. // For simplicity, we will just set it to 0.
  172. GameContext *game_context = ctx;
  173. game_context->fps = game_fps_choices_2[game_fps_index];
  174. game_context->player_context = NULL;
  175. // open the world list from storage, then create a level for each world
  176. char file_path[128];
  177. snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
  178. FuriString *world_list = flipper_http_load_from_file(file_path);
  179. if (!world_list)
  180. {
  181. FURI_LOG_E("Game", "Failed to load world list");
  182. game_context->levels[0] = game_manager_add_level(game_manager, generic_level("town_world", 0));
  183. game_context->level_count = 1;
  184. return;
  185. }
  186. for (int i = 0; i < 10; i++)
  187. {
  188. FuriString *world_name = get_json_array_value_furi("worlds", i, world_list);
  189. if (!world_name)
  190. {
  191. break;
  192. }
  193. game_context->levels[i] = game_manager_add_level(game_manager, generic_level(furi_string_get_cstr(world_name), i));
  194. furi_string_free(world_name);
  195. game_context->level_count++;
  196. }
  197. furi_string_free(world_list);
  198. // add one enemy
  199. game_context->enemies[0] = level_add_entity(game_context->levels[0], enemy(game_manager,
  200. "player",
  201. 0,
  202. (Vector){10, 10},
  203. (Vector){WORLD_WIDTH / 2 + 11, WORLD_HEIGHT / 2 + 16},
  204. (Vector){WORLD_WIDTH / 2 - 11, WORLD_HEIGHT / 2 + 16},
  205. 1,
  206. 32,
  207. 0.5f,
  208. 10,
  209. 100));
  210. // add another enemy
  211. game_context->enemies[1] = level_add_entity(game_context->levels[0], enemy(game_manager,
  212. "player",
  213. 1,
  214. (Vector){10, 10},
  215. (Vector){WORLD_WIDTH / 2 + 11, WORLD_HEIGHT / 2 + 32},
  216. (Vector){WORLD_WIDTH / 2 - 11, WORLD_HEIGHT / 2 + 32},
  217. 1,
  218. 32,
  219. 0.5f,
  220. 10,
  221. 100));
  222. game_context->enemy_count = 2;
  223. game_context->current_level = 0;
  224. }
  225. /*
  226. Write here the stop code for your game, for example, freeing memory, if it was allocated.
  227. You don't need to free level, sprites or entities, it will be done automatically.
  228. Also, you don't need to free game_context, it will be done automatically, after this function.
  229. */
  230. static void game_stop(void *ctx)
  231. {
  232. GameContext *game_context = ctx;
  233. // If you want to do other final logic (like saving scores), do it here.
  234. // But do NOT free levels[] if the engine manages them.
  235. // Just clear out your pointer array if you like (not strictly necessary)
  236. for (int i = 0; i < game_context->level_count; i++)
  237. {
  238. game_context->levels[i] = NULL;
  239. }
  240. game_context->level_count = 0;
  241. }
  242. /*
  243. Your game configuration, do not rename this variable, but you can change its content here.
  244. */
  245. const Game game = {
  246. .target_fps = 0, // set to 0 because we set this in game_app (callback.c line 22)
  247. .show_fps = false, // show fps counter on the screen
  248. .always_backlight = true, // keep display backlight always on
  249. .start = game_start, // will be called once, when game starts
  250. .stop = game_stop, // will be called once, when game stops
  251. .context_size = sizeof(GameContext), // size of game context
  252. };