diff --git a/.cproject b/.cproject index 7f24998..21efad7 100644 --- a/.cproject +++ b/.cproject @@ -16,13 +16,15 @@ - + + @@ -93,7 +98,9 @@ + diff --git a/assets/sprites/player.sprite b/assets/sprites/player.sprite new file mode 100644 index 0000000..fa65df7 --- /dev/null +++ b/assets/sprites/player.sprite @@ -0,0 +1,148 @@ +{ + "name" : "Player", + "anchorX" : 0.5, + "anchorY" : 1, + + "states" : + [ + { + "name" : "Idle right", + "frames" : + [ + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 0, + "width" : 1, + "height" : 2, + "duration" : 1 + } + ] + }, + + { + "name" : "Idle up", + "frames" : + [ + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 2, + "width" : 1, + "height" : 2, + "duration" : 1 + } + ] + }, + + { + "name" : "Idle left", + "frames" : + [ + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 4, + "width" : 1, + "height" : 2, + "duration" : 1 + } + ] + }, + + { + "name" : "Idle down", + "frames" : + [ + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 6, + "width" : 1, + "height" : 2, + "duration" : 1 + } + ] + }, + + { + "name" : "Walking right", + "frames" : + [ + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 0, + "width" : 1, + "height" : 2, + "duration" : 7 + }, + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 1, + "width" : 1, + "height" : 2, + "duration" : 7 + } + ] + }, + + { + "name" : "Walking up", + "frames" : + [ + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 2, + "width" : 1, + "height" : 2, + "duration" : 7 + }, + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 3, + "width" : 1, + "height" : 2, + "duration" : 7 + } + ] + }, + + { + "name" : "Walking left", + "frames" : + [ + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 4, + "width" : 1, + "height" : 2, + "duration" : 7 + }, + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 5, + "width" : 1, + "height" : 2, + "duration" : 7 + } + ] + }, + + { + "name" : "Walking down", + "frames" : + [ + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 6, + "width" : 1, + "height" : 2, + "duration" : 7 + }, + { + "tileSet" : "tilesets/PlayerTiles.png", + "cell" : 7, + "width" : 1, + "height" : 2, + "duration" : 7 + } + ] + } + ] +} \ No newline at end of file diff --git a/assets/player/default.png b/assets/tilesets/PlayerTiles.png similarity index 50% rename from assets/player/default.png rename to assets/tilesets/PlayerTiles.png index 04fa9e4..ec53233 100644 Binary files a/assets/player/default.png and b/assets/tilesets/PlayerTiles.png differ diff --git a/assets_original/player.xcf b/assets_original/player.xcf new file mode 100644 index 0000000..d04c360 Binary files /dev/null and b/assets_original/player.xcf differ diff --git a/build/prepareAssets.py b/build/prepareAssets.py index cd0dc09..505d704 100755 --- a/build/prepareAssets.py +++ b/build/prepareAssets.py @@ -47,7 +47,8 @@ FILE_TYPES = [ ([".png", ".bmp"], "Texture"), ([".level"], "Level"), ([".csv"], "LevelLayer"), - ([".ttf"], "Font"), + ([".ttf"], "Font"), + ([".sprite"], "Sprite"), ] diff --git a/src/Main.cpp b/src/Main.cpp index 71a0f0d..087f8ef 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,8 +1,20 @@ #include +#include + +#include using namespace farmlands; int main() { - return controller::FarmlandsGame().run(); + try + { + return controller::FarmlandsGame().run(); + } + catch (utils::Exception& ex) + { + std::cerr << "Panic: Caught unhandled exception!\n"; + std::cerr << typeid(ex).name() << " : " << ex.message() << "\n"; + std::cerr << "Source: file " << ex.file() << " line " << ex.line() << "\n"; + } } diff --git a/src/controller/GuiController.cpp b/src/controller/GuiController.cpp index 9bd30e9..0346602 100644 --- a/src/controller/GuiController.cpp +++ b/src/controller/GuiController.cpp @@ -38,13 +38,13 @@ void GuiController::initialize(GameState* gameState) // Add a text element auto text = new gui::widgets::TextArea(); - text->setText("Hello world!\nMy name is Tibi!\nThis is a really really long long long, even the longest ever, line.\nThis is a very loooooooooooooooooong word."); - text->setSize(200, 200); + text->setText("Hello world!"); + text->setSize(50, 5); text->setPosition(100, 10); text->setColor(0, 1, 0); text->setBackColor(0.5f, 0, 0, 0.5f); text->setTextSize(11); - text->setHorizontalWrap(gui::widgets::TextHorizontalWrapping::Wrap); + text->setHorizontalWrap(gui::widgets::TextHorizontalWrapping::Ellipsis); text->setVerticalWrap(gui::widgets::TextVerticalWrapping::Trim); text->setAlignment(gui::widgets::TextAlign::BottomRight); m_canvas.addChild(text); diff --git a/src/controller/PlayerController.cpp b/src/controller/PlayerController.cpp index 987c300..9b0f23b 100644 --- a/src/controller/PlayerController.cpp +++ b/src/controller/PlayerController.cpp @@ -7,13 +7,10 @@ #include #include +#include -#include - -namespace farmlands -{ -namespace controller -{ +namespace farmlands { +namespace controller { PlayerController::PlayerController() : m_gameState(nullptr) @@ -26,7 +23,8 @@ PlayerController::~PlayerController() void PlayerController::initialize(GameState* gameState) { - assert(gameState != nullptr); + Assert(gameState != nullptr, "Game state must not be NULL!"); + m_gameState = gameState; } @@ -71,6 +69,9 @@ void PlayerController::updateLogic() { m_gameState->player.posX = newX; m_gameState->player.posY = newY; + m_gameState->player.lastDeltaX = deltaX * deltaMultiplier; + m_gameState->player.lastDeltaY = deltaY * deltaMultiplier; + setDirection(deltaX, deltaY); m_gameState->camera.posX = m_gameState->player.posX; m_gameState->camera.posY = m_gameState->player.posY - 1; @@ -83,5 +84,21 @@ bool PlayerController::canMove(float x, float y) return true; } +static const model::Direction directions[3][3] = +{ + { model::Direction::NorthWest, model::Direction::West, model::Direction::SouthWest }, + { model::Direction::North, model::Direction::South, model::Direction::South }, + { model::Direction::NorthEast, model::Direction::East, model::Direction::SouthEast }, +}; + +void PlayerController::setDirection(float dx, float dy) +{ + int xx = (0 < dx) - (dx < 0); + int yy = (0 < dy) - (dy < 0); + + if (xx != 0 || yy != 0) + m_gameState->player.direction = directions[xx + 1][yy + 1]; +} + } /* namespace controller */ } /* namespace farmlands */ diff --git a/src/controller/PlayerController.h b/src/controller/PlayerController.h index d81712d..0530ff8 100644 --- a/src/controller/PlayerController.h +++ b/src/controller/PlayerController.h @@ -42,6 +42,7 @@ namespace controller { private: bool canMove(float x, float y); + void setDirection(float dx, float dy); GameState* m_gameState; }; diff --git a/src/graphics/GameRenderer.cpp b/src/graphics/GameRenderer.cpp index e769c3f..33feb36 100644 --- a/src/graphics/GameRenderer.cpp +++ b/src/graphics/GameRenderer.cpp @@ -100,26 +100,79 @@ void GameRenderer::renderTileLayers() } } -void GameRenderer::renderPlayer() +void GameRenderer::renderSprite(model::Sprite* sprite, float destX, float destY) { - float posX = xToScreen(m_gameState->player.posX); - float posY = yToScreen(m_gameState->player.posY); + float posX = xToScreen(destX); + float posY = yToScreen(destY); // Obtain texture - int texWidth, texHeight; - SDL_Texture* playerTexture = m_gameState->resManager.texture(resources::R::Player::Default); - SDL_QueryTexture(playerTexture, NULL, NULL, &texWidth, &texHeight); + int texId = sprite->currentFrame().tileSetId; + SDL_Texture* texture = m_gameState->resManager.texture(texId); - // Draw using bottom center of texture as anchor point - SDL_Rect dest = + // Compute src rectangle + SDL_Rect src; + getCell(texture, sprite->currentFrame().tileSetCell, &src.x, &src.y); + src.w = sprite->currentFrame().width * m_gameState->currentLevel->m_cellWidth; + src.h = sprite->currentFrame().height * m_gameState->currentLevel->m_cellHeight; + + // Compute destination rectangle + SDL_Rect dest; + dest.x = posX - sprite->anchorX * src.w * m_gameState->camera.scale; + dest.y = posY - sprite->anchorY * src.h * m_gameState->camera.scale; + dest.w = src.w * m_gameState->camera.scale; + dest.h = src.h * m_gameState->camera.scale; + + // Draw + SDL_RenderCopy(m_gameState->sdlRenderer.internalRenderer(), texture, &src, &dest); + + // Advance animation frame + sprite->advanceTime(1); +} + +void GameRenderer::renderPlayer() +{ + // Compute current state + model::Sprite* sprite = m_gameState->resManager.sprite(resources::R::Sprites::Player); + + bool walking = (m_gameState->player.lastDeltaX != 0 || m_gameState->player.lastDeltaY != 0); + std::string stateName = (walking) ? "Walking " : "Idle "; + + switch(m_gameState->player.direction) { - (int) (posX - texWidth * m_gameState->camera.scale / 2), - (int) (posY - texHeight * m_gameState->camera.scale), - (int) (texWidth * m_gameState->camera.scale), - (int) (texHeight * m_gameState->camera.scale), - }; + case model::Direction::SouthEast: + case model::Direction::East: + case model::Direction::NorthEast: + stateName += "right"; + break; - SDL_RenderCopy(m_gameState->sdlRenderer.internalRenderer(), playerTexture, NULL, &dest); + case model::Direction::North: + stateName += "up"; + break; + + case model::Direction::NorthWest: + case model::Direction::West: + case model::Direction::SouthWest: + stateName += "left"; + break; + + case model::Direction::South: + stateName += "down"; + break; + } + sprite->setState(stateName); + + // Draw + renderSprite(sprite, m_gameState->player.posX, m_gameState->player.posY); +} + +void GameRenderer::getCell(SDL_Texture* texture, int cell, int* outX, int* outY) +{ + int texWidth, texHeight; + SDL_QueryTexture(texture, NULL, NULL, &texWidth, &texHeight); + + // Compute texture coordinates + *outX = (cell * m_gameState->currentLevel->m_cellWidth) % texWidth; + *outY = ((cell * m_gameState->currentLevel->m_cellWidth) / texWidth) * m_gameState->currentLevel->m_cellHeight; } float GameRenderer::xToWorld(float x) diff --git a/src/graphics/GameRenderer.h b/src/graphics/GameRenderer.h index 8af3623..bb4eb05 100644 --- a/src/graphics/GameRenderer.h +++ b/src/graphics/GameRenderer.h @@ -8,6 +8,7 @@ #ifndef GRAPHICS_GAMERENDERER_H_ #define GRAPHICS_GAMERENDERER_H_ +#include #include #include @@ -42,12 +43,15 @@ namespace graphics { void prepareRender(); void renderTileLayers(); void renderPlayer(); + void renderSprite(model::Sprite* sprite, float destX, float destY); float xToWorld(float x); float yToWorld(float y); float xToScreen(float x); float yToScreen(float y); + void getCell(SDL_Texture* texture, int cell, int* outX, int* outY); + GameState* m_gameState; // Size of a cell (scaled) diff --git a/src/model/Player.h b/src/model/Player.h index 38ccff2..92e3582 100644 --- a/src/model/Player.h +++ b/src/model/Player.h @@ -8,6 +8,8 @@ #ifndef MODEL_PLAYER_H_ #define MODEL_PLAYER_H_ +#include + namespace farmlands { namespace model { @@ -16,6 +18,9 @@ namespace model { struct Player { float posX, posY; + float lastDeltaX, lastDeltaY; + Direction direction; + int inventorySelection = -1; int inventory[PLAYER_INVENTORY_SIZE]; diff --git a/src/model/Sprite.cpp b/src/model/Sprite.cpp new file mode 100644 index 0000000..3d57a74 --- /dev/null +++ b/src/model/Sprite.cpp @@ -0,0 +1,102 @@ +/* + * Sprite.cpp + * + * Created on: Nov 29, 2016 + * Author: tibi + */ + +#include +#include + +#include + +namespace farmlands { +namespace model { + +Sprite::Sprite() + : anchorX(0), anchorY(0), + m_states(), + m_currentState(0), + m_currentFrame(0), + m_currentFrameTimeLeft(0) +{ +} + +Sprite::~Sprite() +{ +} + +void Sprite::addState(const SpriteState& state) +{ + Assert(state.frames.size() > 0, "State must have at least one frame!"); +#ifdef BUILD_DEBUG + uint32_t totalDuration = 0; + for (auto frame : state.frames) + totalDuration += frame.duration; + + Assert(totalDuration > 0, "State must have a frame which last at least one tick."); +#endif + Assert(m_stateNames.count(state.name) == 0, "A state with the same name already added!"); + + m_states.push_back(state); + m_stateNames.emplace(state.name, m_states.size() - 1); +} + +void Sprite::setState(size_t stateId) +{ + Assert(stateId < m_states.size(), "Inexistent state."); + + // Avoid resetting state + if (stateId == m_currentState) + return; + + m_currentState = stateId; + m_currentFrame = 0; + m_currentFrameTimeLeft = currentFrame().duration * 1; +} + +void Sprite::setState(const std::string& name) +{ + Assert(m_stateNames.count(name) > 0, "Inexistent state."); + setState(m_stateNames.at(name)); +} + +void Sprite::advanceTime(uint32_t steps) +{ + Assert(m_states.size() > 0, "Sprite must have at least one state!"); + + while (steps > 0) + { + // There is time left in the current frame? + if (m_currentFrameTimeLeft > 0) + { + uint32_t sub = std::min(steps, m_currentFrameTimeLeft); + m_currentFrameTimeLeft -= sub; + steps -= sub; + } + + if (m_currentFrameTimeLeft == 0) + { + // Move to the next frame + if (++m_currentFrame >= currentState().frames.size()) + m_currentFrame = 0; + + m_currentFrameTimeLeft = currentFrame().duration * 1; + } + } +} + +SpriteState& Sprite::currentState() +{ + Assert(m_states.size() > 0, "Sprite must have at least one state!"); + return m_states.at(m_currentState); +} + +Frame& Sprite::currentFrame() +{ + Assert(currentState().frames.size() > 0, "State must have at least one frame!"); + return currentState().frames.at(m_currentFrame); +} + +} /* namespace model */ +} /* namespace farmlands */ diff --git a/src/model/Sprite.h b/src/model/Sprite.h new file mode 100644 index 0000000..3cb3588 --- /dev/null +++ b/src/model/Sprite.h @@ -0,0 +1,88 @@ +/* + * Sprite.h + * + * Created on: Nov 29, 2016 + * Author: tibi + */ + +#ifndef MODEL_SPRITE_H_ +#define MODEL_SPRITE_H_ + +#include + +#include +#include + +namespace farmlands { +namespace model { + + /** + * Defines an animation frame + */ + struct Frame + { + uint32_t tileSetId; + uint32_t tileSetCell; + uint32_t width, height; + uint32_t duration; + }; + + /** + * Defines a sprite state (aka an animation). + */ + struct SpriteState + { + std::string name; + std::vector frames; + }; + + /** + * Defines a sprite + */ + class Sprite + { + public: + Sprite(); + virtual ~Sprite(); + + /** + * Adds a state to the sprite. + */ + void addState(const SpriteState& state); + + /** + * Sets the current state. + */ + void setState(size_t stateId); + + /** + * Sets the current state. + */ + void setState(const std::string& name); + + /** + * Advances the current frame + */ + void advanceTime(uint32_t steps); + + + // Getters + SpriteState& currentState(); + Frame& currentFrame(); + + // Public fields + float anchorX, anchorY; + + private: + std::vector m_states; + std::unordered_map m_stateNames; + + size_t m_currentState; + size_t m_currentFrame; + uint32_t m_currentFrameTimeLeft; + }; + +} /* namespace model */ +} /* namespace farmlands */ + +#endif /* MODEL_SPRITE_H_ */ diff --git a/src/resources/ResourceInfo.h b/src/resources/ResourceInfo.h index 0f1ee24..61e44ae 100644 --- a/src/resources/ResourceInfo.h +++ b/src/resources/ResourceInfo.h @@ -15,6 +15,7 @@ namespace resources { { None, Texture, + Sprite, Level, LevelLayer, Font, diff --git a/src/resources/ResourceManager.h b/src/resources/ResourceManager.h index 5fac075..f4c2469 100644 --- a/src/resources/ResourceManager.h +++ b/src/resources/ResourceManager.h @@ -9,6 +9,7 @@ #define STORAGE_RESOURCEMANAGER_H_ #include +#include #include #include @@ -39,6 +40,7 @@ namespace resources { } texture; model::Level* level; + model::Sprite* sprite; }; }; @@ -57,6 +59,7 @@ namespace resources { TTF_Font* font(int id, int pointSize); SDL_Texture* texture(int id); model::Level* level(int id); + model::Sprite* sprite(int id); private: /** @@ -70,16 +73,10 @@ namespace resources { */ int getId(std::string resourcePath); - /** - * Loads the cell data for a level tile layer. - */ - void loadLevelLayer(model::Level* level, size_t layerNumber, int resourceId); - - /** - * Loads a texture into memory. - */ - void loadTexture(int textureId); void loadFont(int fontId, int pointSize); + void loadTexture(int textureId); + void loadSprite(int spriteId); + void loadLevelLayer(model::Level* level, size_t layerNumber, int resourceId); // State GameState* m_gameState; diff --git a/src/resources/ResourceManager/ResourceManager.cpp b/src/resources/ResourceManager/ResourceManager.cpp index 6215fa5..7f7a30e 100644 --- a/src/resources/ResourceManager/ResourceManager.cpp +++ b/src/resources/ResourceManager/ResourceManager.cpp @@ -7,13 +7,10 @@ #include #include +#include #include -#define FONTID(id,size) (id * 1000 + size) -#define FONTID_SIZE(fontid) (fontid % 1000) -#define FONTID_ID(fontid) (fontid / 1000) - namespace farmlands { namespace resources { @@ -43,8 +40,11 @@ ResourceManager::~ResourceManager() SDL_FreeSurface(m_loadedResources[i].texture.surface); break; + case ResourceType::Sprite: + delete m_loadedResources[i].sprite; + default: - assert(false); + Assert(false, "Cannot free resources!"); break; } } @@ -64,25 +64,13 @@ void ResourceManager::loadMainMenu() void ResourceManager::loadGameAssets() { - loadTexture(R::Player::Default); -} - -TTF_Font* ResourceManager::font(int id, int pointSize) -{ - // Open from cache - auto it = m_fontCache.find(FONTID(id, pointSize)); - if (it != m_fontCache.end()) - return it->second; - - // Open font - TTF_Font* font = TTF_OpenFont(getPath(id).c_str(), pointSize); - m_fontCache.emplace(FONTID(id, pointSize), font); - - return font; + loadSprite(R::Sprites::Player); } std::string ResourceManager::getPath(int resourceId) { + Assert(resourceId >= 0 && resourceId < sizeof(RInfo) / sizeof(RInfo[0]), "Resource id out of bounds."); + boost::filesystem::path resPath(ASSETS_DIR); resPath.append(RInfo[resourceId].path); diff --git a/src/resources/ResourceManager/ResourceManager_Fonts.cpp b/src/resources/ResourceManager/ResourceManager_Fonts.cpp new file mode 100644 index 0000000..d91932f --- /dev/null +++ b/src/resources/ResourceManager/ResourceManager_Fonts.cpp @@ -0,0 +1,37 @@ +/* + * ResourceManager_Fonts.cpp + * + * Created on: Nov 29, 2016 + * Author: tibi + */ +#include +#include + +#define FONTID(id,size) (id * 1000 + size) +#define FONTID_SIZE(fontid) (fontid % 1000) +#define FONTID_ID(fontid) (fontid / 1000) + +namespace farmlands { +namespace resources { + +TTF_Font* ResourceManager::font(int id, int pointSize) +{ + // Open from cache + auto it = m_fontCache.find(FONTID(id, pointSize)); + if (it != m_fontCache.end()) + return it->second; + + // Open font + std::string fontPath = getPath(id); + TTF_Font* font = TTF_OpenFont(fontPath.c_str(), pointSize); + if (!font) + THROW(utils::ResourceLoadException, "Could not load font " + fontPath); + + // Cache it + m_fontCache.emplace(FONTID(id, pointSize), font); + + return font; +} + +} +} diff --git a/src/resources/ResourceManager/ResourceManager_Levels.cpp b/src/resources/ResourceManager/ResourceManager_Levels.cpp index 1b8d112..ffb9a19 100644 --- a/src/resources/ResourceManager/ResourceManager_Levels.cpp +++ b/src/resources/ResourceManager/ResourceManager_Levels.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -20,14 +21,15 @@ namespace resources { void ResourceManager::loadLevelLayer(model::Level* level, size_t layer, int resourceId) { - assert(RInfo[resourceId].type == ResourceType::LevelLayer); + Assert(RInfo[resourceId].type == ResourceType::LevelLayer, "Resource must be a level layer."); char buffer[1024 * 10]; // Open file - std::ifstream in(getPath(resourceId)); + std::string pathIn = getPath(resourceId); + std::ifstream in(pathIn); if (!in) - throw 0; // TODO: replace with exception type + THROW(utils::ResourceLoadException, "Could not load level layer " + pathIn); // Read CSV file line by line for (size_t row = 0; row < level->rowCount(); row++) @@ -35,7 +37,7 @@ void ResourceManager::loadLevelLayer(model::Level* level, size_t layer, int reso in.getline(buffer, sizeof(buffer)); if (in.eof()) - throw 0; // TODO: replace with exception type + THROW(utils::ResourceLoadException, "Unexpected end of file " + pathIn); // Separated by comma (or maybe semicolon) char* nextNum = strtok(buffer, ",;"); @@ -55,12 +57,15 @@ void ResourceManager::loadLevelLayer(model::Level* level, size_t layer, int reso void ResourceManager::loadLevel(int levelId) { // Sanity checks - assert(RInfo[levelId].type == ResourceType::Level); + Assert(RInfo[levelId].type == ResourceType::Level, "Resource must be a level!"); + if (m_loadedResources[levelId].loaded) + return; // Open file + std::string levelPath = getPath(levelId); std::ifstream levelIn(getPath(levelId)); if (!levelIn) - throw 0; // TODO: replace with exception type + THROW(utils::ResourceLoadException, "Failed to load level " + levelPath); // Parse file json levelJs; @@ -97,7 +102,7 @@ void ResourceManager::loadLevel(int levelId) model::Level* ResourceManager::level(int id) { - assert(RInfo[id].type == ResourceType::Level); + Assert(RInfo[id].type == ResourceType::Level, "Resource must be a level!"); return m_loadedResources[id].level; } diff --git a/src/resources/ResourceManager/ResourceManager_Sprites.cpp b/src/resources/ResourceManager/ResourceManager_Sprites.cpp new file mode 100644 index 0000000..36609a9 --- /dev/null +++ b/src/resources/ResourceManager/ResourceManager_Sprites.cpp @@ -0,0 +1,91 @@ +/* + * ResourceManager_Sprites.cpp + * + * Created on: Nov 29, 2016 + * Author: tibi + */ +#include +#include +#include +#include + +#include +#include + +#include + +using namespace nlohmann; + +namespace farmlands { +namespace resources { + + +void ResourceManager::loadSprite(int spriteId) +{ + Assert(RInfo[spriteId].type == ResourceType::Sprite, "Resource must be a sprite!"); + if (m_loadedResources[spriteId].loaded) + return; + + // Open file + std::string spritePath = getPath(spriteId); + std::ifstream spriteIn(spritePath); + if (!spriteIn) + THROW(utils::ResourceLoadException, "Could not load " + spritePath); + + // Parse file + json spriteJs; + spriteIn >> spriteJs; + + model::Sprite* sprite = new model::Sprite(); + sprite->anchorX = spriteJs.value("anchorX", 0.0f); + sprite->anchorY = spriteJs.value("anchorY", 0.0f); + + json statesJs = spriteJs.at("states"); + + for (auto state : statesJs) + { + model::SpriteState spriteState; + spriteState.name = state.value("name", std::string()); + + // Obtain frames + json framesJs = state.at("frames"); + for (auto frame : framesJs) + { + model::Frame spriteFrame; + spriteFrame.tileSetCell = frame.value("cell", 0u); + spriteFrame.width = frame.value("width", 0u); + spriteFrame.height = frame.value("height", 0u); + spriteFrame.duration = frame.value("duration", 0u); + + // Obtain tile set id + std::string tileSetPath = frame.value("tileSet", std::string()); + spriteFrame.tileSetId = getId(tileSetPath); + loadTexture(spriteFrame.tileSetId); + + // Add frame + spriteState.frames.push_back(spriteFrame); + } + + // Add state + sprite->addState(spriteState); + } + + m_loadedResources[spriteId].loaded = true; + m_loadedResources[spriteId].sprite = sprite; +} + +model::Sprite* ResourceManager::sprite(int id) +{ + Assert(RInfo[id].type == ResourceType::Sprite, "Resource must be a sprite!"); + + return m_loadedResources[id].sprite; +} + +} +} + + + + + + diff --git a/src/resources/ResourceManager/ResourceManager_Textures.cpp b/src/resources/ResourceManager/ResourceManager_Textures.cpp index 985ba62..16f6b9d 100644 --- a/src/resources/ResourceManager/ResourceManager_Textures.cpp +++ b/src/resources/ResourceManager/ResourceManager_Textures.cpp @@ -7,25 +7,28 @@ #include #include #include - -#include +#include namespace farmlands { namespace resources { void ResourceManager::loadTexture(int resourceId) { - assert(RInfo[resourceId].type == ResourceType::Texture); + Assert(RInfo[resourceId].type == ResourceType::Texture, "Resource must be a texture!"); + if (m_loadedResources[resourceId].loaded) + return; // Open file - SDL_Surface* surface = IMG_Load(getPath(resourceId).c_str()); + std::string texturePath = getPath(resourceId); + SDL_Surface* surface = IMG_Load(texturePath.c_str()); if (surface == NULL) - throw 0; // TODO: error handling + THROW(utils::ResourceLoadException, "Failed to load texture " + texturePath); SDL_Texture* texture = SDL_CreateTextureFromSurface(m_gameState->sdlRenderer.internalRenderer(), surface); if (texture == NULL) - throw 0; // TODO: error handling + THROW(utils::ResourceLoadException, "Failed to create texture " + texturePath); + // Add to loaded resources m_loadedResources[resourceId].loaded = true; m_loadedResources[resourceId].texture.surface = surface; m_loadedResources[resourceId].texture.texture = texture; @@ -33,7 +36,8 @@ void ResourceManager::loadTexture(int resourceId) SDL_Texture* ResourceManager::texture(int id) { - assert(RInfo[id].type == ResourceType::Texture); + Assert(RInfo[id].type == ResourceType::Texture, "Resource must be a texture!"); + return m_loadedResources[id].texture.texture; } diff --git a/src/resources/Resources.g.h b/src/resources/Resources.g.h index dd2ea3b..208a291 100644 --- a/src/resources/Resources.g.h +++ b/src/resources/Resources.g.h @@ -12,41 +12,43 @@ namespace resources { * The IDs are generated at build time by the 'prepareAssets.py' script. */ namespace R { + enum Sprites + { + Player = 0, + }; enum Fonts { - DejaVuSans = 0, - }; - enum Player - { - Default = 1, + DejaVuSans = 1, }; enum Tilesets { - Ground = 2, + PlayerTiles = 2, + Ground = 3, }; enum Ui { - Cursor = 3, + Cursor = 4, }; enum Levels { - Farm_Background = 4, - Farm = 5, + Farm_Background = 5, + Farm = 6, }; } - const int RInfo_Fonts_Begin = 0; - const int RInfo_Player_Begin = 1; + const int RInfo_Sprites_Begin = 0; + const int RInfo_Fonts_Begin = 1; const int RInfo_Tilesets_Begin = 2; - const int RInfo_Ui_Begin = 3; - const int RInfo_Levels_Begin = 4; + const int RInfo_Ui_Begin = 4; + const int RInfo_Levels_Begin = 5; /** * This array contains the names of all the files, and the corresponding file type. */ const ResourceInfo RInfo[] = { + { "sprites/player.sprite", ResourceType::Sprite }, { "fonts/DejaVuSans.ttf", ResourceType::Font }, - { "player/default.png", ResourceType::Texture }, + { "tilesets/PlayerTiles.png", ResourceType::Texture }, { "tilesets/Ground.png", ResourceType::Texture }, { "ui/cursor.png", ResourceType::Texture }, { "levels/Farm_Background.csv", ResourceType::LevelLayer }, diff --git a/src/utils/Assert.cpp b/src/utils/Assert.cpp new file mode 100644 index 0000000..1311bf9 --- /dev/null +++ b/src/utils/Assert.cpp @@ -0,0 +1,39 @@ +/* + * Assert.cpp + * + * Created on: Nov 29, 2016 + * Author: tibi + */ + +#include +#include + +namespace farmlands { +namespace utils { + +DEFINE_EXCEPTION_CPP(AssertionFailedException, Exception) + +void _AssertInternal(bool condition, const std::string& msg, + const std::string& lineText, const std::string& file, int line) +{ + if (!condition) + { + throw AssertionFailedException("Assertion failed: " + msg + "\n" + lineText, file, line); + } +} + +void _AssertInternalLog(bool condition, const std::string& msg, + const std::string& lineText, const std::string& file, int line) +{ + if (!condition) + { + std::cerr << "In file " << file << ":" << line << ": "; + std::cerr << "Assertion failed: " << msg << "\n"; + std::cerr << lineText << "\n"; + } +} + +} /* namespace utils */ +} /* namespace farmlands */ + + diff --git a/src/utils/Assert.h b/src/utils/Assert.h new file mode 100644 index 0000000..46e88e2 --- /dev/null +++ b/src/utils/Assert.h @@ -0,0 +1,30 @@ +/* + * Assert.h + * + * Created on: Nov 29, 2016 + * Author: tibi + */ + +#ifndef UTILS_ASSERT_H_ +#define UTILS_ASSERT_H_ + +#include + +namespace farmlands { +namespace utils { + + DEFINE_EXCEPTION_CLASS(AssertionFailedException, Exception) + + void _AssertInternal(bool condition, const std::string& msg, const std::string& lineText, const std::string& file, int line); + void _AssertInternalLog(bool condition, const std::string& msg, const std::string& lineText, const std::string& file, int line); + +#ifdef BUILD_DEBUG +#define Assert(condition, message) farmlands::utils::_AssertInternal(condition, message, #condition, __FILE__, __LINE__) +#else +#define Assert(condition, message) farmlands::utils::_AssertInternalLog(condition, message, #condition, __FILE__, __LINE__) +#endif + +} /* namespace utils */ +} /* namespace farmlands */ + +#endif /* UTILS_ASSERT_H_ */ diff --git a/src/utils/Exceptions.cpp b/src/utils/Exceptions.cpp new file mode 100644 index 0000000..eb7ef47 --- /dev/null +++ b/src/utils/Exceptions.cpp @@ -0,0 +1,45 @@ +/* + * Exceptions.cpp + * + * Created on: Nov 29, 2016 + * Author: tibi + */ + +#include + +namespace farmlands { +namespace utils { + +Exception::Exception() + : m_message(), + m_file(), + m_line() +{ +} + +Exception::Exception(const std::string& msg) + : m_message(msg), + m_file(), + m_line() +{ +} + +Exception::Exception(const std::string& msg, const std::string& file, int line) + : m_message(msg), + m_file(file), + m_line(line) +{ +} + +Exception::~Exception() +{ +} + + +DEFINE_EXCEPTION_CPP(InvalidArgumentException, Exception) +DEFINE_EXCEPTION_CPP(IOException, Exception); +DEFINE_EXCEPTION_CPP(ResourceLoadException, IOException); + +} /* namespace utils */ +} /* namespace farmlands */ + diff --git a/src/utils/Exceptions.h b/src/utils/Exceptions.h new file mode 100644 index 0000000..16a6ae7 --- /dev/null +++ b/src/utils/Exceptions.h @@ -0,0 +1,77 @@ +/* + * Exceptions.h + * + * Created on: Nov 29, 2016 + * Author: tibi + */ + +#ifndef UTILS_EXCEPTIONS_H_ +#define UTILS_EXCEPTIONS_H_ + +#include + +namespace farmlands { +namespace utils { + + class Exception + { + public: + Exception(); + Exception(const std::string& msg); + Exception(const std::string& msg, const std::string& file, int line); + virtual ~Exception(); + + inline std::string message() const { return m_message; } + inline std::string file() const { return m_file; } + inline int line() const { return m_line; } + + private: + std::string m_message; + std::string m_file; + int m_line; + }; + +/** + * Defines an exception class + */ +#define DEFINE_EXCEPTION_CLASS(className, baseClass) \ + class className : public baseClass \ + { \ + public: \ + className(); \ + className(const std::string& msg); \ + className(const std::string& msg, const std::string& file, int line); \ + virtual ~className(); \ + }; \ + + +/** + * Defines the implementation for an exception class + */ +#define DEFINE_EXCEPTION_CPP(className, baseClass) \ + className::className() \ + : baseClass() { } \ + className::className(const std::string& msg) \ + : baseClass(msg) { } \ + className::className(const std::string& msg, const std::string& file, int line) \ + : baseClass(msg, file, line) { } \ + className::~className() { } \ + + +/** + * Throws an exception; also adds file and line. + */ +#define THROW(exceptionClass, message) throw exceptionClass(message, __FILE__, __LINE__) + + + // Common exceptions + + DEFINE_EXCEPTION_CLASS(InvalidArgumentException, Exception); + DEFINE_EXCEPTION_CLASS(IOException, Exception); + DEFINE_EXCEPTION_CLASS(ResourceLoadException, IOException); + + +} /* namespace utils */ +} /* namespace farmlands */ + +#endif /* UTILS_EXCEPTIONS_H_ */