#include "raylib.h" #include "raymath.h" #include #define VIRTUAL_WIDTH 640 #define VIRTUAL_HEIGHT 360 #define FONT_SIZE 30 #define PLAYER_LIVES 2 #define PADDLE_SPEED 4.0 #define PADDLE_WIDTH (VIRTUAL_WIDTH / 8.0) #define PADDLE_HEIGHT (VIRTUAL_HEIGHT / 30.0) #define BALL_RADIUS (VIRTUAL_WIDTH / 80.0) #define BALL_BASE_SPEED 2.0f #define BALL_VELOCITY ((Vector2){BALL_BASE_SPEED, BALL_BASE_SPEED}) #define BALL_MUL_SPEED 0.15f #define BLOCK_ROWS 7 #define BLOCKS_PER_ROW 10 #define BLOCK_WIDTH (int)rint((float)VIRTUAL_WIDTH / (float)BLOCKS_PER_ROW) - 1 #define BLOCK_HEIGHT 25 #define LEN(arr) (sizeof arr / sizeof *arr) typedef struct Player { Rectangle shape; int lives; int score; } Player; typedef struct Ball { Rectangle shape; Vector2 velocity; float velocityMultiplier; bool firstBounce; bool velocityApplied; } Ball; typedef struct Block { Rectangle shape; Color color; bool isDestroyed; } Block; void BlocksReset(Block blocks[BLOCK_ROWS * BLOCKS_PER_ROW]) { for (int i = 0; i < BLOCK_ROWS; i++) { for (int j = 0; j < BLOCKS_PER_ROW; j++) { int id = i + (j * BLOCK_ROWS); int blockX = j * (BLOCK_WIDTH + 1); int blockY = 10 + i * (BLOCK_HEIGHT + 1); int hue = (id % BLOCK_ROWS) * 400; Color color = ColorFromHSV(hue, 1.0f, 1.0f); blocks[id] = (Block){(Rectangle){blockX, blockY, BLOCK_WIDTH, BLOCK_HEIGHT}, color, false}; } } } void PaddleMove(Player *player) { Vector2 mouseDelta = GetMouseDelta(); player->shape.x = Clamp(player->shape.x + mouseDelta.x, 0, VIRTUAL_WIDTH - PADDLE_WIDTH); } void BallReset(Ball *ball) { ball->velocityMultiplier = 1.0f; ball->velocity = BALL_VELOCITY; ball->firstBounce = false; ball->shape.x = VIRTUAL_WIDTH / 2.0f; ball->shape.y = (VIRTUAL_HEIGHT / 2.0f) + 20.0; } void BallChangeVelocity(Ball *ball, Vector2 force) { ball->velocity = Vector2Multiply(ball->velocity, force); ball->velocity = Vector2Normalize(ball->velocity); ball->velocity = Vector2Multiply(ball->velocity, BALL_VELOCITY); } void BallMove(Ball *ball, Player *player) { bool isColliding = CheckCollisionRecs(player->shape, ball->shape); if (ball->shape.x < 0 || ball->shape.x + BALL_RADIUS > VIRTUAL_WIDTH) { ball->velocity = Vector2Multiply(ball->velocity, (Vector2){-1.0f, 1.0f}); ball->velocityApplied = false; } else if (ball->shape.y < 0) { ball->velocity = Vector2Multiply(ball->velocity, (Vector2){1.0f, -1.0f}); ball->velocityApplied = false; } else if (isColliding) { float paddleCenter = player->shape.x + (PADDLE_WIDTH / 2.0f); float ballCenter = ball->shape.x + (BALL_RADIUS / 2.0f); float direction = ball->velocity.x / BALL_BASE_SPEED; float boost = (ballCenter / paddleCenter) - 1.0f; float forceX = (boost * direction) > 0 ? 1.0f : -1.0f; float forceY = -(1.5f - (fabs(boost) * 10.0)); forceY = Clamp(forceY, -1.4f, -0.8f); BallChangeVelocity(ball, (Vector2){forceX, forceY}); // Add speed multiplier every bounce to increase difficulty if (!ball->velocityApplied) { ball->velocityMultiplier = Clamp(ball->velocityMultiplier + BALL_MUL_SPEED, 1.0f, 4.0f); // Safety so the ball doesn't get stuck inside the paddle from sides ball->shape.y = player->shape.y - BALL_RADIUS; ball->velocityApplied = true; } // When first bouncing off the paddle increase the speed to normal if (!ball->firstBounce) { ball->firstBounce = true; ball->velocityMultiplier = 1.5f; } } else if (ball->shape.y + BALL_RADIUS > VIRTUAL_HEIGHT - PADDLE_HEIGHT + (BALL_RADIUS / 2.0f)) { // Death or Game Over if (player->lives > 0) { player->lives -= 1; BallReset(ball); } else { } ball->velocityApplied = false; } if (!ball->firstBounce) { ball->velocityMultiplier = 0.5f; } ball->shape.x += ball->velocity.x * ball->velocityMultiplier; ball->shape.y += ball->velocity.y * ball->velocityMultiplier; } int main() { SetConfigFlags(FLAG_VSYNC_HINT); InitWindow(0, 0, "Breakout"); ToggleFullscreen(); const int CURRENT_MONITOR = GetCurrentMonitor(); const int MONITOR_WIDTH = GetMonitorWidth(CURRENT_MONITOR); const int MONITOR_HEIGHT = GetMonitorHeight(CURRENT_MONITOR); int refreshRate = GetMonitorRefreshRate(CURRENT_MONITOR); SetTargetFPS(refreshRate); Camera2D camera = {0}; // Game world camera camera.zoom = 1.0f; const RenderTexture2D RENDER_TEXTURE = LoadRenderTexture(VIRTUAL_WIDTH, VIRTUAL_HEIGHT); const Rectangle RENDER_TEXTURE_SRC = (Rectangle){0.0f, 0.0f, VIRTUAL_WIDTH, -VIRTUAL_HEIGHT}; const Rectangle RENDER_TEXTURE_DST = (Rectangle){0.0f, 0.0f, MONITOR_WIDTH, MONITOR_HEIGHT}; const Vector2 RENDER_TEXTURE_POS = {0}; Ball ball = { (Rectangle){VIRTUAL_WIDTH / 2.0f, VIRTUAL_HEIGHT / 2.0f, BALL_RADIUS, BALL_RADIUS}, BALL_VELOCITY, 1.0f, false, false, }; BallReset(&ball); Player player = { (Rectangle){VIRTUAL_WIDTH / 2.0f, VIRTUAL_HEIGHT - PADDLE_HEIGHT - 10, PADDLE_WIDTH, PADDLE_HEIGHT}, PLAYER_LIVES, 0}; Block blocks[BLOCK_ROWS * BLOCKS_PER_ROW]; BlocksReset(blocks); const int BLOCK_COUNT = LEN(blocks); int brokenBlocks = 0; while (!WindowShouldClose()) { if (IsKeyDown(KEY_R)) { BallReset(&ball); BlocksReset(blocks); } // Keep before the cursor is disabled otherwise the paddle is stuck PaddleMove(&player); DisableCursor(); BallMove(&ball, &player); BeginTextureMode(RENDER_TEXTURE); { ClearBackground(BLACK); DrawRectangleRec(ball.shape, RAYWHITE); // DrawRectangle(ball.shape.x + BALL_RADIUS / 2.0f, ball.shape.y + BALL_RADIUS / 2.0f, 5, 5, RED); DrawRectangleRec(player.shape, RAYWHITE); // DrawRectangle(player.shape.x + PADDLE_WIDTH / 2.0f, player.shape.y, 5, 5, RED); for (int i = 0; i < BLOCK_COUNT; i++) { Block *block = &blocks[i]; if (block->isDestroyed) { continue; } bool isColliding = CheckCollisionRecs(ball.shape, block->shape); if (isColliding) { float blockCenterX = block->shape.x + (BLOCK_WIDTH / 2.0f); float blockCenterY = block->shape.y + (BLOCK_HEIGHT / 2.0f); float ballCenterX = ball.shape.x + (BALL_RADIUS / 2.0f); float ballCenterY = ball.shape.y + (BALL_RADIUS / 2.0f); Vector2 dir = {ballCenterX - blockCenterX, ballCenterY - blockCenterY}; dir = Vector2Normalize(dir); Vector2 force = {1.0f, -1.0f}; if (dir.y < 0 && dir.y > dir.x) { force.y = -1.0f; } else if (dir.x > 0 && dir.x > dir.y) { force.x = -1.0f; } ball.velocity = Vector2Multiply(ball.velocity, force); ball.velocityApplied = false; block->color = BLACK; block->isDestroyed = true; player.score += 100; brokenBlocks += 1; if (brokenBlocks >= BLOCK_COUNT) { BallReset(&ball); BlocksReset(blocks); player.score += 1000; brokenBlocks = 0; } continue; } DrawRectangleRec(block->shape, block->color); } } EndTextureMode(); BeginDrawing(); { ClearBackground(BLACK); BeginMode2D(camera); { DrawTexturePro(RENDER_TEXTURE.texture, RENDER_TEXTURE_SRC, RENDER_TEXTURE_DST, RENDER_TEXTURE_POS, 0, RAYWHITE); } EndMode2D(); const char *scoreText = TextFormat("Score %08i", player.score); // float scoreWidth = MeasureText(scoreText, FONT_SIZE) + 10; DrawText(scoreText, 10, 10, FONT_SIZE, RAYWHITE); const char *livesText = TextFormat("Lives %02i", player.lives); DrawText(livesText, 10, 50, FONT_SIZE, RAYWHITE); DrawFPS(GetScreenWidth() - 80, 5); // DrawText(TextFormat("%ix%i", monitorWidth, monitorHeight), 10, 40, FONT_SIZE, GOLD); DrawText(TextFormat("Speed %.02f", ball.velocityMultiplier), 10, 90, FONT_SIZE, GOLD); } EndDrawing(); } CloseWindow(); }