skizzle/src/game.cpp

308 lines
9.0 KiB
C++

#include <iostream>
#include <cmath>
#include "utility.hpp"
#include "manager.hpp"
#include "game.hpp"
#include "player.hpp"
/**
* Définition des variables et fonctions globales internes
* (accessibles uniquement dans ce fichier)
*/
namespace {
// nombre maximum de frames que l'on peut sauter
// en cas de charge CPU importante
const unsigned int MAX_FRAME_SKIP = 5;
}
Game::Game(Manager& manager) : Level(manager),
mode(Game::Mode::NORMAL),
next_frame_time(manager.getCurrentTime()),
skipped_frames(0),
death_cause(Game::DeathCause::NONE),
widget_timer(manager, false) {}
Game::~Game() {}
void Game::enable() {
Level::enable();
// attributs de la fenêtre
getManager().setTitle(getName());
getManager().setFramerate(0);
// si musique il y a, on la joue
if (getMusic() != "") {
getResourceManager().playMusic(getMusic());
} else {
getResourceManager().stopMusic();
}
}
void Game::processEvent(const sf::Event& event) {
Level::processEvent(event);
if (event.type == sf::Event::KeyPressed) {
// appui sur espace : retour
if (event.key.code == sf::Keyboard::Space) {
getManager().popState();
}
// appui sur retour échap : échange entre le mode pause et normal
if (event.key.code == sf::Keyboard::Escape ||
event.key.code == sf::Keyboard::BackSpace) {
if (getMode() == Game::Mode::NORMAL) {
setMode(Game::Mode::PAUSED);
} else if (getMode() == Game::Mode::PAUSED) {
setMode(Game::Mode::NORMAL);
}
}
}
}
void Game::frame() {
sf::Time current_time = getManager().getCurrentTime();
if (current_time >= next_frame_time) {
// si nous sommes en retard ou dans les temps
// on replanifie la prochaine frame
next_frame_time += Manager::FRAME_TIME;
// on met à jour la physique d'un cran temporel,
// si on est en mode normal
if (getMode() == Game::Mode::NORMAL) {
update();
} else {
// TODO: pour le débogage affichage du mode actuel
switch (getMode()) {
case Game::Mode::NORMAL:
std::cout << "<< Reprise >>" << std::endl;
break;
case Game::Mode::PAUSED:
std::cout << "<< En pause >>" << std::endl;
break;
case Game::Mode::WON:
std::cout << "<< Gagné ! >>" << std::endl;
break;
case Game::Mode::LOST:
std::cout << "<< Perdu : ";
switch (getDeathCause()) {
case Game::DeathCause::OUT_OF_BOUNDS:
std::cout << "sortie du cadre";
break;
case Game::DeathCause::KILLED:
std::cout << "tué par bloc";
break;
case Game::DeathCause::TIME_OUT:
std::cout << "temps écoulé";
break;
case Game::DeathCause::NONE:
std::cout << "sans aucune raison";
break;
}
std::cout << " ! >>" << std::endl;
break;
}
}
// on s'assure que la caméra soit centrée sur nos joueurs
ensureCentered();
// si on a encore suffisamment de temps, ou si on a sauté
// trop de frames, on dessine
if (current_time < next_frame_time || skipped_frames >= MAX_FRAME_SKIP) {
draw();
}
// sinon, on saute une frame de dessin. On stocke
// le nombre de frames sautées pour ne pas sauter plus que
// le maximum
else {
skipped_frames++;
}
} else {
// si nous sommes en avance, on endort le processus
// le temps nécessaire pour revenir dans les temps
sf::sleep(next_frame_time - current_time);
}
}
void Game::draw() {
sf::Vector2i window_size = (sf::Vector2i) getWindow().getSize();
// dessin des objets du niveau
Level::draw();
// on passe au dessin d'éléments d'interface.
// Changement de vue sur la vue par défaut
getManager().useGUIView();
// dessin du timer
widget_timer.setTimeLeft(
std::max(std::ceil(time_left), 0.f)
);
widget_timer.draw(sf::Vector2f(window_size.x / 2 - 50, 0));
}
void Game::ensureCentered() {
std::vector<Player::Ptr>& players = getPlayers();
unsigned int player_count = players.size();
sf::View camera = getCamera();
sf::Vector2f previous_center = camera.getCenter();
sf::Vector2f position_sum, goal_center;
for (unsigned int i = 0; i < player_count; i++) {
position_sum += players[i]->getPosition();
}
if (player_count == 0) {
// on évite la division par zéro
goal_center = sf::Vector2f(0, 0);
} else {
// on place la caméra à la position médiane de tous les joueurs
goal_center = position_sum / (float) player_count;
}
// on anime le centre vers la nouvelle position
previous_center.x = Utility::animateValue(previous_center.x, 5, goal_center.x);
previous_center.y = Utility::animateValue(previous_center.y, 5, goal_center.y);
camera.setCenter(previous_center);
setCamera(camera);
}
void Game::update() {
std::vector<CollisionData> colliding;
std::vector<Object::Ptr>& objects = getObjects();
// on décrémente le temps, si le temps est nul,
// on a perdu
if (time_left <= 0) {
setMode(Game::Mode::LOST);
setDeathCause(Game::DeathCause::TIME_OUT);
return;
} else {
time_left -= Manager::FRAME_TIME.asSeconds();
}
// on tue les objets qui étaient déjà planifiés pour mourir
for (auto it = pending_kill.begin(); it != pending_kill.end(); it++) {
removeObject(*it);
}
pending_kill.empty();
// détection des objets en collision
for (auto it = objects.begin(); it != objects.end(); it++) {
Object::Ptr obj_a = *it;
// l'objet est sorti de la zone, on le signale et on
// planifie sa mort à la prochaine frame
if (obj_a->getMass() != 0 && !isInZone(obj_a)) {
kill(obj_a);
// si c'était un joueur, on a perdu
if (obj_a->getTypeId() == Player::TYPE_ID) {
setMode(Game::Mode::LOST);
setDeathCause(Game::DeathCause::OUT_OF_BOUNDS);
}
}
// on regarde s'il est en collision avec
// d'autres objets
for (auto jt = it + 1; jt != objects.end(); jt++) {
Object::Ptr obj_b = *jt;
// si les objets ne sont pas sur la même couche,
// ils ne peuvent pas entrer en collision
if (obj_a->getLayer() != obj_b->getLayer()) {
continue;
}
// si les deux boîtes englobantes des deux objets ne
// s'intersectent pas, il ne risque pas d'y avoir de collision
if (!obj_a->getAABB().intersects(obj_b->getAABB())) {
continue;
}
CollisionData data;
data.obj_a = obj_a;
data.obj_b = obj_b;
if (getCollisionData(data)) {
colliding.push_back(data);
}
}
}
// intégration des forces dans la vitesse
for (auto it = objects.begin(); it != objects.end(); it++) {
(*it)->updateVelocity(*this);
}
// résolution des collisions détectées
for (auto it = colliding.begin(); it != colliding.end(); it++) {
it->obj_a->solveCollision(*this, it->obj_b, it->normal);
}
// intégration de la vitesse dans la position
for (auto it = objects.begin(); it != objects.end(); it++) {
(*it)->updatePosition();
}
// application de la correction positionnelle
for (auto it = colliding.begin(); it != colliding.end(); it++) {
it->obj_a->positionalCorrection(it->obj_b, it->normal, it->depth);
}
}
void Game::kill(Object::Ptr object) {
pending_kill.push_back(object);
}
/**
* Implémentation tirée de
* http://stackoverflow.com/a/2922778/3452708
*/
bool Game::isInZone(Object::Ptr object) {
std::vector<sf::Vector2f>& zone = getZone();
sf::Vector2f pos = object->getPosition();
unsigned int vertices = getZone().size();
bool result = false;
for (unsigned int i = 0, j = vertices - 1; i < vertices; j = i++) {
if (((zone[i].y > pos.y) != (zone[j].y > pos.y)) &&
(pos.x < (zone[j].x - zone[i].x) * (pos.y - zone[i].y) /
(zone[j].y - zone[i].y) + zone[i].x)) {
result = !result;
}
}
return result;
}
Game::Mode Game::getMode() {
return mode;
}
void Game::setDeathCause(Game::DeathCause set_death_cause) {
death_cause = set_death_cause;
}
Game::DeathCause Game::getDeathCause() {
return death_cause;
}
void Game::setMode(Game::Mode set_mode) {
mode = set_mode;
}
void Game::setTotalTime(int set_total_time) {
Level::setTotalTime(set_total_time);
time_left = getTotalTime();
}