shapes_bullet_hell.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /*******************************************************************************************
  2. *
  3. * raylib [shapes] example - bullet hell
  4. *
  5. * Example complexity rating: [★☆☆☆] 1/4
  6. *
  7. * Example originally created with raylib 5.6, last time updated with raylib 5.6
  8. *
  9. * Example contributed by Zero (@zerohorsepower) and reviewed by Ramon Santamaria (@raysan5)
  10. *
  11. * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
  12. * BSD-like license that allows static linking with closed source software
  13. *
  14. * Copyright (c) 2025 Zero (@zerohorsepower)
  15. *
  16. ********************************************************************************************/
  17. #include "raylib.h"
  18. #include <stdlib.h> // Required for: calloc(), free()
  19. #include <math.h> // Required for: cosf(), sinf()
  20. #define MAX_BULLETS 500000 // Max bullets to be processed
  21. //----------------------------------------------------------------------------------
  22. // Types and Structures Definition
  23. //----------------------------------------------------------------------------------
  24. typedef struct Bullet {
  25. Vector2 position; // Bullet position on screen
  26. Vector2 acceleration; // Amount of pixels to be incremented to position every frame
  27. bool disabled; // Skip processing and draw case out of screen
  28. Color color; // Bullet color
  29. } Bullet;
  30. //------------------------------------------------------------------------------------
  31. // Program main entry point
  32. //------------------------------------------------------------------------------------
  33. int main(void)
  34. {
  35. // Initialization
  36. //--------------------------------------------------------------------------------------
  37. const int screenWidth = 800;
  38. const int screenHeight = 450;
  39. InitWindow(screenWidth, screenHeight, "raylib [shapes] example - bullet hell");
  40. // Bullets definition
  41. Bullet *bullets = (Bullet *)RL_CALLOC(MAX_BULLETS, sizeof(Bullet)); // Bullets array
  42. int bulletCount = 0;
  43. int bulletDisabledCount = 0; // Used to calculate how many bullets are on screen
  44. int bulletRadius = 10;
  45. float bulletSpeed = 3.0f;
  46. int bulletRows = 6;
  47. Color bulletColor[2] = { RED, BLUE };
  48. // Spawner variables
  49. float baseDirection = 0;
  50. int angleIncrement = 5; // After spawn all bullet rows, increment this value on the baseDirection for next the frame
  51. float spawnCooldown = 2;
  52. float spawnCooldownTimer = spawnCooldown;
  53. // Magic circle
  54. float magicCircleRotation = 0;
  55. // Used on performance drawing
  56. RenderTexture bulletTexture = LoadRenderTexture(24, 24);
  57. // Draw circle to bullet texture, then draw bullet using DrawTexture()
  58. // NOTE: This is done to improve the performance, since DrawCircle() is very slow
  59. BeginTextureMode(bulletTexture);
  60. DrawCircle(12, 12, (float)bulletRadius, WHITE);
  61. DrawCircleLines(12, 12, (float)bulletRadius, BLACK);
  62. EndTextureMode();
  63. bool drawInPerformanceMode = true; // Switch between DrawCircle() and DrawTexture()
  64. SetTargetFPS(60);
  65. //--------------------------------------------------------------------------------------
  66. // Main game loop
  67. while (!WindowShouldClose()) // Detect window close button or ESC key
  68. {
  69. // Update
  70. //----------------------------------------------------------------------------------
  71. // Reset the bullet index
  72. // New bullets will replace the old ones that are already disabled due to out-of-screen
  73. if (bulletCount >= MAX_BULLETS)
  74. {
  75. bulletCount = 0;
  76. bulletDisabledCount = 0;
  77. }
  78. spawnCooldownTimer--;
  79. if (spawnCooldownTimer < 0)
  80. {
  81. spawnCooldownTimer = spawnCooldown;
  82. // Spawn bullets
  83. float degreesPerRow = 360.0f/bulletRows;
  84. for (int row = 0; row < bulletRows; row++)
  85. {
  86. if (bulletCount < MAX_BULLETS)
  87. {
  88. bullets[bulletCount].position = (Vector2){(float) screenWidth/2, (float) screenHeight/2};
  89. bullets[bulletCount].disabled = false;
  90. bullets[bulletCount].color = bulletColor[row%2];
  91. float bulletDirection = baseDirection + (degreesPerRow*row);
  92. // Bullet speed*bullet direction, this will determine how much pixels will be incremented/decremented
  93. // from the bullet position every frame. Since the bullets doesn't change its direction and speed,
  94. // only need to calculate it at the spawning time
  95. // 0 degrees = right, 90 degrees = down, 180 degrees = left and 270 degrees = up, basically clockwise
  96. // Case you want it to be anti-clockwise, add "* -1" at the y acceleration
  97. bullets[bulletCount].acceleration = (Vector2){
  98. bulletSpeed*cosf(bulletDirection*DEG2RAD),
  99. bulletSpeed*sinf(bulletDirection*DEG2RAD)
  100. };
  101. bulletCount++;
  102. }
  103. }
  104. baseDirection += angleIncrement;
  105. }
  106. // Update bullets position based on its acceleration
  107. for (int i = 0; i < bulletCount; i++)
  108. {
  109. // Only update bullet if inside the screen
  110. if (!bullets[i].disabled)
  111. {
  112. bullets[i].position.x += bullets[i].acceleration.x;
  113. bullets[i].position.y += bullets[i].acceleration.y;
  114. // Disable bullet if out of screen
  115. if ((bullets[i].position.x < -bulletRadius*2) ||
  116. (bullets[i].position.x > screenWidth + bulletRadius*2) ||
  117. (bullets[i].position.y < -bulletRadius*2) ||
  118. (bullets[i].position.y > screenHeight + bulletRadius*2))
  119. {
  120. bullets[i].disabled = true;
  121. bulletDisabledCount++;
  122. }
  123. }
  124. }
  125. // Input logic
  126. if ((IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_D)) && (bulletRows < 359)) bulletRows++;
  127. if ((IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_A)) && (bulletRows > 1)) bulletRows--;
  128. if (IsKeyPressed(KEY_UP) || IsKeyPressed(KEY_W)) bulletSpeed += 0.25f;
  129. if ((IsKeyPressed(KEY_DOWN) || IsKeyPressed(KEY_S)) && (bulletSpeed > 0.50f)) bulletSpeed -= 0.25f;
  130. if (IsKeyPressed(KEY_Z) && (spawnCooldown > 1)) spawnCooldown--;
  131. if (IsKeyPressed(KEY_X)) spawnCooldown++;
  132. if (IsKeyPressed(KEY_ENTER)) drawInPerformanceMode = !drawInPerformanceMode;
  133. if (IsKeyDown(KEY_SPACE))
  134. {
  135. angleIncrement += 1;
  136. angleIncrement %= 360;
  137. }
  138. if (IsKeyPressed(KEY_C))
  139. {
  140. bulletCount = 0;
  141. bulletDisabledCount = 0;
  142. }
  143. //----------------------------------------------------------------------------------
  144. // Draw
  145. //----------------------------------------------------------------------------------
  146. BeginDrawing();
  147. ClearBackground(RAYWHITE);
  148. // Draw magic circle
  149. magicCircleRotation++;
  150. DrawRectanglePro((Rectangle){ (float)screenWidth/2, (float)screenHeight/2, 120, 120 },
  151. (Vector2){ 60.0f, 60.0f }, magicCircleRotation, PURPLE);
  152. DrawRectanglePro((Rectangle){ (float)screenWidth/2, (float)screenHeight/2, 120, 120 },
  153. (Vector2){ 60.0f, 60.0f }, magicCircleRotation + 45, PURPLE);
  154. DrawCircleLines(screenWidth/2, screenHeight/2, 70, BLACK);
  155. DrawCircleLines(screenWidth/2, screenHeight/2, 50, BLACK);
  156. DrawCircleLines(screenWidth/2, screenHeight/2, 30, BLACK);
  157. // Draw bullets
  158. if (drawInPerformanceMode)
  159. {
  160. // Draw bullets using pre-rendered texture containing circle
  161. for (int i = 0; i < bulletCount; i++)
  162. {
  163. // Do not draw disabled bullets (out of screen)
  164. if (!bullets[i].disabled)
  165. {
  166. DrawTexture(bulletTexture.texture,
  167. (int)(bullets[i].position.x - bulletTexture.texture.width*0.5f),
  168. (int)(bullets[i].position.y - bulletTexture.texture.height*0.5f),
  169. bullets[i].color);
  170. }
  171. }
  172. }
  173. else
  174. {
  175. // Draw bullets using DrawCircle(), less performant
  176. for (int i = 0; i < bulletCount; i++)
  177. {
  178. // Do not draw disabled bullets (out of screen)
  179. if (!bullets[i].disabled)
  180. {
  181. DrawCircleV(bullets[i].position, (float)bulletRadius, bullets[i].color);
  182. DrawCircleLinesV(bullets[i].position, (float)bulletRadius, BLACK);
  183. }
  184. }
  185. }
  186. // Draw UI
  187. DrawRectangle(10, 10, 280, 150, (Color){0,0, 0, 200 });
  188. DrawText("Controls:", 20, 20, 10, LIGHTGRAY);
  189. DrawText("- Right/Left or A/D: Change rows number", 40, 40, 10, LIGHTGRAY);
  190. DrawText("- Up/Down or W/S: Change bullet speed", 40, 60, 10, LIGHTGRAY);
  191. DrawText("- Z or X: Change spawn cooldown", 40, 80, 10, LIGHTGRAY);
  192. DrawText("- Space (Hold): Change the angle increment", 40, 100, 10, LIGHTGRAY);
  193. DrawText("- Enter: Switch draw method (Performance)", 40, 120, 10, LIGHTGRAY);
  194. DrawText("- C: Clear bullets", 40, 140, 10, LIGHTGRAY);
  195. DrawRectangle(610, 10, 170, 30, (Color){0,0, 0, 200 });
  196. if (drawInPerformanceMode) DrawText("Draw method: DrawTexture(*)", 620, 20, 10, GREEN);
  197. else DrawText("Draw method: DrawCircle(*)", 620, 20, 10, RED);
  198. DrawRectangle(135, 410, 530, 30, (Color){0,0, 0, 200 });
  199. DrawText(TextFormat("[ FPS: %d, Bullets: %d, Rows: %d, Bullet speed: %.2f, Angle increment per frame: %d, Cooldown: %.0f ]",
  200. GetFPS(), bulletCount - bulletDisabledCount, bulletRows, bulletSpeed, angleIncrement, spawnCooldown),
  201. 155, 420, 10, GREEN);
  202. EndDrawing();
  203. //----------------------------------------------------------------------------------
  204. }
  205. // De-Initialization
  206. //--------------------------------------------------------------------------------------
  207. UnloadRenderTexture(bulletTexture); // Unload bullet texture
  208. RL_FREE(bullets); // Free bullets array data
  209. CloseWindow(); // Close window and OpenGL context
  210. //--------------------------------------------------------------------------------------
  211. return 0;
  212. }