added my simple simulation implementation with trivial graphics

This commit is contained in:
Andreas Ortmann 2016-09-27 18:23:58 +02:00
parent ea0aa01b2d
commit 183a26412a
28 changed files with 950 additions and 36 deletions

View file

@ -8,10 +8,20 @@ set(GAME_SRC
main.cpp
opengl.cpp
glclasses.cpp
config.cpp
simulation.cpp
renderer.cpp
)
game_window.cpp
triangle_window.cpp
util.cpp
game.cpp
state/object.cpp
state/state.cpp
state/player.cpp
state/planet.cpp
state/missile.cpp
state/commands.cpp
state/state.cpp
)
set(GAME_HEADERS
opengl.h
@ -20,7 +30,7 @@ set(GAME_HEADERS
config.h
simulation.h
renderer.h
)
)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

67
game/game.cpp Normal file
View file

@ -0,0 +1,67 @@
#include "game.hpp"
#include "state/commands.hpp"
#include "util.hpp"
#include <cmath>
Game::Game()
{
// advance simulation with 100 Hz
m_time_step = 1.0 / 100.0;
m_time_for_next_step = 0.0;
m_state = new game::State();
m_state->init();
m_state->addPlayer(0);
m_state->addPlayer(1);
m_state->addPlayer(2);
m_state->addPlayer(3);
//m_state->addPlayer(2);
}
bool Game::cycle(float dt)
{
static float acc = 0.0;
acc += dt;
float spawnInterval = 0.1;
while(acc > spawnInterval) {
acc -= spawnInterval;
float a = 2.0 * M_PI * util::randf_0_1();
float speed = 0.002;
m_state->players[0]->addCommand(new game::ShootCommand(a, speed));
}
#if 1
if (dt >= 10.0) {
//std::cout<<"time to big: " << dt << std::endl;
dt = m_time_step;
}
//std::cout<<"adding dt: " << dt << std::endl;
m_time_for_next_step += dt;
int steps = 0;
while(m_time_for_next_step >= m_time_step) {
//std::cout<<"time now: " << m_time_for_next_step << std::endl;
m_time_for_next_step -= m_time_step;
m_state->advance(m_time_step);
steps++;
}
//std::cout << m_time_for_next_step << " s remaining time, " << steps << " steps taken." << std::endl;
return true;
#else
(void) dt;
m_state->advance(dt);
return true;
#endif
}

21
game/game.hpp Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include "state/state.hpp"
class Game {
public:
Game();
// main method of the game. run this regulary
// return false if want to exit.
bool cycle(float dt);
// for rendering
const game::State *state() const { return m_state; }
private:
game::State *m_state;
float m_time_for_next_step;
float m_time_step;
};

0
game/game_window.cpp Normal file
View file

89
game/game_window.h Normal file
View file

@ -0,0 +1,89 @@
#pragma once
#include "opengl.h"
#include "renderer.h"
#include "game.hpp"
#include "state/object.hpp"
#include "state/missile.hpp"
#include "state/player.hpp"
#include "state/planet.hpp"
#include "state/ship.hpp"
class GameWindow : public endofthejedi::GLWindow {
private:
protected:
void init() override {
glClearColor(0.5f, 0.6f, 0.7f, 1.0f);
resize();
glEnable(GL_DEPTH_TEST);
}
void render(double time) override {
//static bool once = false;
//if (!once) {
// once = true;
// for (int i=0; i<1000; i++) {
// m_game.cycle(time);
// }
//}
if (!m_game.cycle(static_cast<float>(time/1000000000.0))) {
std::cout<<"stopping the game..." << std::endl;
stop();
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
for (const game::Planet *planet : m_game.state()->planets) {
drawPlanet(planet->position, planet->radius);
}
for (const game::Ship *ship : m_game.state()->ships) {
drawShip(ship->position);
}
for (const game::Player *player : m_game.state()->players) {
for (const game::Missile *missile : player->missiles) {
drawMissile(missile->position);
}
}
}
void resize() override { glViewport(0, 0, getwidth(), getheight()); }
void drawShip(const glm::vec2 &pos)
{
//std::cout<<"draw ship @ " << pos.x << ", " << pos.y << std::endl;
glm::vec3 color = glm::vec3(0.2, 1.0, 0.3);
float radius = m_game.state()->shipRadius();
m_renderer.drawCircle(pos.x, pos.y, radius, color.x, color.y, color.z, 12);
}
void drawPlanet(const glm::vec2 &pos, float radius)
{
glm::vec3 color = glm::vec3(0.7, 0.1, 0.2);
//std::cout<<"draw planet @ " << pos.x << ", " << pos.y << std::endl;
m_renderer.drawCircle(pos.x, pos.y, radius, color.x, color.y, color.z, 32);
}
void drawMissile(const glm::vec2 &pos)
{
glm::vec3 color = glm::vec3(1.0, 1.0, 0.3);
m_renderer.drawCircle(pos.x, pos.y, 0.005, color.x, color.y, color.z, 5);
}
public:
GameWindow(unsigned int width, unsigned int height)
: endofthejedi::GLWindow(width, height) {}
private:
Game m_game;
endofthejedi::Renderer m_renderer;
};

View file

@ -1,36 +1,11 @@
#include "opengl.h"
#include <iostream>
class MainWindow : public endofthejedi::GLWindow {
private:
protected:
void init() override {
glClearColor(0.5f, 0.6f, 0.7f, 1.0f);
resize();
glEnable(GL_DEPTH_TEST);
}
void render(double time) override {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(0.0f, -1.0f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 0.0f);
glEnd();
}
void resize() override { glViewport(0, 0, getwidth(), getheight()); }
public:
MainWindow(unsigned int width, unsigned int height)
: endofthejedi::GLWindow(width, height) {}
};
//#include "triangle_window.h"
#include "game_window.h"
int main(int argc, const char *argv[]) {
MainWindow window(500, 500);
GameWindow window(500, 500);
window.set_maxfps(60.0);
window.loop();
window.stop();

View file

@ -7,12 +7,12 @@ endofthejedi::Renderer::Renderer() {
endofthejedi::Renderer::~Renderer() {}
void endofthejedi::Renderer::drawCircle(float x, float y, float radius, float r,
float g, float b) {
float g, float b, int numSides) {
glBegin(GL_TRIANGLE_FAN);
glVertex2f(x, y); // center of circle
for (int i = 0; i <= 64; i++) {
for (int i = 0; i <= numSides; i++) {
glColor3f(r,g,b);
glVertex2f(x + (radius * cos(i * 2 * M_PI / 64)), y + (radius * sin(i * 2 * M_PI / 64)));
glVertex2f(x + (radius * cos(i * 2 * M_PI / numSides)), y + (radius * sin(i * 2 * M_PI / numSides)));
}
glEnd();
}

View file

@ -19,7 +19,7 @@ class Renderer {
Renderer();
~Renderer();
void drawCircle(float x, float y, float radius, float r,
float g, float b);
float g, float b, int numSides=12);
};
}

22
game/state/commands.cpp Normal file
View file

@ -0,0 +1,22 @@
#include "commands.hpp"
namespace game {
void ShootCommand::apply(Player *player, State *state) const
{
(void) state;
// TODO spawn missile if alive and enough energy
//std::cout<<"apply command " << name() << std::endl;
player->energy -= m_speed;
player->missiles.push_back(new Missile(player->id, player->ship->position, m_angle, m_speed));
}
bool ShootCommand::allowed(const Player *player, const State *state) const
{
(void) state;
// TODO
return player->alive && player->energy >= m_speed;
//return player->alive;
}
}

56
game/state/commands.hpp Normal file
View file

@ -0,0 +1,56 @@
#pragma once
#include "state.hpp"
#include "player.hpp"
#include "ship.hpp"
#include "planet.hpp"
#include "missile.hpp"
#include <iostream>
namespace game {
class Command {
public:
Command()
{
}
virtual ~Command()
{
}
virtual bool allowed(const Player *player, const State *state) const
{
(void) player;
(void) state;
return true;
}
virtual void apply(Player *player, State *state) const
{
(void) state;
(void) player;
std::cout<<"Command '" << name() << "' not yet implemented!" << std::endl;
}
virtual std::string name() const { return "<unnamed>"; }
};
class ShootCommand : public Command {
public:
ShootCommand(float angle, float speed) : m_angle(angle), m_speed(speed)
{
}
std::string name() const { return "<shoot>"; }
void apply( Player *player, State *state) const;
bool allowed(const Player *player, const State *state) const;
private:
float m_angle;
float m_speed;
};
}

0
game/state/explosion.cpp Normal file
View file

17
game/state/explosion.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#if 0
class Explosion {
public:
enum class Kind {
Normal,
MissileWithShip
};
glm::vec2 position;
Explosion(Kind kind);
public:
};
#endif

82
game/state/missile.cpp Normal file
View file

@ -0,0 +1,82 @@
#include "missile.hpp"
#include <cmath>
#include "state.hpp"
#include "player.hpp"
#include <glm/gtx/norm.hpp>
namespace game {
Missile::Missile(int playerId, const glm::vec2 &pos, float angle, float speed)
: playerId(playerId)
, position(pos)
{
velocity = speed * glm::vec2(std::sin(angle), std::cos(angle));
//std::cout << "spawned missile for player " << playerId << " @ ("
// << pos.x << ", " << pos.y << ") speed = " << speed << std::endl;
}
Missile::~Missile()
{
//std::cout<<"destroyed missile" << std::endl;
}
Missile::Event Missile::advance(const game::State *state, float dt)
{
glm::vec2 gravityForce = glm::vec2(0.0, 0.0);
for (const Player *other : state->players) {
if (other->ship != nullptr && other->id != playerId) {
glm::vec2 diff = other->ship->position - position;
float dist = glm::length(diff);
if (dist <= other->ship->radius) {
// TODO: collect all hits and return the first one only
// TODO: find exact hit position!
return Missile::Event(position, playerId, other->id);
}
}
}
// get forces
for (const Planet *planet : state->planets) {
glm::vec2 diff = planet->position - position;
float dist = glm::length(diff);
if (dist <= planet->radius) {
// TODO: collect all hits and return the first one only
// TODO: find exact hit position!
return Missile::Event(position, Missile::HitObject::HitPlanet);
}
dist *= 20.0;
if (dist > 0.001) {
float G = 0.1;
gravityForce += G * diff / (1.0f + dist*dist);
}
}
// simple (and bad) euler integration.
position += 0.5f * gravityForce*dt*dt + velocity*dt;
velocity += gravityForce * dt;
//const float thr = 0.01;
//float s = glm::length(velocity);
//if (s > thr) {
// velocity = glm::normalize(velocity) * thr;
//}
glm::vec2 nextPos = position + velocity;
// TODO: collision checks
position = nextPos;
// check if distance to center of the universe is getting too big
float distToCenter = glm::length(position);
if (distToCenter > state->maxMissileDistance()) {
return Missile::Event(position, Missile::HitObject::LeftUniverse);
}
return Missile::Event(position);
}
}

68
game/state/missile.hpp Normal file
View file

@ -0,0 +1,68 @@
#pragma once
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include "state.hpp"
#include "ship.hpp"
#include "planet.hpp"
namespace game {
class State;
// missile belongs to a player and optionally fills a trace behind it.
// trace then belongs to the player.
class Missile {
public:
enum class HitObject {
Nothing,
HitPlayer,
HitPlanet,
// HitMissile,
LeftUniverse
};
// missile advances to pos. if hit != Nothing, it hits something and
// stops existing afterwards.
class Event {
public:
Event(const glm::vec2 &pos) : Event(pos, HitObject::Nothing)
{
}
Event(const glm::vec2 &pos, HitObject hit)
: hit(hit), pos(pos)
{
}
Event(const glm::vec2 &pos, int playerIdKiller, int playerIdVictim)
: Event(pos, HitObject::HitPlayer)
{
this->playerIdKiller = playerIdKiller;
this->playerIdVictim = playerIdVictim;
}
HitObject hit;
glm::vec2 pos;
int playerIdKiller;
int playerIdVictim;
};
// XXX
int playerId; // owner won't be hit by own missiles
glm::vec2 position;
glm::vec2 velocity;
//Trace *trace;
Missile(int playerId, const glm::vec2 &pos, float angle, float speed);
~Missile();
// try to advance. if something will be hit, return the first hit in
// time.
Missile::Event advance(const game::State *state, float dt);
};
}

0
game/state/object.cpp Normal file
View file

15
game/state/object.hpp Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
class Object {
public:
Object(const glm::vec2 &pos, float r) : position(pos), radius(r)
{
}
const glm::vec2 position;
const float radius;
};

0
game/state/planet.cpp Normal file
View file

12
game/state/planet.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include "object.hpp"
namespace game {
class Planet : public Object {
public:
Planet(const glm::vec2 &pos, float r) : Object(pos, r)
{
}
};
}

33
game/state/player.cpp Normal file
View file

@ -0,0 +1,33 @@
#include "player.hpp"
#include <iostream>
#include "commands.hpp"
#include "ship.hpp"
namespace game {
void Player::popCommand()
{
//std::cout<<"popCommand()" << std::endl;
Command *cmd = commands.front();
commands.erase(commands.begin());
delete(cmd);
}
Command *Player::peekCommand()
{
return commands.front();
}
bool Player::hasCommandInQueue()
{
return !commands.empty();
}
void Player::addCommand(Command *cmd)
{
commands.push_back(cmd);
}
}

36
game/state/player.hpp Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include <deque>
#include <vector>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
namespace game {
class Ship;
class Command;
class Missile;
class Player {
public:
int id;
bool alive;
float energy;
float deadTimeCounter;
Ship *ship;
std::vector<Missile*> missiles;
Player(int id) : id(id), alive(true), energy(0.0), ship(nullptr)
{
}
void addCommand(Command *cmd);
bool hasCommandInQueue();
Command *peekCommand();
void popCommand();
private:
std::deque<Command*> commands;
};
}

0
game/state/ship.cpp Normal file
View file

12
game/state/ship.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include "object.hpp"
namespace game {
class Ship : public Object {
public:
Ship(const glm::vec2 &pos, float r) : Object(pos, r)
{
}
};
}

262
game/state/state.cpp Normal file
View file

@ -0,0 +1,262 @@
#include "state.hpp"
#include <cmath>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/gtx/norm.hpp>
#include "object.hpp"
#include "missile.hpp"
#include "player.hpp"
#include "planet.hpp"
#include "ship.hpp"
#include "commands.hpp"
#include "util.hpp"
namespace game {
void State::init()
{
m_shipRadius = 0.05;
m_maxMissileDistance = 2.0;
m_playerRespawnTime = 2.0;
m_defaultEnergy = 10.0;
bool planetsOnCircle = false;
int numPlanets = 10;
for (int i=0; i<numPlanets; i++) {
float t = i / static_cast<float>(numPlanets);
t *= 2.0*M_PI;
// distribute but not in the center
int tries = 0;
glm::vec2 pos;
do {
float cr = 0.7;
if (planetsOnCircle) {
pos = cr * glm::vec2(std::sin(t), std::cos(t));
} else {
pos = cr * util::randv2_m1_1();
}
} while(glm::length(pos) < 0.2 && tries++ < 1000);
planets.push_back(new Planet(pos, 0.03 + 0.07*util::randf_0_1()));
}
}
bool State::spawnShipForPlayer(Player *player)
{
glm::vec2 spawnPos;
if (!findFreePositionWithRadius(5.0 * m_shipRadius, spawnPos)) {
return false;
}
player->ship = new Ship(spawnPos, m_shipRadius);
ships.push_back(player->ship);
player->energy = m_defaultEnergy;
return true;
}
void State::addPlayer(int playerId)
{
Player *player = new Player(playerId);
players.push_back(player);
}
void State::playerLeft(int playerId)
{
(void) playerId;
}
void State::advancePlayerShipSpawns(float dt)
{
(void) dt;
for (Player *player : players) {
if (!player->alive) {
player->deadTimeCounter += dt;
if (player->deadTimeCounter >= m_playerRespawnTime) {
player->deadTimeCounter = 0;
player->alive = true;
std::cout<<"respawning player " << player->id << " now!" << std::endl;
}
}
if (player->alive && player->ship == NULL) {
if (!spawnShipForPlayer(player)) {
std::cout<<"could not spawn ship for player!" << std::endl;
}
}
}
}
void State::advancePlayerCommands(float dt)
{
(void) dt;
for (Player *player : players) {
if (player->alive) {
player->energy += dt;
}
// try to execute as much queued commands as possible.
while (player->hasCommandInQueue()) {
Command *command = player->peekCommand();
if (!command->allowed(player, this)) {
break;
}
command->apply(player, this);
player->popCommand();
}
}
}
void State::playerKillsPlayer(Player *killer, Player *victim)
{
if (killer == nullptr || victim == nullptr) {
std::cerr <<"error: killer / victim is NULL!" << std::endl;
exit(-1);
return;
}
std::cout<<"player " << killer->id << " killed " << victim->id << std::endl;
// destroy ship
size_t i;
for (i=0; i<ships.size(); i++) {
if (ships[i] == victim->ship) {
ships.erase(ships.begin() + i);
break;
}
}
delete(victim->ship);
victim->ship = nullptr;
victim->alive = false;
victim->deadTimeCounter = 0.0;
// TODO
// add points
//
// TODO
// message
//
// TODO
// respawn timer
}
void State::advancePlayerMissiles(float dt)
{
// advance missiles
for (Player *player : players) {
size_t i = 0;
while (i<player->missiles.size()) {
Missile *missile = player->missiles[i];
//std::cout<<"missile: " << (long unsigned int) missile << std::endl;
const Missile::Event evt = missile->advance(this, dt);
if (evt.hit == Missile::HitObject::Nothing) {
i++;
} else {
switch(evt.hit) {
case Missile::HitObject::HitPlanet:
//std::cout<<"hit Planet" << std::endl;
break;
case Missile::HitObject::HitPlayer:
//std::cout<<"hit Player" << std::endl;
playerKillsPlayer(playerForId(evt.playerIdKiller), playerForId(evt.playerIdVictim));
break;
case Missile::HitObject::LeftUniverse:
//std::cout<<"missile left the universe." << std::endl;
break;
default:
break;
}
player->missiles.erase(player->missiles.begin() + i);
delete(missile);
//std::cout<<std::endl;
}
// TODO
// add trace to the missile
}
}
}
void State::advance(float dt)
{
//std::cout<<"advance ship spawns" << std::endl;
advancePlayerShipSpawns(dt);
//std::cout<<"advance commands" << std::endl;
advancePlayerCommands(dt);
//std::cout<<"advance missiles" << std::endl;
advancePlayerMissiles(dt);
}
Player *State::playerForId(int playerId)
{
for (Player *p : players) {
if (p->id == playerId) {
return p;
}
}
return nullptr;
}
bool State::findFreePositionWithRadius(float radius, glm::vec2 &pos)
{
bool first = ships.size() == 0;
int tries = 0;
while(true) {
bool noCollision = true;
//pos = glm::linearRand2(-1.0, 1.0);
if (first) {
first = false;
pos = glm::vec2(0.0, 0.0);
} else {
pos = glm::vec2(util::randf_0_1(), util::randf_m1_1());
//glm::linearRand(-1.0, 1.0), glm::linearRand(-1.0, 1.0));
}
for (Planet *p : planets) {
if (glm::distance(p->position, pos) <= radius) {
noCollision = false;
break;
}
}
if (noCollision) {
return true;
}
if (tries++ >= 1000) {
pos = glm::vec2(0.0, 0.0);
return false;
}
}
}
void State::commandForPlayer(int playerId, game::Command *cmd)
{
Player *player = playerForId(playerId);
if (player != nullptr) {
player->addCommand(cmd);
}
}
}

64
game/state/state.hpp Normal file
View file

@ -0,0 +1,64 @@
#pragma once
#include <vector>
#include <queue>
#include <iostream>
#include <glm/vec2.hpp>
namespace game {
class Command;
class Missile;
class Player;
class Planet;
class Ship;
// trace of a missile. exists without a missile at player.
//class Trace {
//public:
// std::vector<glm::vec2> points;
//};
class State {
public:
void init();
void advance(float dt);
// the (network) layer calling these three functions should keep id's
// unique and give one (network) input an id.
void addPlayer(int playerId);
void playerLeft(int playerId);
void commandForPlayer(int playerId, Command *cmd);
// lookup. return nullptr on invalid playerId
Player *playerForId(int playerId);
float maxMissileDistance() const { return m_maxMissileDistance; }
float shipRadius() const { return m_shipRadius; }
std::vector<Planet*> planets;
std::vector<Ship*> ships;
std::vector<Player*> players;
private:
void playerKillsPlayer(Player *killer, Player *victim);
void advancePlayerShipSpawns(float dt);
void advancePlayerCommands(float dt);
void advancePlayerMissiles(float dt);
// try to spawn a ship for this player.
// return true on success, false on failure to find a spot.
bool spawnShipForPlayer(Player *player);
// find some place where nothing is placed nearby (ships/planets).
// usefule for spanwing things
bool findFreePositionWithRadius(float r, glm::vec2 &pos);
float m_maxMissileDistance;
float m_playerRespawnTime;
float m_shipRadius;
float m_defaultEnergy;
};
};

0
game/triangle_window.cpp Normal file
View file

36
game/triangle_window.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include "opengl.h"
class TriangleWindow : public endofthejedi::GLWindow {
private:
protected:
void init() override {
glClearColor(0.5f, 0.6f, 0.7f, 1.0f);
resize();
glEnable(GL_DEPTH_TEST);
}
void render(double time) override {
(void) time;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(0.0f, -1.0f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f(1.0f, 1.0f, 0.0f);
glEnd();
}
void resize() override { glViewport(0, 0, getwidth(), getheight()); }
public:
TriangleWindow(unsigned int width, unsigned int height)
: endofthejedi::GLWindow(width, height)
{
}
};

25
game/util.cpp Normal file
View file

@ -0,0 +1,25 @@
#include "util.hpp"
#include <cstdlib>
namespace util {
float randf_m1_1()
{
return 2.0f*randf_0_1() - 1.0f;
}
float randf_0_1()
{
return static_cast<float>(static_cast<double>(rand()) / (double) RAND_MAX);
}
glm::vec2 randv2_m1_1()
{
return glm::vec2(randf_m1_1(), randf_m1_1());
}
glm::vec2 randv2_0_1()
{
return glm::vec2(randf_0_1(), randf_0_1());
}
}

12
game/util.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <glm/vec2.hpp>
namespace util {
float randf_0_1();
float randf_m1_1();
glm::vec2 randv2_m1_1();
glm::vec2 randv2_0_1();
}