diff --git a/README.md b/README.md
index 576b9e4..acb88d6 100644
--- a/README.md
+++ b/README.md
@@ -13,19 +13,19 @@ This game is aimed to be a puzzle game in which the player sets different laws f
- [ ] Level
- [x] Structure
- - [ ] Parsing from XML
+ - [x] Parsing from XML
- [ ] A*
- [ ] Entities
- [x] Structure
- - [ ] Parsing from XML
- - [ ] Moving
+ - [x] Parsing from XML
+ - [x] Moving
- [ ] Decorators (Rules)
- [ ] Rules
- [ ] Creation
- [ ] Interaction with entities
- [ ] Parsing of rules and creation of subsequent decorators
- [ ] Graphics
- - [ ] Scene rendering
+ - [x] Scene rendering
- [ ] UI
- [ ] Menu ?
- [ ] Gameloop
diff --git a/UML_Class_Diagram.png b/UML_Class_Diagram.png
index 5f40a3d..7800665 100644
Binary files a/UML_Class_Diagram.png and b/UML_Class_Diagram.png differ
diff --git a/resources/Building.png b/resources/Building.png
new file mode 100644
index 0000000..32fc6fa
Binary files /dev/null and b/resources/Building.png differ
diff --git a/resources/Head_Boy.png b/resources/Head_Boy.png
new file mode 100644
index 0000000..3a741fc
Binary files /dev/null and b/resources/Head_Boy.png differ
diff --git a/resources/Head_Significant_Boy.png b/resources/Head_Significant_Boy.png
new file mode 100644
index 0000000..8bca5c3
Binary files /dev/null and b/resources/Head_Significant_Boy.png differ
diff --git a/resources/test_level.xml b/resources/test_level.xml
index 7b4af30..65faa1f 100644
--- a/resources/test_level.xml
+++ b/resources/test_level.xml
@@ -2,5 +2,6 @@
-
+
+
\ No newline at end of file
diff --git a/resources/verdana.ttf b/resources/verdana.ttf
new file mode 100644
index 0000000..0176950
Binary files /dev/null and b/resources/verdana.ttf differ
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index df21f86..3f9b184 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,9 +4,10 @@ project(project_maat)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH})
-add_library(engine Level.cpp Level.h Entity.cpp Entity.h Game.cpp Game.h Utils.h Utils.h)
+add_library(engine Level.cpp Level.h Entity.cpp Entity.h Game.cpp Game.h Utils.h Utils.h Utils.cpp)
target_link_libraries(engine
sfml-window
sfml-graphics
- sfml-system)
\ No newline at end of file
+ sfml-system
+ pugixml)
\ No newline at end of file
diff --git a/src/Entity.cpp b/src/Entity.cpp
index fd4cfca..921df1f 100644
--- a/src/Entity.cpp
+++ b/src/Entity.cpp
@@ -6,36 +6,95 @@
const std::map Entity::entityTypeLookup = {
{"Citizen",EntityType::Citizen},
- {"Player",EntityType::Player},
+ {"Significant",EntityType::Significant},
{"House",EntityType::House},
{"Car",EntityType::Car}};
-Entity::Entity(int x, int y, EntityType type, int width, int height)
+Entity::Entity(int x, int 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));
+ // Sets the origin at the center of the entity
+ shape.setOrigin(shape.getSize()/2.0f);
+ // Adjust position for offset origin
+ shape.setPosition((x+0.5*width)*pro_maat::pixelsPerUnit,(y+0.5*width)*pro_maat::pixelsPerUnit);
+ shape.setTexture(texture);
+ currentState = State::Idle;
+ nextState = State::Idle;
+ target = sf::Vector2i(shape.getPosition());
+ nextTarget = target;
}
-Entity::Entity(const pugi::xml_node& entityNode)
+Entity::Entity(const pugi::xml_node& entityNode, sf::Texture* texture)
+ : Entity(entityNode.attribute("x").as_int(),
+ entityNode.attribute("y").as_int(),
+ entityTypeLookup.at(entityNode.attribute("type").as_string()),
+ texture,
+ entityNode.attribute("w").as_int(1),
+ entityNode.attribute("h").as_int(1)) {}
+
+void Entity::move(Orientation orientation)
{
+ // TODO : Add speed ?
+ shape.setRotation(static_cast(orientation));
-}
-
-void Entity::render(sf::RenderWindow& renderWindow) const
-{
-
-}
-
-void Entity::move()
-{
+ sf::Vector2f movementVector(0,0);
+ switch (orientation)
+ {
+ case Orientation::North:
+ movementVector.y = -pro_maat::pixelsPerUnit;
+ break;
+ case Orientation::East:
+ movementVector.x = pro_maat::pixelsPerUnit;
+ break;
+ case Orientation::South:
+ movementVector.y = pro_maat::pixelsPerUnit;
+ break;
+ case Orientation::West:
+ movementVector.x = -pro_maat::pixelsPerUnit;
+ break;
+ }
+ shape.setPosition(shape.getPosition()+movementVector);
}
void Entity::update()
-{
-
-}
+{}
const sf::RectangleShape& Entity::getShape() const
{
return(shape);
}
+
+const State Entity::getState() const
+{
+ return currentState;
+}
+
+const sf::Vector2i Entity::getPosition() const
+{
+ // Safe : size is a multiple of pro_maat::pixelsPerUnit
+ uint8_t x = (shape.getPosition().x-0.5*shape.getSize().x)/pro_maat::pixelsPerUnit;
+ uint8_t y = (shape.getPosition().y-0.5*shape.getSize().y)/pro_maat::pixelsPerUnit;
+ return sf::Vector2i(x,y);
+}
+
+const std::vector> Entity::getOccupiedSquares() const
+{
+ std::vector> occupiedSquares;
+ // Safe : size is a multiple of pro_maat::pixelsPerUnit
+ uint8_t w = shape.getSize().x/pro_maat::pixelsPerUnit;
+ uint8_t h = shape.getSize().y/pro_maat::pixelsPerUnit;
+ uint8_t x = shape.getPosition().x/pro_maat::pixelsPerUnit - 0.5*w;
+ uint8_t y = shape.getPosition().y/pro_maat::pixelsPerUnit - 0.5*h;
+
+ for(int i = 0;i
#include
+#include
#include "Utils.h"
@@ -14,7 +15,7 @@
enum class EntityType
{
Citizen,
- Player,
+ Significant,
House,
Car,
};
@@ -29,10 +30,10 @@ enum class State
enum class Orientation
{
- Nort,
- East,
- South,
- West,
+ North = 0,
+ East = 90,
+ South = 180,
+ West = 270,
};
@@ -40,20 +41,26 @@ class Entity
{
public:
/// x,y, width and height are in grid coordinates
- Entity(int x, int y, EntityType type, int width = 1, int height = 1);
- Entity(const pugi::xml_node& entityNode);
+ Entity(int x, int y, EntityType type, sf::Texture* texture, int width = 1, int height = 1);
+ explicit Entity(const pugi::xml_node& entityNode, sf::Texture* texture);
- void render(sf::RenderWindow& renderWindow) const;
- void move();
+ void move(Orientation orientation);
void update();
const sf::RectangleShape& getShape() const;
+ const State getState() const;
+ const sf::Vector2i getPosition() const;
+ // Don't like it : iterates over every square every tick
+ const std::vector> getOccupiedSquares() const;
+
/// Position of the target of the current action on the map
sf::Vector2i target;
private:
static const std::map entityTypeLookup;
+ EntityType type;
+
// As it contains position, size and orientation, we do not need anything more
sf::RectangleShape shape;
diff --git a/src/Game.cpp b/src/Game.cpp
index 6983d39..b1c7df0 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -7,22 +7,20 @@
#include "Game.h"
-Game::Game(const std::vector& levels, const std::vector& textures)
+Game::Game(std::vector& levels, std::vector& textures) : levelFiles(std::move(levels)),
+ textureFiles(std::move(textures))
{
-
-
loadTextures();
}
void Game::loadLevel(int levelId)
{
pugi::xml_document document;
- pugi::xml_parse_result result = document.load_file(levelFiles.at(levelId).c_str());
+ pugi::xml_parse_result result = document.load_file((pro_maat::levelFolder+levelFiles.at(levelId)).c_str());
if(!result)
{
- std::cerr << "Error while loading level :\n" << "\t" << result.description() << std::endl;
- exit(-1);
+ pro_maat::errorWindow(result.description());
}
currentLevel = std::make_unique(document,textures);
@@ -34,11 +32,39 @@ void Game::loadTextures()
for(const std::string& filePath : textureFiles)
{
textures.emplace_back(std::make_unique());
- textures.back()->loadFromFile(filePath);
+ textures.back()->loadFromFile(pro_maat::textureFolder+filePath);
}
}
void Game::runGame()
{
+ sf::RenderWindow window(sf::VideoMode(1280,1024),"Project Maat");
+ sf::Clock clock;
+
+ while (window.isOpen())
+ {
+ sf::Event event;
+ while (window.pollEvent(event))
+ {
+ if (event.type == sf::Event::Closed)
+ {
+ window.close();
+ }
+ }
+ if (clock.getElapsedTime().asMilliseconds() >= 200)
+ {
+ currentLevel->runStep();
+ clock.restart();
+ }
+ window.clear(sf::Color::White);
+ // TODO : Consider drawing in a sf::RenderTexture to allow positioning the level at a fixed position ?
+ currentLevel->render(window);
+ window.display();
+ }
+}
+
+Game::operator bool() const
+{
+ return (currentLevel ? true : false);
}
diff --git a/src/Game.h b/src/Game.h
index da9f7b2..aa149d7 100644
--- a/src/Game.h
+++ b/src/Game.h
@@ -11,14 +11,14 @@
#include "Level.h"
#include "Entity.h"
-
+#include "Utils.h"
// Used for convenience
using TextureStore = std::vector>;
class Game {
public:
- Game(const std::vector& levels, const std::vector& textures);
+ Game(std::vector& levels, std::vector& textures);
/// Loads the level of corresponding ID from Game::levelFiles
/// Closes the program if there is a fatal error (Missing file, bad file...)
@@ -29,6 +29,8 @@ public:
// This should not be called before a level has been loaded
void runGame();
+ explicit operator bool() const;
+
private:
/// Store the paths to level files
const std::vector levelFiles;
diff --git a/src/Level.cpp b/src/Level.cpp
index 1c0903b..e5c4e22 100644
--- a/src/Level.cpp
+++ b/src/Level.cpp
@@ -14,17 +14,66 @@ Level::Level(const pugi::xml_document& xmlDoc, const TextureStore& textureStore)
{
if(!strncmp(child.name(),"Entity",6))
{
- entities.emplace_back(child);
+ entities.emplace_back(child,textures.at(child.attribute("textureId").as_int(0)).get());
+
+ // Initialize the occupied squares vector with the new entity's squares
+ std::vector> entitySquares = entities.rbegin()->getOccupiedSquares();
+ std::move(entitySquares.begin(),entitySquares.end(),std::back_inserter(occupiedSquares));
}
}
}
void Level::render(sf::RenderWindow& renderWindow) const
{
-
+ for(const Entity& entity : entities)
+ {
+ renderWindow.draw(entity.getShape());
+ }
}
-void Level::runStep() const
+void Level::runStep()
{
+ std::vector> newOccupiedSquares{};
+ newOccupiedSquares.reserve(occupiedSquares.size());
+ for(Entity& entity: entities)
+ {
+ entity.update();
+ int heuristicSign = 0;
+ switch (entity.getState())
+ {
+ case State::Moving:
+ {
+ heuristicSign = 1;
+ }
+ case State::Fleeing:
+ {
+ if(heuristicSign == 0)
+ {
+ heuristicSign = -1;
+ }
+ if(entity.target != entity.getPosition())
+ {
+ entity.move(findPath(entity.getPosition(),entity.target,heuristicSign));
+ }
+ break;
+ }
+ case State::Waiting:break;
+ case State::Idle:break;
+ }
+
+ // Moves the occupied squares from the entity to the new occupied squares vector
+ std::vector> entitySquares = entity.getOccupiedSquares();
+ std::move(entitySquares.begin(),entitySquares.end(),std::back_inserter(newOccupiedSquares));
+ }
+
+ occupiedSquares.swap(newOccupiedSquares);
+ // Sort the vector as to get O(ln(n)) complexity when searching for a square
+ std::sort(occupiedSquares.begin(),occupiedSquares.end());
+}
+
+Orientation Level::findPath(sf::Vector2i start, sf::Vector2i end, int sign)
+{
+ // TODO : A* which returns the next move
+ return Orientation::East;
}
diff --git a/src/Level.h b/src/Level.h
index 63379f9..681a6a6 100644
--- a/src/Level.h
+++ b/src/Level.h
@@ -20,7 +20,7 @@ public:
Level(const pugi::xml_document& xmlDoc, const TextureStore& textureStore);
void render(sf::RenderWindow& renderWindow) const;
- void runStep() const;
+ void runStep();
private:
const sf::Vector2i size;
@@ -28,6 +28,14 @@ private:
std::vector entities;
const TextureStore& textures;
+
+ //
+ // Pathfinding
+ //
+
+ std::vector> occupiedSquares;
+
+ Orientation findPath(sf::Vector2i start, sf::Vector2i end, int sign);
};
diff --git a/src/Utils.cpp b/src/Utils.cpp
new file mode 100644
index 0000000..4c75c2f
--- /dev/null
+++ b/src/Utils.cpp
@@ -0,0 +1,39 @@
+//
+// Created by trotfunky on 07/06/19.
+//
+
+#include "Utils.h"
+
+namespace pro_maat
+{
+ void errorWindow(const std::string& error)
+ {
+ sf::Font font;
+ if(!font.loadFromFile(std::string(pro_maat::fontFolder)+"verdana.ttf"))
+ {
+ std::cerr << "Error while loading font for error :" << std::endl;
+ std::cerr << error << std::endl;
+ }
+
+ sf::Text errorMessage(error,font);
+ errorMessage.setFillColor(sf::Color::Red);
+ sf::Rect errorArea = errorMessage.getGlobalBounds();
+ errorMessage.setPosition(errorArea.width*(1.5/2-0.5),errorArea.height*(0.5));
+ sf::RenderWindow window(sf::VideoMode(errorArea.width*1.5,errorArea.height*2),"ERROR");
+ while (window.isOpen())
+ {
+ sf::Event event;
+ while(window.pollEvent(event))
+ {
+ if (event.type == sf::Event::Closed || event.type == sf::Event::KeyPressed || event.type == sf::Event::MouseButtonPressed)
+ {
+ window.close();
+ exit(-1);
+ }
+ }
+ window.clear(sf::Color::White);
+ window.draw(errorMessage);
+ window.display();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Utils.h b/src/Utils.h
index 8fa40a5..4632cb0 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -6,12 +6,19 @@
#define PROJECT_MAAT_UTILS_H
#include
-
+#include
+#include
+#include
namespace pro_maat
{
-static constexpr uint8_t pixelsPerUnit = 20;
+static constexpr uint8_t pixelsPerUnit = 50;
+static constexpr char levelFolder[] = "resources/";
+static constexpr char textureFolder[] = "resources/";
+static constexpr char fontFolder[] = "resources/";
+
+void errorWindow(const std::string& error);
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 0924c9a..bdde3da 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -6,7 +6,13 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH})
add_executable(gTests gTests.cpp gTests.cpp)
+target_include_directories(gTests PRIVATE ${PROJECT_SOURCE_DIR}/../src)
+
target_link_libraries(gTests
engine
gtest
- pthread)
\ No newline at end of file
+ pthread
+ sfml-window
+ sfml-graphics
+ sfml-system
+ pugixml)
\ No newline at end of file
diff --git a/tests/gTests.cpp b/tests/gTests.cpp
index 0eccc74..bcdb5ff 100644
--- a/tests/gTests.cpp
+++ b/tests/gTests.cpp
@@ -3,6 +3,23 @@
//
#include
+#include
+
+#include "Game.h"
+
+TEST(Setup,GameSetup)
+{
+ // TODO : Think about parsing from file ? Currently cumbersome and error-prone
+ std::vector textures = {"Head_Boy.png","Head_Significant_Boy.png","Building.png"};
+ std::vector levels = {"test_level.xml"};
+
+ Game game(levels,textures);
+ ASSERT_FALSE(game);
+ game.loadLevel(0);
+ ASSERT_TRUE(game);
+ game.runGame();
+}
+
int main(int argc, char** argv)
{