Compare commits

..

5 commits

Author SHA1 Message Date
1a79679c21 Added representation of occupied squares and a way to retrieve them for A* implementation
Updated UML
2019-06-08 02:32:06 +02:00
d53f8b32e9 Moved the clock check outside of the event loop
It caused the game to update only if events were received (Mouse movement, button press, etc)
2019-06-08 01:54:58 +02:00
81364c7b4c Level updates are now triggered at timed intervals 2019-06-08 01:14:52 +02:00
dafc442512 Implemented move function for entities
Does not consider speed (TODO ?)
2019-06-07 22:38:55 +02:00
3666b9e7e9 Entities are now constructable
Added utility function to display error window
Added textures
Added font
Added a way to choose resources location
2019-06-07 22:20:34 +02:00
18 changed files with 269 additions and 47 deletions

View file

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

BIN
resources/Building.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

BIN
resources/Head_Boy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

View file

@ -2,5 +2,6 @@
<Level w="10" h="10" textureId="0">
<Entity x="0" y="0" type="Citizen"/>
<Entity x="0" y="1" type="Citizen"/>
<Entity x="1" y="1" type="House"/>
<Entity x="10" y="10" type="Significant" textureId="1"/>
<Entity x="1" y="1" w="8" h="8" type="House" textureId="2"/>
</Level>

BIN
resources/verdana.ttf Normal file

Binary file not shown.

View file

@ -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)
sfml-system
pugixml)

View file

@ -6,36 +6,95 @@
const std::map<std::string,EntityType> 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<float>(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<std::pair<uint8_t, uint8_t>> Entity::getOccupiedSquares() const
{
std::vector<std::pair<uint8_t, uint8_t>> 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<w;i++)
{
for(int j = 0;j<h;j++)
{
occupiedSquares.emplace_back(x+i,y+j);
}
}
return std::move(occupiedSquares);
}

View file

@ -7,6 +7,7 @@
#include <SFML/Graphics.hpp>
#include <pugixml.hpp>
#include <cinttypes>
#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<std::pair<uint8_t, uint8_t>> getOccupiedSquares() const;
/// Position of the target of the current action on the map
sf::Vector2i target;
private:
static const std::map<std::string,EntityType> entityTypeLookup;
EntityType type;
// As it contains position, size and orientation, we do not need anything more
sf::RectangleShape shape;

View file

@ -7,22 +7,20 @@
#include "Game.h"
Game::Game(const std::vector<std::string>& levels, const std::vector<std::string>& textures)
Game::Game(std::vector<std::string>& levels, std::vector<std::string>& 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<Level>(document,textures);
@ -34,11 +32,39 @@ void Game::loadTextures()
for(const std::string& filePath : textureFiles)
{
textures.emplace_back(std::make_unique<sf::Texture>());
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);
}

View file

@ -11,14 +11,14 @@
#include "Level.h"
#include "Entity.h"
#include "Utils.h"
// Used for convenience
using TextureStore = std::vector<std::unique_ptr<sf::Texture>>;
class Game {
public:
Game(const std::vector<std::string>& levels, const std::vector<std::string>& textures);
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...)
@ -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<std::string> levelFiles;

View file

@ -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<std::pair<uint8_t,uint8_t>> 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<std::pair<uint8_t,uint8_t>> 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<std::pair<uint8_t,uint8_t>> 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;
}

View file

@ -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<Entity> entities;
const TextureStore& textures;
//
// Pathfinding
//
std::vector<std::pair<uint8_t,uint8_t>> occupiedSquares;
Orientation findPath(sf::Vector2i start, sf::Vector2i end, int sign);
};

39
src/Utils.cpp Normal file
View file

@ -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();
}
}
}

View file

@ -6,12 +6,19 @@
#define PROJECT_MAAT_UTILS_H
#include <cstdint>
#include <string>
#include <SFML/Graphics.hpp>
#include <iostream>
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);
}

View file

@ -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)
pthread
sfml-window
sfml-graphics
sfml-system
pugixml)

View file

@ -3,6 +3,23 @@
//
#include <gtest/gtest.h>
#include <vector>
#include "Game.h"
TEST(Setup,GameSetup)
{
// TODO : Think about parsing from file ? Currently cumbersome and error-prone
std::vector<std::string> textures = {"Head_Boy.png","Head_Significant_Boy.png","Building.png"};
std::vector<std::string> 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)
{