The Snivy Video Game Is Complete
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include "../resource/texture.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace glm;
|
||||
using namespace game::util;
|
||||
@@ -16,20 +17,70 @@ using namespace game::anm2;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
std::shared_ptr<void> texture_callback(const std::filesystem::path& path) { return std::make_shared<Texture>(path); }
|
||||
std::shared_ptr<void> sound_callback(const std::filesystem::path& path) { return std::make_shared<Audio>(path); }
|
||||
|
||||
Actor::Actor(const std::filesystem::path& path, vec2 position) : anm2(path, texture_callback, sound_callback)
|
||||
Actor::Actor(Anm2* _anm2, vec2 _position, Mode mode, float time) : anm2(_anm2), position(_position)
|
||||
{
|
||||
this->position = position;
|
||||
play(anm2.animations.defaultAnimation);
|
||||
if (anm2)
|
||||
{
|
||||
this->mode = mode;
|
||||
this->startTime = time;
|
||||
play(anm2->animations.defaultAnimation, mode, time);
|
||||
}
|
||||
}
|
||||
|
||||
anm2::Animation* Actor::animation_get() { return vector::find(anm2.animations.items, animationIndex); }
|
||||
|
||||
anm2::Item* Actor::item_get(anm2::Type type, int id)
|
||||
anm2::Animation* Actor::animation_get(int index)
|
||||
{
|
||||
if (auto animation = animation_get())
|
||||
if (!anm2) return nullptr;
|
||||
if (index == -1) index = animationIndex;
|
||||
if (anm2->animations.mapReverse.contains(index)) return &anm2->animations.items[index];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
anm2::Animation* Actor::animation_get(const std::string& name)
|
||||
{
|
||||
if (!anm2) return nullptr;
|
||||
if (anm2->animations.map.contains(name)) return &anm2->animations.items[anm2->animations.map[name]];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Actor::is_playing(const std::string& name)
|
||||
{
|
||||
if (!anm2) return false;
|
||||
if (name.empty())
|
||||
return isPlaying;
|
||||
else
|
||||
return isPlaying && anm2->animations.map[name] == animationIndex;
|
||||
}
|
||||
|
||||
int Actor::animation_index_get(const std::string& name)
|
||||
{
|
||||
if (!anm2) return -1;
|
||||
if (anm2->animations.map.contains(name)) return anm2->animations.map[name];
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Actor::item_id_get(const std::string& name, anm2::Type type)
|
||||
{
|
||||
if (!anm2 || (type != anm2::LAYER && type != anm2::NULL_)) return -1;
|
||||
|
||||
if (type == anm2::LAYER)
|
||||
{
|
||||
for (int i = 0; i < anm2->content.layers.size(); i++)
|
||||
if (anm2->content.layers.at(i).name == name) return i;
|
||||
}
|
||||
else if (type == anm2::NULL_)
|
||||
{
|
||||
for (int i = 0; i < anm2->content.nulls.size(); i++)
|
||||
if (anm2->content.nulls.at(i).name == name) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
anm2::Item* Actor::item_get(anm2::Type type, int id, int animationIndex)
|
||||
{
|
||||
if (!anm2) return nullptr;
|
||||
if (animationIndex == -1) animationIndex = this->animationIndex;
|
||||
if (auto animation = animation_get(animationIndex))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
@@ -51,6 +102,16 @@ namespace game::resource
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Actor::item_length(anm2::Item* item)
|
||||
{
|
||||
if (!item) return -1;
|
||||
|
||||
int duration{};
|
||||
for (auto& frame : item->frames)
|
||||
duration += frame.duration;
|
||||
return duration;
|
||||
}
|
||||
|
||||
anm2::Frame* Actor::trigger_get(int atFrame)
|
||||
{
|
||||
if (auto item = item_get(anm2::TRIGGER))
|
||||
@@ -66,7 +127,14 @@ namespace game::resource
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
anm2::Frame Actor::frame_generate(anm2::Item& item, float time)
|
||||
bool Actor::is_event(const std::string& event)
|
||||
{
|
||||
if (!anm2) return false;
|
||||
if (playedEventID == -1) return false;
|
||||
return event == anm2->content.events.at(playedEventID).name;
|
||||
}
|
||||
|
||||
anm2::Frame Actor::frame_generate(anm2::Item& item, float time, anm2::Type type, int id)
|
||||
{
|
||||
anm2::Frame frame{};
|
||||
frame.isVisible = false;
|
||||
@@ -76,6 +144,7 @@ namespace game::resource
|
||||
time = time < 0.0f ? 0.0f : time;
|
||||
|
||||
anm2::Frame* frameNext = nullptr;
|
||||
anm2::Frame frameNextCopy{};
|
||||
int durationCurrent = 0;
|
||||
int durationNext = 0;
|
||||
|
||||
@@ -90,7 +159,10 @@ namespace game::resource
|
||||
if (time >= durationCurrent && time < durationNext)
|
||||
{
|
||||
if (i + 1 < (int)item.frames.size())
|
||||
{
|
||||
frameNext = &item.frames[i + 1];
|
||||
frameNextCopy = *frameNext;
|
||||
}
|
||||
else
|
||||
frameNext = nullptr;
|
||||
break;
|
||||
@@ -99,53 +171,110 @@ namespace game::resource
|
||||
durationCurrent += frame.duration;
|
||||
}
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override || !override->isEnabled) continue;
|
||||
|
||||
if (id == override->destinationID)
|
||||
{
|
||||
switch (override->mode)
|
||||
{
|
||||
case Override::FRAME_ADD:
|
||||
if (override->frame.scale.has_value())
|
||||
{
|
||||
frame.scale += *override->frame.scale;
|
||||
if (frameNext) frameNextCopy.scale += *override->frame.scale;
|
||||
}
|
||||
if (override->frame.rotation.has_value())
|
||||
{
|
||||
frame.rotation += *override->frame.rotation;
|
||||
if (frameNext) frameNextCopy.rotation += *override->frame.rotation;
|
||||
}
|
||||
break;
|
||||
case Override::FRAME_SET:
|
||||
if (override->frame.scale.has_value())
|
||||
{
|
||||
frame.scale = *override->frame.scale;
|
||||
if (frameNext) frameNextCopy.scale = *override->frame.scale;
|
||||
}
|
||||
if (override->frame.rotation.has_value())
|
||||
{
|
||||
frame.rotation = *override->frame.rotation;
|
||||
if (frameNext) frameNextCopy.rotation = *override->frame.rotation;
|
||||
}
|
||||
break;
|
||||
case Override::ITEM_SET:
|
||||
default:
|
||||
if (override->animationIndex == -1) break;
|
||||
auto& animation = anm2->animations.items[override->animationIndex];
|
||||
auto overrideFrame = frame_generate(animation.layerAnimations[override->sourceID], override->time,
|
||||
anm2::LAYER, override->sourceID);
|
||||
frame.crop = overrideFrame.crop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.isInterpolated && frameNext && frame.duration > 1)
|
||||
{
|
||||
auto interpolation = (time - durationCurrent) / (durationNext - durationCurrent);
|
||||
|
||||
frame.rotation = glm::mix(frame.rotation, frameNext->rotation, interpolation);
|
||||
frame.position = glm::mix(frame.position, frameNext->position, interpolation);
|
||||
frame.scale = glm::mix(frame.scale, frameNext->scale, interpolation);
|
||||
frame.colorOffset = glm::mix(frame.colorOffset, frameNext->colorOffset, interpolation);
|
||||
frame.tint = glm::mix(frame.tint, frameNext->tint, interpolation);
|
||||
frame.rotation = glm::mix(frame.rotation, frameNextCopy.rotation, interpolation);
|
||||
frame.position = glm::mix(frame.position, frameNextCopy.position, interpolation);
|
||||
frame.scale = glm::mix(frame.scale, frameNextCopy.scale, interpolation);
|
||||
frame.colorOffset = glm::mix(frame.colorOffset, frameNextCopy.colorOffset, interpolation);
|
||||
frame.tint = glm::mix(frame.tint, frameNextCopy.tint, interpolation);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void Actor::play(const std::string& name)
|
||||
void Actor::play(int index, Mode mode, float time, float speedMultiplier)
|
||||
{
|
||||
for (int i = 0; i < anm2.animations.items.size(); i++)
|
||||
{
|
||||
if (anm2.animations.items[i].name == name)
|
||||
{
|
||||
animationIndex = i;
|
||||
time = 0.0f;
|
||||
isPlaying = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->playedEventID = -1;
|
||||
this->playedTriggers.clear();
|
||||
|
||||
if (!anm2) return;
|
||||
if (mode != FORCE_PLAY && this->animationIndex == index) return;
|
||||
if (!vector::in_bounds(anm2->animations.items, index)) return;
|
||||
this->speedMultiplier = speedMultiplier;
|
||||
this->previousAnimationIndex = animationIndex;
|
||||
this->animationIndex = index;
|
||||
this->time = time;
|
||||
if (mode == PLAY || mode == FORCE_PLAY) isPlaying = true;
|
||||
}
|
||||
|
||||
void Actor::play(const std::string& name, Mode mode, float time, float speedMultiplier)
|
||||
{
|
||||
if (!anm2) return;
|
||||
if (anm2->animations.map.contains(name))
|
||||
play(anm2->animations.map.at(name), mode, time, speedMultiplier);
|
||||
else
|
||||
std::cout << "Animation \"" << name << "\" does not exist! Unable to play!\n";
|
||||
}
|
||||
|
||||
void Actor::tick()
|
||||
{
|
||||
if (!anm2) return;
|
||||
if (!isPlaying) return;
|
||||
auto animation = animation_get();
|
||||
if (!animation) return;
|
||||
|
||||
time += anm2.info.fps / 30.0f;
|
||||
playedEventID = -1;
|
||||
|
||||
auto intTime = (int)time;
|
||||
|
||||
if (auto trigger = trigger_get(intTime))
|
||||
for (auto& trigger : animation->triggers.frames)
|
||||
{
|
||||
if (!playedTriggers.contains(intTime))
|
||||
if (!playedTriggers.contains(trigger.atFrame) && time >= trigger.atFrame)
|
||||
{
|
||||
if (auto sound = map::find(anm2.content.sounds, trigger->soundID)) sound->audio.play();
|
||||
playedTriggers.insert(intTime);
|
||||
if (auto sound = map::find(anm2->content.sounds, trigger.soundID)) sound->audio.play();
|
||||
playedTriggers.insert((int)trigger.atFrame);
|
||||
playedEventID = trigger.eventID;
|
||||
}
|
||||
}
|
||||
|
||||
auto increment = (anm2->info.fps / 30.0f) * speedMultiplier;
|
||||
time += increment;
|
||||
|
||||
if (time >= animation->frameNum)
|
||||
{
|
||||
if (animation->isLoop)
|
||||
@@ -155,43 +284,138 @@ namespace game::resource
|
||||
|
||||
playedTriggers.clear();
|
||||
}
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override->isEnabled || override->length < 0) continue;
|
||||
override->time += increment;
|
||||
if (override->time > override->length) override->isLoop ? override->time = 0.0f : override->isEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Actor::render(Shader& shader, Canvas& canvas)
|
||||
glm::vec4 Actor::null_frame_rect(int nullID)
|
||||
{
|
||||
auto invalidRect = glm::vec4(0.0f / 0.0f);
|
||||
if (!anm2 || nullID == -1) return invalidRect;
|
||||
auto item = item_get(anm2::NULL_, nullID);
|
||||
if (!item) return invalidRect;
|
||||
|
||||
auto animation = animation_get();
|
||||
if (!animation) return invalidRect;
|
||||
|
||||
auto root = frame_generate(animation->rootAnimation, time, anm2::ROOT);
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override || !override->isEnabled || override->type != anm2::ROOT) continue;
|
||||
|
||||
switch (override->mode)
|
||||
{
|
||||
case Override::FRAME_ADD:
|
||||
if (override->frame.scale.has_value()) root.scale += *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation += *override->frame.rotation;
|
||||
break;
|
||||
case Override::FRAME_SET:
|
||||
if (override->frame.scale.has_value()) root.scale = *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation = *override->frame.rotation;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto frame = frame_generate(*item, time, anm2::NULL_, nullID);
|
||||
if (!frame.isVisible) return invalidRect;
|
||||
|
||||
auto rootScale = math::to_unit(root.scale);
|
||||
auto frameScale = math::to_unit(frame.scale);
|
||||
auto combinedScale = rootScale * frameScale;
|
||||
auto scaledSize = NULL_SIZE * glm::abs(combinedScale);
|
||||
|
||||
auto worldPosition = position + root.position + frame.position * rootScale;
|
||||
auto halfSize = scaledSize * 0.5f;
|
||||
|
||||
return glm::vec4(worldPosition - halfSize, scaledSize);
|
||||
}
|
||||
|
||||
void Actor::render(Shader& textureShader, Shader& rectShader, Canvas& canvas)
|
||||
{
|
||||
if (!anm2) return;
|
||||
auto animation = animation_get();
|
||||
if (!animation) return;
|
||||
|
||||
auto root = frame_generate(animation->rootAnimation, time);
|
||||
auto rootModel = math::quad_model_parent_get(root.position + position, root.pivot,
|
||||
math::percent_to_unit(root.scale), root.rotation);
|
||||
auto root = frame_generate(animation->rootAnimation, time, anm2::ROOT);
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override || !override->isEnabled || override->type != anm2::ROOT) continue;
|
||||
|
||||
switch (override->mode)
|
||||
{
|
||||
case Override::FRAME_ADD:
|
||||
if (override->frame.scale.has_value()) root.scale += *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation += *override->frame.rotation;
|
||||
break;
|
||||
case Override::FRAME_SET:
|
||||
if (override->frame.scale.has_value()) root.scale = *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation = *override->frame.rotation;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto rootModel =
|
||||
math::quad_model_parent_get(root.position + position, root.pivot, math::to_unit(root.scale), root.rotation);
|
||||
|
||||
for (auto& i : animation->layerOrder)
|
||||
{
|
||||
auto& layerAnimation = animation->layerAnimations[i];
|
||||
if (!layerAnimation.isVisible) continue;
|
||||
|
||||
auto layer = map::find(anm2.content.layers, i);
|
||||
auto layer = map::find(anm2->content.layers, i);
|
||||
if (!layer) continue;
|
||||
|
||||
auto spritesheet = map::find(anm2.content.spritesheets, layer->spritesheetID);
|
||||
auto spritesheet = map::find(anm2->content.spritesheets, layer->spritesheetID);
|
||||
if (!spritesheet) continue;
|
||||
|
||||
auto frame = frame_generate(layerAnimation, time);
|
||||
auto frame = frame_generate(layerAnimation, time, anm2::LAYER, i);
|
||||
if (!frame.isVisible) continue;
|
||||
|
||||
auto model = math::quad_model_get(frame.size, frame.position, frame.pivot, math::percent_to_unit(frame.scale),
|
||||
frame.rotation);
|
||||
auto model =
|
||||
math::quad_model_get(frame.size, frame.position, frame.pivot, math::to_unit(frame.scale), frame.rotation);
|
||||
model = rootModel * model;
|
||||
|
||||
auto& texture = spritesheet->texture;
|
||||
if (!texture.is_valid()) return;
|
||||
|
||||
auto tint = frame.tint * root.tint;
|
||||
auto colorOffset = frame.colorOffset + root.colorOffset;
|
||||
|
||||
auto uvMin = frame.crop / vec2(texture.size);
|
||||
auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
|
||||
auto uvVertices = math::uv_vertices_get(uvMin, uvMax);
|
||||
|
||||
canvas.texture_render(shader, texture.id, model, frame.tint, frame.colorOffset, uvVertices.data());
|
||||
canvas.texture_render(textureShader, texture.id, model, tint, colorOffset, uvVertices.data());
|
||||
}
|
||||
|
||||
if (isShowNulls)
|
||||
{
|
||||
for (int i = 0; i < animation->nullAnimations.size(); i++)
|
||||
{
|
||||
auto& nullAnimation = animation->nullAnimations[i];
|
||||
if (!nullAnimation.isVisible) continue;
|
||||
|
||||
auto frame = frame_generate(nullAnimation, time, anm2::NULL_, i);
|
||||
if (!frame.isVisible) continue;
|
||||
|
||||
auto model = math::quad_model_get(frame.scale, frame.position, frame.scale * 0.5f, vec2(1.0f), frame.rotation);
|
||||
model = rootModel * model;
|
||||
|
||||
canvas.rect_render(rectShader, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Actor::consume_played_event() { playedEventID = -1; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user