Added first UI elements

Rules can be created from the UI
Game can be stopped/started from the UI
Current level can be reset (Load from file again)
Added "Game" target and main file

TODO (A lot):
 - Unit tests
 - Fully functionnal UI
 - Win conditions
 - Level background
 - Level switching/progression
 - Interesting and varied rules
 - Multi-scale pathfinding
 - etc
This commit is contained in:
trotFunky 2019-06-11 00:33:44 +02:00
parent 8199b7d036
commit 60770b5395
16 changed files with 260 additions and 29 deletions

View file

@ -10,7 +10,8 @@ target_link_libraries(engine
sfml-window
sfml-graphics
sfml-system
pugixml)
pugixml
tgui)
if (CMAKE_COMPILER_IS_GNUCXX)
target_compile_options(engine PRIVATE -Wall -Wpedantic -Wextra)

View file

@ -6,10 +6,14 @@
const std::map<std::string,EntityType> Entity::entityTypeLookup = {
{"Citizen",EntityType::Citizen},
{"Significant",EntityType::Significant},
{"Noble",EntityType::Noble},
{"House",EntityType::House},
{"Car",EntityType::Car}};
const std::map<std::string,State> Entity::stateLookup = {
{"Seek",State::Moving},
{"Flee",State::Fleeing}};
Entity::Entity(pro_maat::GridUnit x, pro_maat::GridUnit y, EntityType type, sf::Texture* texture, int width, int height) : type(type)
{
shape = sf::RectangleShape(sf::Vector2f(width*pro_maat::pixelsPerUnit,height*pro_maat::pixelsPerUnit));

View file

@ -15,7 +15,7 @@
enum class EntityType
{
Citizen,
Significant,
Noble,
House,
Car,
};
@ -62,14 +62,15 @@ public:
virtual const std::vector<pro_maat::GridPos> getOccupiedSquares() const;
static const std::map<std::string,EntityType> entityTypeLookup;
static const std::map<std::string,State> stateLookup;
protected:
/// Empty constructor for derived class instanciation
Entity();
private:
static const std::map<std::string,EntityType> entityTypeLookup;
EntityType type;
// As it contains position, size and orientation, we do not need anything more

View file

@ -8,22 +8,25 @@
Game::Game(std::vector<std::string>& levels, std::vector<std::string>& textures) : levelFiles(std::move(levels)),
textureFiles(std::move(textures))
textureFiles(std::move(textures)), running(false), ruleCount(0)
{
loadTextures();
}
void Game::loadLevel(int levelId)
void Game::loadLevel(int levelID)
{
// TODO : Reset rules when reseting level
running = false;
pugi::xml_document document;
pugi::xml_parse_result result = document.load_file((pro_maat::levelFolder+levelFiles.at(levelId)).c_str());
pugi::xml_parse_result result = document.load_file((pro_maat::levelFolder+levelFiles.at(levelID)).c_str());
if(!result)
{
pro_maat::errorWindow(result.description());
}
currentLevel = std::make_unique<Level>(document,textures);
currentLevel = std::make_unique<Level>(document,textures,levelID);
}
void Game::loadTextures()
@ -42,6 +45,9 @@ void Game::runGame()
sf::Clock clock;
tgui::Gui gui(window);
addWidgets(gui);
while (window.isOpen())
{
sf::Event event;
@ -51,8 +57,10 @@ void Game::runGame()
{
window.close();
}
gui.handleEvent(event);
}
if (clock.getElapsedTime().asMilliseconds() >= 200)
if (running && clock.getElapsedTime().asMilliseconds() >= 200)
{
currentLevel->runStep();
clock.restart();
@ -60,11 +68,135 @@ void Game::runGame()
window.clear(sf::Color::White);
// TODO : Consider drawing in a sf::RenderTexture to allow positioning the level at a fixed position ?
currentLevel->render(window);
gui.draw();
window.display();
}
}
void Game::addWidgets(tgui::Gui& gui)
{
tgui::Button::Ptr button = tgui::Button::create("Start");
button->setSize("10%","5%");
button->setPosition("89%","94%");
button->connect("pressed",&Game::setRunning,this,true,std::ref(gui));
gui.add(button,"start");
// TODO : Use clone/copy ?
button = tgui::Button::create("Stop");
button->setSize("10%","5%");
button->setPosition("79%","94%");
button->connect("pressed",&Game::setRunning,this,false,std::ref(gui));
gui.add(button,"stop");
button = tgui::Button::create("Reset");
button->setSize("10%","5%");
button->setPosition("69%","94%");
button->connect("pressed",&Game::loadLevel,this,currentLevel->getLevelID());
gui.add(button,"reset");
button = tgui::Button::create("Add a new rule");
button->setSize("10%","5%");
button->setPosition("89%","6%");
button->connect("pressed",&Game::addRule,this,std::ref(gui));
gui.add(button,"add");
}
Game::operator bool() const
{
return (currentLevel ? true : false);
}
//
// Widget callbacks
//
void Game::setRunning(bool newState, tgui::Gui& gui)
{
running = newState;
// TODO : Iterate over all rules
auto affectedTypeCombo = gui.get<tgui::ComboBox>("AffectedType0");
if(!affectedTypeCombo) return;
auto targetTypeCombo = gui.get<tgui::ComboBox>("TargetType0");
if(!targetTypeCombo) return;
auto actionCombo = gui.get<tgui::ComboBox>("Action0");
if(!actionCombo) return;
// If the rule is complete only
if(!affectedTypeCombo->getSelectedItem().isEmpty() &&
!targetTypeCombo->getSelectedItem().isEmpty() &&
!actionCombo->getSelectedItem().isEmpty())
{
currentLevel->addRule(Entity::entityTypeLookup.at(affectedTypeCombo->getSelectedItem()),
Entity::stateLookup.at(actionCombo->getSelectedItem()),
Entity::entityTypeLookup.at(targetTypeCombo->getSelectedItem()));
}
}
void Game::addRule(tgui::Gui& gui)
{
if(ruleCount >= pro_maat::maxRules) return;
// To update names with indexes
std::stringstream string;
tgui::ComboBox::Ptr combo = tgui::ComboBox::create();
combo->addItem("Citizen");
combo->addItem("Noble");
combo->setPosition("85%","5%");
// TODO : Move height according to number of rules
// Keep the proportions while moving
// sf::Vector2f tempPos = combo->getPosition();
// tempPos.y += tempPos.y*2*ruleCount;
// combo->setPosition(tempPos);
combo->setSize("5%","2%");
string << "AffectedType" << ruleCount;
gui.add(combo,string.str());
string.str("");
// TODO : Use clone/copy ?
combo = tgui::ComboBox::create();
combo->addItem("Citizen");
combo->addItem("Noble");
combo->addItem("House");
combo->setSize("5%","2%");
combo->setPosition("95%","5%");
// Keep the proportions while moving
// tempPos = combo->getPosition();
// tempPos.y += tempPos.y*2*ruleCount;
// combo->setPosition(tempPos);
string << "TargetType" << ruleCount;
gui.add(combo,string.str());
string.str("");
combo = tgui::ComboBox::create();
combo->addItem("Seek");
combo->addItem("Flee");
combo->setSize("4%","2%");
combo->setPosition("90.5%","5%");
// Keep the proportions while moving
// tempPos = combo->getPosition();
// tempPos.y += tempPos.y*2*ruleCount;
// combo->setPosition(tempPos);
string << "Action" << ruleCount;
gui.add(combo,string.str());
string.str("");
// TODO : Move the button down with each rule then hide it
// TODO : Hide button when max rules hit
// FIXME : Cannot hide ?
gui.get<tgui::Button>("add")->setSize("0%","0%");
gui.get<tgui::Button>("add")->setTextSize(0);
gui.get<tgui::Button>("add")->setPosition("-10%","-10%");
ruleCount++;
}

View file

@ -6,21 +6,22 @@
#define SRC_GAME_H
#include <SFML/Graphics.hpp>
#include <TGUI/TGUI.hpp>
#include <vector>
#include <iostream>
#include <sstream>
#include "Level.h"
#include "Entity.h"
#include "Utils.h"
class Game {
public:
Game(std::vector<std::string>& levels, std::vector<std::string>& textures);
/// Loads the level of corresponding ID from Game::levelFiles
/// Closes the program if there is a fatal error (Missing file, bad file...)
void loadLevel(int levelId);
void loadLevel(int levelID);
std::unique_ptr<Level> currentLevel;
@ -37,7 +38,18 @@ private:
/// Stores pointers to textures for future use
pro_maat::TextureStore textures;
bool running;
unsigned int ruleCount;
void loadTextures();
void addWidgets(tgui::Gui& gui);
//
// Widget callbacks
//
void setRunning(bool newState,tgui::Gui& gui);
void addRule(tgui::Gui& gui);
};

View file

@ -5,8 +5,8 @@
#include "Level.h"
Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore)
: size(xmlDoc.child("Level").attribute("w").as_int(),xmlDoc.child("Level").attribute("h").as_int()),
Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore, int id)
: levelID(id), size(xmlDoc.child("Level").attribute("w").as_int(),xmlDoc.child("Level").attribute("h").as_int()),
textures(textureStore)
{
pugi::xml_node levelNode = xmlDoc.child("Level");
@ -23,8 +23,8 @@ Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& tex
}
// FIXME : For testing purposes
addRule(EntityType::Significant,State::Fleeing,EntityType::Citizen);
addRule(EntityType::Citizen,State::Moving,EntityType::Significant);
// addRule(EntityType::Noble,State::Moving,EntityType::House);
// addRule(EntityType::Citizen,State::Moving,EntityType::Citizen);
}
void Level::addRule(EntityType affectedEntities, const State targetState, EntityType targetEntities)
@ -128,6 +128,7 @@ Orientation Level::findPath(pro_maat::GridPos start, pro_maat::GridPos goal, int
// Expand from the open node with the smallest estimated cost
pro_maat::GridPos currentNode = std::min_element(estimatedCosts.begin(),estimatedCosts.end(),compWithOpen)->first;
// FIXME : Lots of bad cases when fleeing
if(currentNode == goal)
{
if(currentNode == start)
@ -198,3 +199,8 @@ Orientation Level::findPath(pro_maat::GridPos start, pro_maat::GridPos goal, int
// If we did not find a path, do not move
return Orientation::None;
}
int Level::getLevelID()
{
return levelID;
}

View file

@ -20,7 +20,7 @@
class Level {
public:
Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore);
Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore, int id = 0);
/// Add a new rule on top of existing ones, thus with a lower priority
///
@ -32,7 +32,11 @@ public:
void render(sf::RenderWindow& renderWindow) const;
void runStep();
int getLevelID();
private:
int levelID;
const pro_maat::GridPos size;
std::vector<std::shared_ptr<Entity>> entities;

View file

@ -20,6 +20,8 @@ using GridUnit = int16_t;
using GridPos = std::pair<GridUnit,GridUnit>;
static constexpr uint8_t pixelsPerUnit = 30;
static constexpr unsigned int maxRules = 5;
static constexpr char levelFolder[] = "resources/";
static constexpr char textureFolder[] = "resources/";
static constexpr char fontFolder[] = "resources/";