blobbyball/main.c
2025-03-08 10:31:01 +02:00

380 lines
9.2 KiB
C

#include "math.h"
#include "raylib/include/raylib.h"
#define FPS 60
#define FRAME_SPEED 8
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define SPRITE_SIZE 64
#define BOTTOM_POSITION ((float)SCREEN_HEIGHT - (float)SPRITE_SIZE)
/* Player */
typedef struct {
Texture2D texture;
Rectangle frame;
int counter;
int speed;
int current;
int total_frames;
} Graphics;
void updateGraphics(Graphics* graphics) {
// update animation
graphics->counter++;
if (graphics->counter >= (FPS/graphics->speed)) {
graphics->counter = 0;
graphics->current++;
if (graphics->current >= graphics->total_frames) graphics->current = 0;
graphics->frame.y = (float)graphics->current * (float)graphics->texture.height/graphics->total_frames;
}
}
typedef struct {
int up;
int left;
int right;
} Keys;
typedef struct {
Vector2 position;
Vector2 acceleration;
Graphics graphics;
Keys keys;
} Player;
Player newPlayer(const char* path, Keys keys, float position) {
Texture2D texture = LoadTexture(path);
Graphics graphics = {
.texture = texture,
.frame = { 0.0f, 0.0f, (float)SPRITE_SIZE, (float)SPRITE_SIZE },
.counter = 0,
.speed = 8,
.current = 0,
.total_frames = 20,
};
Player player = {
.position = { position - (float)SPRITE_SIZE / 2, BOTTOM_POSITION },
.acceleration = { (float)0, (float)0 },
.graphics = graphics,
.keys = keys
};
return player;
}
void updatePlayer(Player* player, Sound* jump) {
// update animation
updateGraphics(&(player->graphics));
// handle inputs X
if (IsKeyDown(player->keys.right)) { player->acceleration.x += 0.25f; }
if (IsKeyDown(player->keys.left)) { player->acceleration.x -= 0.25f; }
if (!IsKeyDown(player->keys.left) && !IsKeyDown(player->keys.right)) {
if (player->acceleration.x > 0) {
player->acceleration.x -= 0.2f;
}
else if (player->acceleration.x < 0) {
player->acceleration.x += 0.2f;
}
}
// handle inputs Y
if (IsKeyDown(player->keys.up) && player->position.y == BOTTOM_POSITION) {
player->acceleration.y -= 7.0f;
PlaySound(*jump);
}
if (player->position.y < BOTTOM_POSITION) {
player->acceleration.y += 0.28f;
}
// fix acceleration
if (player->acceleration.x > 6.0f) {
player->acceleration.x = 6.0f;
}
else if (player->acceleration.x < -6.0f) {
player->acceleration.x = -6.0f;
}
else if (-0.15f < player->acceleration.x && player->acceleration.x < 0.15f) {
player->acceleration.x = 0.0f;
}
// update position
player->position.y += player->acceleration.y;
if (player->position.y > BOTTOM_POSITION) {
player->position.y = BOTTOM_POSITION;
player->acceleration.y = 0.0f;
}
player->position.x += player->acceleration.x;
if (player->position.x > SCREEN_WIDTH - SPRITE_SIZE) {
player->position.x = SCREEN_WIDTH - SPRITE_SIZE;
player->acceleration.x = 0.0f;
}
else if (player->position.x < 0) {
player->position.x = 0;
player->acceleration.x = 0.0f;
}
}
Rectangle getPlayerBox(const Player* player) {
Rectangle rectangle = {
player->position.x,
player->position.y,
SPRITE_SIZE,
SPRITE_SIZE,
};
return rectangle;
}
/* Ball */
typedef struct {
Vector2 position;
Vector2 acceleration;
Vector2 origin;
float rotation;
Texture2D texture;
Rectangle frame;
} Ball;
Ball newBall(const char* path) {
Texture2D texture = LoadTexture(path);
Ball ball = {
.position = { (float)SCREEN_WIDTH/2 - (float)SPRITE_SIZE / 2, SCREEN_HEIGHT/2 },
.acceleration = { (float)-3.0f, (float)-8.0f },
.origin = { (float)SPRITE_SIZE / 2, (float)SPRITE_SIZE / 2 },
.rotation = (float)0.0f,
.texture = texture,
.frame = { 0.0f, 0.0f, (float)SPRITE_SIZE, (float)SPRITE_SIZE },
};
return ball;
}
Rectangle getBallBox(const Ball* ball) {
Rectangle rectangle = {
ball->position.x,
ball->position.y,
SPRITE_SIZE,
SPRITE_SIZE,
};
return rectangle;
}
float min(float a, float b) {
return a < b ? a : b;
}
typedef enum {
Air,
Dropped,
Saved,
} BallStatus;
BallStatus updateBall(Ball* ball, const Player* player1, const Player* player2) {
BallStatus status = Air;
// collision
bool collision1 = CheckCollisionRecs(getBallBox(ball), getPlayerBox(player1));
bool collision2 = CheckCollisionRecs(getBallBox(ball), getPlayerBox(player2));
if (collision1 && collision2) {
status = Saved;
ball->acceleration.y = -8.0f + min(min(0, player1->acceleration.y), player2->acceleration.y);
ball->acceleration.x = 1.3f + player1->acceleration.x;
if (fabsf(player1->acceleration.x) > fabsf(player1->acceleration.x)) {
ball->acceleration.x = 1.3f + player1->acceleration.x;
} else {
ball->acceleration.x = 1.2f + player2->acceleration.x;
}
}
else if (collision1) {
status = Saved;
ball->acceleration.y = -8.0f + min(0, player1->acceleration.y);
ball->acceleration.x += 1.0f * player1->acceleration.x;
}
else if (collision2) {
status = Saved;
ball->acceleration.y = -7.0f + min(0, player2->acceleration.y);
ball->acceleration.x += 1.0f * player2->acceleration.x;
}
// physics
// y
else if (ball->position.y >= BOTTOM_POSITION) {
status = Dropped;
ball->position.y = BOTTOM_POSITION;
if (-0.5f < ball->acceleration.y && ball->acceleration.y < 0.5f) {
ball->acceleration.y = 0;
}
else {
ball->acceleration.y *= -0.9f;
}
}
if (ball->position.y < BOTTOM_POSITION) {
ball->acceleration.y += 0.28f;
}
ball->position.y += ball->acceleration.y;
// x
if (ball->position.x > SCREEN_WIDTH - SPRITE_SIZE) {
ball->position.x = SCREEN_WIDTH - SPRITE_SIZE;
ball->acceleration.x *= -0.9f;
}
else if (ball->position.x < 0) {
ball->position.x = 0;
ball->acceleration.x *= -0.9f;
}
if (ball->acceleration.x < -10.0f) {
ball->acceleration.x = -10.0f;
}
if (ball->acceleration.x > 10.0f) {
ball->acceleration.x = 10.0f;
}
ball->position.x += ball->acceleration.x;
ball->rotation += ball->acceleration.x;
return status;
}
typedef struct {
Graphics graphics;
Vector2 position;
} Background;
Background newBackground(const char* path) {
Texture2D texture = LoadTexture(path);
Graphics graphics = {
.texture = texture,
.frame = { 0.0f, 0.0f, (float)SCREEN_WIDTH, (float)SCREEN_HEIGHT },
.counter = 0,
.speed = 4,
.current = 0,
.total_frames = 24,
};
Background background = {
.graphics = graphics,
.position = { 0, 0 },
};
return background;
}
void updateBackground(Background* background) {
updateGraphics(&background->graphics);
}
/* Main */
int main(void) {
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Volleyball");
InitAudioDevice();
// sounds
Music music = LoadMusicStream("assets/blobbybeach.mp3");
Sound jump = LoadSound("assets/jump.wav");
Sound floor = LoadSound("assets/floor.wav");
Sound bop = LoadSound("assets/bop.wav");
Background background = newBackground("assets/background.png");
Keys pink_keys = { .up = KEY_W, .left = KEY_A, .right = KEY_D };
Player pink_player = newPlayer("assets/pink_player.png", pink_keys, (float)SCREEN_WIDTH/4);
Keys blue_keys = { .up = KEY_UP, .left = KEY_LEFT, .right = KEY_RIGHT };
Player blue_player = newPlayer("assets/blue_player.png", blue_keys, (float)SCREEN_WIDTH/4 * 3);
Ball ball = newBall("assets/ball.png");
unsigned current_score = 0;
unsigned best_record = 0;
unsigned cooloff = 0;
bool pause_music = false;
PlayMusicStream(music);
SetTargetFPS(FPS);
// Game loop
while (!WindowShouldClose())
{
UpdateMusicStream(music);
// Restart music playing (stop and play)
if (IsKeyPressed(KEY_M))
{
if (pause_music) {
PlayMusicStream(music);
pause_music = false;
}
else {
StopMusicStream(music);
pause_music = true;
}
}
// Update
if (cooloff > 0) { cooloff--; }
updateBackground(&background);
updatePlayer(&pink_player, &jump);
updatePlayer(&blue_player, &jump);
BallStatus ball_status = updateBall(&ball, &pink_player, &blue_player);
switch (ball_status) {
case Saved:
if (cooloff == 0) {
cooloff = 10;
current_score++;
if (current_score > best_record) {
best_record = current_score;
}
PlaySound(bop);
}
break;
case Dropped:
current_score = 0;
PlaySound(floor);
break;
default:
break;
}
// Draw
BeginDrawing();
ClearBackground(RAYWHITE);
DrawTextureRec(background.graphics.texture, background.graphics.frame, background.position, WHITE);
DrawTextureRec(pink_player.graphics.texture, pink_player.graphics.frame, pink_player.position, WHITE);
DrawTextureRec(blue_player.graphics.texture, blue_player.graphics.frame, blue_player.position, WHITE);
//DrawTextureRec(ball.texture, ball.frame, ball.position, WHITE);
Rectangle dest = { ball.position.x + SPRITE_SIZE/2, ball.position.y + SPRITE_SIZE/2, ball.origin.x * 2, ball.origin.y * 2 };
DrawTexturePro(ball.texture, ball.frame, dest, ball.origin, ball.rotation, WHITE);
DrawRectangle(0, 0, 240, 90, (Color){ 0, 10, 60, 120 });
if (current_score == best_record) {
DrawText(TextFormat("Best Record: %i", best_record), 10, 10, 24 + cooloff / 2, PINK);
DrawText(TextFormat("Score: %i", current_score), 10, 50, 24 + cooloff / 2, PINK);
} else {
DrawText(TextFormat("Best Record: %i", best_record), 10, 10, 24, BLACK);
DrawText(TextFormat("Score: %i", current_score), 10, 50, 24 + cooloff / 2, WHITE);
}
EndDrawing();
}
// De-Initialization
UnloadTexture(pink_player.graphics.texture);
UnloadTexture(blue_player.graphics.texture);
UnloadTexture(ball.texture);
CloseWindow();
return 0;
}