Skip to content

Commit aeba820

Browse files
authored
Add raylib post (#288)
* add post * wiip * minor changes * wip * update structure * wip * wip * wip * add flappy loco * wip * update date
1 parent 2f87c81 commit aeba820

File tree

4 files changed

+336
-0
lines changed

4 files changed

+336
-0
lines changed
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
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!
1.63 MB
Loading
200 KB
Loading
522 KB
Loading

0 commit comments

Comments
 (0)