#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; }