game.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. #include "game.h"
  2. Wall walls[] = {
  3. WALL(true, 12, 0, 3),
  4. WALL(false, 3, 3, 17),
  5. WALL(false, 23, 3, 6),
  6. WALL(true, 3, 4, 57),
  7. WALL(true, 28, 4, 56),
  8. WALL(false, 4, 7, 5),
  9. WALL(false, 12, 7, 13),
  10. WALL(true, 8, 8, 34),
  11. WALL(true, 12, 8, 42),
  12. WALL(true, 24, 8, 8),
  13. WALL(true, 16, 11, 8),
  14. WALL(false, 17, 11, 4),
  15. WALL(true, 20, 12, 22),
  16. WALL(false, 6, 17, 2),
  17. WALL(true, 24, 19, 15),
  18. WALL(true, 16, 22, 16),
  19. WALL(false, 4, 24, 1),
  20. WALL(false, 21, 28, 2),
  21. WALL(false, 6, 33, 2),
  22. WALL(false, 13, 34, 3),
  23. WALL(false, 17, 37, 11),
  24. WALL(true, 16, 41, 14),
  25. WALL(false, 20, 41, 5),
  26. WALL(true, 20, 45, 12),
  27. WALL(true, 24, 45, 12),
  28. WALL(false, 4, 46, 2),
  29. WALL(false, 9, 46, 3),
  30. WALL(false, 6, 50, 3),
  31. WALL(true, 12, 53, 7),
  32. WALL(true, 8, 54, 6),
  33. WALL(false, 4, 60, 19),
  34. WALL(false, 26, 60, 6),
  35. };
  36. // update the background as the player moves
  37. void background_render(Canvas *canvas, Vector pos)
  38. {
  39. // clear the screen
  40. canvas_clear(canvas);
  41. // player starts at 64 (x) and 32 (y)
  42. // test with a static dot at (72, 40)
  43. canvas_draw_dot(canvas, 72 + (64 - pos.x), 40 + (32 - pos.y));
  44. // test with a static circl at (16, 16)
  45. canvas_draw_circle(canvas, 16 + (64 - pos.x), 16 + (32 - pos.y), 4);
  46. }
  47. /****** Entities: Player ******/
  48. typedef struct
  49. {
  50. Vector trajectory; // Direction player would like to move.
  51. float radius; // collision radius
  52. int8_t dx; // x direction
  53. int8_t dy; // y direction
  54. Sprite *sprite; // player sprite
  55. } PlayerContext;
  56. // Forward declaration of player_desc, because it's used in player_spawn function.
  57. static const EntityDescription player_desc;
  58. static void player_spawn(Level *level, GameManager *manager)
  59. {
  60. Entity *player = level_add_entity(level, &player_desc);
  61. // Set player position.
  62. // Depends on your game logic, it can be done in start entity function, but also can be done here.
  63. entity_pos_set(player, (Vector){64, 32});
  64. // Add collision box to player entity
  65. // Box is centered in player x and y, and it's size is 10x10
  66. entity_collider_add_rect(player, 10, 10);
  67. // Get player context
  68. PlayerContext *player_context = entity_context_get(player);
  69. // Load player sprite
  70. player_context->sprite = game_manager_sprite_load(manager, "player.fxbm");
  71. }
  72. static void player_update(Entity *self, GameManager *manager, void *context)
  73. {
  74. UNUSED(context);
  75. // Get game input
  76. InputState input = game_manager_input_get(manager);
  77. // Get player position
  78. Vector pos = entity_pos_get(self);
  79. // Control player movement
  80. if (input.held & GameKeyUp)
  81. pos.y -= 2;
  82. if (input.held & GameKeyDown)
  83. pos.y += 2;
  84. if (input.held & GameKeyLeft)
  85. pos.x -= 2;
  86. if (input.held & GameKeyRight)
  87. pos.x += 2;
  88. // Clamp player position to screen bounds, considering player sprite size (10x10)
  89. pos.x = CLAMP(pos.x, SCREEN_WIDTH - 5, 5);
  90. pos.y = CLAMP(pos.y, SCREEN_HEIGHT - 5, 5);
  91. // Set new player position
  92. entity_pos_set(self, pos);
  93. // Control game exit
  94. if (input.pressed & GameKeyBack)
  95. {
  96. game_manager_game_stop(manager);
  97. }
  98. }
  99. static void player_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
  100. {
  101. // Get player context
  102. PlayerContext *player = context;
  103. // Get player position
  104. Vector pos = entity_pos_get(self);
  105. // draw background
  106. background_render(canvas, pos);
  107. // Draw player sprite
  108. // We subtract 5 from x and y, because collision box is 10x10, and we want to center sprite in it.
  109. canvas_draw_sprite(canvas, player->sprite, pos.x - 5, pos.y - 5);
  110. // Get game context
  111. GameContext *game_context = game_manager_game_context_get(manager);
  112. // Draw score
  113. UNUSED(game_context);
  114. // canvas_printf(canvas, 0, 7, "Score: %lu", game_context->score);
  115. }
  116. static const EntityDescription player_desc = {
  117. .start = NULL, // called when entity is added to the level
  118. .stop = NULL, // called when entity is removed from the level
  119. .update = player_update, // called every frame
  120. .render = player_render, // called every frame, after update
  121. .collision = NULL, // called when entity collides with another entity
  122. .event = NULL, // called when entity receives an event
  123. .context_size = sizeof(PlayerContext), // size of entity context, will be automatically allocated and freed
  124. };
  125. /****** Entities: Target ******/
  126. static Vector random_pos(void)
  127. {
  128. return (Vector){rand() % (SCREEN_WIDTH - 8) + 4, rand() % (SCREEN_HEIGHT - 8) + 4};
  129. }
  130. static void target_start(Entity *self, GameManager *manager, void *context)
  131. {
  132. UNUSED(context);
  133. UNUSED(manager);
  134. // Set target position
  135. entity_pos_set(self, random_pos());
  136. // Add collision circle to target entity
  137. // Circle is centered in target x and y, and it's radius is 3
  138. entity_collider_add_circle(self, 3);
  139. }
  140. static void target_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
  141. {
  142. UNUSED(context);
  143. UNUSED(manager);
  144. // Get target position
  145. Vector pos = entity_pos_get(self);
  146. // Draw target
  147. canvas_draw_disc(canvas, pos.x, pos.y, 3);
  148. }
  149. static void target_collision(Entity *self, Entity *other, GameManager *manager, void *context)
  150. {
  151. UNUSED(context);
  152. // Check if target collided with player
  153. if (entity_description_get(other) == &player_desc)
  154. {
  155. // Increase score
  156. GameContext *game_context = game_manager_game_context_get(manager);
  157. game_context->score++;
  158. // Move target to new random position
  159. entity_pos_set(self, random_pos());
  160. }
  161. }
  162. static const EntityDescription target_desc = {
  163. .start = target_start, // called when entity is added to the level
  164. .stop = NULL, // called when entity is removed from the level
  165. .update = NULL, // called every frame
  166. .render = target_render, // called every frame, after update
  167. .collision = target_collision, // called when entity collides with another entity
  168. .event = NULL, // called when entity receives an event
  169. .context_size = 0, // size of entity context, will be automatically allocated and freed
  170. };
  171. /****** Entities: Wall ******/
  172. static uint8_t wall_index;
  173. static void wall_start(Entity *self, GameManager *manager, void *context);
  174. typedef struct
  175. {
  176. float width;
  177. float height;
  178. } WallContext;
  179. static void wall_render(Entity *self, GameManager *manager, Canvas *canvas, void *context)
  180. {
  181. UNUSED(manager);
  182. WallContext *wall = context;
  183. Vector pos = entity_pos_get(self);
  184. UNUSED(wall);
  185. UNUSED(canvas);
  186. UNUSED(pos);
  187. // canvas_draw_box(
  188. // canvas, pos.x - wall->width / 2, pos.y - wall->height / 2, wall->width, wall->height);
  189. }
  190. static void wall_collision(Entity *self, Entity *other, GameManager *manager, void *context)
  191. {
  192. WallContext *wall = context;
  193. // Check if wall collided with player
  194. if (entity_description_get(other) == &player_desc)
  195. {
  196. // Increase score
  197. GameContext *game_context = game_manager_game_context_get(manager);
  198. game_context->score++;
  199. PlayerContext *player = (PlayerContext *)entity_context_get(other);
  200. if (player)
  201. {
  202. if (player->dx || player->dy)
  203. {
  204. Vector pos = entity_pos_get(other);
  205. // TODO: Based on where we collided, we should still slide across/down the wall.
  206. UNUSED(wall);
  207. if (player->dx)
  208. {
  209. FURI_LOG_D(
  210. "Player",
  211. "Player collided with wall, dx: %d. center:%f,%f",
  212. player->dx,
  213. (double)pos.x,
  214. (double)pos.y);
  215. pos.x -= player->dx;
  216. player->dx = 0;
  217. }
  218. if (player->dy)
  219. {
  220. FURI_LOG_D(
  221. "Player",
  222. "Player collided with wall, dy: %d. center:%f,%f",
  223. player->dy,
  224. (double)pos.x,
  225. (double)pos.y);
  226. pos.y -= player->dy;
  227. player->dy = 0;
  228. }
  229. entity_pos_set(other, pos);
  230. FURI_LOG_D("Player", "Set to center:%f,%f", (double)pos.x, (double)pos.y);
  231. }
  232. }
  233. else
  234. {
  235. FURI_LOG_D("Player", "Player collided with wall, but context null.");
  236. }
  237. }
  238. else
  239. {
  240. // HACK: Wall touching other items destroys each other (to help find collider issues)
  241. Level *level = game_manager_current_level_get(manager);
  242. level_remove_entity(level, self);
  243. level_remove_entity(level, other);
  244. }
  245. }
  246. static const EntityDescription wall_desc = {
  247. .start = wall_start, // called when entity is added to the level
  248. .stop = NULL, // called when entity is removed from the level
  249. .update = NULL, // called every frame
  250. .render = wall_render, // called every frame, after update
  251. .collision = wall_collision, // called when entity collides with another entity
  252. .event = NULL, // called when entity receives an event
  253. .context_size =
  254. sizeof(WallContext), // size of entity context, will be automatically allocated and freed
  255. };
  256. static void wall_start(Entity *self, GameManager *manager, void *context)
  257. {
  258. UNUSED(manager);
  259. WallContext *wall = context;
  260. // TODO: We can get the current number of items from the level (instead of wall_index).
  261. if (wall_index < COUNT_OF(walls))
  262. {
  263. if (walls[wall_index].horizontal)
  264. {
  265. wall->width = walls[wall_index].length * 2;
  266. wall->height = 1 * 2;
  267. }
  268. else
  269. {
  270. wall->width = 1 * 2;
  271. wall->height = walls[wall_index].length * 2;
  272. }
  273. entity_pos_set(
  274. self,
  275. (Vector){
  276. walls[wall_index].x + wall->width / 2, walls[wall_index].y + wall->height / 2});
  277. entity_collider_add_rect(self, wall->width, wall->height);
  278. wall_index++;
  279. }
  280. }
  281. /****** Level ******/
  282. static void level_alloc(Level *level, GameManager *manager, void *context)
  283. {
  284. UNUSED(manager);
  285. UNUSED(context);
  286. // Add player entity to the level
  287. player_spawn(level, manager);
  288. // Add first target entity to the level
  289. level_add_entity(level, &target_desc);
  290. // Add wall entities to the level
  291. wall_index = 0;
  292. for (size_t i = 0; i < COUNT_OF(walls); i++)
  293. {
  294. level_add_entity(level, &wall_desc);
  295. }
  296. }
  297. static const LevelBehaviour level = {
  298. .alloc = level_alloc, // called once, when level allocated
  299. .free = NULL, // called once, when level freed
  300. .start = NULL, // called when level is changed to this level
  301. .stop = NULL, // called when level is changed from this level
  302. .context_size = 0, // size of level context, will be automatically allocated and freed
  303. };
  304. /****** Game ******/
  305. /*
  306. Write here the start code for your game, for example: creating a level and so on.
  307. Game context is allocated (game.context_size) and passed to this function, you can use it to store your game data.
  308. */
  309. static void game_start(GameManager *game_manager, void *ctx)
  310. {
  311. UNUSED(game_manager);
  312. // Do some initialization here, for example you can load score from storage.
  313. // For simplicity, we will just set it to 0.
  314. GameContext *game_context = ctx;
  315. game_context->score = 0;
  316. // Add level to the game
  317. game_manager_add_level(game_manager, &level);
  318. }
  319. /*
  320. Write here the stop code for your game, for example, freeing memory, if it was allocated.
  321. You don't need to free level, sprites or entities, it will be done automatically.
  322. Also, you don't need to free game_context, it will be done automatically, after this function.
  323. */
  324. static void game_stop(void *ctx)
  325. {
  326. UNUSED(ctx);
  327. // GameContext *game_context = ctx;
  328. // Do some deinitialization here, for example you can save score to storage.
  329. // For simplicity, we will just print it.
  330. // FURI_LOG_I("Game", "Your score: %lu", game_context->score);
  331. }
  332. /*
  333. Your game configuration, do not rename this variable, but you can change its content here.
  334. */
  335. const Game game = {
  336. .target_fps = 30, // target fps, game will try to keep this value
  337. .show_fps = false, // show fps counter on the screen
  338. .always_backlight = true, // keep display backlight always on
  339. .start = game_start, // will be called once, when game starts
  340. .stop = game_stop, // will be called once, when game stops
  341. .context_size = sizeof(GameContext), // size of game context
  342. };