raylib/games/missile_commander.c
Ahmad Fatoum 2c219fb814
Allow use of main instead of android_main
Inspired by #504.
Instead of requiring the user to do PLATFORM_ANDROID #ifdefery,
have the android_main entry point exported by raylib and call
the user-defined main. This way many games could (in theory)
run unmodified on Android and elsewhere.

This is untested!
2018-03-16 21:37:22 +01:00

535 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];
static Interceptor interceptor[MAX_INTERCEPTORS];
static Explosion explosion[MAX_EXPLOSIONS];
static Launcher launcher[LAUNCHERS_AMOUNT];
static Building building[BUILDINGS_AMOUNT];
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(FormatText("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;
}
}