1+ /*******************************************************************************************
2+ *
3+ * raylib [shapes] example - physics bouncing balls
4+ *
5+ * Example complexity rating: [★★☆☆] 2/4
6+ *
7+ * Example originally created with raylib 5.5
8+ *
9+ * Example contributed by David Buzatto (@davidbuzatto) and reviewed by Ramon Santamaria (@raysan5)
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 David Buzatto (@davidbuzatto)
15+ *
16+ ********************************************************************************************/
17+
18+ #include <stdlib.h>
19+ #include <math.h>
20+ #include "raylib.h"
21+
22+ #define MAX_BALLS 5000 // Maximum quantity of balls
23+
24+ typedef struct Ball {
25+ Vector2 pos ; // Position
26+ Vector2 vel ; // Velocity
27+ Vector2 ppos ; // Previous position
28+ float radius ;
29+ float friction ;
30+ float elasticity ;
31+ Color color ;
32+ bool grabbed ;
33+ } Ball ;
34+
35+ //------------------------------------------------------------------------------------
36+ // Program main entry point
37+ //------------------------------------------------------------------------------------
38+ int main (void )
39+ {
40+ // Initialization
41+ //--------------------------------------------------------------------------------------
42+ const int screenWidth = 800 ;
43+ const int screenHeight = 450 ;
44+
45+ InitWindow (screenWidth , screenHeight , "raylib [shapes] example - physics bouncing balls" );
46+
47+ Ball balls [MAX_BALLS ] = {{
48+ .pos = {GetScreenWidth ()/2 , GetScreenHeight ()/2 },
49+ .vel = {200 , 200 },
50+ .ppos = {0 },
51+ .radius = 40 ,
52+ .friction = 0.99 ,
53+ .elasticity = 0.9 ,
54+ .color = BLUE ,
55+ .grabbed = false
56+ }};
57+
58+ int ballQuantity = 1 ;
59+ Ball * grabbedBall = NULL ; // A pointer to the current ball that is grabbed
60+ Vector2 pressOffset = {0 }; // Mouse press offset relative to the ball that grabbedd
61+
62+ float gravity = 100 ; // World gravity
63+
64+ SetTargetFPS (60 ); // Set our game to run at 60 frames-per-second
65+ //---------------------------------------------------------------------------------------
66+
67+ // Main game loop
68+ while (!WindowShouldClose ()) // Detect window close button or ESC key
69+ {
70+ // Update
71+ //----------------------------------------------------------------------------------
72+ float delta = GetFrameTime ();
73+ Vector2 mousePos = GetMousePosition ();
74+
75+ // Checks if a ball was grabbed
76+ if (IsMouseButtonPressed (MOUSE_BUTTON_LEFT ))
77+ {
78+ for (int i = ballQuantity - 1 ; i >= 0 ; i -- ) {
79+
80+ Ball * ball = & balls [i ];
81+ pressOffset .x = mousePos .x - ball -> pos .x ;
82+ pressOffset .y = mousePos .y - ball -> pos .y ;
83+
84+ // If the distance between the ball position and the mouse press position
85+ // is less or equal the ball radius, the event occured inside the ball
86+ if (hypot (pressOffset .x , pressOffset .y ) <= ball -> radius )
87+ {
88+ ball -> grabbed = true;
89+ grabbedBall = ball ;
90+ break ;
91+ }
92+
93+ }
94+ }
95+
96+ // Releases any ball the was grabbed
97+ if (IsMouseButtonReleased (MOUSE_BUTTON_LEFT ))
98+ {
99+ if (grabbedBall != NULL )
100+ {
101+ grabbedBall -> grabbed = false;
102+ grabbedBall = NULL ;
103+ }
104+ }
105+
106+ // Creates a new ball
107+ if (IsMouseButtonPressed (MOUSE_BUTTON_RIGHT ) || (IsKeyDown (KEY_LEFT_CONTROL ) && IsMouseButtonDown (MOUSE_BUTTON_RIGHT ))) {
108+ if (ballQuantity < MAX_BALLS ) {
109+ balls [ballQuantity ++ ] = (Ball ) {
110+ .pos = mousePos ,
111+ .vel = {GetRandomValue (-300 , 300 ), GetRandomValue (-300 , 300 )},
112+ .ppos = {0 },
113+ .radius = 20 + GetRandomValue (0 , 30 ),
114+ .friction = 0.99 ,
115+ .elasticity = 0.9 ,
116+ .color = {GetRandomValue (0 , 255 ), GetRandomValue (0 , 255 ), GetRandomValue (0 , 255 ), 255 },
117+ .grabbed = false
118+ };
119+ }
120+ }
121+
122+ // Shake balls
123+ if (IsMouseButtonPressed (MOUSE_BUTTON_MIDDLE )) {
124+ for (int i = 0 ; i < ballQuantity ; i ++ ) {
125+ Ball * ball = & balls [i ];
126+ if (!ball -> grabbed ) {
127+ ball -> vel = (Vector2 ) {GetRandomValue (-2000 , 2000 ), GetRandomValue (-2000 , 2000 )};
128+ }
129+ }
130+ }
131+
132+ // Changes gravity
133+ gravity += GetMouseWheelMove () * 5 ;
134+
135+ // Updates each ball state
136+ for (int i = 0 ; i < ballQuantity ; i ++ ) {
137+
138+ Ball * ball = & balls [i ];
139+
140+ // The ball is not grabbed
141+ if (!ball -> grabbed )
142+ {
143+ // Ball repositioning using the velocity
144+ ball -> pos .x += ball -> vel .x * delta ;
145+ ball -> pos .y += ball -> vel .y * delta ;
146+
147+ // Does the ball hit the screen right boundary?
148+ if (ball -> pos .x + ball -> radius >= screenWidth )
149+ {
150+ ball -> pos .x = screenWidth - ball -> radius ; // Ball repositioning
151+ ball -> vel .x = - ball -> vel .x * ball -> elasticity ; // Elasticity makes the ball lose 10% of its velocity on hit
152+ }
153+ // Does the ball hit the screen left boundary?
154+ else if (ball -> pos .x - ball -> radius <= 0 )
155+ {
156+ ball -> pos .x = ball -> radius ;
157+ ball -> vel .x = - ball -> vel .x * ball -> elasticity ;
158+ }
159+
160+ // The same for y axis
161+ if (ball -> pos .y + ball -> radius >= screenHeight )
162+ {
163+ ball -> pos .y = screenHeight - ball -> radius ;
164+ ball -> vel .y = - ball -> vel .y * ball -> elasticity ;
165+ }
166+ else if (ball -> pos .y - ball -> radius <= 0 )
167+ {
168+ ball -> pos .y = ball -> radius ;
169+ ball -> vel .y = - ball -> vel .y * ball -> elasticity ;
170+ }
171+
172+ // Friction makes the ball lose 1% of its velocity each frame
173+ ball -> vel .x = ball -> vel .x * ball -> friction ;
174+ // Gravity affects only the y axis
175+ ball -> vel .y = ball -> vel .y * ball -> friction + gravity ;
176+
177+ }
178+ else
179+ {
180+ // Ball repositioning using the mouse position
181+ ball -> pos .x = mousePos .x - pressOffset .x ;
182+ ball -> pos .y = mousePos .y - pressOffset .y ;
183+ // While the ball is grabbed, recalculates its velocity
184+ ball -> vel .x = (ball -> pos .x - ball -> ppos .x ) / delta ;
185+ ball -> vel .y = (ball -> pos .y - ball -> ppos .y ) / delta ;
186+ ball -> ppos = ball -> pos ;
187+ }
188+ }
189+
190+ //----------------------------------------------------------------------------------
191+
192+ // Draw
193+ //----------------------------------------------------------------------------------
194+ BeginDrawing ();
195+
196+ ClearBackground (RAYWHITE );
197+
198+ for (int i = 0 ; i < ballQuantity ; i ++ )
199+ {
200+ Ball * ball = & balls [i ];
201+ DrawCircleV (ball -> pos , ball -> radius , ball -> color );
202+ DrawCircleLinesV (ball -> pos , ball -> radius , BLACK );
203+ }
204+
205+ DrawText ("grab a ball by pressing with the mouse and throw it by releasing" , 10 , 10 , 20 , DARKGRAY );
206+ DrawText ("right click to create new balls (keep left control pressed to create a lot)" , 10 , 30 , 20 , DARKGRAY );
207+ DrawText ("use mouse wheel to change gravity" , 10 , 50 , 20 , DARKGRAY );
208+ DrawText ("middle click to shake" , 10 , 70 , 20 , DARKGRAY );
209+ DrawText (TextFormat ("ball quantity: %d" , ballQuantity ), 10 , GetScreenHeight () - 55 , 20 , BLACK );
210+ DrawText (TextFormat ("gravity: %.2f" , gravity ), 10 , GetScreenHeight () - 35 , 20 , BLACK );
211+
212+ EndDrawing ();
213+ //----------------------------------------------------------------------------------
214+ }
215+
216+ // De-Initialization
217+ //--------------------------------------------------------------------------------------
218+ CloseWindow (); // Close window and OpenGL context
219+ //--------------------------------------------------------------------------------------
220+
221+ return 0 ;
222+ }
0 commit comments