360 lines
9.2 KiB
C++
360 lines
9.2 KiB
C++
#include <math.h>
|
|
#include <time.h>
|
|
#include <vector>
|
|
#include <list>
|
|
#include <algorithm>
|
|
#include "raylib.h"
|
|
#include "Sprite2D.h"
|
|
|
|
Texture2D background;
|
|
Texture2D entity_texture;
|
|
Texture2D player_texture;
|
|
Texture2D enemy_texture;
|
|
Texture2D obstacle_texture;
|
|
|
|
Sound background_music;
|
|
|
|
const int GAME_WIDTH = 800;
|
|
const int GAME_HEIGHT = GAME_WIDTH * (3.0f / 5.0f);
|
|
|
|
const int SIDE_WALK1 = 40;
|
|
const int SIDE_WALK1_W = 50;
|
|
const int SIDE_WALK2_W = SIDE_WALK1_W;
|
|
const int SIDE_WALK2 = GAME_HEIGHT - SIDE_WALK2_W;
|
|
|
|
const int AGRO_BUFFER_DISTANCE = 10;
|
|
const double RIGHT_MARGIN = (3.0f / 5.0f);
|
|
|
|
float v;
|
|
|
|
#include "util.h"
|
|
#include "my_random.hpp"
|
|
#include "main_menu.h"
|
|
#include "Effect.hpp"
|
|
#include "Entity.hpp"
|
|
#include "death_manager.h"
|
|
|
|
using namespace std;
|
|
|
|
float absolute_x;
|
|
|
|
Spawner enemy_spawner = (Spawner) {
|
|
.min_distance = 400,
|
|
.denominator = 10,
|
|
};
|
|
Spawner obstacle_spawner = (Spawner) {
|
|
.min_distance = 1200,
|
|
.denominator = 1000,
|
|
};
|
|
|
|
const float max_v = 3.4f;
|
|
|
|
list<Enemy> enemies;
|
|
vector<Enemy*> ordered_enemies;
|
|
|
|
Obstacle obstacle;
|
|
|
|
const float MOVEMENT_SPEED = 3.2;
|
|
|
|
float move_player(bool left, bool right, bool up, bool down) {
|
|
static const float mod = MOVEMENT_SPEED;
|
|
|
|
float x_mod = 0;
|
|
float y_mod = 0;
|
|
|
|
if (left) { x_mod -= mod; }
|
|
if (right) { x_mod += mod; }
|
|
if (up) { y_mod -= mod; }
|
|
if (down) { y_mod += mod; }
|
|
|
|
if (player.x + x_mod > 0
|
|
&& player.x + x_mod < GAME_WIDTH * RIGHT_MARGIN) {
|
|
player.x += x_mod;
|
|
}
|
|
|
|
if (player.y + y_mod > SIDE_WALK1
|
|
&& player.y < GAME_HEIGHT) {
|
|
player.y += y_mod;
|
|
}
|
|
|
|
return x_mod;
|
|
}
|
|
|
|
void update_scroll(float mod) {
|
|
if (mod > 0
|
|
&& v + 0.01f < max_v) {
|
|
v += 0.01f;
|
|
} else
|
|
if (v - 0.1f > 0.0f) {
|
|
v -= 0.1f;
|
|
} else {
|
|
v = 0;
|
|
}
|
|
|
|
absolute_x += v*v;
|
|
}
|
|
|
|
int game_loop(void) {
|
|
// Init
|
|
enum {
|
|
GAME_RUNNING,
|
|
GAME_OVER,
|
|
} game_state = GAME_RUNNING;
|
|
double gameover_time;
|
|
|
|
enemy_spawner.reset();
|
|
obstacle_spawner.reset();
|
|
reset_deaths();
|
|
enemies.clear();
|
|
ordered_enemies.clear();
|
|
obstacle.regen();
|
|
|
|
player.x = 100;
|
|
player.y = 100;
|
|
|
|
absolute_x = 0;
|
|
v = 0.0f;
|
|
|
|
// Work
|
|
PlaySound(background_music);
|
|
float mod = 0;
|
|
while (!WindowShouldClose()) {
|
|
input:
|
|
if (game_state == GAME_OVER) {
|
|
if ((GetTime() - gameover_time) > 0.5
|
|
&& GetKeyPressed() != KEY_NULL) {
|
|
return 0;
|
|
}
|
|
|
|
mod = 0;
|
|
goto simulate;
|
|
}
|
|
|
|
mod = move_player(
|
|
IsKeyDown(KEY_A) | IsKeyDown(KEY_LEFT) | IsKeyDown(KEY_H),
|
|
IsKeyDown(KEY_D) | IsKeyDown(KEY_RIGHT) | IsKeyDown(KEY_L),
|
|
IsKeyDown(KEY_W) | IsKeyDown(KEY_UP) | IsKeyDown(KEY_K),
|
|
IsKeyDown(KEY_S) | IsKeyDown(KEY_DOWN) | IsKeyDown(KEY_J)
|
|
);
|
|
|
|
simulate:
|
|
// Map movement
|
|
update_scroll(mod);
|
|
|
|
// Player animation
|
|
if (((int)absolute_x / 10) % 2) {
|
|
player.frame = player.frame xor 0x01;
|
|
}
|
|
|
|
// Spawn
|
|
if (enemy_spawner.blaze(absolute_x)) {
|
|
enemies.emplace_back();
|
|
}
|
|
if (obstacle_spawner.blaze(absolute_x)) {
|
|
obstacle.regen();
|
|
}
|
|
|
|
// Obstacle movement
|
|
obstacle.x -= v*v + obstacle.speed;
|
|
|
|
// Player death
|
|
if (CheckCollisionRecs((Rectangle)obstacle, (Rectangle)player)) {
|
|
add_death((Vector2) { player.x, player.y });
|
|
gameover_time = GetTime();
|
|
game_state = GAME_OVER;
|
|
}
|
|
|
|
// Enemy Death
|
|
vector<decltype(enemies.begin())> to_erase;
|
|
for (auto e = enemies.begin(); e != enemies.end(); e++) {
|
|
if (CheckCollisionRecs((Rectangle)*e, (Rectangle)obstacle)) {
|
|
add_death((Vector2) { e->x, e->y });
|
|
to_erase.push_back(e);
|
|
}
|
|
}
|
|
for (auto e : to_erase) {
|
|
enemies.erase(e);
|
|
}
|
|
|
|
// Enemy movement
|
|
ordered_enemies.clear();
|
|
for (auto &e : enemies) {
|
|
ordered_enemies.push_back(&e);
|
|
}
|
|
|
|
sort(ordered_enemies.begin(), ordered_enemies.end(), [](Enemy * a, Enemy * b) {
|
|
return distance(a->x, a->y, player.x, player.y)
|
|
< distance(b->x, b->y, player.x, player.y);
|
|
});
|
|
|
|
for (auto &e : ordered_enemies) {
|
|
// non-agro
|
|
if (not e->is_agrod) {
|
|
e->x -= v*v;
|
|
if (e->x - AGRO_BUFFER_DISTANCE < player.x) {
|
|
e->is_agrod = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// murder player
|
|
if (CheckCollisionRecs((Rectangle)*e, (Rectangle)player)) {
|
|
add_death((Vector2) { player.x, player.y });
|
|
gameover_time = GetTime();
|
|
game_state = GAME_OVER;
|
|
}
|
|
|
|
// move
|
|
Rectangle would_be_rect = (Rectangle)*e;
|
|
float diff = player.y - e->y;
|
|
would_be_rect.y += diff * 0.05f;
|
|
float mod = -(v*v) + (MOVEMENT_SPEED*0.9f);
|
|
if (e->x + mod > 0) {
|
|
would_be_rect.x += mod;
|
|
}
|
|
|
|
would_be_rect.y += random_wiggle(64, 6);
|
|
would_be_rect.y += -abs(random_wiggle(32, 3));
|
|
|
|
bool would_collide_x = false;
|
|
bool would_collide_y = false;
|
|
for (const auto &r : enemies) {
|
|
if (would_collide_x
|
|
&& would_collide_y) {
|
|
break;
|
|
}
|
|
|
|
if (e == &r) { continue; }
|
|
|
|
if (CheckCollisionRecs(
|
|
(Rectangle) {
|
|
.x = would_be_rect.x,
|
|
.y = e->y,
|
|
.width = e->width,
|
|
.height = e->height,
|
|
},
|
|
(Rectangle)r
|
|
)
|
|
) {
|
|
would_collide_x = true;
|
|
}
|
|
|
|
if (CheckCollisionRecs(
|
|
(Rectangle) {
|
|
.x = e->x,
|
|
.y = would_be_rect.y,
|
|
.width = e->width,
|
|
.height = e->height,
|
|
},
|
|
(Rectangle)r
|
|
)
|
|
) {
|
|
would_collide_y = true;
|
|
}
|
|
}
|
|
|
|
if (not would_collide_x) {
|
|
e->x = would_be_rect.x;
|
|
}
|
|
if (not would_collide_y) {
|
|
e->y = would_be_rect.y;
|
|
}
|
|
}
|
|
|
|
update_deaths();
|
|
|
|
draw:
|
|
BeginDrawing();
|
|
ClearBackground(RAYWHITE);
|
|
|
|
// draw street
|
|
int background_one_start = - (int)absolute_x % background.width;
|
|
int background_two_start = background_one_start + background.width;
|
|
DrawTexture(background, background_one_start, 0, WHITE);
|
|
DrawTexture(background, background_two_start, 0, WHITE);
|
|
|
|
display_deaths();
|
|
|
|
// draw entities
|
|
obstacle.draw();
|
|
|
|
if (game_state == GAME_RUNNING) {
|
|
player.draw();
|
|
}
|
|
|
|
for (auto &e : enemies) {
|
|
e.draw();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
for (int i = 0; i < ordered_enemies.size(); i++) {
|
|
DrawText(
|
|
TextFormat("%d", i),
|
|
ordered_enemies[i]->x,
|
|
ordered_enemies[i]->y,
|
|
20,
|
|
BLUE
|
|
);
|
|
}
|
|
#endif
|
|
|
|
// draw HUD
|
|
#ifdef DEBUG
|
|
DrawText(TextFormat("d: %f", absolute_x), 10, 15, 20, BLACK);
|
|
DrawText(TextFormat("V: %f", v), 10, 40, 20, BLACK);
|
|
#endif
|
|
|
|
if (game_state == GAME_OVER) {
|
|
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{128, 128, 128, 128});
|
|
DrawText("Game Over", 100, 100, 40, BLACK);
|
|
DrawText(
|
|
TextFormat(
|
|
"You lead the masses for %d meters.",
|
|
((int)absolute_x) / 10
|
|
),
|
|
200,
|
|
200,
|
|
20,
|
|
BLACK
|
|
);
|
|
}
|
|
EndDrawing();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int main(void) {
|
|
// Init
|
|
srand(time(NULL));
|
|
InitWindow(GAME_WIDTH, GAME_HEIGHT, "Masses");
|
|
InitAudioDevice();
|
|
|
|
Image image = LoadImage("resources/street.png");
|
|
ImageResize(&image, GAME_WIDTH, GAME_HEIGHT);
|
|
background = LoadTextureFromImage(image);
|
|
UnloadImage(image);
|
|
|
|
player_texture = LoadTexture("resources/player.png");
|
|
player.sprite.texture = &player_texture;
|
|
player.sprite.width = player.sprite.texture->width;
|
|
player.sprite.height = player.sprite.texture->height / 2;
|
|
|
|
entity_texture = LoadTexture("resources/entity.png");
|
|
enemy_texture = LoadTexture("resources/enemy.png");
|
|
obstacle_texture = LoadTexture("resources/obstackle.png");
|
|
|
|
background_music = LoadSound("resources/From_Nothing_To_Zero_-_(Sybreed_song_reversed).mp3");
|
|
|
|
SetTargetFPS(60);
|
|
|
|
// Game
|
|
main_menu(); // hang until initial user input
|
|
|
|
while (not game_loop()) { ; }
|
|
|
|
CloseWindow();
|
|
|
|
return 0;
|
|
}
|