-
Tomas Vybiral authoredTomas Vybiral authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
game.cpp 6.71 KiB
#include "game.h"
#include "input/sdl_input.h"
#include "renderer/sdl_renderer.h"
#include "single_shot.h"
#include "multi_shot.h"
#include "state/level.h"
#include <variant>
#include <chrono>
#include <cmath>
#include <SDL2/SDL.h>
game::game(uint32_t fps):current_state(),
input_processor(nullptr),
renderer(nullptr),
shoot_control(nullptr),
player_position_delta(0),
player_angle_delta(0.0),
player_force_delta(0.0),
delta_time(1.0 / static_cast<double>(fps)),
is_running(false),
shoot(false),
debug(false) {
input_processor = new sdl_input();
renderer = new sdl_renderer();
shoot_control = new single_shot();
load_levels();
level first_level = std::move(level::get("start"));
current_state.enemies = first_level.enemies;
current_state.next_level_id = first_level.next_level_id;
}
void game::load_levels() {
level::create().from_file("levels/01.txt");
level::create().with_enemy_at({320, 120}).with_enemy_at({160, 240}).with_enemy_at({480, 360}).save_as("level_01");
}
game::~game() {
if (!input_processor)
delete input_processor;
if (!renderer) {
delete renderer;
}
if (!shoot_control) {
delete shoot_control;
}
}
void game::run_loop() {
is_running = true;
while (is_running) {
auto start = std::chrono::system_clock::now();
process_events();
update_state();
render_current_state();
if (try_to_switch_level())
break;
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end - start;
SDL_Delay(static_cast<uint32_t>((delta_time - diff.count())*1000));
}
}
bool game::try_to_switch_level() {
if (current_state.enemies.size() == 0 && current_state.projectiles.size() == 0) {
if (current_state.next_level_id != "") {
level next_level = std::move(level::get(current_state.next_level_id));
current_state.enemies = next_level.enemies;
current_state.next_level_id = next_level.next_level_id;
} else {
renderer->show_win_dialog();
return true;
}
}
return false;
}
void game::process_events() {
input_processor->poll_events();
while(input_processor->has_events()) {
event& e = input_processor->get_next_event();
// FIXME: use visit
process_event(e);
}
}
void game::process_event(const event& e) {
do_event<quit_game>(e, [&](auto&) { is_running = false; });
do_event<change<player_angle>>(e, [&](const auto& cpa){
player_angle_delta += cpa.data.value;
player_angle_delta = min(player_angle::DEFAULT,
max(-player_angle::DEFAULT,
player_angle_delta));
});
do_event<change<player_force>>(e, [&](const auto& cpf){
player_force_delta += cpf.data.value;
player_force_delta = min(player_force::DEFAULT,
max(-player_force::DEFAULT,
player_force_delta));
});
do_event<change<move_vector>>(e, [&](const auto& cmv) {
player_position_delta += cmv.data.value; // FIXME: Use ranged value here;
player_position_delta = min(move_vector::DEFAULT,
max(-move_vector::DEFAULT,
player_position_delta));
});
do_event<push_state>(e, [&](auto&) {
if (debug)
current_state.push();
});
do_event<pop_state>(e, [&](auto&) {
if (state::can_pop())
current_state = std::move(state::pop());
});
do_event<shoot_event>(e, [&](auto&) {shoot = true;});
do_event<change<shooting_controller>>(e, [&](const auto& sc) {
if (shoot_control->can_switch()) {
delete shoot_control;
switch(sc.data.mode) {
case shooting_mode::SINGLE:
shoot_control = new single_shot();
break;
case shooting_mode::MULTIPLE:
shoot_control = new multi_shot();
break;
}
}
});
do_event<switch_debug>(e, [&](auto&) {debug = !debug;});
}
template<typename T>
void game::do_event(const event& e, const std::function<void(const T&)>& trigger) {
auto ptr = std::get_if<T>(&e);
if (ptr) {
trigger(*ptr);
}
}
void game::update_state() {
// Update shooter
current_state.player.position.set(current_state.player.position.get() + player_position_delta);
current_state.player.angle.set(current_state.player.angle.get() + player_angle_delta);
current_state.player.force.set(current_state.player.force.get() + player_force_delta);
// Check controler if can shoot
if (shoot_control->shoot(shoot)) {
// If no projectile is present preserve current state
if (current_state.projectiles.size() == 0) {
current_state.push();
}
// Create new projectile
vector2 start_pos{13, current_state.player.position.get()};
current_state.projectiles.emplace_back(start_pos, 15, current_state.player.force.get(), current_state.player.angle.get());
}
shoot_control->update_timer(delta_time);
shoot = false;
move_and_test_collisions();
}
void game::move_and_test_collisions() { // TODO: Replace with real physics
// Update projectiles
for (int i = static_cast<int>(current_state.projectiles.size()) - 1; i >= 0 ; --i) {
auto& proj = current_state.projectiles[static_cast<size_t>(i)];
// Is projectile out of window bounds
if (proj.position.x > 640 || proj.position.y > 480) {
current_state.projectiles.erase(current_state.projectiles.cbegin() + i);
}
// Did hit somebody?
for (int j = static_cast<int>(current_state.enemies.size()) - 1; j >= 0 ; --j) {
auto& en = current_state.enemies[static_cast<size_t>(j)];
if (proj.did_collide(en)) {
current_state.enemies.erase(current_state.enemies.cbegin() + j);
current_state.score += 100;
}
}
// Update position
vector2 speed{static_cast<int>(cos(proj.angle) * 12),
-static_cast<int>(ceil(sin(proj.angle) * 12))};
proj.position.x += speed.x;
proj.position.y += speed.y;
proj.angle -= M_PI/(75*proj.speed);
if (proj.angle < -5*M_PI/12) {
proj.angle = -5*M_PI/12;
}
}
}
void game::render_current_state() {
renderer->render_state(current_state, debug);
}