breakout/main.c

268 lines
7.6 KiB
C
Raw Normal View History

#include "raylib.h"
#include "raymath.h"
#include <math.h>
#define VIRTUAL_WIDTH 640
#define VIRTUAL_HEIGHT 360
#define FONT_SIZE 30
2025-08-30 01:22:02 +03:00
#define PLAYER_LIVES 2
#define PADDLE_SPEED 4.0
2025-08-30 01:22:02 +03:00
#define PADDLE_WIDTH (VIRTUAL_WIDTH / 8.0)
#define PADDLE_HEIGHT (VIRTUAL_HEIGHT / 30.0)
2025-08-30 01:22:02 +03:00
#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
2025-08-30 01:22:02 +03:00
#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;
2025-08-30 01:22:02 +03:00
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) {
2025-08-30 01:22:02 +03:00
ball->velocityMultiplier = 1.0f;
ball->velocity = BALL_VELOCITY;
2025-08-30 01:22:02 +03:00
ball->firstBounce = false;
ball->shape.x = VIRTUAL_WIDTH / 2.0f;
ball->shape.y = (VIRTUAL_HEIGHT / 2.0f) + 20.0;
2025-08-30 01:22:02 +03:00
}
void BallChangeVelocity(Ball *ball, Vector2 force) {
ball->velocity = Vector2Multiply(ball->velocity, force);
ball->velocity = Vector2Normalize(ball->velocity);
ball->velocity = Vector2Multiply(ball->velocity, BALL_VELOCITY);
}
2025-09-05 00:13:58 +03:00
void BallMove(Ball *ball, Player *player, float delta_time) {
bool isColliding = CheckCollisionRecs(player->shape, ball->shape);
2025-08-30 01:22:02 +03:00
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;
2025-08-30 01:22:02 +03:00
} 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;
}
2025-09-05 00:13:58 +03:00
ball->shape.x += 50 * ball->velocity.x * ball->velocityMultiplier * delta_time;
ball->shape.y += 50 * ball->velocity.y * ball->velocityMultiplier * delta_time;
}
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 = {
2025-08-30 01:22:02 +03:00
(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);
2025-08-30 01:22:02 +03:00
const int BLOCK_COUNT = LEN(blocks);
int brokenBlocks = 0;
while (!WindowShouldClose()) {
2025-09-05 00:13:58 +03:00
float dt = GetFrameTime();
2025-08-30 01:22:02 +03:00
if (IsKeyDown(KEY_R)) {
BallReset(&ball);
BlocksReset(blocks);
2025-08-30 01:22:02 +03:00
}
// Keep before the cursor is disabled otherwise the paddle is stuck
PaddleMove(&player);
DisableCursor();
2025-09-05 00:13:58 +03:00
BallMove(&ball, &player, dt);
BeginTextureMode(RENDER_TEXTURE);
{
ClearBackground(BLACK);
DrawRectangleRec(ball.shape, RAYWHITE);
2025-08-30 01:22:02 +03:00
// DrawRectangle(ball.shape.x + BALL_RADIUS / 2.0f, ball.shape.y + BALL_RADIUS / 2.0f, 5, 5, RED);
DrawRectangleRec(player.shape, RAYWHITE);
2025-08-30 01:22:02 +03:00
// 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;
2025-08-30 01:22:02 +03:00
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;
2025-08-30 01:22:02 +03:00
}
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();
}