diff --git a/_posts/2025-05-13-GameDev-Raylib-CLion.markdown b/_posts/2025-05-13-GameDev-Raylib-CLion.markdown new file mode 100644 index 00000000..69f941cd --- /dev/null +++ b/_posts/2025-05-13-GameDev-Raylib-CLion.markdown @@ -0,0 +1,336 @@ +--- +layout: post +title: "Level Up Your C++ Game Dev: raylib, the Free CLion, and Conan!" +description: "Explore how to use raylib for game development with the newly free CLion for non-commercial use and manage dependencies with the Conan C++ package manager plugin." +meta_title: "Level Up Your C++ Game Dev - Conan Blog" +categories: [cpp, gamedev, clion, conan, raylib] +--- + +Great news for C++ enthusiasts and aspiring game developers! JetBrains [recently +announced](https://blog.jetbrains.com/clion/2025/05/clion-is-now-free-for-non-commercial-use/) +that **CLion**, their C++ IDE, is now **free for non-commercial use**! + +This is the perfect opportunity to dive into game development with C++ using +**Conan** and **CLion**. In this post, we'll explore [raylib](https://www.raylib.com/), +a simple and fun C library for game programming. We'll show you how to set up a +project for a small [runner game](https://en.wikipedia.org/wiki/Endless_runner) +and manage its dependencies seamlessly using the [CMake +presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) +generated by Conan. + +
+ Jump to Survive Mini-Game +
+ +
+ +## About raylib + +Created by Ramon Santamaria, **raylib** is an excellent choice for starting your +game development journey. It offers a straightforward, easy-to-use C library +ideal for beginners and rapid prototyping. It's cross-platform (Windows, Linux, +macOS, Android, HTML5, etc.) and uses hardware-accelerated OpenGL for rendering. +Some of its most relevant features include 2D/3D graphics, audio processing, a +powerful math module, input handling, and [extensive +examples](https://github.com/raysan5/raylib/tree/master/examples) to learn from. + +## Our Project: A Simple Runner Game with raylib + +To showcase **raylib** in action, we'll build a classic 2D runner game. The +player, a blue rectangle, must jump over red rectangular obstacles that approach +from the right. The goal is to survive as long as possible, with the score +increasing for each successfully avoided obstacle. To make it a bit more +challenging, the width of the obstacles and the space between them will be +randomized. + +You can find all the code for the project in the Conan 2 examples repository. To +get the code, clone the repo and navigate to the example's folder: + +{% highlight bash %} +$ git clone https://github.com/conan-io/examples2 +$ cd examples2/examples/libraries/raylib/introduction +{% endhighlight %} + +Before diving into the specifics of the code, it's helpful to understand +raylib's 2D coordinate system. By default, the origin (0,0) is at the **top-left +corner** of the window. The X-axis increases to the right, and the Y-axis +increases downwards. This is a common convention in 2D graphics libraries. + +
+ raylib 2D Coordinate System +
+
+ +Now, let's dive into the code. + +### Code Structure and Game Loop Overview + +Most games, including ours, follow a fundamental structure: + +1. **Initialization**: Set up everything needed before the game starts (window, + variables, etc.). +2. **Game Loop**: The core of the game that runs repeatedly. In each iteration + (frame), we process user input, update the game world based on that input + and internal logic, and then draw the new state of the world. +3. **Cleanup**: Release resources when the game ends. + +Here's a simplified overview of what happens in our `main()` function: + +{% highlight cpp %} +initialize_everything(); // 1) SETUP – assets, variables, window… +while (game_is_running) // 2) GAME LOOP – repeats ~60 times per sec +{ + float dt = time_since_last_frame(); // Get time difference for smooth updates + + update_world(dt); // a) PROCESS INPUT + APPLY LOGIC + HANDLE PHYSICS + draw_world(); // b) RENDER the current state of the game +} +release_resources(); // 3) CLEANUP – free memory, close application +{% endhighlight %} + +#### 1. Creating the World: Initialization + +Every **raylib** game begins by setting up the main window. The `InitWindow()` +function defines its dimensions and title. Our player is a simple rectangle, and +we define its initial position (`x`, `y` from the top-left) and size, along with +variables for its physics. We also define the ground's vertical coordinate and +initialize variables for dynamically adding obstacles during the game loop. +Finally, we set a target frame rate using `SetTargetFPS()` for consistent game +speed. + +{% highlight cpp %} +// --- Initialization --- +const int screenW = 800; +const int screenH = 450; +InitWindow(screenW, screenH, "Jump to Survive!"); // Create window + +// --- Player Setup --- +Rectangle player = { 100, screenH - 80, 40, 60 }; // Define player: {x, y, width, height} +float vy = 0; // Player's vertical velocity +const float gravity = 1000.0f; // Downward acceleration +const float jumpImpulse = -450.0f; // Upward force for jump + +// --- Ground Definition --- +const int groundY = screenH - 20; // Y-coordinate of the ground + +// --- Obstacle Management --- +std::vector obstacles; // To store active obstacles +float spawnTimer = 0.0f; // Timer for spawning new obstacles +float spawnInterval = 1.2f; // Initial interval between spawns +const float obstacleSpeed = 300.0f; // How fast obstacles move + +// Parameters for randomizing obstacles +const float minSpawnInterval = 0.8f; +const float maxSpawnInterval = 1.6f; +const int minObsWidth = 40; +const int maxObsWidth = 120; + +// --- Game State Variables --- +int score = 0; +bool gameOver = false; + +SetTargetFPS(60); // Aim for 60 frames per second +{% endhighlight %} + +#### 2. The Game Loop — Update First, Then Draw + +The game loop is where all the action happens, frame after frame. We first +handle updates to the game state (movement, collisions) and then draw +everything. + +**Player Movement and Physics** + +The player's movement starts by checking for jump input using `IsKeyPressed()`. +If the spacebar is pressed and the player is on the ground, an upward +`jumpImpulse` is applied. Gravity is then applied consistently using `deltaTime` +(obtained from `GetFrameTime()`), which represents the time elapsed since the +last frame, ensuring physics are independent of frame rate. Finally, we check +for ground collision to prevent the player from falling through the floor. + +{% highlight cpp %} +// Inside the main game loop, if (!gameOver) +if (IsKeyPressed(KEY_SPACE) && player.y + player.height >= groundY) { + vy = jumpImpulse; // Apply upward force for the jump +} + +// Apply gravity +vy += gravity * dt; // Update vertical velocity +player.y += vy * dt; // Update player's y-position (positive Y is downwards) + +// Ground collision check +if (player.y + player.height > groundY) { + player.y = groundY - player.height; // Snap player's bottom to ground level + vy = 0; // Reset vertical speed +} +{% endhighlight %} + +**Obstacle Spawning and Management** + +Obstacles are managed in a `std::vector`. We use a `spawnTimer` and +`spawnInterval` to control their appearance. To add unpredictability, both the +`spawnInterval` for the *next* obstacle and the `width` of the *current* +obstacle are randomized using `GetRandomValue()`. + +{% highlight cpp %} +// Inside the game loop, if (!gameOver) +spawnTimer += dt; // Increment timer +if (spawnTimer >= spawnInterval) { // Time to spawn a new one? + spawnTimer = 0.0f; // Reset timer + // Recalculate the next spawn interval randomly + spawnInterval = GetRandomValue(int(minSpawnInterval*100), int(maxSpawnInterval*100)) / 100.0f; + // Determine a random width for the new obstacle + int w = GetRandomValue(minObsWidth, maxObsWidth); + // Spawn obstacle at the right edge, resting on the ground, with the random width + obstacles.push_back({ (float)screenW, (float)(groundY - 40), (float)w, 40.0f }); +} +{% endhighlight %} + +**Obstacle Movement and Collision Detection** + +Each obstacle in the `obstacles` vector is moved to the left based on +`obstacleSpeed` and `dt`. We use `CheckCollisionRecs()` to detect if the +player's rectangle collides with any obstacle rectangle. If a collision occurs, +the `gameOver` flag is set. + +{% highlight cpp %} +// Still inside the game loop, iterating through obstacles +for (int i = 0; i < (int)obstacles.size(); i++) { + obstacles[i].x -= obstacleSpeed * dt; // Move obstacle left + if (CheckCollisionRecs(player, obstacles[i])) { + gameOver = true; // Set game over state upon collision + } +} +{% endhighlight %} + +Obstacles that move completely off-screen to the left are removed from the +vector to save resources, and the player's `score` is incremented. + +{% highlight cpp %} +// After iterating through obstacles +if (!obstacles.empty() && obstacles.front().x + obstacles.front().width < 0) { + obstacles.erase(obstacles.begin()); // Remove the first (oldest) obstacle if off-screen + score++; // Increment score +} +{% endhighlight %} + +**Drawing the Scene** + +All drawing operations must occur between `BeginDrawing()` and `EndDrawing()`. +`ClearBackground()` wipes the previous frame. Then, we use raylib's `Draw...` +functions to render the ground, player, obstacles, and text elements like the +score and game over message. `TextFormat()` is useful for creating strings with +dynamic content. You can find more drawing functions in the [raylib +cheatsheet](https://www.raylib.com/cheatsheet/cheatsheet.html). + +{% highlight cpp %} +// This entire block is inside the main while(!WindowShouldClose()) loop +BeginDrawing(); // Start the drawing phase for the current frame + ClearBackground(RAYWHITE); // Clear the screen to a background color + + DrawRectangle(0, groundY, screenW, 20, DARKGRAY); // Draw the ground + DrawRectangleRec(player, BLUE); // Draw the player + + // Draw all current obstacles + for (auto &obs : obstacles) { + DrawRectangleRec(obs, RED); + } + + DrawText(TextFormat("Score: %d", score), 10, 10, 20, BLACK); // Display current score + + // If game is over, show the game over message + if (gameOver) { + DrawText("GAME OVER! Press R to restart", 200, screenH/2 - 20, 20, MAROON); + } +EndDrawing(); // End the drawing phase and display the frame +{% endhighlight %} + +**Game Over and Restart Logic** + +When `gameOver` is true, the main game update logic is skipped. If the player +presses 'R' (`IsKeyPressed(KEY_R)`), the game state is reset: player position, +velocity, obstacles are cleared, timers and score are reset, and `gameOver` is +set back to `false`. Resetting `spawnInterval` to a default value ensures a fair +restart. + +{% highlight cpp %} +// Inside the game loop, in the 'else' branch of 'if (!gameOver)' +if (IsKeyPressed(KEY_R)) { + // Reset all necessary game variables to their initial state + player.y = screenH - 80; // Reset player's Y position + vy = 0; // Reset vertical velocity + obstacles.clear(); // Remove all obstacles + spawnTimer = 0.0f; // Reset spawn timer + spawnInterval = 1.2f; // Reset spawn interval to initial/average + score = 0; // Reset score + gameOver = false; // Set game state back to active +} +{% endhighlight %} + +#### 3. Cleanup + +Finally, when the game loop (the `while` loop) exits, `CloseWindow()` is called. +This is essential for properly releasing all resources used by raylib, such as +the OpenGL context and any loaded assets. + +{% highlight cpp %} +CloseWindow(); // Unload all loaded data and close the game window +return 0; // Indicate successful program termination +{% endhighlight %} + +## Building and running our project + +We have previously discussed working with Conan in **CLion** [using the Conan CLion +Plugin](https://blog.conan.io/introducing-new-conan-clion-plugin/). This time, +we'll demonstrate a different approach: manually invoking Conan to generate +CMake presets using the `CMakeToolchain` generator, and then letting CLion +detect and use these presets for building. + +1. **Open Project**: First, start CLion and go to *File → Open* to open the + project folder you cloned from the examples repository. +2. **Generate Presets**: Open a terminal in the project's root directory (where + the `conanfile.py` is located) and run `conan install . --build=missing`. + This command will install **raylib** (building it from source if a + pre-compiled binary isn't available for your system in the Conan cache) and + generate the necessary CMake preset files (e.g., `CMakeUserPresets.json`). +3. **Reload CMake Project in CLion**: In CLion, right-click on the + `CMakeLists.txt` file in the project view and select "Reload CMake Project". + CLion should detect and load the presets. +4. **Select and Enable Preset**: Go to *CLion → Settings... → Build, Execution, + Deployment → CMake*. In the "CMake Presets" section, you should see the + presets generated by Conan (e.g., `conan-release` or `conan-debug`). Select + the desired preset (e.g., `conan-release`) to make it active for your build + configuration. +5. **Build and Play**: Now, click the Build button (hammer icon) in CLion. Once + the build is successful, click the Run button (play icon) to start the game! + +## Next Steps: Your Turn to Create! + +Now that you have the basic runner game up and running, the fun really begins! +This project serves as a great starting point. Consider these ideas to get you +started: + +* **New Mechanics**: Transform the game into a "Flappy Bird" style by changing + obstacle spawning to create gaps and modifying player movement for repeated + "flaps". +* **Add Depth**: Introduce power-ups (like invincibility or higher jumps), + diverse obstacle types (circles, polygons, sprites with varied behaviors), or + a better scoring system. +* **Polish**: Enhance the game with improved visuals like textures, scrolling + backgrounds, particle effects, and sound effects. + +
+ Flappy Loco +
+ +## Conclusion + +Whether you're a student taking your first steps into coding, a hobbyist with a +cool game idea, or an open-source developer, now is a fantastic time to explore +what you can create. So, [download CLion](https://www.jetbrains.com/clion/), +grab **raylib** using Conan (either via the plugin or CMake presets), and start +building your dream game today! + +Happy coding! diff --git a/assets/post_images/2025-05-13/flappy-loco.gif b/assets/post_images/2025-05-13/flappy-loco.gif new file mode 100644 index 00000000..a1b8b8a7 Binary files /dev/null and b/assets/post_images/2025-05-13/flappy-loco.gif differ diff --git a/assets/post_images/2025-05-13/jump-to-survive.gif b/assets/post_images/2025-05-13/jump-to-survive.gif new file mode 100644 index 00000000..227555da Binary files /dev/null and b/assets/post_images/2025-05-13/jump-to-survive.gif differ diff --git a/assets/post_images/2025-05-13/raylib-coordinate-system.png b/assets/post_images/2025-05-13/raylib-coordinate-system.png new file mode 100644 index 00000000..f4716107 Binary files /dev/null and b/assets/post_images/2025-05-13/raylib-coordinate-system.png differ