| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- /*******************************************************************************************
- *
- * raylib [shapes] example - simple particles
- *
- * Example complexity rating: [★★☆☆] 2/4
- *
- * Example originally created with raylib 5.6, last time updated with raylib 5.6
- *
- * Example contributed by Jordi Santonja (@JordSant)
- *
- * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
- * BSD-like license that allows static linking with closed source software
- *
- * Copyright (c) 2025 Jordi Santonja (@JordSant)
- *
- ********************************************************************************************/
- #include "raylib.h"
- #include <stdlib.h> // Required for: calloc(), free()
- #include <math.h> // Required for: cosf(), sinf()
- #define MAX_PARTICLES 3000 // Max number of particles
- //----------------------------------------------------------------------------------
- // Types and Structures Definition
- //----------------------------------------------------------------------------------
- typedef enum ParticleType {
- WATER = 0,
- SMOKE,
- FIRE
- } ParticleType;
- static const char particleTypeNames[3][10] = { "WATER", "SMOKE", "FIRE" };
- typedef struct Particle {
- ParticleType type; // Particle type (WATER, SMOKE, FIRE)
- Vector2 position; // Particle position on screen
- Vector2 velocity; // Particle current speed and direction
- float radius; // Particle radius
- Color color; // Particle color
- float lifeTime; // Particle life time
- bool alive; // Particle alive: inside screen and life time
- } Particle;
- typedef struct CircularBuffer {
- int head; // Index for the next write
- int tail; // Index for the next read
- Particle *buffer; // Particle buffer array
- } CircularBuffer;
- //----------------------------------------------------------------------------------
- // Module Functions Declaration
- //----------------------------------------------------------------------------------
- static void EmitParticle(CircularBuffer *circularBuffer, Vector2 emitterPosition, ParticleType type);
- static Particle *AddToCircularBuffer(CircularBuffer *circularBuffer);
- static void UpdateParticles(CircularBuffer *circularBuffer, int screenWidth, int screenHeight);
- static void UpdateCircularBuffer(CircularBuffer *circularBuffer);
- static void DrawParticles(CircularBuffer *circularBuffer);
- //------------------------------------------------------------------------------------
- // Program main entry point
- //------------------------------------------------------------------------------------
- int main(void)
- {
- // Initialization
- //--------------------------------------------------------------------------------------
- const int screenWidth = 800;
- const int screenHeight = 450;
- InitWindow(screenWidth, screenHeight, "raylib [shapes] example - simple particles");
- // Definition of particles
- Particle *particles = (Particle*)RL_CALLOC(MAX_PARTICLES, sizeof(Particle)); // Particle array
- CircularBuffer circularBuffer = { 0, 0, particles };
- // Particle emitter parameters
- int emissionRate = -2; // Negative: on average every -X frames. Positive: particles per frame
- ParticleType currentType = WATER;
- Vector2 emitterPosition = { screenWidth/2.0f, screenHeight/2.0f };
- SetTargetFPS(60); // Set our game to run at 60 frames-per-second
- //--------------------------------------------------------------------------------------
- // Main game loop
- while (!WindowShouldClose()) // Detect window close button or ESC key
- {
- // Update
- //----------------------------------------------------------------------------------
- // Emit new particles: when emissionRate is 1, emit every frame
- if (emissionRate < 0)
- {
- if (rand()%(-emissionRate) == 0) EmitParticle(&circularBuffer, emitterPosition, currentType);
- }
- else
- {
- for (int i = 0; i <= emissionRate; i++) EmitParticle(&circularBuffer, emitterPosition, currentType);
- }
- // Update the parameters of each particle
- UpdateParticles(&circularBuffer, screenWidth, screenHeight);
- // Remove dead particles from the circular buffer
- UpdateCircularBuffer(&circularBuffer);
- // Change Particle Emission Rate (UP/DOWN arrows)
- if (IsKeyPressed(KEY_UP)) emissionRate++;
- if (IsKeyPressed(KEY_DOWN)) emissionRate--;
- // Change Particle Type (LEFT/RIGHT arrows)
- if (IsKeyPressed(KEY_RIGHT)) (currentType == FIRE)? (currentType = WATER) : currentType++;
- if (IsKeyPressed(KEY_LEFT)) (currentType == WATER)? (currentType = FIRE) : currentType--;
- if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) emitterPosition = GetMousePosition();
- //----------------------------------------------------------------------------------
- // Draw
- //----------------------------------------------------------------------------------
- BeginDrawing();
- ClearBackground(RAYWHITE);
- // Call the function with a loop to draw all particles
- DrawParticles(&circularBuffer);
- // Draw UI and Instructions
- DrawRectangle(5, 5, 315, 75, Fade(SKYBLUE, 0.5f));
- DrawRectangleLines(5, 5, 315, 75, BLUE);
- DrawText("CONTROLS:", 15, 15, 10, BLACK);
- DrawText("UP/DOWN: Change Particle Emission Rate", 15, 35, 10, BLACK);
- DrawText("LEFT/RIGHT: Change Particle Type (Water, Smoke, Fire)", 15, 55, 10, BLACK);
- if (emissionRate < 0) DrawText(TextFormat("Particles every %d frames | Type: %s", -emissionRate, particleTypeNames[currentType]), 15, 95, 10, DARKGRAY);
- else DrawText(TextFormat("%d Particles per frame | Type: %s", emissionRate + 1, particleTypeNames[currentType]), 15, 95, 10, DARKGRAY);
- DrawFPS(screenWidth - 80, 10);
- EndDrawing();
- //----------------------------------------------------------------------------------
- }
- // De-Initialization
- //--------------------------------------------------------------------------------------
- RL_FREE(particles); // Free particles array data
- CloseWindow(); // Close window and OpenGL context
- //--------------------------------------------------------------------------------------
- return 0;
- }
- //----------------------------------------------------------------------------------
- // Module Functions Definition
- //----------------------------------------------------------------------------------
- static void EmitParticle(CircularBuffer *circularBuffer, Vector2 emitterPosition, ParticleType type)
- {
- Particle *newParticle = AddToCircularBuffer(circularBuffer);
- // If buffer is full, newParticle is NULL
- if (newParticle != NULL)
- {
- // Fill particle properties
- newParticle->position = emitterPosition;
- newParticle->alive = true;
- newParticle->lifeTime = 0.0f;
- newParticle->type = type;
- float speed = (float)(rand()%10)/5.0f;
- switch (type)
- {
- case WATER:
- {
- newParticle->radius = 5.0f;
- newParticle->color = BLUE;
- } break;
- case SMOKE:
- {
- newParticle->radius = 7.0f;
- newParticle->color = GRAY;
- } break;
- case FIRE:
- {
- newParticle->radius = 10.0f;
- newParticle->color = YELLOW;
- speed /= 10.0f;
- } break;
- default: break;
- }
- float direction = (float)(rand()%360);
- newParticle->velocity = (Vector2){ speed*cosf(direction*DEG2RAD), speed*sinf(direction*DEG2RAD) };
- }
- }
- static Particle *AddToCircularBuffer(CircularBuffer *circularBuffer)
- {
- Particle *particle = NULL;
- // Check if buffer full
- if (((circularBuffer->head + 1)%MAX_PARTICLES) != circularBuffer->tail)
- {
- // Add new particle to the head position and advance head
- particle = &circularBuffer->buffer[circularBuffer->head];
- circularBuffer->head = (circularBuffer->head + 1)%MAX_PARTICLES;
- }
- return particle;
- }
- static void UpdateParticles(CircularBuffer *circularBuffer, int screenWidth, int screenHeight)
- {
- for (int i = circularBuffer->tail; i != circularBuffer->head; i = (i + 1)%MAX_PARTICLES)
- {
- // Update particle life and positions
- circularBuffer->buffer[i].lifeTime += 1.0f/60.0f; // 60 FPS -> 1/60 seconds per frame
- switch (circularBuffer->buffer[i].type)
- {
- case WATER:
- {
- circularBuffer->buffer[i].position.x += circularBuffer->buffer[i].velocity.x;
- circularBuffer->buffer[i].velocity.y += 0.2f; // Gravity
- circularBuffer->buffer[i].position.y += circularBuffer->buffer[i].velocity.y;
- } break;
- case SMOKE:
- {
- circularBuffer->buffer[i].position.x += circularBuffer->buffer[i].velocity.x;
- circularBuffer->buffer[i].velocity.y -= 0.05f; // Upwards
- circularBuffer->buffer[i].position.y += circularBuffer->buffer[i].velocity.y;
- circularBuffer->buffer[i].radius += 0.5f; // Increment radius: smoke expands
- circularBuffer->buffer[i].color.a -= 4; // Decrement alpha: smoke fades
- // If alpha transparent, particle dies
- if (circularBuffer->buffer[i].color.a < 4) circularBuffer->buffer[i].alive = false;
- } break;
- case FIRE:
- {
- // Add a little horizontal oscillation to fire particles
- circularBuffer->buffer[i].position.x += circularBuffer->buffer[i].velocity.x + cosf(circularBuffer->buffer[i].lifeTime*215.0f);
- circularBuffer->buffer[i].velocity.y -= 0.05f; // Upwards
- circularBuffer->buffer[i].position.y += circularBuffer->buffer[i].velocity.y;
- circularBuffer->buffer[i].radius -= 0.15f; // Decrement radius: fire shrinks
- circularBuffer->buffer[i].color.g -= 3; // Decrement green: fire turns reddish starting from yellow
- // If radius too small, particle dies
- if (circularBuffer->buffer[i].radius <= 0.02f) circularBuffer->buffer[i].alive = false;
- } break;
- default: break;
- }
- // Disable particle when out of screen
- Vector2 center = circularBuffer->buffer[i].position;
- float radius = circularBuffer->buffer[i].radius;
- if ((center.x < -radius) || (center.x > (screenWidth + radius)) ||
- (center.y < -radius) || (center.y > (screenHeight + radius)))
- {
- circularBuffer->buffer[i].alive = false;
- }
- }
- }
- static void UpdateCircularBuffer(CircularBuffer *circularBuffer)
- {
- // Update circular buffer: advance tail over dead particles
- while ((circularBuffer->tail != circularBuffer->head) && !circularBuffer->buffer[circularBuffer->tail].alive)
- {
- circularBuffer->tail = (circularBuffer->tail + 1)%MAX_PARTICLES;
- }
- }
- static void DrawParticles(CircularBuffer *circularBuffer)
- {
- for (int i = circularBuffer->tail; i != circularBuffer->head; i = (i + 1)%MAX_PARTICLES)
- {
- if (circularBuffer->buffer[i].alive)
- {
- DrawCircleV(circularBuffer->buffer[i].position,
- circularBuffer->buffer[i].radius,
- circularBuffer->buffer[i].color);
- }
- }
- }
|