/******************************************************************************************* * * raylib - sample game: gorilas * * Sample game Marc Palau and Ramon Santamaria * * This game has been created using raylib v1.3 (www.raylib.com) * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) * * Copyright (c) 2015 Ramon Santamaria (@raysan5) * ********************************************************************************************/ #include "raylib.h" #include #include #include #include #if defined(PLATFORM_ANDROID) #include "android_native_app_glue.h" #endif #if defined(PLATFORM_WEB) #include #endif //---------------------------------------------------------------------------------- // Some Defines //---------------------------------------------------------------------------------- #define MAX_BUILDINGS 15 #define MAX_EXPLOSIONS 200 #define MAX_PLAYERS 2 #define BUILDING_RELATIVE_ERROR 30 // Building size random range % #define BUILDING_MIN_RELATIVE_HEIGHT 20 // Minimum height in % of the screenHeight #define BUILDING_MAX_RELATIVE_HEIGHT 60 // Maximum height in % of the screenHeight #define BUILDING_MIN_GRAYSCALE_COLOR 120 // Minimum gray color for the buildings #define BUILDING_MAX_GRAYSCALE_COLOR 200 // Maximum gray color for the buildings #define MIN_PLAYER_POSITION 5 // Minimum x position % #define MAX_PLAYER_POSITION 20 // Maximum x position % #define GRAVITY 9.81f #define DELTA_FPS 60 //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- typedef struct Player { Vector2 position; Vector2 size; Vector2 aimingPoint; int aimingAngle; int aimingPower; Vector2 previousPoint; int previousAngle; int previousPower; Vector2 impactPoint; bool isLeftTeam; // This player belongs to the left or to the right team bool isPlayer; // If is a player or an AI bool isAlive; } Player; typedef struct Building { Rectangle rectangle; Color color; } Building; typedef struct Explosion { Vector2 position; int radius; bool active; } Explosion; typedef struct Ball { Vector2 position; Vector2 speed; int radius; bool active; } Ball; //------------------------------------------------------------------------------------ // Global Variables Declaration //------------------------------------------------------------------------------------ static int screenWidth = 800; static int screenHeight = 450; static bool gameOver = false; static bool pause = false; static Player player[MAX_PLAYERS]; static Building building[MAX_BUILDINGS]; static Explosion explosion[MAX_EXPLOSIONS]; static Ball ball; static int playerTurn = 0; static bool ballOnAir = false; //------------------------------------------------------------------------------------ // Module Functions Declaration (local) //------------------------------------------------------------------------------------ static void InitGame(void); // Initialize game static void UpdateGame(void); // Update game (one frame) static void DrawGame(void); // Draw game (one frame) static void UnloadGame(void); // Unload game static void UpdateDrawFrame(void); // Update and Draw (one frame) // Additional module functions static void InitBuildings(void); static void InitPlayers(void); static bool UpdatePlayer(int playerTurn); static bool UpdateBall(int playerTurn); //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ #if defined(PLATFORM_ANDROID) void android_main(struct android_app *app) #else int main(void) #endif { // Initialization //--------------------------------------------------------- #if defined(PLATFORM_ANDROID) InitWindow(screenWidth, screenHeight, app); #else InitWindow(screenWidth, screenHeight, "sample game: gorilas"); #endif InitGame(); #if defined(PLATFORM_WEB) emscripten_set_main_loop(UpdateDrawFrame, 0, 1); #else SetTargetFPS(60); //-------------------------------------------------------------------------------------- // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { // Update and Draw //---------------------------------------------------------------------------------- UpdateDrawFrame(); //---------------------------------------------------------------------------------- } #endif // De-Initialization //-------------------------------------------------------------------------------------- UnloadGame(); // Unload loaded data (textures, sounds, models...) CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- #if !defined(PLATFORM_ANDROID) return 0; #endif } //------------------------------------------------------------------------------------ // Module Functions Definitions (local) //------------------------------------------------------------------------------------ // Initialize game variables void InitGame(void) { // Init shoot ball.radius = 10; ballOnAir = false; ball.active = false; InitBuildings(); InitPlayers(); // Init explosions for (int i = 0; i < MAX_EXPLOSIONS; i++) { explosion[i].position = (Vector2){ 0.0f, 0.0f }; explosion[i].radius = 30; explosion[i].active = false; } } // Update game (one frame) void UpdateGame(void) { if (!gameOver) { if (IsKeyPressed('P')) pause = !pause; if (!pause) { if (!ballOnAir) ballOnAir = UpdatePlayer(playerTurn); // If we are aiming else { if (UpdateBall(playerTurn)) // If collision { // Game over logic bool leftTeamAlive = false; bool rightTeamAlive = false; for (int i = 0; i < MAX_PLAYERS; i++) { if (player[i].isAlive) { if (player[i].isLeftTeam) leftTeamAlive = true; if (!player[i].isLeftTeam) rightTeamAlive = true; } } if (leftTeamAlive && rightTeamAlive) { ballOnAir = false; ball.active = false; playerTurn++; if (playerTurn == MAX_PLAYERS) playerTurn = 0; } else { gameOver = true; // if (leftTeamAlive) left team wins // if (rightTeamAlive) right team wins } } } } } else { if (IsKeyPressed(KEY_ENTER)) { InitGame(); gameOver = false; } } } // Draw game (one frame) void DrawGame(void) { BeginDrawing(); ClearBackground(RAYWHITE); if (!gameOver) { // Draw buildings for (int i = 0; i < MAX_BUILDINGS; i++) DrawRectangleRec(building[i].rectangle, building[i].color); // Draw explosions for (int i = 0; i < MAX_EXPLOSIONS; i++) { if (explosion[i].active) DrawCircle(explosion[i].position.x, explosion[i].position.y, explosion[i].radius, RAYWHITE); } // Draw players for (int i = 0; i < MAX_PLAYERS; i++) { if (player[i].isAlive) { if (player[i].isLeftTeam) DrawRectangle(player[i].position.x - player[i].size.x/2, player[i].position.y - player[i].size.y/2, player[i].size.x, player[i].size.y, BLUE); else DrawRectangle(player[i].position.x - player[i].size.x/2, player[i].position.y - player[i].size.y/2, player[i].size.x, player[i].size.y, RED); } } // Draw ball if (ball.active) DrawCircle(ball.position.x, ball.position.y, ball.radius, MAROON); // Draw the angle and the power of the aim, and the previous ones if (!ballOnAir) { // Draw shot information /* if (player[playerTurn].isLeftTeam) { DrawText(FormatText("Previous Point %i, %i", (int)player[playerTurn].previousPoint.x, (int)player[playerTurn].previousPoint.y), 20, 20, 20, DARKBLUE); DrawText(FormatText("Previous Angle %i", player[playerTurn].previousAngle), 20, 50, 20, DARKBLUE); DrawText(FormatText("Previous Power %i", player[playerTurn].previousPower), 20, 80, 20, DARKBLUE); DrawText(FormatText("Aiming Point %i, %i", (int)player[playerTurn].aimingPoint.x, (int)player[playerTurn].aimingPoint.y), 20, 110, 20, DARKBLUE); DrawText(FormatText("Aiming Angle %i", player[playerTurn].aimingAngle), 20, 140, 20, DARKBLUE); DrawText(FormatText("Aiming Power %i", player[playerTurn].aimingPower), 20, 170, 20, DARKBLUE); } else { DrawText(FormatText("Previous Point %i, %i", (int)player[playerTurn].previousPoint.x, (int)player[playerTurn].previousPoint.y), screenWidth*3/4, 20, 20, DARKBLUE); DrawText(FormatText("Previous Angle %i", player[playerTurn].previousAngle), screenWidth*3/4, 50, 20, DARKBLUE); DrawText(FormatText("Previous Power %i", player[playerTurn].previousPower), screenWidth*3/4, 80, 20, DARKBLUE); DrawText(FormatText("Aiming Point %i, %i", (int)player[playerTurn].aimingPoint.x, (int)player[playerTurn].aimingPoint.y), screenWidth*3/4, 110, 20, DARKBLUE); DrawText(FormatText("Aiming Angle %i", player[playerTurn].aimingAngle), screenWidth*3/4, 140, 20, DARKBLUE); DrawText(FormatText("Aiming Power %i", player[playerTurn].aimingPower), screenWidth*3/4, 170, 20, DARKBLUE); } */ // Draw aim if (player[playerTurn].isLeftTeam) { // Previous aiming DrawTriangle((Vector2){ player[playerTurn].position.x - player[playerTurn].size.x/4, player[playerTurn].position.y - player[playerTurn].size.y/4 }, (Vector2){ player[playerTurn].position.x + player[playerTurn].size.x/4, player[playerTurn].position.y + player[playerTurn].size.y/4 }, player[playerTurn].previousPoint, GRAY); // Actual aiming DrawTriangle((Vector2){ player[playerTurn].position.x - player[playerTurn].size.x/4, player[playerTurn].position.y - player[playerTurn].size.y/4 }, (Vector2){ player[playerTurn].position.x + player[playerTurn].size.x/4, player[playerTurn].position.y + player[playerTurn].size.y/4 }, player[playerTurn].aimingPoint, DARKBLUE); } else { // Previous aiming DrawTriangle((Vector2){ player[playerTurn].position.x - player[playerTurn].size.x/4, player[playerTurn].position.y + player[playerTurn].size.y/4 }, (Vector2){ player[playerTurn].position.x + player[playerTurn].size.x/4, player[playerTurn].position.y - player[playerTurn].size.y/4 }, player[playerTurn].previousPoint, GRAY); // Actual aiming DrawTriangle((Vector2){ player[playerTurn].position.x - player[playerTurn].size.x/4, player[playerTurn].position.y + player[playerTurn].size.y/4 }, (Vector2){ player[playerTurn].position.x + player[playerTurn].size.x/4, player[playerTurn].position.y - player[playerTurn].size.y/4 }, player[playerTurn].aimingPoint, MAROON); } } if (pause) DrawText("GAME PAUSED", screenWidth/2 - MeasureText("GAME PAUSED", 40)/2, screenHeight/2 - 40, 40, GRAY); } else DrawText("PRESS [ENTER] TO PLAY AGAIN", GetScreenWidth()/2 - MeasureText("PRESS [ENTER] TO PLAY AGAIN", 20)/2, GetScreenHeight()/2 - 50, 20, GRAY); EndDrawing(); } // Unload game variables void UnloadGame(void) { // TODO: Unload all dynamic loaded data (textures, sounds, models...) } // Update and Draw (one frame) void UpdateDrawFrame(void) { UpdateGame(); DrawGame(); } //-------------------------------------------------------------------------------------- // Additional module functions //-------------------------------------------------------------------------------------- static void InitBuildings(void) { // Horizontal generation int currentWidth = 0; // We make sure the absolute error randomly generated for each building, has as a minimum value the screenWidth. // This way all the screen will be filled with buildings. Each building will have a different, random width. float relativeWidth = 100/(100 - BUILDING_RELATIVE_ERROR); float buildingWidthMean = (screenWidth*relativeWidth/MAX_BUILDINGS) + 1; // We add one to make sure we will cover the whole screen. // Vertical generation int currentHeighth = 0; int grayLevel; // Creation for (int i = 0; i < MAX_BUILDINGS; i++) { // Horizontal building[i].rectangle.x = currentWidth; building[i].rectangle.width = GetRandomValue(buildingWidthMean*(100 - BUILDING_RELATIVE_ERROR/2)/100 + 1, buildingWidthMean*(100 + BUILDING_RELATIVE_ERROR)/100); currentWidth += building[i].rectangle.width; // Vertical currentHeighth = GetRandomValue(BUILDING_MIN_RELATIVE_HEIGHT, BUILDING_MAX_RELATIVE_HEIGHT); building[i].rectangle.y = screenHeight - (screenHeight*currentHeighth/100); building[i].rectangle.height = screenHeight*currentHeighth/100 + 1; // Color grayLevel = GetRandomValue(BUILDING_MIN_GRAYSCALE_COLOR, BUILDING_MAX_GRAYSCALE_COLOR); building[i].color = (Color){ grayLevel, grayLevel, grayLevel, 255 }; } } static void InitPlayers(void) { for (int i = 0; i < MAX_PLAYERS; i++) { player[i].isAlive = true; // Decide the team of this player if (i % 2 == 0) player[i].isLeftTeam = true; else player[i].isLeftTeam = false; // Now there is no AI player[i].isPlayer = true; // Set size, by default by now player[i].size = (Vector2){ 40, 40 }; // Set position if (player[i].isLeftTeam) player[i].position.x = GetRandomValue(screenWidth*MIN_PLAYER_POSITION/100, screenWidth*MAX_PLAYER_POSITION/100); else player[i].position.x = screenWidth - GetRandomValue(screenWidth*MIN_PLAYER_POSITION/100, screenWidth*MAX_PLAYER_POSITION/100); for (int j = 0; j < MAX_BUILDINGS; j++) { if (building[j].rectangle.x > player[i].position.x) { // Set the player in the center of the building player[i].position.x = building[j-1].rectangle.x + building[j-1].rectangle.width/2; // Set the player at the top of the building player[i].position.y = building[j-1].rectangle.y - player[i].size.y/2; break; } } // Set statistics to 0 player[i].aimingPoint = player[i].position; player[i].previousAngle = 0; player[i].previousPower = 0; player[i].previousPoint = player[i].position; player[i].aimingAngle = 0; player[i].aimingPower = 0; player[i].impactPoint = (Vector2){ -100, -100 }; } } static bool UpdatePlayer(int playerTurn) { // If we are aiming at the firing quadrant, we calculate the angle if (GetMousePosition().y <= player[playerTurn].position.y) { // Left team if (player[playerTurn].isLeftTeam && GetMousePosition().x >= player[playerTurn].position.x) { // Distance (calculating the fire power) player[playerTurn].aimingPower = sqrt(pow(player[playerTurn].position.x - GetMousePosition().x, 2) + pow(player[playerTurn].position.y - GetMousePosition().y, 2)); // Calculates the angle via arcsin player[playerTurn].aimingAngle = asin((player[playerTurn].position.y - GetMousePosition().y)/player[playerTurn].aimingPower)*RAD2DEG; // Point of the screen we are aiming at player[playerTurn].aimingPoint = GetMousePosition(); // Ball fired if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { player[playerTurn].previousPoint = player[playerTurn].aimingPoint; player[playerTurn].previousPower = player[playerTurn].aimingPower; player[playerTurn].previousAngle = player[playerTurn].aimingAngle; ball.position = player[playerTurn].position; return true; } } // Right team else if (!player[playerTurn].isLeftTeam && GetMousePosition().x <= player[playerTurn].position.x) { // Distance (calculating the fire power) player[playerTurn].aimingPower = sqrt(pow(player[playerTurn].position.x - GetMousePosition().x, 2) + pow(player[playerTurn].position.y - GetMousePosition().y, 2)); // Calculates the angle via arcsin player[playerTurn].aimingAngle = asin((player[playerTurn].position.y - GetMousePosition().y)/player[playerTurn].aimingPower)*RAD2DEG; // Point of the screen we are aiming at player[playerTurn].aimingPoint = GetMousePosition(); // Ball fired if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { player[playerTurn].previousPoint = player[playerTurn].aimingPoint; player[playerTurn].previousPower = player[playerTurn].aimingPower; player[playerTurn].previousAngle = player[playerTurn].aimingAngle; ball.position = player[playerTurn].position; return true; } } else { player[playerTurn].aimingPoint = player[playerTurn].position; player[playerTurn].aimingPower = 0; player[playerTurn].aimingAngle = 0; } } else { player[playerTurn].aimingPoint = player[playerTurn].position; player[playerTurn].aimingPower = 0; player[playerTurn].aimingAngle = 0; } return false; } static bool UpdateBall(int playerTurn) { static int explosionNumber = 0; // Activate ball if (!ball.active) { if (player[playerTurn].isLeftTeam) { ball.speed.x = cos(player[playerTurn].previousAngle*DEG2RAD)*player[playerTurn].previousPower*3/DELTA_FPS; ball.speed.y = -sin(player[playerTurn].previousAngle*DEG2RAD)*player[playerTurn].previousPower*3/DELTA_FPS; ball.active = true; } else { ball.speed.x = -cos(player[playerTurn].previousAngle*DEG2RAD)*player[playerTurn].previousPower*3/DELTA_FPS; ball.speed.y = -sin(player[playerTurn].previousAngle*DEG2RAD)*player[playerTurn].previousPower*3/DELTA_FPS; ball.active = true; } } ball.position.x += ball.speed.x; ball.position.y += ball.speed.y; ball.speed.y += GRAVITY/DELTA_FPS; // Collision if (ball.position.x + ball.radius < 0) return true; else if (ball.position.x - ball.radius > screenWidth) return true; else { // Player collision for (int i = 0; i < MAX_PLAYERS; i++) { if (CheckCollisionCircleRec(ball.position, ball.radius, (Rectangle){ player[i].position.x - player[i].size.x/2, player[i].position.y - player[i].size.y/2, player[i].size.x, player[i].size.y })) { // We can't hit ourselves if (i == playerTurn) return false; else { // We set the impact point player[playerTurn].impactPoint.x = ball.position.x; player[playerTurn].impactPoint.y = ball.position.y + ball.radius; // We destroy the player player[i].isAlive = false; return true; } } } // Building collision // NOTE: We only check building collision if we are not inside an explosion for (int i = 0; i < MAX_BUILDINGS; i++) { if (CheckCollisionCircles(ball.position, ball.radius, explosion[i].position, explosion[i].radius - ball.radius)) { return false; } } for (int i = 0; i < MAX_BUILDINGS; i++) { if (CheckCollisionCircleRec(ball.position, ball.radius, building[i].rectangle)) { // We set the impact point player[playerTurn].impactPoint.x = ball.position.x; player[playerTurn].impactPoint.y = ball.position.y + ball.radius; // We create an explosion explosion[explosionNumber].position = player[playerTurn].impactPoint; explosion[explosionNumber].active = true; explosionNumber++; return true; } } } return false; }