2025-08-29 19:32:05 +03:00
|
|
|
#include "raylib.h"
|
|
|
|
|
#include "raymath.h"
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
#define VIRTUAL_WIDTH 640
|
|
|
|
|
#define VIRTUAL_HEIGHT 360
|
2025-08-29 19:32:05 +03:00
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
#define FONT_SIZE 30
|
2025-08-29 19:32:05 +03:00
|
|
|
|
2025-08-30 01:22:02 +03:00
|
|
|
#define PLAYER_LIVES 2
|
2025-08-29 19:32:05 +03:00
|
|
|
#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-29 19:32:05 +03:00
|
|
|
|
2025-08-30 01:22:02 +03:00
|
|
|
#define BALL_RADIUS (VIRTUAL_WIDTH / 80.0)
|
2025-08-29 22:42:52 +03:00
|
|
|
#define BALL_BASE_SPEED 2.0f
|
|
|
|
|
#define BALL_MUL_SPEED 0.15f
|
|
|
|
|
|
2025-08-30 01:22:02 +03:00
|
|
|
#define LEN(arr) (sizeof arr / sizeof *arr)
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
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;
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
void MovePaddle(Player *player) {
|
|
|
|
|
Vector2 mouseDelta = GetMouseDelta();
|
|
|
|
|
player->shape.x = Clamp(player->shape.x + mouseDelta.x, 0, VIRTUAL_WIDTH - PADDLE_WIDTH);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-30 01:22:02 +03:00
|
|
|
void ResetBall(Ball *ball) {
|
|
|
|
|
ball->velocityMultiplier = 1.0f;
|
|
|
|
|
// Resetting ball's position
|
|
|
|
|
ball->velocity = (Vector2){BALL_BASE_SPEED, BALL_BASE_SPEED};
|
|
|
|
|
ball->firstBounce = false;
|
|
|
|
|
ball->shape.x = VIRTUAL_WIDTH / 2.0f;
|
|
|
|
|
ball->shape.y = VIRTUAL_HEIGHT / 2.0f;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
void MoveBall(Ball *ball, Player *player) {
|
|
|
|
|
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) {
|
2025-08-29 22:42:52 +03:00
|
|
|
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) {
|
2025-08-29 22:42:52 +03:00
|
|
|
ball->velocity = Vector2Multiply(ball->velocity, (Vector2){1.0f, -1.0f});
|
|
|
|
|
ball->velocityApplied = false;
|
|
|
|
|
} else if (isColliding) {
|
2025-08-30 01:22:02 +03:00
|
|
|
float direction = 1 - (ball->shape.x + (BALL_RADIUS / 2.0f)) / (player->shape.x + (PADDLE_WIDTH / 2.0f) + 1);
|
|
|
|
|
float forceX = 1.0f; // direction > 0 ? 1.0f : -1.0f;
|
|
|
|
|
float forceY = -1.0f; // + (direction * 10.0);
|
|
|
|
|
TraceLog(LOG_INFO, "%f %f %f", direction, forceX, forceY);
|
|
|
|
|
ball->velocity = Vector2Multiply(ball->velocity, (Vector2){forceX, forceY});
|
2025-08-29 22:42:52 +03:00
|
|
|
|
|
|
|
|
// 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.0f;
|
|
|
|
|
}
|
|
|
|
|
} 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;
|
2025-08-30 01:22:02 +03:00
|
|
|
ResetBall(ball);
|
2025-08-29 22:42:52 +03:00
|
|
|
} 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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-29 19:32:05 +03:00
|
|
|
int main() {
|
|
|
|
|
InitWindow(0, 0, "Breakout");
|
|
|
|
|
ToggleFullscreen();
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
const int CURRENT_MONITOR = GetCurrentMonitor();
|
|
|
|
|
const int MONITOR_WIDTH = GetMonitorWidth(CURRENT_MONITOR);
|
|
|
|
|
const int MONITOR_HEIGHT = GetMonitorHeight(CURRENT_MONITOR);
|
2025-08-29 19:32:05 +03:00
|
|
|
|
|
|
|
|
SetTargetFPS(60);
|
|
|
|
|
|
|
|
|
|
Camera2D camera = {0}; // Game world camera
|
|
|
|
|
camera.zoom = 1.0f;
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
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},
|
|
|
|
|
(Vector2){BALL_BASE_SPEED, BALL_BASE_SPEED},
|
|
|
|
|
1.0f,
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
};
|
2025-08-29 19:32:05 +03:00
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
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};
|
|
|
|
|
|
|
|
|
|
const int BLOCK_ROWS = 7;
|
|
|
|
|
const int BLOCKS_PER_ROW = 15;
|
|
|
|
|
const int BLOCK_WIDTH = VIRTUAL_WIDTH / (float)BLOCKS_PER_ROW;
|
|
|
|
|
const int BLOCK_HEIGHT = 20;
|
|
|
|
|
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};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int BLOCK_COUNT = LEN(blocks);
|
2025-08-29 19:32:05 +03:00
|
|
|
|
|
|
|
|
while (!WindowShouldClose()) {
|
2025-08-30 01:22:02 +03:00
|
|
|
if (IsKeyDown(KEY_R)) {
|
|
|
|
|
ResetBall(&ball);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
// Keep before the cursor is disabled otherwise the paddle is stuck
|
|
|
|
|
MovePaddle(&player);
|
2025-08-29 19:32:05 +03:00
|
|
|
|
|
|
|
|
DisableCursor();
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
MoveBall(&ball, &player);
|
2025-08-29 19:32:05 +03:00
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
BeginTextureMode(RENDER_TEXTURE);
|
2025-08-29 19:32:05 +03:00
|
|
|
{
|
|
|
|
|
ClearBackground(BLACK);
|
2025-08-29 22:42:52 +03:00
|
|
|
|
|
|
|
|
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);
|
2025-08-29 22:42:52 +03:00
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
ball.velocity = Vector2Multiply(ball.velocity, (Vector2){1.0f, -1.0f});
|
|
|
|
|
block->color = BLACK;
|
|
|
|
|
block->isDestroyed = true;
|
|
|
|
|
player.score += 100;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DrawRectangleRec(block->shape, block->color);
|
|
|
|
|
}
|
2025-08-29 19:32:05 +03:00
|
|
|
}
|
|
|
|
|
EndTextureMode();
|
|
|
|
|
|
|
|
|
|
BeginDrawing();
|
|
|
|
|
{
|
|
|
|
|
ClearBackground(BLACK);
|
|
|
|
|
BeginMode2D(camera);
|
|
|
|
|
{
|
2025-08-29 22:42:52 +03:00
|
|
|
DrawTexturePro(RENDER_TEXTURE.texture, RENDER_TEXTURE_SRC, RENDER_TEXTURE_DST, RENDER_TEXTURE_POS, 0,
|
|
|
|
|
RAYWHITE);
|
2025-08-29 19:32:05 +03:00
|
|
|
}
|
|
|
|
|
EndMode2D();
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
const char *scoreText = TextFormat("Score %08i", player.score);
|
|
|
|
|
// float scoreWidth = MeasureText(scoreText, FONT_SIZE) + 10;
|
2025-08-29 19:32:05 +03:00
|
|
|
DrawText(scoreText, 10, 10, FONT_SIZE, RAYWHITE);
|
|
|
|
|
|
2025-08-29 22:42:52 +03:00
|
|
|
const char *livesText = TextFormat("Lives %02i", player.lives);
|
|
|
|
|
DrawText(livesText, 10, 50, FONT_SIZE, RAYWHITE);
|
|
|
|
|
|
2025-08-29 19:32:05 +03:00
|
|
|
DrawFPS(GetScreenWidth() - 80, 5);
|
|
|
|
|
// DrawText(TextFormat("%ix%i", monitorWidth, monitorHeight), 10, 40, FONT_SIZE, GOLD);
|
2025-08-29 22:42:52 +03:00
|
|
|
DrawText(TextFormat("Speed %.02f", ball.velocityMultiplier), 10, 90, FONT_SIZE, GOLD);
|
2025-08-29 19:32:05 +03:00
|
|
|
}
|
|
|
|
|
EndDrawing();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CloseWindow();
|
|
|
|
|
}
|