533 lines
20 KiB
C
533 lines
20 KiB
C
/*******************************************************************************************
|
|
*
|
|
* raylib - sample game: missile commander
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <math.h>
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
#include <emscripten/emscripten.h>
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Some Defines
|
|
//----------------------------------------------------------------------------------
|
|
#define MAX_MISSILES 100
|
|
#define MAX_INTERCEPTORS 30
|
|
#define MAX_EXPLOSIONS 100
|
|
#define LAUNCHERS_AMOUNT 3 // Not a variable, should not be changed
|
|
#define BUILDINGS_AMOUNT 6 // Not a variable, should not be changed
|
|
|
|
#define LAUNCHER_SIZE 80
|
|
#define BUILDING_SIZE 60
|
|
#define EXPLOSION_RADIUS 40
|
|
|
|
#define MISSILE_SPEED 1
|
|
#define MISSILE_LAUNCH_FRAMES 80
|
|
#define INTERCEPTOR_SPEED 10
|
|
#define EXPLOSION_INCREASE_TIME 90 // In frames
|
|
#define EXPLOSION_TOTAL_TIME 210 // In frames
|
|
|
|
#define EXPLOSION_COLOR (Color){ 125, 125, 125, 125 }
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Types and Structures Definition
|
|
//----------------------------------------------------------------------------------
|
|
typedef struct Missile {
|
|
Vector2 origin;
|
|
Vector2 position;
|
|
Vector2 objective;
|
|
Vector2 speed;
|
|
|
|
bool active;
|
|
} Missile;
|
|
|
|
typedef struct Interceptor {
|
|
Vector2 origin;
|
|
Vector2 position;
|
|
Vector2 objective;
|
|
Vector2 speed;
|
|
|
|
bool active;
|
|
} Interceptor;
|
|
|
|
typedef struct Explosion {
|
|
Vector2 position;
|
|
float radiusMultiplier;
|
|
int frame;
|
|
bool active;
|
|
} Explosion;
|
|
|
|
typedef struct Launcher {
|
|
Vector2 position;
|
|
bool active;
|
|
} Launcher;
|
|
|
|
typedef struct Building {
|
|
Vector2 position;
|
|
bool active;
|
|
} Building;
|
|
|
|
//------------------------------------------------------------------------------------
|
|
// Global Variables Declaration
|
|
//------------------------------------------------------------------------------------
|
|
static int screenWidth = 800;
|
|
static int screenHeight = 450;
|
|
|
|
static int framesCounter = 0;
|
|
static bool gameOver = false;
|
|
static bool pause = false;
|
|
static int score = 0;
|
|
|
|
static Missile missile[MAX_MISSILES] = { 0 };
|
|
static Interceptor interceptor[MAX_INTERCEPTORS] = { 0 };
|
|
static Explosion explosion[MAX_EXPLOSIONS] = { 0 };
|
|
static Launcher launcher[LAUNCHERS_AMOUNT] = { 0 };
|
|
static Building building[BUILDINGS_AMOUNT] = { 0 };
|
|
static int explosionIndex = 0;
|
|
|
|
//------------------------------------------------------------------------------------
|
|
// 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 UpdateOutgoingFire();
|
|
static void UpdateIncomingFire();
|
|
|
|
//------------------------------------------------------------------------------------
|
|
// Program main entry point
|
|
//------------------------------------------------------------------------------------
|
|
int main(void)
|
|
{
|
|
// Initialization (Note windowTitle is unused on Android)
|
|
//---------------------------------------------------------
|
|
InitWindow(screenWidth, screenHeight, "sample game: missile commander");
|
|
|
|
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
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
return 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
// Game Module Functions Definition
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
// Initialize game variables
|
|
void InitGame(void)
|
|
{
|
|
// Initialize missiles
|
|
for (int i = 0; i < MAX_MISSILES; i++)
|
|
{
|
|
missile[i].origin = (Vector2){ 0, 0 };
|
|
missile[i].speed = (Vector2){ 0, 0 };
|
|
missile[i].position = (Vector2){ 0, 0 };
|
|
|
|
missile[i].active = false;
|
|
}
|
|
|
|
// Initialize interceptors
|
|
for (int i = 0; i < MAX_INTERCEPTORS; i++)
|
|
{
|
|
interceptor[i].origin = (Vector2){ 0, 0 };
|
|
interceptor[i].speed = (Vector2){ 0, 0 };
|
|
interceptor[i].position = (Vector2){ 0, 0 };
|
|
|
|
interceptor[i].active = false;
|
|
}
|
|
|
|
// Initialize explosions
|
|
for (int i = 0; i < MAX_EXPLOSIONS; i++)
|
|
{
|
|
explosion[i].position = (Vector2){ 0, 0 };
|
|
explosion[i].frame = 0;
|
|
explosion[i].active = false;
|
|
}
|
|
|
|
// Initialize buildings and launchers
|
|
int sparcing = screenWidth/(LAUNCHERS_AMOUNT + BUILDINGS_AMOUNT + 1);
|
|
|
|
// Buildings and launchers placing
|
|
launcher[0].position = (Vector2){ 1*sparcing, screenHeight - LAUNCHER_SIZE/2 };
|
|
building[0].position = (Vector2){ 2*sparcing, screenHeight - BUILDING_SIZE/2 };
|
|
building[1].position = (Vector2){ 3*sparcing, screenHeight - BUILDING_SIZE/2 };
|
|
building[2].position = (Vector2){ 4*sparcing, screenHeight - BUILDING_SIZE/2 };
|
|
launcher[1].position = (Vector2){ 5*sparcing, screenHeight - LAUNCHER_SIZE/2 };
|
|
building[3].position = (Vector2){ 6*sparcing, screenHeight - BUILDING_SIZE/2 };
|
|
building[4].position = (Vector2){ 7*sparcing, screenHeight - BUILDING_SIZE/2 };
|
|
building[5].position = (Vector2){ 8*sparcing, screenHeight - BUILDING_SIZE/2 };
|
|
launcher[2].position = (Vector2){ 9*sparcing, screenHeight - LAUNCHER_SIZE/2 };
|
|
|
|
// Buildings and launchers activation
|
|
for (int i = 0; i < LAUNCHERS_AMOUNT; i++) launcher[i].active = true;
|
|
for (int i = 0; i < BUILDINGS_AMOUNT; i++) building[i].active = true;
|
|
|
|
// Initialize game variables
|
|
score = 0;
|
|
}
|
|
|
|
// Update game (one frame)
|
|
void UpdateGame(void)
|
|
{
|
|
if (!gameOver)
|
|
{
|
|
if (IsKeyPressed('P')) pause = !pause;
|
|
|
|
if (!pause)
|
|
{
|
|
framesCounter++;
|
|
|
|
static
|
|
float distance;
|
|
|
|
// Interceptors update
|
|
for (int i = 0; i < MAX_INTERCEPTORS; i++)
|
|
{
|
|
if (interceptor[i].active)
|
|
{
|
|
// Update position
|
|
interceptor[i].position.x += interceptor[i].speed.x;
|
|
interceptor[i].position.y += interceptor[i].speed.y;
|
|
|
|
// Distance to objective
|
|
distance = sqrt( pow(interceptor[i].position.x - interceptor[i].objective.x, 2) +
|
|
pow(interceptor[i].position.y - interceptor[i].objective.y, 2));
|
|
|
|
if (distance < INTERCEPTOR_SPEED)
|
|
{
|
|
// Interceptor dissapears
|
|
interceptor[i].active = false;
|
|
|
|
// Explosion
|
|
explosion[explosionIndex].position = interceptor[i].position;
|
|
explosion[explosionIndex].active = true;
|
|
explosion[explosionIndex].frame = 0;
|
|
explosionIndex++;
|
|
if (explosionIndex == MAX_EXPLOSIONS) explosionIndex = 0;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Missiles update
|
|
for (int i = 0; i < MAX_MISSILES; i++)
|
|
{
|
|
if (missile[i].active)
|
|
{
|
|
// Update position
|
|
missile[i].position.x += missile[i].speed.x;
|
|
missile[i].position.y += missile[i].speed.y;
|
|
|
|
// Collision and missile out of bounds
|
|
if (missile[i].position.y > screenHeight) missile[i].active = false;
|
|
else
|
|
{
|
|
// CHeck collision with launchers
|
|
for (int j = 0; j < LAUNCHERS_AMOUNT; j++)
|
|
{
|
|
if (launcher[j].active)
|
|
{
|
|
if (CheckCollisionPointRec(missile[i].position, (Rectangle){ launcher[j].position.x - LAUNCHER_SIZE/2, launcher[j].position.y - LAUNCHER_SIZE/2,
|
|
LAUNCHER_SIZE, LAUNCHER_SIZE }))
|
|
{
|
|
// Missile dissapears
|
|
missile[i].active = false;
|
|
|
|
// Explosion and destroy building
|
|
launcher[j].active = false;
|
|
|
|
explosion[explosionIndex].position = missile[i].position;
|
|
explosion[explosionIndex].active = true;
|
|
explosion[explosionIndex].frame = 0;
|
|
explosionIndex++;
|
|
if (explosionIndex == MAX_EXPLOSIONS) explosionIndex = 0;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// CHeck collision with buildings
|
|
for (int j = 0; j < BUILDINGS_AMOUNT; j++)
|
|
{
|
|
if (building[j].active)
|
|
{
|
|
if (CheckCollisionPointRec(missile[i].position, (Rectangle){ building[j].position.x - BUILDING_SIZE/2, building[j].position.y - BUILDING_SIZE/2,
|
|
BUILDING_SIZE, BUILDING_SIZE }))
|
|
{
|
|
// Missile dissapears
|
|
missile[i].active = false;
|
|
|
|
// Explosion and destroy building
|
|
building[j].active = false;
|
|
|
|
explosion[explosionIndex].position = missile[i].position;
|
|
explosion[explosionIndex].active = true;
|
|
explosion[explosionIndex].frame = 0;
|
|
explosionIndex++;
|
|
if (explosionIndex == MAX_EXPLOSIONS) explosionIndex = 0;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// CHeck collision with explosions
|
|
for (int j = 0; j < MAX_EXPLOSIONS; j++)
|
|
{
|
|
if (explosion[j].active)
|
|
{
|
|
if (CheckCollisionPointCircle(missile[i].position, explosion[j].position, EXPLOSION_RADIUS*explosion[j].radiusMultiplier))
|
|
{
|
|
// Missile dissapears and we earn 100 points
|
|
missile[i].active = false;
|
|
score += 100;
|
|
|
|
explosion[explosionIndex].position = missile[i].position;
|
|
explosion[explosionIndex].active = true;
|
|
explosion[explosionIndex].frame = 0;
|
|
explosionIndex++;
|
|
if (explosionIndex == MAX_EXPLOSIONS) explosionIndex = 0;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Explosions update
|
|
for (int i = 0; i < MAX_EXPLOSIONS; i++)
|
|
{
|
|
if (explosion[i].active)
|
|
{
|
|
explosion[i].frame++;
|
|
|
|
if (explosion[i].frame <= EXPLOSION_INCREASE_TIME) explosion[i].radiusMultiplier = explosion[i].frame/(float)EXPLOSION_INCREASE_TIME;
|
|
else if (explosion[i].frame <= EXPLOSION_TOTAL_TIME) explosion[i].radiusMultiplier = 1 - (explosion[i].frame - (float)EXPLOSION_INCREASE_TIME)/(float)EXPLOSION_TOTAL_TIME;
|
|
else
|
|
{
|
|
explosion[i].frame = 0;
|
|
explosion[i].active = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fire logic
|
|
UpdateOutgoingFire();
|
|
UpdateIncomingFire();
|
|
|
|
// Game over logic
|
|
int checker = 0;
|
|
|
|
for (int i = 0; i < LAUNCHERS_AMOUNT; i++)
|
|
{
|
|
if (!launcher[i].active) checker++;
|
|
if (checker == LAUNCHERS_AMOUNT) gameOver = true;
|
|
}
|
|
|
|
checker = 0;
|
|
for (int i = 0; i < BUILDINGS_AMOUNT; i++)
|
|
{
|
|
if (!building[i].active) checker++;
|
|
if (checker == BUILDINGS_AMOUNT) gameOver = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (IsKeyPressed(KEY_ENTER))
|
|
{
|
|
InitGame();
|
|
gameOver = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw game (one frame)
|
|
void DrawGame(void)
|
|
{
|
|
BeginDrawing();
|
|
|
|
ClearBackground(RAYWHITE);
|
|
|
|
if (!gameOver)
|
|
{
|
|
// Draw missiles
|
|
for (int i = 0; i < MAX_MISSILES; i++)
|
|
{
|
|
if (missile[i].active)
|
|
{
|
|
DrawLine(missile[i].origin.x, missile[i].origin.y, missile[i].position.x, missile[i].position.y, RED);
|
|
|
|
if (framesCounter % 16 < 8) DrawCircle(missile[i].position.x, missile[i].position.y, 3, YELLOW);
|
|
}
|
|
}
|
|
|
|
// Draw interceptors
|
|
for (int i = 0; i < MAX_INTERCEPTORS; i++)
|
|
{
|
|
if (interceptor[i].active)
|
|
{
|
|
DrawLine(interceptor[i].origin.x, interceptor[i].origin.y, interceptor[i].position.x, interceptor[i].position.y, GREEN);
|
|
|
|
if (framesCounter % 16 < 8) DrawCircle(interceptor[i].position.x, interceptor[i].position.y, 3, BLUE);
|
|
}
|
|
}
|
|
|
|
// Draw explosions
|
|
for (int i = 0; i < MAX_EXPLOSIONS; i++)
|
|
{
|
|
if (explosion[i].active) DrawCircle(explosion[i].position.x, explosion[i].position.y, EXPLOSION_RADIUS*explosion[i].radiusMultiplier, EXPLOSION_COLOR);
|
|
}
|
|
|
|
// Draw buildings and launchers
|
|
for (int i = 0; i < LAUNCHERS_AMOUNT; i++)
|
|
{
|
|
if (launcher[i].active) DrawRectangle(launcher[i].position.x - LAUNCHER_SIZE/2, launcher[i].position.y - LAUNCHER_SIZE/2, LAUNCHER_SIZE, LAUNCHER_SIZE, GRAY);
|
|
}
|
|
|
|
for (int i = 0; i < BUILDINGS_AMOUNT; i++)
|
|
{
|
|
if (building[i].active) DrawRectangle(building[i].position.x - BUILDING_SIZE/2, building[i].position.y - BUILDING_SIZE/2, BUILDING_SIZE, BUILDING_SIZE, LIGHTGRAY);
|
|
}
|
|
|
|
// Draw score
|
|
DrawText(TextFormat("SCORE %4i", score), 20, 20, 40, LIGHTGRAY);
|
|
|
|
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 UpdateOutgoingFire()
|
|
{
|
|
static int interceptorNumber = 0;
|
|
int launcherShooting = 0;
|
|
|
|
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) launcherShooting = 1;
|
|
if (IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) launcherShooting = 2;
|
|
if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) launcherShooting = 3;
|
|
|
|
if (launcherShooting > 0 && launcher[launcherShooting - 1].active)
|
|
{
|
|
float module;
|
|
float sideX;
|
|
float sideY;
|
|
|
|
// Activate the interceptor
|
|
interceptor[interceptorNumber].active = true;
|
|
|
|
// Assign start position
|
|
interceptor[interceptorNumber].origin = launcher[launcherShooting - 1].position;
|
|
interceptor[interceptorNumber].position = interceptor[interceptorNumber].origin;
|
|
interceptor[interceptorNumber].objective = GetMousePosition();
|
|
|
|
// Calculate speed
|
|
module = sqrt( pow(interceptor[interceptorNumber].objective.x - interceptor[interceptorNumber].origin.x, 2) +
|
|
pow(interceptor[interceptorNumber].objective.y - interceptor[interceptorNumber].origin.y, 2));
|
|
|
|
sideX = (interceptor[interceptorNumber].objective.x - interceptor[interceptorNumber].origin.x)*INTERCEPTOR_SPEED/module;
|
|
sideY = (interceptor[interceptorNumber].objective.y - interceptor[interceptorNumber].origin.y)*INTERCEPTOR_SPEED/module;
|
|
|
|
interceptor[interceptorNumber].speed = (Vector2){ sideX, sideY };
|
|
|
|
// Update
|
|
interceptorNumber++;
|
|
if (interceptorNumber == MAX_INTERCEPTORS) interceptorNumber = 0;
|
|
}
|
|
}
|
|
|
|
static void UpdateIncomingFire()
|
|
{
|
|
static int missileIndex = 0;
|
|
|
|
// Launch missile
|
|
if (framesCounter%MISSILE_LAUNCH_FRAMES == 0)
|
|
{
|
|
float module;
|
|
float sideX;
|
|
float sideY;
|
|
|
|
// Activate the missile
|
|
missile[missileIndex].active = true;
|
|
|
|
// Assign start position
|
|
missile[missileIndex].origin = (Vector2){ GetRandomValue(20, screenWidth - 20), -10 };
|
|
missile[missileIndex].position = missile[missileIndex].origin;
|
|
missile[missileIndex].objective = (Vector2){ GetRandomValue(20, screenWidth - 20), screenHeight + 10 };
|
|
|
|
// Calculate speed
|
|
module = sqrt( pow(missile[missileIndex].objective.x - missile[missileIndex].origin.x, 2) +
|
|
pow(missile[missileIndex].objective.y - missile[missileIndex].origin.y, 2));
|
|
|
|
sideX = (missile[missileIndex].objective.x - missile[missileIndex].origin.x)*MISSILE_SPEED/module;
|
|
sideY = (missile[missileIndex].objective.y - missile[missileIndex].origin.y)*MISSILE_SPEED/module;
|
|
|
|
missile[missileIndex].speed = (Vector2){ sideX, sideY };
|
|
|
|
// Update
|
|
missileIndex++;
|
|
if (missileIndex == MAX_MISSILES) missileIndex = 0;
|
|
}
|
|
}
|