|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Level Up Your C++ Game Dev: raylib, the Free CLion, and Conan!" |
| 4 | +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." |
| 5 | +meta_title: "Level Up Your C++ Game Dev - Conan Blog" |
| 6 | +categories: [cpp, gamedev, clion, conan, raylib] |
| 7 | +--- |
| 8 | + |
| 9 | +Great news for C++ enthusiasts and aspiring game developers! JetBrains [recently |
| 10 | +announced](https://blog.jetbrains.com/clion/2025/05/clion-is-now-free-for-non-commercial-use/) |
| 11 | +that **CLion**, their C++ IDE, is now **free for non-commercial use**! |
| 12 | + |
| 13 | +This is the perfect opportunity to dive into game development with C++ using |
| 14 | +**Conan** and **CLion**. In this post, we'll explore [raylib](https://www.raylib.com/), |
| 15 | +a simple and fun C library for game programming. We'll show you how to set up a |
| 16 | +project for a small [runner game](https://en.wikipedia.org/wiki/Endless_runner) |
| 17 | +and manage its dependencies seamlessly using the [CMake |
| 18 | +presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) |
| 19 | +generated by Conan. |
| 20 | + |
| 21 | +<div style="text-align: center;"> |
| 22 | + <img src="{{ site.baseurl }}/assets/post_images/2025-05-13/jump-to-survive.gif" |
| 23 | + alt="Jump to Survive Mini-Game"/> |
| 24 | +</div> |
| 25 | + |
| 26 | +<br> |
| 27 | + |
| 28 | +## About raylib |
| 29 | + |
| 30 | +Created by Ramon Santamaria, **raylib** is an excellent choice for starting your |
| 31 | +game development journey. It offers a straightforward, easy-to-use C library |
| 32 | +ideal for beginners and rapid prototyping. It's cross-platform (Windows, Linux, |
| 33 | +macOS, Android, HTML5, etc.) and uses hardware-accelerated OpenGL for rendering. |
| 34 | +Some of its most relevant features include 2D/3D graphics, audio processing, a |
| 35 | +powerful math module, input handling, and [extensive |
| 36 | +examples](https://github.com/raysan5/raylib/tree/master/examples) to learn from. |
| 37 | + |
| 38 | +## Our Project: A Simple Runner Game with raylib |
| 39 | + |
| 40 | +To showcase **raylib** in action, we'll build a classic 2D runner game. The |
| 41 | +player, a blue rectangle, must jump over red rectangular obstacles that approach |
| 42 | +from the right. The goal is to survive as long as possible, with the score |
| 43 | +increasing for each successfully avoided obstacle. To make it a bit more |
| 44 | +challenging, the width of the obstacles and the space between them will be |
| 45 | +randomized. |
| 46 | + |
| 47 | +You can find all the code for the project in the Conan 2 examples repository. To |
| 48 | +get the code, clone the repo and navigate to the example's folder: |
| 49 | + |
| 50 | +{% highlight bash %} |
| 51 | +$ git clone https://github.com/conan-io/examples2 |
| 52 | +$ cd examples2/examples/libraries/raylib/introduction |
| 53 | +{% endhighlight %} |
| 54 | + |
| 55 | +Before diving into the specifics of the code, it's helpful to understand |
| 56 | +raylib's 2D coordinate system. By default, the origin (0,0) is at the **top-left |
| 57 | +corner** of the window. The X-axis increases to the right, and the Y-axis |
| 58 | +increases downwards. This is a common convention in 2D graphics libraries. |
| 59 | + |
| 60 | +<div style="text-align: center;"> |
| 61 | + <img src="{{ site.baseurl }}/assets/post_images/2025-05-13/raylib-coordinate-system.png" |
| 62 | + alt="raylib 2D Coordinate System"/> |
| 63 | +</div> |
| 64 | +<br> |
| 65 | + |
| 66 | +Now, let's dive into the code. |
| 67 | + |
| 68 | +### Code Structure and Game Loop Overview |
| 69 | + |
| 70 | +Most games, including ours, follow a fundamental structure: |
| 71 | + |
| 72 | +1. **Initialization**: Set up everything needed before the game starts (window, |
| 73 | + variables, etc.). |
| 74 | +2. **Game Loop**: The core of the game that runs repeatedly. In each iteration |
| 75 | + (frame), we process user input, update the game world based on that input |
| 76 | + and internal logic, and then draw the new state of the world. |
| 77 | +3. **Cleanup**: Release resources when the game ends. |
| 78 | + |
| 79 | +Here's a simplified overview of what happens in our `main()` function: |
| 80 | + |
| 81 | +{% highlight cpp %} |
| 82 | +initialize_everything(); // 1) SETUP – assets, variables, window… |
| 83 | +while (game_is_running) // 2) GAME LOOP – repeats ~60 times per sec |
| 84 | +{ |
| 85 | + float dt = time_since_last_frame(); // Get time difference for smooth updates |
| 86 | + |
| 87 | + update_world(dt); // a) PROCESS INPUT + APPLY LOGIC + HANDLE PHYSICS |
| 88 | + draw_world(); // b) RENDER the current state of the game |
| 89 | +} |
| 90 | +release_resources(); // 3) CLEANUP – free memory, close application |
| 91 | +{% endhighlight %} |
| 92 | + |
| 93 | +#### 1. Creating the World: Initialization |
| 94 | + |
| 95 | +Every **raylib** game begins by setting up the main window. The `InitWindow()` |
| 96 | +function defines its dimensions and title. Our player is a simple rectangle, and |
| 97 | +we define its initial position (`x`, `y` from the top-left) and size, along with |
| 98 | +variables for its physics. We also define the ground's vertical coordinate and |
| 99 | +initialize variables for dynamically adding obstacles during the game loop. |
| 100 | +Finally, we set a target frame rate using `SetTargetFPS()` for consistent game |
| 101 | +speed. |
| 102 | + |
| 103 | +{% highlight cpp %} |
| 104 | +// --- Initialization --- |
| 105 | +const int screenW = 800; |
| 106 | +const int screenH = 450; |
| 107 | +InitWindow(screenW, screenH, "Jump to Survive!"); // Create window |
| 108 | + |
| 109 | +// --- Player Setup --- |
| 110 | +Rectangle player = { 100, screenH - 80, 40, 60 }; // Define player: {x, y, width, height} |
| 111 | +float vy = 0; // Player's vertical velocity |
| 112 | +const float gravity = 1000.0f; // Downward acceleration |
| 113 | +const float jumpImpulse = -450.0f; // Upward force for jump |
| 114 | + |
| 115 | +// --- Ground Definition --- |
| 116 | +const int groundY = screenH - 20; // Y-coordinate of the ground |
| 117 | + |
| 118 | +// --- Obstacle Management --- |
| 119 | +std::vector<Rectangle> obstacles; // To store active obstacles |
| 120 | +float spawnTimer = 0.0f; // Timer for spawning new obstacles |
| 121 | +float spawnInterval = 1.2f; // Initial interval between spawns |
| 122 | +const float obstacleSpeed = 300.0f; // How fast obstacles move |
| 123 | + |
| 124 | +// Parameters for randomizing obstacles |
| 125 | +const float minSpawnInterval = 0.8f; |
| 126 | +const float maxSpawnInterval = 1.6f; |
| 127 | +const int minObsWidth = 40; |
| 128 | +const int maxObsWidth = 120; |
| 129 | + |
| 130 | +// --- Game State Variables --- |
| 131 | +int score = 0; |
| 132 | +bool gameOver = false; |
| 133 | + |
| 134 | +SetTargetFPS(60); // Aim for 60 frames per second |
| 135 | +{% endhighlight %} |
| 136 | + |
| 137 | +#### 2. The Game Loop — Update First, Then Draw |
| 138 | + |
| 139 | +The game loop is where all the action happens, frame after frame. We first |
| 140 | +handle updates to the game state (movement, collisions) and then draw |
| 141 | +everything. |
| 142 | + |
| 143 | +**Player Movement and Physics** |
| 144 | + |
| 145 | +The player's movement starts by checking for jump input using `IsKeyPressed()`. |
| 146 | +If the spacebar is pressed and the player is on the ground, an upward |
| 147 | +`jumpImpulse` is applied. Gravity is then applied consistently using `deltaTime` |
| 148 | +(obtained from `GetFrameTime()`), which represents the time elapsed since the |
| 149 | +last frame, ensuring physics are independent of frame rate. Finally, we check |
| 150 | +for ground collision to prevent the player from falling through the floor. |
| 151 | + |
| 152 | +{% highlight cpp %} |
| 153 | +// Inside the main game loop, if (!gameOver) |
| 154 | +if (IsKeyPressed(KEY_SPACE) && player.y + player.height >= groundY) { |
| 155 | + vy = jumpImpulse; // Apply upward force for the jump |
| 156 | +} |
| 157 | + |
| 158 | +// Apply gravity |
| 159 | +vy += gravity * dt; // Update vertical velocity |
| 160 | +player.y += vy * dt; // Update player's y-position (positive Y is downwards) |
| 161 | + |
| 162 | +// Ground collision check |
| 163 | +if (player.y + player.height > groundY) { |
| 164 | + player.y = groundY - player.height; // Snap player's bottom to ground level |
| 165 | + vy = 0; // Reset vertical speed |
| 166 | +} |
| 167 | +{% endhighlight %} |
| 168 | + |
| 169 | +**Obstacle Spawning and Management** |
| 170 | + |
| 171 | +Obstacles are managed in a `std::vector`. We use a `spawnTimer` and |
| 172 | +`spawnInterval` to control their appearance. To add unpredictability, both the |
| 173 | +`spawnInterval` for the *next* obstacle and the `width` of the *current* |
| 174 | +obstacle are randomized using `GetRandomValue()`. |
| 175 | + |
| 176 | +{% highlight cpp %} |
| 177 | +// Inside the game loop, if (!gameOver) |
| 178 | +spawnTimer += dt; // Increment timer |
| 179 | +if (spawnTimer >= spawnInterval) { // Time to spawn a new one? |
| 180 | + spawnTimer = 0.0f; // Reset timer |
| 181 | + // Recalculate the next spawn interval randomly |
| 182 | + spawnInterval = GetRandomValue(int(minSpawnInterval*100), int(maxSpawnInterval*100)) / 100.0f; |
| 183 | + // Determine a random width for the new obstacle |
| 184 | + int w = GetRandomValue(minObsWidth, maxObsWidth); |
| 185 | + // Spawn obstacle at the right edge, resting on the ground, with the random width |
| 186 | + obstacles.push_back({ (float)screenW, (float)(groundY - 40), (float)w, 40.0f }); |
| 187 | +} |
| 188 | +{% endhighlight %} |
| 189 | + |
| 190 | +**Obstacle Movement and Collision Detection** |
| 191 | + |
| 192 | +Each obstacle in the `obstacles` vector is moved to the left based on |
| 193 | +`obstacleSpeed` and `dt`. We use `CheckCollisionRecs()` to detect if the |
| 194 | +player's rectangle collides with any obstacle rectangle. If a collision occurs, |
| 195 | +the `gameOver` flag is set. |
| 196 | + |
| 197 | +{% highlight cpp %} |
| 198 | +// Still inside the game loop, iterating through obstacles |
| 199 | +for (int i = 0; i < (int)obstacles.size(); i++) { |
| 200 | + obstacles[i].x -= obstacleSpeed * dt; // Move obstacle left |
| 201 | + if (CheckCollisionRecs(player, obstacles[i])) { |
| 202 | + gameOver = true; // Set game over state upon collision |
| 203 | + } |
| 204 | +} |
| 205 | +{% endhighlight %} |
| 206 | + |
| 207 | +Obstacles that move completely off-screen to the left are removed from the |
| 208 | +vector to save resources, and the player's `score` is incremented. |
| 209 | + |
| 210 | +{% highlight cpp %} |
| 211 | +// After iterating through obstacles |
| 212 | +if (!obstacles.empty() && obstacles.front().x + obstacles.front().width < 0) { |
| 213 | + obstacles.erase(obstacles.begin()); // Remove the first (oldest) obstacle if off-screen |
| 214 | + score++; // Increment score |
| 215 | +} |
| 216 | +{% endhighlight %} |
| 217 | + |
| 218 | +**Drawing the Scene** |
| 219 | + |
| 220 | +All drawing operations must occur between `BeginDrawing()` and `EndDrawing()`. |
| 221 | +`ClearBackground()` wipes the previous frame. Then, we use raylib's `Draw...` |
| 222 | +functions to render the ground, player, obstacles, and text elements like the |
| 223 | +score and game over message. `TextFormat()` is useful for creating strings with |
| 224 | +dynamic content. You can find more drawing functions in the [raylib |
| 225 | +cheatsheet](https://www.raylib.com/cheatsheet/cheatsheet.html). |
| 226 | + |
| 227 | +{% highlight cpp %} |
| 228 | +// This entire block is inside the main while(!WindowShouldClose()) loop |
| 229 | +BeginDrawing(); // Start the drawing phase for the current frame |
| 230 | + ClearBackground(RAYWHITE); // Clear the screen to a background color |
| 231 | + |
| 232 | + DrawRectangle(0, groundY, screenW, 20, DARKGRAY); // Draw the ground |
| 233 | + DrawRectangleRec(player, BLUE); // Draw the player |
| 234 | + |
| 235 | + // Draw all current obstacles |
| 236 | + for (auto &obs : obstacles) { |
| 237 | + DrawRectangleRec(obs, RED); |
| 238 | + } |
| 239 | + |
| 240 | + DrawText(TextFormat("Score: %d", score), 10, 10, 20, BLACK); // Display current score |
| 241 | + |
| 242 | + // If game is over, show the game over message |
| 243 | + if (gameOver) { |
| 244 | + DrawText("GAME OVER! Press R to restart", 200, screenH/2 - 20, 20, MAROON); |
| 245 | + } |
| 246 | +EndDrawing(); // End the drawing phase and display the frame |
| 247 | +{% endhighlight %} |
| 248 | + |
| 249 | +**Game Over and Restart Logic** |
| 250 | + |
| 251 | +When `gameOver` is true, the main game update logic is skipped. If the player |
| 252 | +presses 'R' (`IsKeyPressed(KEY_R)`), the game state is reset: player position, |
| 253 | +velocity, obstacles are cleared, timers and score are reset, and `gameOver` is |
| 254 | +set back to `false`. Resetting `spawnInterval` to a default value ensures a fair |
| 255 | +restart. |
| 256 | + |
| 257 | +{% highlight cpp %} |
| 258 | +// Inside the game loop, in the 'else' branch of 'if (!gameOver)' |
| 259 | +if (IsKeyPressed(KEY_R)) { |
| 260 | + // Reset all necessary game variables to their initial state |
| 261 | + player.y = screenH - 80; // Reset player's Y position |
| 262 | + vy = 0; // Reset vertical velocity |
| 263 | + obstacles.clear(); // Remove all obstacles |
| 264 | + spawnTimer = 0.0f; // Reset spawn timer |
| 265 | + spawnInterval = 1.2f; // Reset spawn interval to initial/average |
| 266 | + score = 0; // Reset score |
| 267 | + gameOver = false; // Set game state back to active |
| 268 | +} |
| 269 | +{% endhighlight %} |
| 270 | + |
| 271 | +#### 3. Cleanup |
| 272 | + |
| 273 | +Finally, when the game loop (the `while` loop) exits, `CloseWindow()` is called. |
| 274 | +This is essential for properly releasing all resources used by raylib, such as |
| 275 | +the OpenGL context and any loaded assets. |
| 276 | + |
| 277 | +{% highlight cpp %} |
| 278 | +CloseWindow(); // Unload all loaded data and close the game window |
| 279 | +return 0; // Indicate successful program termination |
| 280 | +{% endhighlight %} |
| 281 | + |
| 282 | +## Building and running our project |
| 283 | + |
| 284 | +We have previously discussed working with Conan in **CLion** [using the Conan CLion |
| 285 | +Plugin](https://blog.conan.io/introducing-new-conan-clion-plugin/). This time, |
| 286 | +we'll demonstrate a different approach: manually invoking Conan to generate |
| 287 | +CMake presets using the `CMakeToolchain` generator, and then letting CLion |
| 288 | +detect and use these presets for building. |
| 289 | + |
| 290 | +1. **Open Project**: First, start CLion and go to *File → Open* to open the |
| 291 | + project folder you cloned from the examples repository. |
| 292 | +2. **Generate Presets**: Open a terminal in the project's root directory (where |
| 293 | + the `conanfile.py` is located) and run `conan install . --build=missing`. |
| 294 | + This command will install **raylib** (building it from source if a |
| 295 | + pre-compiled binary isn't available for your system in the Conan cache) and |
| 296 | + generate the necessary CMake preset files (e.g., `CMakeUserPresets.json`). |
| 297 | +3. **Reload CMake Project in CLion**: In CLion, right-click on the |
| 298 | + `CMakeLists.txt` file in the project view and select "Reload CMake Project". |
| 299 | + CLion should detect and load the presets. |
| 300 | +4. **Select and Enable Preset**: Go to *CLion → Settings... → Build, Execution, |
| 301 | + Deployment → CMake*. In the "CMake Presets" section, you should see the |
| 302 | + presets generated by Conan (e.g., `conan-release` or `conan-debug`). Select |
| 303 | + the desired preset (e.g., `conan-release`) to make it active for your build |
| 304 | + configuration. |
| 305 | +5. **Build and Play**: Now, click the Build button (hammer icon) in CLion. Once |
| 306 | + the build is successful, click the Run button (play icon) to start the game! |
| 307 | + |
| 308 | +## Next Steps: Your Turn to Create! |
| 309 | + |
| 310 | +Now that you have the basic runner game up and running, the fun really begins! |
| 311 | +This project serves as a great starting point. Consider these ideas to get you |
| 312 | +started: |
| 313 | + |
| 314 | +* **New Mechanics**: Transform the game into a "Flappy Bird" style by changing |
| 315 | + obstacle spawning to create gaps and modifying player movement for repeated |
| 316 | + "flaps". |
| 317 | +* **Add Depth**: Introduce power-ups (like invincibility or higher jumps), |
| 318 | + diverse obstacle types (circles, polygons, sprites with varied behaviors), or |
| 319 | + a better scoring system. |
| 320 | +* **Polish**: Enhance the game with improved visuals like textures, scrolling |
| 321 | + backgrounds, particle effects, and sound effects. |
| 322 | + |
| 323 | +<div style="text-align: center;"> |
| 324 | + <img src="{{ site.baseurl }}/assets/post_images/2025-05-13/flappy-loco.gif" |
| 325 | + alt="Flappy Loco"/> |
| 326 | +</div> |
| 327 | + |
| 328 | +## Conclusion |
| 329 | + |
| 330 | +Whether you're a student taking your first steps into coding, a hobbyist with a |
| 331 | +cool game idea, or an open-source developer, now is a fantastic time to explore |
| 332 | +what you can create. So, [download CLion](https://www.jetbrains.com/clion/), |
| 333 | +grab **raylib** using Conan (either via the plugin or CMake presets), and start |
| 334 | +building your dream game today! |
| 335 | + |
| 336 | +Happy coding! |
0 commit comments