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) {