Implement character animations and dynamic sprite character loading.
1
.gitignore
vendored
@ -444,4 +444,3 @@ FodyWeavers.xsd
|
||||
/CMakeCache.txt
|
||||
/assets/gfx/Cute RPG
|
||||
/bin/assets/gfx/Cute RPG
|
||||
/bin
|
||||
|
BIN
bin/Shep.data
Normal file
After Width: | Height: | Size: 3.2 MiB |
1
bin/Shep.html
Normal file
1
bin/Shep.js
Normal file
BIN
bin/Shep.wasm
Normal file
3
bin/assets/gfx/CREDITS.txt
Normal file
@ -0,0 +1,3 @@
|
||||
- Credits to PixyMoon for many pixel art graphics, characters, and world tiles that are used in the game
|
||||
|
||||
Cute RPG Graphics
|
BIN
bin/assets/gfx/Character_001.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
bin/assets/gfx/Character_002.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
bin/assets/gfx/Character_003.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
bin/assets/gfx/Character_004.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
bin/assets/gfx/Character_005.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
bin/assets/gfx/Character_006.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
bin/assets/gfx/Character_007.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
bin/assets/gfx/Character_008.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
bin/assets/gfx/Character_009.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
bin/assets/gfx/Character_010.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
bin/assets/gfx/Character_011.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
bin/assets/gfx/Character_012.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
bin/assets/gfx/Character_013.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
bin/assets/gfx/Character_014.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
bin/assets/gfx/Character_015.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
bin/assets/gfx/Character_016.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
bin/assets/gfx/Character_017.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
bin/assets/gfx/Character_018.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
bin/assets/gfx/Character_019.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
bin/assets/gfx/Character_020.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
bin/assets/gfx/Character_021.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
bin/assets/gfx/Character_022.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
bin/assets/gfx/Character_023.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
bin/assets/gfx/Character_024.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
bin/assets/gfx/Character_025.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
bin/assets/gfx/Character_026.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
bin/assets/gfx/Character_027.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
bin/assets/gfx/Character_028.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
bin/assets/gfx/Character_029.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
bin/assets/gfx/Character_030.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
bin/assets/gfx/Character_031.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
bin/assets/gfx/Character_032.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
bin/assets/gfx/Character_033.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
bin/assets/gfx/Character_034.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
bin/assets/gfx/Character_035.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
bin/assets/gfx/Character_036.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
bin/assets/gfx/Character_037.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
bin/assets/gfx/Character_038.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
bin/assets/gfx/Character_039.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
bin/assets/gfx/Character_040.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
bin/assets/gfx/Character_041.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 4.8 KiB |
BIN
bin/assets/gfx/nico-trapper.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 4.8 KiB |
@ -38,9 +38,19 @@ All rights reserved.
|
||||
#pragma once
|
||||
|
||||
enum class AnimationState{
|
||||
STAND,
|
||||
STAND_N,
|
||||
STAND_E,
|
||||
STAND_S,
|
||||
STAND_W,
|
||||
WALK_N,
|
||||
WALK_E,
|
||||
WALK_S,
|
||||
WALK_W,
|
||||
};
|
||||
|
||||
enum class SkinTone{
|
||||
TONE_1,
|
||||
TONE_2,
|
||||
TONE_3,
|
||||
TONE_4,
|
||||
};
|
@ -65,6 +65,34 @@ const hw3d::mesh&GameObject::GetMesh()const{
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::array<float,2>>GameObject::GetUVs()const{
|
||||
if(anim){
|
||||
switch(GetMeshType()){
|
||||
case MeshType::FLOOR:
|
||||
case MeshType::OBJ:{
|
||||
std::cout<<"WARNING! Unimplemented animation code reached!"<<std::endl;
|
||||
throw;
|
||||
}break;
|
||||
case MeshType::SPRITE:{
|
||||
geom2d::rect<float>uv{animState.GetFrame(*anim).GetSourceUV()};
|
||||
return std::vector<std::array<float,2>>{{
|
||||
{{uv.pos.x,uv.pos.y+uv.size.y}},
|
||||
{{uv.pos.x+uv.size.x,uv.pos.y+uv.size.y}},
|
||||
{{uv.pos.x+uv.size.x,uv.pos.y}},
|
||||
{{uv.pos.x,uv.pos.y+uv.size.y}},
|
||||
{{uv.pos.x+uv.size.x,uv.pos.y}},
|
||||
{{uv.pos.x,uv.pos.y}},
|
||||
}};
|
||||
}break;
|
||||
default:{
|
||||
std::cout<<"WARNING! Mesh Type is not any of the expected values! THIS SHOULD NOT BE HAPPENING!"<<std::endl;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
else return GetMesh().uv;
|
||||
}
|
||||
|
||||
const vf3d&GameObject::GetPos()const{
|
||||
return pos;
|
||||
}
|
||||
@ -74,7 +102,8 @@ const vf3d&GameObject::GetScale()const{
|
||||
}
|
||||
|
||||
const Renderable&GameObject::GetSprite()const{
|
||||
return ShepGame::Game().GetSpr(spriteMeshName);
|
||||
if(anim)return *animState.GetFrame(*anim).GetSourceImage();
|
||||
else return ShepGame::Game().GetSpr(spriteMeshName);
|
||||
}
|
||||
|
||||
void GameObject::SetAutoScale(const vf2d&unitDivision){
|
||||
@ -85,15 +114,47 @@ void GameObject::SetAutoScale(const vf2d&unitDivision){
|
||||
}
|
||||
|
||||
void GameObject::Update(const float&fElapsedTime){
|
||||
if(anim)ShepGame::Game().animation.UpdateState(*anim,fElapsedTime);
|
||||
if(anim)animState.UpdateState(*anim,fElapsedTime);
|
||||
auto&game{ShepGame::Game()};
|
||||
|
||||
switch(id){
|
||||
case ObjectID::PLAYER:{
|
||||
if(game.GetKey(Key::W).bHeld){pos.z-=fElapsedTime*GameSettings::playerSpd;}
|
||||
if(game.GetKey(Key::A).bHeld){pos.x-=fElapsedTime*GameSettings::playerSpd;}
|
||||
if(game.GetKey(Key::S).bHeld){pos.z+=fElapsedTime*GameSettings::playerSpd;}
|
||||
if(game.GetKey(Key::D).bHeld){pos.x+=fElapsedTime*GameSettings::playerSpd;}
|
||||
if(game.GetKey(Key::W).bHeld){
|
||||
pos.z-=fElapsedTime*GameSettings::playerSpd;
|
||||
if(anim)animState.ChangeState(*anim,AnimationState::WALK_N);
|
||||
}
|
||||
if(game.GetKey(Key::A).bHeld){
|
||||
pos.x-=fElapsedTime*GameSettings::playerSpd;
|
||||
if(anim)animState.ChangeState(*anim,AnimationState::WALK_W);
|
||||
}
|
||||
if(game.GetKey(Key::S).bHeld){
|
||||
pos.z+=fElapsedTime*GameSettings::playerSpd;
|
||||
if(anim)animState.ChangeState(*anim,AnimationState::WALK_S);
|
||||
}
|
||||
if(game.GetKey(Key::D).bHeld){
|
||||
pos.x+=fElapsedTime*GameSettings::playerSpd;
|
||||
if(anim)animState.ChangeState(*anim,AnimationState::WALK_E);
|
||||
}
|
||||
if(game.GetKey(Key::W).bReleased){
|
||||
if(anim)animState.ChangeState(*anim,AnimationState::STAND_N);
|
||||
}
|
||||
if(game.GetKey(Key::A).bReleased){
|
||||
if(anim)animState.ChangeState(*anim,AnimationState::STAND_W);
|
||||
}
|
||||
if(game.GetKey(Key::S).bReleased){
|
||||
if(anim)animState.ChangeState(*anim,AnimationState::STAND_S);
|
||||
}
|
||||
if(game.GetKey(Key::D).bReleased){
|
||||
if(anim)animState.ChangeState(*anim,AnimationState::STAND_E);
|
||||
}
|
||||
}break;
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::ApplyCharacterAnimation(const uint8_t animInd,const SkinTone tone){
|
||||
std::unordered_map<AnimationState,Animate2D::FrameSequence>&animations{ShepGame::Game().GetCharacterAnimation(animInd,tone)};
|
||||
for(AnimationState&state:std::vector<AnimationState>{AnimationState::STAND_S,AnimationState::STAND_W,AnimationState::STAND_E,AnimationState::STAND_N,AnimationState::WALK_S,AnimationState::WALK_W,AnimationState::WALK_E,AnimationState::WALK_N}){
|
||||
animState.AddState(state,animations.at(state));
|
||||
}
|
||||
anim=Animate2D::AnimationState{};
|
||||
}
|
@ -52,16 +52,18 @@ public:
|
||||
void Update(const float&fElapsedTime);
|
||||
const MeshType GetMeshType()const;
|
||||
const hw3d::mesh&GetMesh()const;
|
||||
const std::vector<std::array<float,2>>GetUVs()const;
|
||||
const vf3d&GetPos()const;
|
||||
const vf3d&GetScale()const;
|
||||
const Renderable&GetSprite()const;
|
||||
void SetAutoScale(const vf2d&unitDivision); //Takes the texture width/height and divides it by unitDivion then scales the Game Object accordingly. Useful for scaling tilemaps.
|
||||
void ApplyCharacterAnimation(const uint8_t animInd,const SkinTone tone);
|
||||
private:
|
||||
vf3d pos;
|
||||
vf3d scale;
|
||||
ObjectID id;
|
||||
std::string spriteMeshName;
|
||||
MeshType type;
|
||||
AnimationState state;
|
||||
std::optional<Animate2D::AnimationState>anim;
|
||||
Animate2D::Animation<AnimationState>animState;
|
||||
};
|
@ -41,12 +41,13 @@ void ShepGame::LoadObj(const std::string&filename){
|
||||
else assets[texFilename].Load("assets/models/"+texFilename);
|
||||
}
|
||||
|
||||
void ShepGame::LoadSprite(const std::string&filename){
|
||||
const Renderable&ShepGame::LoadSprite(const std::string&filename){
|
||||
if(assets.count(filename)){
|
||||
std::cout<<"WARNING! Duplicate sprite filename detected: "<<filename<<std::endl;
|
||||
throw;
|
||||
}
|
||||
assets[filename].Load("assets/gfx/"+filename);
|
||||
return assets.at(filename);
|
||||
}
|
||||
|
||||
GameObject&ShepGame::AddGameObject(const vf3d&pos,const vf3d&scale,const GameObject::ObjectID&id,const std::string&spriteMeshName,const MeshType&type){
|
||||
@ -96,12 +97,12 @@ bool ShepGame::OnUserCreate(){
|
||||
|
||||
InitializeSpriteMesh();
|
||||
InitializeFloorSpriteMesh();
|
||||
LoadAnimations();
|
||||
|
||||
LoadMap("Town1.tmx");
|
||||
|
||||
AddGameObject({0,0,0},{1,2,1},GameObject::ObjectID::PLAYER,"nico-Trapper_512.png",MeshType::SPRITE);
|
||||
auto&town{AddGameObject({0,0,0},{1,1,1},GameObject::ObjectID::DEFAULT,"Town1.png",MeshType::FLOOR)};
|
||||
town.SetAutoScale({16,16});
|
||||
auto&player{AddGameObject({0,0,0},{1,2,1},GameObject::ObjectID::PLAYER,"nico-trapper.png",MeshType::SPRITE)};
|
||||
player.ApplyCharacterAnimation(29,SkinTone::TONE_2);
|
||||
|
||||
AddLight({{4,0,10},WHITE});
|
||||
|
||||
@ -144,6 +145,33 @@ void ShepGame::LoadMap(const std::string&filename){
|
||||
}
|
||||
}
|
||||
}
|
||||
auto&town{AddGameObject({0,0,0},{1,1,1},GameObject::ObjectID::DEFAULT,"../maps/"+filename.substr(0,filename.length()-3)+"png",MeshType::FLOOR)};
|
||||
town.SetAutoScale({16,16});
|
||||
}
|
||||
|
||||
std::unordered_map<AnimationState,Animate2D::FrameSequence>&ShepGame::GetCharacterAnimation(const uint8_t animInd,const SkinTone tone){
|
||||
return characterAnimations.at((animInd-1)*4+int(tone));
|
||||
}
|
||||
|
||||
void ShepGame::LoadAnimations(){
|
||||
for(int i:std::ranges::iota_view(1,42)){
|
||||
const Renderable&sheet{LoadSprite(std::format("Character_{:03}.png",i))};
|
||||
for(int j:std::ranges::iota_view(0,4)){
|
||||
std::unordered_map<AnimationState,Animate2D::FrameSequence>animation;
|
||||
(*animation.insert({AnimationState::STAND_N,Animate2D::FrameSequence{0.2f,Animate2D::Style::Repeat}}).first).second.AddFrame(Animate2D::Frame{&sheet,{{24+j*96,24*3},{24,24}}});
|
||||
(*animation.insert({AnimationState::STAND_E,Animate2D::FrameSequence{0.2f,Animate2D::Style::Repeat}}).first).second.AddFrame(Animate2D::Frame{&sheet,{{24+j*96,24*2},{24,24}}});
|
||||
(*animation.insert({AnimationState::STAND_S,Animate2D::FrameSequence{0.2f,Animate2D::Style::Repeat}}).first).second.AddFrame(Animate2D::Frame{&sheet,{{24+j*96,24*0},{24,24}}});
|
||||
(*animation.insert({AnimationState::STAND_W,Animate2D::FrameSequence{0.2f,Animate2D::Style::Repeat}}).first).second.AddFrame(Animate2D::Frame{&sheet,{{24+j*96,24*1},{24,24}}});
|
||||
for(uint8_t row{};AnimationState&state:std::vector<AnimationState>{AnimationState::WALK_S,AnimationState::WALK_W,AnimationState::WALK_E,AnimationState::WALK_N}){
|
||||
animation.insert({state,Animate2D::FrameSequence{0.2f,Animate2D::Style::Repeat}});
|
||||
for(int k:std::ranges::iota_view(0,4)){
|
||||
animation[state].AddFrame(Animate2D::Frame{&sheet,{{24*k+j*96,24*row},{24,24}}});
|
||||
}
|
||||
row++;
|
||||
}
|
||||
characterAnimations.emplace_back(std::move(animation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ShepGame::OnUserUpdate(float fElapsedTime){
|
||||
@ -211,7 +239,7 @@ bool ShepGame::OnUserUpdate(float fElapsedTime){
|
||||
matWorld.translate(vf3d{-0.5f,0,-0.5f}+pos+obj.GetPos());
|
||||
mf4d scale;
|
||||
scale.scale(obj.GetScale());
|
||||
HW3D_DrawObject((matView * matWorld * scale).m, obj.GetSprite().Decal(), mesh.layout, mesh.pos, mesh.uv, mesh.col);
|
||||
HW3D_DrawObject((matView * matWorld * scale).m, obj.GetSprite().Decal(), mesh.layout, mesh.pos, obj.GetUVs(), mesh.col);
|
||||
};
|
||||
|
||||
for(GameObject&obj:objects){
|
||||
|
@ -57,7 +57,7 @@ public:
|
||||
const hw3d::mesh&GetSpriteMesh()const;
|
||||
static ShepGame&Game();
|
||||
void LoadMap(const std::string&filename);
|
||||
Animate2D::Animation<AnimationState>animation;
|
||||
std::unordered_map<AnimationState,Animate2D::FrameSequence>&GetCharacterAnimation(const uint8_t animInd,const SkinTone tone); //Provide the EXACT index of the character you want from the character images in gfx. NOTE: They are 1-indexed! Use 1 for the first one etc.
|
||||
private:
|
||||
|
||||
static ShepGame*game;
|
||||
@ -77,7 +77,10 @@ private:
|
||||
void InitializeSpriteMesh();
|
||||
void InitializeFloorSpriteMesh();
|
||||
void LoadObj(const std::string&filename); //filename is the base filename inside the assets/models directory. NOTE: Textures loaded from this function are also based from the assets/models directory and match the model name Ex. building1.obj expects building1.png.
|
||||
void LoadSprite(const std::string&filename); //filename is the base filename inside the assets/gfx directory
|
||||
const Renderable&LoadSprite(const std::string&filename); //filename is the base filename inside the assets/gfx directory
|
||||
void LoadAnimations();
|
||||
|
||||
std::vector<std::unordered_map<AnimationState,Animate2D::FrameSequence>>characterAnimations;
|
||||
|
||||
virtual bool OnUserCreate()override;
|
||||
|
||||
|