Implemented sprite & added player animation.

This commit is contained in:
Tiberiu Chibici 2016-11-30 00:34:36 +02:00
parent d2c335bfa5
commit bcd0a359fc
26 changed files with 843 additions and 86 deletions

View File

@ -16,13 +16,15 @@
<folderInfo id="cdt.managedbuild.config.gnu.exe.debug.1767279125." name="/" resourcePath="">
<toolChain id="cdt.managedbuild.toolchain.gnu.exe.debug.263074825" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.exe.debug">
<targetPlatform id="cdt.managedbuild.target.gnu.platform.exe.debug.361182110" name="Debug Platform" superClass="cdt.managedbuild.target.gnu.platform.exe.debug"/>
<builder buildPath="${workspace_loc:/Farmlands}/Debug" id="org.eclipse.cdt.build.core.internal.builder.1184678368" superClass="org.eclipse.cdt.build.core.internal.builder"/>
<builder buildPath="${workspace_loc:/Farmlands}/Debug" id="org.eclipse.cdt.build.core.internal.builder.1184678368" keepEnvironmentInBuildfile="false" name="CDT Internal Builder" superClass="org.eclipse.cdt.build.core.internal.builder"/>
<tool id="cdt.managedbuild.tool.gnu.archiver.base.1607221000" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
<tool command="g++" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug.1808198807" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug">
<option id="gnu.cpp.compiler.exe.debug.option.optimization.level.593370343" name="Optimization Level" superClass="gnu.cpp.compiler.exe.debug.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.none" valueType="enumerated"/>
<option id="gnu.cpp.compiler.exe.debug.option.debugging.level.1975874858" name="Debug Level" superClass="gnu.cpp.compiler.exe.debug.option.debugging.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.debugging.level.max" valueType="enumerated"/>
<option id="gnu.cpp.compiler.option.dialect.std.2087263669" name="Language standard" superClass="gnu.cpp.compiler.option.dialect.std" useByScannerDiscovery="true" value="gnu.cpp.compiler.dialect.c++11" valueType="enumerated"/>
<option id="gnu.cpp.compiler.option.preprocessor.def.954705469" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false"/>
<option id="gnu.cpp.compiler.option.preprocessor.def.954705469" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
<listOptionValue builtIn="false" value="BUILD_DEBUG"/>
</option>
<option id="gnu.cpp.compiler.option.include.paths.1179839838" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Farmlands/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Farmlands/import/include}&quot;"/>
@ -37,6 +39,9 @@
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Farmlands/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Farmlands/import/include}&quot;"/>
</option>
<option id="gnu.c.compiler.option.preprocessor.def.symbols.1611927348" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols">
<listOptionValue builtIn="false" value="BUILD_DEBUG"/>
</option>
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1905365757" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.linker.exe.debug.1471902586" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.exe.debug"/>
@ -93,7 +98,9 @@
<option id="gnu.cpp.compiler.exe.release.option.optimization.level.1342166702" name="Optimization Level" superClass="gnu.cpp.compiler.exe.release.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.most" valueType="enumerated"/>
<option id="gnu.cpp.compiler.exe.release.option.debugging.level.1515472655" name="Debug Level" superClass="gnu.cpp.compiler.exe.release.option.debugging.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.debugging.level.none" valueType="enumerated"/>
<option id="gnu.cpp.compiler.option.dialect.std.1039507910" name="Language standard" superClass="gnu.cpp.compiler.option.dialect.std" useByScannerDiscovery="true" value="gnu.cpp.compiler.dialect.c++11" valueType="enumerated"/>
<option id="gnu.cpp.compiler.option.preprocessor.def.317772284" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false"/>
<option id="gnu.cpp.compiler.option.preprocessor.def.317772284" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
<listOptionValue builtIn="false" value="BUILD_RELEASE"/>
</option>
<option id="gnu.cpp.compiler.option.include.paths.664997027" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Farmlands/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Farmlands/import/include}&quot;"/>
@ -108,6 +115,9 @@
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Farmlands/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/Farmlands/import/include}&quot;"/>
</option>
<option id="gnu.c.compiler.option.preprocessor.def.symbols.252229749" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols">
<listOptionValue builtIn="false" value="BUILD_RELEASE"/>
</option>
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1918347020" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.linker.exe.release.1752295375" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.exe.release"/>

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
assets_original/player.xcf Normal file

Binary file not shown.

View File

@ -48,6 +48,7 @@ FILE_TYPES = [
([".level"], "Level"),
([".csv"], "LevelLayer"),
([".ttf"], "Font"),
([".sprite"], "Sprite"),
]

View File

@ -1,8 +1,20 @@
#include <controller/FarmlandsGame.h>
#include <utils/Exceptions.h>
#include <iostream>
using namespace farmlands;
int main()
{
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";
}
}

View File

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

View File

@ -7,13 +7,10 @@
#include <GameState.h>
#include <controller/PlayerController.h>
#include <utils/Assert.h>
#include <cassert>
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 */

View File

@ -42,6 +42,7 @@ namespace controller {
private:
bool canMove(float x, float y);
void setDirection(float dx, float dy);
GameState* m_gameState;
};

View File

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

View File

@ -8,6 +8,7 @@
#ifndef GRAPHICS_GAMERENDERER_H_
#define GRAPHICS_GAMERENDERER_H_
#include <model/Sprite.h>
#include <graphics/SdlRenderer.h>
#include <resources/ResourceManager.h>
@ -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)

View File

@ -8,6 +8,8 @@
#ifndef MODEL_PLAYER_H_
#define MODEL_PLAYER_H_
#include <model/Direction.h>
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];

102
src/model/Sprite.cpp Normal file
View File

@ -0,0 +1,102 @@
/*
* Sprite.cpp
*
* Created on: Nov 29, 2016
* Author: tibi
*/
#include <model/Sprite.h>
#include <utils/Assert.h>
#include <algorithm>
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 */

88
src/model/Sprite.h Normal file
View File

@ -0,0 +1,88 @@
/*
* Sprite.h
*
* Created on: Nov 29, 2016
* Author: tibi
*/
#ifndef MODEL_SPRITE_H_
#define MODEL_SPRITE_H_
#include <utils/Exceptions.h>
#include <vector>
#include <unordered_map>
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<Frame> 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<SpriteState> m_states;
std::unordered_map<std::string, size_t> m_stateNames;
size_t m_currentState;
size_t m_currentFrame;
uint32_t m_currentFrameTimeLeft;
};
} /* namespace model */
} /* namespace farmlands */
#endif /* MODEL_SPRITE_H_ */

View File

@ -15,6 +15,7 @@ namespace resources {
{
None,
Texture,
Sprite,
Level,
LevelLayer,
Font,

View File

@ -9,6 +9,7 @@
#define STORAGE_RESOURCEMANAGER_H_
#include <model/Level.h>
#include <model/Sprite.h>
#include <vector>
#include <map>
@ -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;

View File

@ -7,13 +7,10 @@
#include <resources/ResourceManager.h>
#include <resources/Resources.h>
#include <utils/Assert.h>
#include <boost/filesystem.hpp>
#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);

View File

@ -0,0 +1,37 @@
/*
* ResourceManager_Fonts.cpp
*
* Created on: Nov 29, 2016
* Author: tibi
*/
#include <resources/ResourceManager.h>
#include <resources/Resources.h>
#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;
}
}
}

View File

@ -7,6 +7,7 @@
#include <resources/ResourceManager.h>
#include <resources/Resources.h>
#include <utils/Assert.h>
#include <fstream>
#include <string>
@ -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;
}

View File

@ -0,0 +1,91 @@
/*
* ResourceManager_Sprites.cpp
*
* Created on: Nov 29, 2016
* Author: tibi
*/
#include <model/Sprite.h>
#include <resources/ResourceManager.h>
#include <resources/Resources.h>
#include <utils/Assert.h>
#include <fstream>
#include <string>
#include <json.hpp>
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;
}
}
}

View File

@ -7,25 +7,28 @@
#include <GameState.h>
#include <resources/ResourceManager.h>
#include <resources/Resources.h>
#include <cassert>
#include <utils/Assert.h>
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;
}

View File

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

39
src/utils/Assert.cpp Normal file
View File

@ -0,0 +1,39 @@
/*
* Assert.cpp
*
* Created on: Nov 29, 2016
* Author: tibi
*/
#include <utils/Assert.h>
#include <iostream>
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 */

30
src/utils/Assert.h Normal file
View File

@ -0,0 +1,30 @@
/*
* Assert.h
*
* Created on: Nov 29, 2016
* Author: tibi
*/
#ifndef UTILS_ASSERT_H_
#define UTILS_ASSERT_H_
#include <utils/Exceptions.h>
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_ */

45
src/utils/Exceptions.cpp Normal file
View File

@ -0,0 +1,45 @@
/*
* Exceptions.cpp
*
* Created on: Nov 29, 2016
* Author: tibi
*/
#include <utils/Exceptions.h>
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 */

77
src/utils/Exceptions.h Normal file
View File

@ -0,0 +1,77 @@
/*
* Exceptions.h
*
* Created on: Nov 29, 2016
* Author: tibi
*/
#ifndef UTILS_EXCEPTIONS_H_
#define UTILS_EXCEPTIONS_H_
#include <string>
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_ */