diff --git a/README.md b/README.md index 3f476d4..a7fa85a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,111 @@ # olcPGEX_AnimatedSprite -Easily use animated sprites in the OLC Pixel Game Engine + +This extension is designed for use with the [olcPixelGameEngine](https://github.com/OneLoneCoder/olcPixelGameEngine) by [Javidx9](https://github.com/OneLoneCoder). It allows you to easily use animated sprites that utilise either single-file spritesheets or multiple image files for each frame. + +--- + +## Requirements + +The extension requires the [olcPGEX_Graphics2D](https://github.com/OneLoneCoder/olcPixelGameEngine/blob/master/Extensions/olcPGEX_Graphics2D.h) extension to work, but should require no additional libraries beyond that and the olcPixelGameEngine itself. + +--- + +## Usage + +To use the olcPGEX_AnimatedSprite extension, it needs to be included in your application. This is done like so: + +```cpp +#define OLC_PGEX_ANIMSPR +#include "olcPGEX_AnimatedSprite.h" +``` + +### Single Spritesheet + +```cpp +// define sprite in your PGE program +olc::AnimatedSprite sprite; + +bool OnUserCreate() +{ + // configure the sprite: + sprite.mode = olc::AnimatedSprite::SPRITE_MODE::SINGLE; // set sprite to use a single spritesheet + sprite.spriteSheet = new olc::Sprite("spritesheet.png"); // define image to use for the spritesheet + sprite.SetSpriteSize({50, 50}); // define size of each sprite with an olc::vi2d + sprite.SetSpriteScale(2.0f); // define scale of sprite; 1.0f is original size. Must be above 0 and defaults to 1.0f + + // define states - state name and vector of olc::vi2d to define the top-left position of each frame in the spritesheet + sprite.AddState("idle", { + // let's assume a sprite sheet with 8 rows and columns, using the 50x50 sprite size defined above + {0, 0}, // row 1, column 1 (top left of entire image) + {0, 50}, // row 1, column 2 + {400, 200}, // row 8, column 4 + }); + + sprite.AddState("walking", { + {50, 0}, + {50, 50}, + {50, 100}, + }); + + // set initial state + sprite.SetState("idle") + + return true; +} + +bool OnUserUpdate(float fElapsedTime) +{ + sprite.Draw(felapsedTime, {20.0f, 20.0f}); // draws the sprite at location x:20, y:20 and animates it + + return true; +} +``` + +### Multiple Sprite Files + +```cpp +// define sprite in your PGE program +olc::AnimatedSprite sprite; + +bool OnUserCreate() +{ + // configure the sprite + sprite.mode = olc::AnimatedSprite::SPRITE_MODE::MULTI; + sprite.SetSpriteSize({50.0f, 50.0f); + sprite.SetSpriteScale(2.0f); + + // define states - state name and a vector of std::strings that define the location of each image file + sprite.AddState("idle", { + "frame1.png", + "frame2.png", + "frame3.png", + "frame4.png", + }); + + // set default state + sprite.SetState("idle"); + + return true; +} + +bool OnUserUpdate(float fElapsedTime) +{ + sprite.Draw(fElapsedTime, {20.0f, 20.0f}); +} +``` + +### Flipping Sprites + +You may want to flip a sprite vertically or horizontally - for example, if your spritesheet only has right-facing images. This can be achieved by changing the flip mode, like so: + +```cpp +sprite.flip = olc::AnimatedSprite::FLIP_MODE::HORIZONTAL; // flip horizontally (e.g. make right-facing image face the left) +sprite.flip = olc::AnimatedSprite::FLIP_MODE::VERTICAL; // flip vertically (e.g. make image upside down) +sprite.flip = olc::AnimatedSprite::FLIP_MODE::NONE; // display original image +``` + +--- + +## Contributions + +Contributions are more than welcome. They can be in the form of reporting bugs and making feature requests (use GitHub issues for both of these), or PRs for changes. \ No newline at end of file diff --git a/olcPGEX_AnimatedSprite.h b/olcPGEX_AnimatedSprite.h new file mode 100644 index 0000000..e7ab019 --- /dev/null +++ b/olcPGEX_AnimatedSprite.h @@ -0,0 +1,231 @@ +/* + olcPGEX_AnimatedSprite.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | AnimatedSprites - v1.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 +*/ + +#include "olcPGEX_Graphics2D.h" + +#ifndef OLC_PGEX_ANIMATEDSPRITE +#define OLC_PGEX_ANIMATEDSPRITE + +namespace olc +{ + class AnimatedSprite : public olc::PGEX + { + public: + void SetState(std::string newState); + std::string GetState(); + void Draw(float fElapsedTime, olc::vf2d position); + void AddState(std::string stateName, std::vector imagePaths); + void AddState(std::string stateName, std::vector spriteLocations); + void SetSpriteSize(olc::vi2d size); + olc::vi2d GetSpriteSize(); + void SetSpriteScale(float scale); + + protected: + olc::Sprite* GetMultiFrame(float fElapsedTime); + olc::vi2d GetSingleFrame(float fElapsedTime); + + public: + bool flipped = false; + enum class FLIP_MODE { + NONE = 0, + HORIZONTAL = 1, + VERTICAL = 2 + }; + enum class SPRITE_MODE { + MULTI = 0, + SINGLE = 1 + }; + FLIP_MODE flip = FLIP_MODE::NONE; + SPRITE_MODE mode = SPRITE_MODE::MULTI; + olc::Sprite* spriteSheet = nullptr; + + protected: + std::string state; + std::map> multiFrames; + std::map> singleFrames; + float frameTimer = 0.0f; + float frameDuration = 0.1f; + unsigned int currentFrame; + olc::vi2d spriteSize; + float spriteScale = 1.0f; + }; +} + +#ifdef OLC_PGEX_ANIMSPR +#undef OLC_PGEX_ANIMSPR + +namespace olc +{ + olc::Sprite* AnimatedSprite::GetMultiFrame(float fElapsedTime) + { + frameTimer += fElapsedTime; + + if (frameTimer >= frameDuration) { + currentFrame++; + frameTimer = 0.0f; + + if (currentFrame >= multiFrames[state].size()) { + currentFrame = 0; + } + } + + return multiFrames[state][currentFrame]; + } + + olc::vi2d AnimatedSprite::GetSingleFrame(float fElapsedTime) + { + frameTimer += fElapsedTime; + + if (frameTimer >= frameDuration) { + currentFrame++; + frameTimer = 0.0f; + + if (currentFrame >= singleFrames[state].size()) { + currentFrame = 0; + } + } + + return singleFrames[state][currentFrame]; + } + + void AnimatedSprite::SetState(std::string newState) + { + 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; + } + + if (newState != state) { + state = newState; + currentFrame = 0; + } + } + + std::string AnimatedSprite::GetState() + { + return state; + } + + void AnimatedSprite::AddState(std::string stateName, std::vector imgPaths) + { + for (std::string& path : imgPaths) { + multiFrames[stateName].push_back(new olc::Sprite(path)); + } + } + + void AnimatedSprite::AddState(std::string stateName, std::vector spriteLocations) + { + for (olc::vi2d& location : spriteLocations) { + singleFrames[stateName].push_back(location); + } + } + + void AnimatedSprite::SetSpriteSize(olc::vi2d size) + { + spriteSize = size; + } + + 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) + { + olc::GFX2D::Transform2D t; + + if (flip == FLIP_MODE::HORIZONTAL) { + t.Translate(-((spriteSize.x / 2) * spriteScale), 0); + t.Scale(-spriteScale, spriteScale); + } else if (flip == FLIP_MODE::VERTICAL) { + t.Translate(0, -((spriteSize.y / 2) * spriteScale)); + t.Scale(spriteScale, -spriteScale); + } + else { + t.Scale(spriteScale, spriteScale); + } + + t.Translate(position.x, position.y); + + if (mode == SPRITE_MODE::MULTI) { + olc::GFX2D::DrawSprite(GetMultiFrame(fElapsedTime), t); + } + else { + olc::Sprite* sprite = new olc::Sprite(spriteSize.x, spriteSize.y); + pge->SetDrawTarget(sprite); + pge->DrawPartialSprite({ 0, 0 }, spriteSheet, GetSingleFrame(fElapsedTime), spriteSize); + pge->SetDrawTarget(nullptr); + olc::GFX2D::DrawSprite(sprite, t); + delete sprite; + } + } +} + +#endif +#endif \ No newline at end of file