diff --git a/TODO b/TODO index ad047c8..082d6ab 100644 --- a/TODO +++ b/TODO @@ -4,12 +4,12 @@ # Today -[-] Lighting (and torches) -[ ] Lighting flicker (fire) -[ ] NO TIME raytraced lighting -[ ] Fix collision -[ ] Better player spritesheet -[ ] Animation system +[x] Lighting (and torches) +[x] Lighting flicker (fire) +[d] Raytraced lighting +[d] Fix collision +[x] Better player spritesheet +[x] Animation system [ ] Enemies / AI [ ] Dungeon fixed entity spawning [ ] Enemy AI diff --git a/The Great Machine/Camera.hpp b/The Great Machine/Camera.hpp index baa3d16..8be6442 100644 --- a/The Great Machine/Camera.hpp +++ b/The Great Machine/Camera.hpp @@ -22,7 +22,7 @@ class Camera Entity* _Track = nullptr; olc::vi2d _DesiredCoords; - float _SmoothSpeed = 0.025f; + float _SmoothSpeed = 0.015f; }; diff --git a/The Great Machine/Collisions.cpp b/The Great Machine/Collisions.cpp index 4b3229e..2f9fc34 100644 --- a/The Great Machine/Collisions.cpp +++ b/The Great Machine/Collisions.cpp @@ -7,6 +7,10 @@ bool EntityCollide(Entity* entity, std::vector& nearby, int tileSize, CollisionInfo* info, olc::PixelGameEngine* engine) { + static bool ShowCollisionDebug = false; + if (engine->GetKey(olc::P).bPressed) + ShowCollisionDebug = !ShowCollisionDebug; + if (!entity->HitBox) return false; static Logger& _Logger = Logger::getInstance(); @@ -16,19 +20,21 @@ bool EntityCollide(Entity* entity, std::vector& nearby, int tileSize, Col float entityX = static_cast(entity->Coords.x - entity->TrackingCamera->Coords.x); float entityY = static_cast(entity->Coords.y - entity->TrackingCamera->Coords.y); - float entityW = static_cast(tileSize / 3.0f) * 2.0f; - float entityH = static_cast(tileSize / 3.0f) * 2.0f; + float entityW = static_cast(entity->HitBox->w); + float entityH = static_cast(entity->HitBox->h); int entityLeft = static_cast(entityX); int entityRight = static_cast(entityX + entityW); int entityTop = static_cast(entityY); int entityBottom = static_cast(entityY + entityH); - //engine->DrawRect({(entity->HitBox->x + (int)entity->Coords.x) - (int)entity->TrackingCamera->Coords.x, (entity->HitBox->y + (int)entity->Coords.y) - (int)entity->TrackingCamera->Coords.y}, {entity->HitBox->w, entity->HitBox->h}, olc::RED); + if (ShowDebug) + engine->DrawRect(entityX, entityY, entityW, entityH, olc::RED); for (auto tile : nearby) { - //engine->DrawRect({ static_cast(static_cast((tile->Coords.x * tileSize) - entity->TrackingCamera->Coords.x)), static_cast(static_cast((tile->Coords.y * tileSize) - entity->TrackingCamera->Coords.y)) }, {tileSize, tileSize}, olc::BLUE); + if (ShowDebug) + engine->DrawRect({ static_cast(static_cast((tile->Coords.x * tileSize) - entity->TrackingCamera->Coords.x)), static_cast(static_cast((tile->Coords.y * tileSize) - entity->TrackingCamera->Coords.y)) }, {tileSize, tileSize}, olc::BLUE); // return if not collidable if (!tile->IsSolid) continue; @@ -45,7 +51,8 @@ bool EntityCollide(Entity* entity, std::vector& nearby, int tileSize, Col bool collision = xOverlaps && yOverlaps; - //engine->FillRect({static_cast(static_cast((tile->Coords.x * tileSize) - entity->TrackingCamera->Coords.x)), static_cast(static_cast((tile->Coords.y * tileSize) - entity->TrackingCamera->Coords.y))}, {tileSize, tileSize}, collision ? olc::RED : olc::BLUE); + if (ShowDebug) + engine->FillRect({static_cast(static_cast((tile->Coords.x * tileSize) - entity->TrackingCamera->Coords.x)), static_cast(static_cast((tile->Coords.y * tileSize) - entity->TrackingCamera->Coords.y))}, {tileSize, tileSize}, collision ? olc::RED : olc::BLUE); if (!collision) continue; @@ -59,6 +66,5 @@ bool EntityCollide(Entity* entity, std::vector& nearby, int tileSize, Col } return false; - } diff --git a/The Great Machine/Dungeon.cpp b/The Great Machine/Dungeon.cpp index b79025c..a818356 100644 --- a/The Great Machine/Dungeon.cpp +++ b/The Great Machine/Dungeon.cpp @@ -3,14 +3,73 @@ #include #include +#include "olcPixelGameEngine.hpp" +#include "olcPGEX_AnimatedSprite.hpp" + #include "Collisions.hpp" #include "Things.hpp" #include "Camera.hpp" #include "Logger.hpp" + +void PerlinNoise1D(int nCount, float *fSeed, int nOctaves, float fBias, float *fOutput) +{ + // Used 1D Perlin Noise + for (int x = 0; x < nCount; x++) + { + float fNoise = 0.0f; + float fScaleAcc = 0.0f; + float fScale = 1.0f / fBias; + + for (int o = 0; o < nOctaves; o++) + { + int nPitch = nCount >> o; + int nSample1 = (x / nPitch) * nPitch; + int nSample2 = (nSample1 + nPitch) % nCount; + + float fBlend = (float)(x - nSample1) / (float)nPitch; + + float fSample = (1.0f - fBlend) * fSeed[nSample1] + fBlend * fSeed[nSample2]; + + fScaleAcc += fScale; + fNoise += fSample * fScale; + // fScale = fScale / fBias; + } + + // Scale to seed range + fOutput[x] = fNoise / fScaleAcc; + } +} + +static int perlinSize = 10000; +static float* perlinSeed; +static float* perlinOutput; + +void SetupPerlin() +{ + perlinSeed = (float*)malloc(sizeof(float) * perlinSize); + perlinOutput = (float*)malloc(sizeof(float) * perlinSize); + for (int i = 0; i < perlinSize; i++) + perlinSeed[i] = static_cast(rand()) / static_cast(RAND_MAX); + + PerlinNoise1D(perlinSize, perlinSeed, 8, 2.0f, perlinOutput); +} + +float GetNextPerlin() +{ + static int i = 0; + i++; + if (i > perlinSize) + i = 0; + return perlinOutput[i]; +} + + Dungeon::Dungeon() : _Logger(Logger::getInstance()) { + SetupPerlin(); + ActiveCamera = new Camera(); ActiveCamera->Coords = { 0, 0 }; ActiveCamera->ViewPort = { 1280, 720 }; @@ -21,7 +80,45 @@ Dungeon::Dungeon() Player->Type = EEntity::Type::Player; // Relative to player TL corner // not really used ? lol - Player->HitBox = new Collider{ 0, 0, static_cast((static_cast(TileSize) / 3.0f) * 2.0f), static_cast((static_cast(TileSize) / 3.0f) * 2.0f) } ; + Player->HitBox = new Collider{ 0, 0, 28, 36 } ; + + Player->Renderable = new olc::Renderable(); + Player->Renderable->Load("res/player.png"); + Player->Animator = new olc::AnimatedSprite(); + Player->Animator->mode = olc::AnimatedSprite::SPRITE_MODE::SINGLE; + Player->Animator->type = olc::AnimatedSprite::SPRITE_TYPE::DECAL; + Player->Animator->spriteSheet = Player->Renderable; + Player->Animator->SetSpriteSize({28, 36}); + + Player->Animator->AddState("idle", std::vector{ + {28, 0} + }); + + Player->Animator->AddState("north", std::vector{ + {0, 110}, + {28, 110}, + {56, 110}, + {84, 110} + }); + Player->Animator->AddState("east", std::vector{ + {0, 36}, + {28, 36}, + {56, 36}, + {84, 36} + }); + Player->Animator->AddState("south", std::vector{ + {0, 0}, + {28, 0}, + {56, 0}, + {84, 0} + }); + Player->Animator->AddState("west", std::vector{ + {0, 72}, + {28, 72}, + {56, 72}, + {84, 72} + }); + Player->Animator->SetState("idle"); ActiveCamera->TrackEntity(Player); ActiveCamera->Update(0.0f); @@ -193,38 +290,76 @@ void Dungeon::SpawnEntity(Entity* entity) void Dungeon::Input(olc::PixelGameEngine* engine, float fTime) { + olc::vf2d oldCoords = Player->Coords; + static std::string state = "idle"; + static std::string lastState = "idle"; + if (engine->GetKey(olc::W).bHeld) + { Player->Coords.y -= static_cast(TileSize) * (fTime * Player->Speed); + if (state != "north") + state = "north"; + } if (engine->GetKey(olc::A).bHeld) + { Player->Coords.x -= static_cast(TileSize) * (fTime * Player->Speed); + if (state != "west") + state = "west"; + } if (engine->GetKey(olc::S).bHeld) + { Player->Coords.y += static_cast(TileSize) * (fTime * Player->Speed); + if (state != "south") + state = "south"; + } if (engine->GetKey(olc::D).bHeld) + { Player->Coords.x += static_cast(TileSize) * (fTime * Player->Speed); + if (state != "east") + state = "east"; + } if (engine->GetKey(olc::W).bHeld && engine->GetKey(olc::A).bHeld) { Player->Coords.y += static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); Player->Coords.x += static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); + if (state != "west") + state = "west"; + } if (engine->GetKey(olc::W).bHeld && engine->GetKey(olc::D).bHeld) { Player->Coords.y += static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); Player->Coords.x -= static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); + if (state != "east") + state = "east"; + } if (engine->GetKey(olc::S).bHeld && engine->GetKey(olc::D).bHeld) { Player->Coords.y -= static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); Player->Coords.x -= static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); + if (state != "east") + state = "east"; } if (engine->GetKey(olc::S).bHeld && engine->GetKey(olc::A).bHeld) { Player->Coords.y -= static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); Player->Coords.x += static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); + if (state != "west") + state = "west"; } + if (oldCoords == Player->Coords) + state = "idle"; + if (state != lastState) + Player->Animator->SetState(state); + lastState = state; + + + // Map collisions olc::vi2d currentTile = { static_cast(Player->Coords.x / TileSize), static_cast(Player->Coords.y / TileSize) }; @@ -304,7 +439,7 @@ olc::Pixel pixelMultiply(const int x, const int y, const olc::Pixel& pSource, co return ret; } -void Dungeon::Draw(olc::PixelGameEngine* engine) +void Dungeon::Draw(olc::PixelGameEngine* engine, float fTime) { // Maps not gonna be big enough for me to care about optimistaion // maybe i should care? i don't @@ -312,57 +447,65 @@ void Dungeon::Draw(olc::PixelGameEngine* engine) // Entities are always (tilesize / 3) * 2 // Dungeon Layer - engine->SetDrawTarget(4); + engine->SetDrawTarget(3); engine->Clear({38, 36, 40}); engine->SetPixelMode(olc::Pixel::ALPHA); for (std::pair tile : DungeonTiles) - { - // if (tile.second->Type == ETile::Type::Void) continue; engine->DrawPartialDecal({ static_cast((tile.first.x * TileSize) - ActiveCamera->Coords.x), static_cast((tile.first.y * TileSize) - ActiveCamera->Coords.y) }, { static_cast(TileSize), static_cast(TileSize) }, TileSet->Decal(), TileSetDictionary->Dictionary[tile.second->Type], { 16, 16 }); - } - // engine->SetPixelMode(olc::Pixel::NORMAL); // Entity Layer - engine->SetDrawTarget(3); + engine->SetDrawTarget(2); engine->Clear(olc::BLANK); // Draw character - engine->DrawPartialDecal({ static_cast(Player->Coords.x - ActiveCamera->Coords.x), static_cast(Player->Coords.y - ActiveCamera->Coords.y) }, - { (static_cast(TileSize) / 3.0f) * 2.0f, (static_cast(TileSize) / 3.0f) * 2.0f }, TileSet->Decal(), { 143, 130 }, { 16, 16 }); + Player->Animator->Draw(fTime, {Player->Coords.x - ActiveCamera->Coords.x, Player->Coords.y - ActiveCamera->Coords.y}); // Lighting layers - engine->SetDrawTarget(2); + engine->SetDrawTarget(1); engine->Clear(olc::BLANK); static std::function fPixelMultiply = pixelMultiply; - float lightX = static_cast(Player->Coords.x - ActiveCamera->Coords.x) - (FireOverlay->Sprite()->width / 2.0f); - float lightY = static_cast(Player->Coords.y - ActiveCamera->Coords.y) - (FireOverlay->Sprite()->height / 2.0f); + // loads to make it more chaotic + float lightScale = 1.4f + GetNextPerlin(); GetNextPerlin(); GetNextPerlin(); GetNextPerlin(); GetNextPerlin(); - float lightLeft = lightX; - float lightRight = lightX + FireOverlay->Sprite()->width; - float lightTop = lightY; - float lightBottom = lightY + FireOverlay->Sprite()->height; + float lightX = static_cast(Player->Coords.x - ActiveCamera->Coords.x) - ((FireOverlay->Sprite()->width * lightScale) / 2.0f); + float lightY = static_cast(Player->Coords.y - ActiveCamera->Coords.y) - ((FireOverlay->Sprite()->height * lightScale) / 2.0f); + + float lightLeft = lightX + 1.0f; + float lightRight = lightX + FireOverlay->Sprite()->width * lightScale - 1.0f; + float lightTop = lightY + 1.0f; + float lightBottom = lightY + FireOverlay->Sprite()->height * lightScale - 1.0f; + + // orange tint + engine->FillRectDecal({0.0f, 0.0f}, {static_cast(engine->ScreenWidth()), static_cast(engine->ScreenHeight())}, olc::Pixel(255, 123, 0, 30)); engine->SetPixelMode(fPixelMultiply); - engine->DrawDecal({ lightX, lightY }, FireOverlay->Decal()); - - engine->SetDrawTarget(1); - engine->Clear(olc::BLANK); - // top - - engine->FillRect({0, static_cast(lightTop)}, {engine->ScreenWidth(), engine->ScreenHeight()}, olc::BLACK); + engine->DrawDecal({ lightX, lightY }, FireOverlay->Decal(), {lightScale, lightScale}, olc::RED); + engine->SetPixelMode(olc::Pixel::ALPHA); + // top + engine->FillRectDecal({0.0f, 0.0f}, {static_cast(engine->ScreenWidth()), lightTop}, olc::BLACK); + // right + engine->FillRectDecal({lightRight, 0.0f}, {static_cast(engine->ScreenWidth()) - lightRight, static_cast(engine->ScreenHeight())}, olc::BLACK); + // bottom + engine->FillRectDecal({0.0f, lightBottom}, {static_cast(engine->ScreenWidth()), static_cast(engine->ScreenHeight()) - lightBottom}, olc::BLACK); + // left + engine->FillRectDecal({0.0f, 0.0f}, {lightLeft, static_cast(engine->ScreenHeight())}, olc::BLACK); } Dungeon::~Dungeon() { + delete Player->HitBox; + delete Player->Renderable; + delete Player->Animator; delete Player; + delete ActiveCamera; delete TileSetDictionary; delete TileSet; @@ -372,4 +515,7 @@ Dungeon::~Dungeon() delete entity.second; for (std::pair entity : FixedItems) delete entity.second; + + free(perlinSeed); + free(perlinOutput); } diff --git a/The Great Machine/Dungeon.hpp b/The Great Machine/Dungeon.hpp index 433d6a9..10d06fa 100644 --- a/The Great Machine/Dungeon.hpp +++ b/The Great Machine/Dungeon.hpp @@ -26,9 +26,9 @@ class Dungeon void Input(olc::PixelGameEngine* engine, float fTime); void Update(olc::PixelGameEngine* engine, float fTime); - void Draw(olc::PixelGameEngine* engine); + void Draw(olc::PixelGameEngine* engine, float fTime); - Playable* Player; + Playable* Player; Camera* ActiveCamera; int TileSize = 64; @@ -41,7 +41,6 @@ class Dungeon TileDictionary* TileSetDictionary; olc::Renderable* TileSet; - olc::Renderable* FireOverlay; ~Dungeon(); diff --git a/The Great Machine/The Great Machine.vcxproj b/The Great Machine/The Great Machine.vcxproj index 1db844f..3eb96a7 100644 --- a/The Great Machine/The Great Machine.vcxproj +++ b/The Great Machine/The Great Machine.vcxproj @@ -154,6 +154,7 @@ + diff --git a/The Great Machine/The Great Machine.vcxproj.filters b/The Great Machine/The Great Machine.vcxproj.filters index 798274f..e5f503e 100644 --- a/The Great Machine/The Great Machine.vcxproj.filters +++ b/The Great Machine/The Great Machine.vcxproj.filters @@ -59,5 +59,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/The Great Machine/Things.hpp b/The Great Machine/Things.hpp index dab04ef..fbf1499 100644 --- a/The Great Machine/Things.hpp +++ b/The Great Machine/Things.hpp @@ -5,6 +5,7 @@ #include #include "olcPixelGameEngine.hpp" +#include "olcPGEX_AnimatedSprite.hpp" class Camera; @@ -101,9 +102,10 @@ class Entity olc::vf2d Coords; EEntity::Type Type; Collider* HitBox; + // Does not own Camera* TrackingCamera; - olc::vf2d SpriteTextureMask; - olc::Renderable* SpriteMap; + olc::Renderable* Renderable; + olc::AnimatedSprite* Animator; }; class Item : public Entity @@ -122,7 +124,7 @@ class FixedItem : public Entity class Playable : public Entity { public: - float Speed = 10.0f; + float Speed = 4.0f; int SelectedInventoryItem = 0; std::array Inventory; }; diff --git a/The Great Machine/main.cpp b/The Great Machine/main.cpp index dd9a4fd..be0f8f7 100644 --- a/The Great Machine/main.cpp +++ b/The Great Machine/main.cpp @@ -2,6 +2,8 @@ #define OLC_PGE_APPLICATION #include "olcPixelGameEngine.hpp" +#define OLC_PGEX_ANIMSPR +#include "olcPGEX_AnimatedSprite.hpp" #include "Dungeon.hpp" @@ -60,7 +62,7 @@ class Game : public olc::PixelGameEngine _Dungeon->Update(this, fTime); - _Dungeon->Draw(this); + _Dungeon->Draw(this, fTime); for (int i = 0; i < 5; i++) EnableLayer(i, true); diff --git a/The Great Machine/olcPGEX_AnimatedSprite.hpp b/The Great Machine/olcPGEX_AnimatedSprite.hpp new file mode 100644 index 0000000..9c7295a --- /dev/null +++ b/The Great Machine/olcPGEX_AnimatedSprite.hpp @@ -0,0 +1,354 @@ +/* + olcPGEX_AnimatedSprite.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | AnimatedSprites - v2.0.0 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + the ability to easily animate sprites with either a single + spritesheets or individual image files for each frame. + + Use of this extension requires the olcPGEX_Graphics2D extension. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + Homepage: https://matthewhayward.co.uk + + Author + ~~~~~~ + Matt Hayward aka SaladinAkara + + Contributors + ~~~~~~~~~~~~ + 0xnicholasc - https://github.com/0xnicholasc + Moros1138 - https://github.com/Moros1138 +*/ + +#ifndef OLC_PGEX_ANIMATEDSPRITE +#define OLC_PGEX_ANIMATEDSPRITE + +namespace olc +{ + class AnimatedSprite : public olc::PGEX + { + public: + enum class SPRITE_MODE { + MULTI, + SINGLE + }; + enum class SPRITE_TYPE { + SPRITE, + DECAL + }; + enum class PLAY_MODE { + LOOP, + PING_PONG + }; + + public: + // Set current state of sprite + void SetState(std::string newState); + // Get current sprite state + std::string GetState(); + // Draw sprite + void Draw(float fElapsedTime, olc::vf2d position, uint8_t flip = olc::Sprite::Flip::NONE, olc::Pixel tint = olc::WHITE); + // Add state for sprite in SPRITE_MODE::MULTI with a specified frameDuration and playMode + void AddState(std::string stateName, float frameDuration, PLAY_MODE mode, std::vector imagePaths); + // Add state for sprite in SPRITE_MODE::SINGLE with a specified frameDuration and playMode + void AddState(std::string stateName, float frameDuration, PLAY_MODE mode, std::vector spriteLocations); + // Add state for sprite in SPRITE_MODE::MULTI using the default frameDuration and playMode + void AddState(std::string stateName, std::vector imagePaths); + // Add state for sprite in SPRITE_MODE::SINGLE using the default frameDuration and playMode + void AddState(std::string stateName, std::vector spriteLocations); + // Set size of sprite + void SetSpriteSize(olc::vi2d size); + // Get size of sprite + olc::vi2d GetSpriteSize(); + // Set sprite scale factor + void SetSpriteScale(float scale); + + protected: + olc::Sprite* GetMultiFrame(float fElapsedTime); + olc::Decal* GetMultiRenderable(float fElapsedTime); + olc::vi2d GetSingleFrame(float fElapsedTime); + olc::vf2d GetDecalScale(uint8_t flip); + olc::vf2d GetDecalPosition(olc::vf2d position, uint8_t flip); + + public: + float defaultFrameDuration = 0.1f; // Frame duration to be used if one is not specified otherwise + SPRITE_MODE mode = SPRITE_MODE::MULTI; + SPRITE_TYPE type = SPRITE_TYPE::SPRITE; + Renderable* spriteSheet; + + protected: + std::string state; + std::map> multiFrames; + std::map> singleFrames; + std::map> multiRenderables; + std::map frameDurations; + std::map playModes; + float frameTimer = 0.0f, spriteScale = 1.0f; + int currentFrame; + olc::vi2d spriteSize; + olc::Sprite* placeholder = nullptr; + bool playForward = true; + }; +} + +#ifdef OLC_PGEX_ANIMSPR +#undef OLC_PGEX_ANIMSPR + +namespace olc +{ + olc::Sprite* AnimatedSprite::GetMultiFrame(float fElapsedTime) + { + frameTimer += fElapsedTime; + + if (frameTimer >= frameDurations[state]) { + frameTimer = 0.0f; + + if (playModes[state] == PLAY_MODE::PING_PONG && !playForward) { + currentFrame--; + } else { + currentFrame++; + } + + if (currentFrame >= multiFrames[state].size()) { + currentFrame = playModes[state] == PLAY_MODE::LOOP + ? 0 + : multiFrames[state].size() - 2; + playForward = false; + } else if (currentFrame <= 0) { + playForward = true; + } + } + + return multiFrames[state][currentFrame]; + } + + olc::Decal* AnimatedSprite::GetMultiRenderable(float fElapsedTime) + { + frameTimer += fElapsedTime; + + if (frameTimer >= frameDurations[state]) { + frameTimer = 0.0f; + + if (playModes[state] == PLAY_MODE::PING_PONG && !playForward) { + currentFrame--; + } else { + currentFrame++; + } + + if (currentFrame >= multiRenderables[state].size()) { + currentFrame = playModes[state] == PLAY_MODE::LOOP + ? 0 + : multiRenderables[state].size() - 2; + playForward = false; + } else if (currentFrame <= 0) { + playForward = true; + } + } + + return multiRenderables[state][currentFrame]->Decal(); + } + + olc::vi2d AnimatedSprite::GetSingleFrame(float fElapsedTime) + { + frameTimer += fElapsedTime; + + if (frameTimer >= frameDurations[state]) { + frameTimer = 0.0f; + + if (playModes[state] == PLAY_MODE::PING_PONG && !playForward) { + currentFrame--; + } else { + currentFrame++; + } + + if (currentFrame >= singleFrames[state].size()) { + currentFrame = playModes[state] == PLAY_MODE::LOOP + ? 0 + : singleFrames[state].size() - 2; + playForward = false; + } else if (currentFrame <= 0) { + playForward = true; + } + } + + return singleFrames[state][currentFrame]; + } + + void AnimatedSprite::SetState(std::string newState) + { + bool stateFound = false; + if (type == SPRITE_TYPE::SPRITE) { + if ((mode == SPRITE_MODE::MULTI && multiFrames.find(newState) == multiFrames.end()) + || (mode == SPRITE_MODE::SINGLE && singleFrames.find(newState) == singleFrames.end())) { + + std::cout << "Error: State " << newState << " does not exist." << std::endl; + return; + } + } else if ((mode == SPRITE_MODE::MULTI && multiRenderables.find(newState) == multiRenderables.end()) + || (mode == SPRITE_MODE::SINGLE && singleFrames.find(newState) == singleFrames.end())) { + + std::cout << "Error: State " << newState << " does not exist." << std::endl; + return; + } + + if (newState != state) { + state = newState; + currentFrame = 0; + } + } + + + std::string AnimatedSprite::GetState() + { + return state; + } + + void AnimatedSprite::AddState(std::string stateName, std::vector imgPaths) + { + AnimatedSprite::AddState(stateName, defaultFrameDuration, PLAY_MODE::LOOP, imgPaths); + } + + void AnimatedSprite::AddState(std::string stateName, std::vector spriteLocations) + { + AnimatedSprite::AddState(stateName, defaultFrameDuration, PLAY_MODE::LOOP, spriteLocations); + } + + void AnimatedSprite::AddState(std::string stateName, float frameDuration, PLAY_MODE mode, std::vector imgPaths) + { + for (std::string& path : imgPaths) { + if (type == SPRITE_TYPE::SPRITE) { + multiFrames[stateName].push_back(new olc::Sprite(path)); + } else { + multiRenderables[stateName].push_back(new Renderable()); + multiRenderables[stateName].back()->Load(path); + } + } + + frameDurations[stateName] = frameDuration; + playModes[stateName] = mode; + } + + void AnimatedSprite::AddState(std::string stateName, float frameDuration, PLAY_MODE mode, std::vector spriteLocations) + { + for (olc::vi2d& location : spriteLocations) { + singleFrames[stateName].push_back(location); + } + + frameDurations[stateName] = frameDuration; + playModes[stateName] = mode; + } + + void AnimatedSprite::SetSpriteSize(olc::vi2d size) + { + spriteSize = size; + if (placeholder != nullptr) { + delete placeholder; + } + placeholder = new olc::Sprite(size.x, size.y); + } + + olc::vi2d AnimatedSprite::GetSpriteSize() + { + return spriteSize; + } + + void AnimatedSprite::SetSpriteScale(float scale) + { + if (scale <= 0.0f) { + spriteScale = 1.0f; + } + else { + spriteScale = scale; + } + } + + void AnimatedSprite::Draw(float fElapsedTime, olc::vf2d position, uint8_t flip, olc::Pixel tint) + { + if (mode == SPRITE_MODE::MULTI) { + if (type == SPRITE_TYPE::SPRITE) { + pge->DrawSprite(position, GetMultiFrame(fElapsedTime), spriteScale, flip); + } else { + pge->DrawDecal(GetDecalPosition(position, flip), GetMultiRenderable(fElapsedTime), GetDecalScale(flip), tint); + } + } + else { + if (type == SPRITE_TYPE::SPRITE) { + pge->DrawPartialSprite(position, spriteSheet->Sprite(), GetSingleFrame(fElapsedTime), spriteSize, spriteScale, flip); + } else { + pge->DrawPartialDecal(GetDecalPosition(position, flip), spriteSheet->Decal(), GetSingleFrame(fElapsedTime), spriteSize, GetDecalScale(flip), tint); + } + } + } + + olc::vf2d AnimatedSprite::GetDecalScale(uint8_t flip) + { + olc::vf2d scale = { (float)spriteScale, (float)spriteScale }; + + if (flip == olc::Sprite::Flip::HORIZ) { + return { -(scale.x), scale.y }; + } + + if (flip == olc::Sprite::Flip::VERT) { + return { scale.x, -scale.y }; + } + + return scale; + } + + olc::vf2d AnimatedSprite::GetDecalPosition(olc::vf2d position, uint8_t flip) + { + if (flip == olc::Sprite::Flip::HORIZ) { + return { position.x + (spriteSize.x * spriteScale), position.y }; + } + + if (flip == olc::Sprite::Flip::VERT) { + return { position.x, position.y + (spriteSize.y * spriteScale) }; + } + + return position; + } +} + +#endif +#endif diff --git a/The Great Machine/res/player.png b/The Great Machine/res/player.png new file mode 100644 index 0000000..6954bb9 Binary files /dev/null and b/The Great Machine/res/player.png differ diff --git a/res/player.png b/res/player.png new file mode 100644 index 0000000..6954bb9 Binary files /dev/null and b/res/player.png differ