shapes_simple_particles.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /*******************************************************************************************
  2. *
  3. * raylib [shapes] example - simple particles
  4. *
  5. * Example complexity rating: [★★☆☆] 2/4
  6. *
  7. * Example originally created with raylib 5.6, last time updated with raylib 5.6
  8. *
  9. * Example contributed by Jordi Santonja (@JordSant)
  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 Jordi Santonja (@JordSant)
  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_PARTICLES 3000 // Max number of particles
  21. //----------------------------------------------------------------------------------
  22. // Types and Structures Definition
  23. //----------------------------------------------------------------------------------
  24. typedef enum ParticleType {
  25. WATER = 0,
  26. SMOKE,
  27. FIRE
  28. } ParticleType;
  29. static const char particleTypeNames[3][10] = { "WATER", "SMOKE", "FIRE" };
  30. typedef struct Particle {
  31. ParticleType type; // Particle type (WATER, SMOKE, FIRE)
  32. Vector2 position; // Particle position on screen
  33. Vector2 velocity; // Particle current speed and direction
  34. float radius; // Particle radius
  35. Color color; // Particle color
  36. float lifeTime; // Particle life time
  37. bool alive; // Particle alive: inside screen and life time
  38. } Particle;
  39. typedef struct CircularBuffer {
  40. int head; // Index for the next write
  41. int tail; // Index for the next read
  42. Particle *buffer; // Particle buffer array
  43. } CircularBuffer;
  44. //----------------------------------------------------------------------------------
  45. // Module Functions Declaration
  46. //----------------------------------------------------------------------------------
  47. static void EmitParticle(CircularBuffer *circularBuffer, Vector2 emitterPosition, ParticleType type);
  48. static Particle *AddToCircularBuffer(CircularBuffer *circularBuffer);
  49. static void UpdateParticles(CircularBuffer *circularBuffer, int screenWidth, int screenHeight);
  50. static void UpdateCircularBuffer(CircularBuffer *circularBuffer);
  51. static void DrawParticles(CircularBuffer *circularBuffer);
  52. //------------------------------------------------------------------------------------
  53. // Program main entry point
  54. //------------------------------------------------------------------------------------
  55. int main(void)
  56. {
  57. // Initialization
  58. //--------------------------------------------------------------------------------------
  59. const int screenWidth = 800;
  60. const int screenHeight = 450;
  61. InitWindow(screenWidth, screenHeight, "raylib [shapes] example - simple particles");
  62. // Definition of particles
  63. Particle *particles = (Particle*)RL_CALLOC(MAX_PARTICLES, sizeof(Particle)); // Particle array
  64. CircularBuffer circularBuffer = { 0, 0, particles };
  65. // Particle emitter parameters
  66. int emissionRate = -2; // Negative: on average every -X frames. Positive: particles per frame
  67. ParticleType currentType = WATER;
  68. Vector2 emitterPosition = { screenWidth/2.0f, screenHeight/2.0f };
  69. SetTargetFPS(60); // Set our game to run at 60 frames-per-second
  70. //--------------------------------------------------------------------------------------
  71. // Main game loop
  72. while (!WindowShouldClose()) // Detect window close button or ESC key
  73. {
  74. // Update
  75. //----------------------------------------------------------------------------------
  76. // Emit new particles: when emissionRate is 1, emit every frame
  77. if (emissionRate < 0)
  78. {
  79. if (rand()%(-emissionRate) == 0) EmitParticle(&circularBuffer, emitterPosition, currentType);
  80. }
  81. else
  82. {
  83. for (int i = 0; i <= emissionRate; i++) EmitParticle(&circularBuffer, emitterPosition, currentType);
  84. }
  85. // Update the parameters of each particle
  86. UpdateParticles(&circularBuffer, screenWidth, screenHeight);
  87. // Remove dead particles from the circular buffer
  88. UpdateCircularBuffer(&circularBuffer);
  89. // Change Particle Emission Rate (UP/DOWN arrows)
  90. if (IsKeyPressed(KEY_UP)) emissionRate++;
  91. if (IsKeyPressed(KEY_DOWN)) emissionRate--;
  92. // Change Particle Type (LEFT/RIGHT arrows)
  93. if (IsKeyPressed(KEY_RIGHT)) (currentType == FIRE)? (currentType = WATER) : currentType++;
  94. if (IsKeyPressed(KEY_LEFT)) (currentType == WATER)? (currentType = FIRE) : currentType--;
  95. if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) emitterPosition = GetMousePosition();
  96. //----------------------------------------------------------------------------------
  97. // Draw
  98. //----------------------------------------------------------------------------------
  99. BeginDrawing();
  100. ClearBackground(RAYWHITE);
  101. // Call the function with a loop to draw all particles
  102. DrawParticles(&circularBuffer);
  103. // Draw UI and Instructions
  104. DrawRectangle(5, 5, 315, 75, Fade(SKYBLUE, 0.5f));
  105. DrawRectangleLines(5, 5, 315, 75, BLUE);
  106. DrawText("CONTROLS:", 15, 15, 10, BLACK);
  107. DrawText("UP/DOWN: Change Particle Emission Rate", 15, 35, 10, BLACK);
  108. DrawText("LEFT/RIGHT: Change Particle Type (Water, Smoke, Fire)", 15, 55, 10, BLACK);
  109. if (emissionRate < 0) DrawText(TextFormat("Particles every %d frames | Type: %s", -emissionRate, particleTypeNames[currentType]), 15, 95, 10, DARKGRAY);
  110. else DrawText(TextFormat("%d Particles per frame | Type: %s", emissionRate + 1, particleTypeNames[currentType]), 15, 95, 10, DARKGRAY);
  111. DrawFPS(screenWidth - 80, 10);
  112. EndDrawing();
  113. //----------------------------------------------------------------------------------
  114. }
  115. // De-Initialization
  116. //--------------------------------------------------------------------------------------
  117. RL_FREE(particles); // Free particles array data
  118. CloseWindow(); // Close window and OpenGL context
  119. //--------------------------------------------------------------------------------------
  120. return 0;
  121. }
  122. //----------------------------------------------------------------------------------
  123. // Module Functions Definition
  124. //----------------------------------------------------------------------------------
  125. static void EmitParticle(CircularBuffer *circularBuffer, Vector2 emitterPosition, ParticleType type)
  126. {
  127. Particle *newParticle = AddToCircularBuffer(circularBuffer);
  128. // If buffer is full, newParticle is NULL
  129. if (newParticle != NULL)
  130. {
  131. // Fill particle properties
  132. newParticle->position = emitterPosition;
  133. newParticle->alive = true;
  134. newParticle->lifeTime = 0.0f;
  135. newParticle->type = type;
  136. float speed = (float)(rand()%10)/5.0f;
  137. switch (type)
  138. {
  139. case WATER:
  140. {
  141. newParticle->radius = 5.0f;
  142. newParticle->color = BLUE;
  143. } break;
  144. case SMOKE:
  145. {
  146. newParticle->radius = 7.0f;
  147. newParticle->color = GRAY;
  148. } break;
  149. case FIRE:
  150. {
  151. newParticle->radius = 10.0f;
  152. newParticle->color = YELLOW;
  153. speed /= 10.0f;
  154. } break;
  155. default: break;
  156. }
  157. float direction = (float)(rand()%360);
  158. newParticle->velocity = (Vector2){ speed*cosf(direction*DEG2RAD), speed*sinf(direction*DEG2RAD) };
  159. }
  160. }
  161. static Particle *AddToCircularBuffer(CircularBuffer *circularBuffer)
  162. {
  163. Particle *particle = NULL;
  164. // Check if buffer full
  165. if (((circularBuffer->head + 1)%MAX_PARTICLES) != circularBuffer->tail)
  166. {
  167. // Add new particle to the head position and advance head
  168. particle = &circularBuffer->buffer[circularBuffer->head];
  169. circularBuffer->head = (circularBuffer->head + 1)%MAX_PARTICLES;
  170. }
  171. return particle;
  172. }
  173. static void UpdateParticles(CircularBuffer *circularBuffer, int screenWidth, int screenHeight)
  174. {
  175. for (int i = circularBuffer->tail; i != circularBuffer->head; i = (i + 1)%MAX_PARTICLES)
  176. {
  177. // Update particle life and positions
  178. circularBuffer->buffer[i].lifeTime += 1.0f/60.0f; // 60 FPS -> 1/60 seconds per frame
  179. switch (circularBuffer->buffer[i].type)
  180. {
  181. case WATER:
  182. {
  183. circularBuffer->buffer[i].position.x += circularBuffer->buffer[i].velocity.x;
  184. circularBuffer->buffer[i].velocity.y += 0.2f; // Gravity
  185. circularBuffer->buffer[i].position.y += circularBuffer->buffer[i].velocity.y;
  186. } break;
  187. case SMOKE:
  188. {
  189. circularBuffer->buffer[i].position.x += circularBuffer->buffer[i].velocity.x;
  190. circularBuffer->buffer[i].velocity.y -= 0.05f; // Upwards
  191. circularBuffer->buffer[i].position.y += circularBuffer->buffer[i].velocity.y;
  192. circularBuffer->buffer[i].radius += 0.5f; // Increment radius: smoke expands
  193. circularBuffer->buffer[i].color.a -= 4; // Decrement alpha: smoke fades
  194. // If alpha transparent, particle dies
  195. if (circularBuffer->buffer[i].color.a < 4) circularBuffer->buffer[i].alive = false;
  196. } break;
  197. case FIRE:
  198. {
  199. // Add a little horizontal oscillation to fire particles
  200. circularBuffer->buffer[i].position.x += circularBuffer->buffer[i].velocity.x + cosf(circularBuffer->buffer[i].lifeTime*215.0f);
  201. circularBuffer->buffer[i].velocity.y -= 0.05f; // Upwards
  202. circularBuffer->buffer[i].position.y += circularBuffer->buffer[i].velocity.y;
  203. circularBuffer->buffer[i].radius -= 0.15f; // Decrement radius: fire shrinks
  204. circularBuffer->buffer[i].color.g -= 3; // Decrement green: fire turns reddish starting from yellow
  205. // If radius too small, particle dies
  206. if (circularBuffer->buffer[i].radius <= 0.02f) circularBuffer->buffer[i].alive = false;
  207. } break;
  208. default: break;
  209. }
  210. // Disable particle when out of screen
  211. Vector2 center = circularBuffer->buffer[i].position;
  212. float radius = circularBuffer->buffer[i].radius;
  213. if ((center.x < -radius) || (center.x > (screenWidth + radius)) ||
  214. (center.y < -radius) || (center.y > (screenHeight + radius)))
  215. {
  216. circularBuffer->buffer[i].alive = false;
  217. }
  218. }
  219. }
  220. static void UpdateCircularBuffer(CircularBuffer *circularBuffer)
  221. {
  222. // Update circular buffer: advance tail over dead particles
  223. while ((circularBuffer->tail != circularBuffer->head) && !circularBuffer->buffer[circularBuffer->tail].alive)
  224. {
  225. circularBuffer->tail = (circularBuffer->tail + 1)%MAX_PARTICLES;
  226. }
  227. }
  228. static void DrawParticles(CircularBuffer *circularBuffer)
  229. {
  230. for (int i = circularBuffer->tail; i != circularBuffer->head; i = (i + 1)%MAX_PARTICLES)
  231. {
  232. if (circularBuffer->buffer[i].alive)
  233. {
  234. DrawCircleV(circularBuffer->buffer[i].position,
  235. circularBuffer->buffer[i].radius,
  236. circularBuffer->buffer[i].color);
  237. }
  238. }
  239. }