Tileset animations.
This commit is contained in:
@ -60,8 +60,8 @@ namespace Dawn {
|
||||
this->columns = columns;
|
||||
|
||||
// Calculate division sizes (pixels)
|
||||
this->divX = (w - (borderX * 2.0f) - (gapX * (columns - 1))) / columns;
|
||||
this->divY = (h - (borderY * 2.0f) - (gapY * (rows - 1))) / rows;
|
||||
this->divX = (w - (borderX * 2) - (gapX * (columns - 1))) / columns;
|
||||
this->divY = (h - (borderY * 2) - (gapY * (rows - 1))) / rows;
|
||||
|
||||
// Calculate the division sizes (units)
|
||||
float_t tdivX = (float_t)this->divX / (float_t)w;
|
||||
|
@ -9,155 +9,20 @@
|
||||
#include "util/mathutils.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
template<typename T>
|
||||
struct Keyframe {
|
||||
float_t time;
|
||||
T value;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct TimelineItem {
|
||||
T *modifies;
|
||||
std::vector<struct Keyframe<T>> keyframes;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Animation {
|
||||
public:
|
||||
bool_t loop = false;
|
||||
bool_t finished = false;
|
||||
float_t time = 0;
|
||||
float_t duration = 0;
|
||||
easefunction_t *easing = &easeOutQuad;
|
||||
std::vector<struct TimelineItem<T>> timelineItems;
|
||||
Event<> eventAnimationEnd;
|
||||
|
||||
|
||||
virtual void tick(float_t delta) = 0;
|
||||
|
||||
/**
|
||||
* Get an existing timeline item based on the value that will be modified.
|
||||
*
|
||||
* @param modifies Value that is intended to be modified for the timeline.
|
||||
* @return The existing timeline item OR NULL if not found.
|
||||
* Restart a running animation.
|
||||
*/
|
||||
struct TimelineItem<T> * getTimelineItem(T *modifies) {
|
||||
assertNotNull(modifies);
|
||||
|
||||
auto it = this->timelineItems.begin();
|
||||
while(it != this->timelineItems.end()) {
|
||||
if(it->modifies == modifies) return &(*it);
|
||||
++it;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a timeline item to an animation.
|
||||
*
|
||||
* @param modifies Value that will be modified for the timeline item.
|
||||
* @return The timeline item for that modified value.
|
||||
*/
|
||||
struct TimelineItem<T> * addTimelineItem(T *modifies) {
|
||||
assertNotNull(modifies);
|
||||
struct TimelineItem<T> item;
|
||||
item.modifies = modifies;
|
||||
this->timelineItems.push_back(item);
|
||||
return &this->timelineItems.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a keyframe to the animation.
|
||||
*
|
||||
* @param modifies Pointer to the value that will be modified.
|
||||
* @param time Time that the animation will occur at (gametime seconds).
|
||||
* @param value Value for this keyframe
|
||||
* @return The keyframe that was added.
|
||||
*/
|
||||
struct Keyframe<T> * addKeyframe(T *modifies, float_t time, T value) {
|
||||
auto item = this->getTimelineItem(modifies);
|
||||
if(item == nullptr) item = this->addTimelineItem(modifies);
|
||||
|
||||
struct Keyframe<T> keyframe;
|
||||
keyframe.time = time;
|
||||
keyframe.value = value;
|
||||
this->duration = mathMax<float_t>(this->duration, time);
|
||||
this->finished = false;
|
||||
|
||||
item->keyframes.push_back(keyframe);
|
||||
return &item->keyframes.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick/Update the animation.
|
||||
*
|
||||
* @param delta Delta (in seconds) to update the animation by.
|
||||
*/
|
||||
void tick(float_t delta) {
|
||||
if(this->finished) return;
|
||||
|
||||
float_t newTime = this->time + delta;
|
||||
|
||||
auto it = this->timelineItems.begin();
|
||||
while(it != this->timelineItems.end()) {
|
||||
struct Keyframe<T> *keyframeNext = nullptr;
|
||||
struct Keyframe<T> *keyframeCurrent = nullptr;
|
||||
|
||||
// For each keyframe
|
||||
auto itKey = it->keyframes.begin();
|
||||
while(itKey != it->keyframes.end()) {
|
||||
if(itKey->time > newTime) {
|
||||
keyframeNext = &(*itKey);
|
||||
break;
|
||||
}
|
||||
keyframeCurrent = &(*itKey);
|
||||
++itKey;
|
||||
}
|
||||
|
||||
// Skip when no keyframe
|
||||
float_t oldTime;
|
||||
T oldValue;
|
||||
|
||||
if(keyframeCurrent == nullptr) {
|
||||
if(keyframeNext == nullptr) continue;
|
||||
oldTime = this->time;
|
||||
oldValue = *it->modifies;
|
||||
} else if(keyframeNext == nullptr) {
|
||||
*it->modifies = keyframeCurrent->value;
|
||||
++it;
|
||||
continue;
|
||||
} else {
|
||||
oldValue = keyframeCurrent->value;
|
||||
oldTime = keyframeCurrent->time;
|
||||
}
|
||||
|
||||
// Slerp between keyframes
|
||||
float_t keyframeDelta = this->easing(
|
||||
(newTime - oldTime) / (keyframeNext->time - oldTime)
|
||||
);
|
||||
*it->modifies = oldValue + (
|
||||
(keyframeNext->value - oldValue) * keyframeDelta
|
||||
);
|
||||
++it;
|
||||
}
|
||||
|
||||
// Update time.
|
||||
this->time = newTime;
|
||||
|
||||
// Has the animation finished?
|
||||
if(newTime < this->duration) return;
|
||||
|
||||
// Do we need to loop?
|
||||
if(this->loop) {
|
||||
this->time = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Animation end.
|
||||
this->finished = true;
|
||||
|
||||
this->eventAnimationEnd.invoke();
|
||||
}
|
||||
|
||||
void restart() {
|
||||
virtual void restart() {
|
||||
this->time = 0;
|
||||
this->finished = false;
|
||||
}
|
||||
@ -165,8 +30,7 @@ namespace Dawn {
|
||||
/**
|
||||
* Clears an animaton of all its animation items and keyframes.
|
||||
*/
|
||||
void clear() {
|
||||
this->timelineItems.clear();
|
||||
virtual void clear() {
|
||||
this->duration = 0;
|
||||
}
|
||||
};
|
||||
|
124
src/dawn/display/animation/SimpleAnimation.hpp
Normal file
124
src/dawn/display/animation/SimpleAnimation.hpp
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2022 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "Animation.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
template<typename T>
|
||||
struct SimpleKeyframe {
|
||||
float_t time;
|
||||
T value;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct SimpleAnimation : public Animation {
|
||||
public:
|
||||
easefunction_t *easing = &easeLinear;
|
||||
T *modifies;
|
||||
std::vector<struct SimpleKeyframe<T>> keyframes;
|
||||
|
||||
SimpleAnimation(T *modifies) {
|
||||
this->modifies = modifies;
|
||||
}
|
||||
|
||||
void addKeyframe(float_t time, T value) {
|
||||
struct SimpleKeyframe<T> keyframe;
|
||||
keyframe.time = time;
|
||||
keyframe.value = value;
|
||||
this->duration = mathMax<float_t>(this->duration, time);
|
||||
this->finished = false;
|
||||
this->keyframes.push_back(keyframe);
|
||||
}
|
||||
|
||||
void addSequentialKeyframes(
|
||||
float_t startTime,
|
||||
float_t frameTime,
|
||||
T start,
|
||||
T end,
|
||||
T step
|
||||
) {
|
||||
T v = start;
|
||||
float_t n = startTime;
|
||||
while(v != end) {
|
||||
this->addKeyframe(n, v);
|
||||
n += frameTime;
|
||||
v += step;
|
||||
}
|
||||
}
|
||||
|
||||
void addSequentialKeyframes(float_t frameTime, T start, T end) {
|
||||
this->addSequentialKeyframes(0, frameTime, start, end, 1);
|
||||
}
|
||||
|
||||
void tick(float_t delta) override {
|
||||
if(this->finished) return;
|
||||
|
||||
float_t newTime = this->time + delta;
|
||||
|
||||
struct SimpleKeyframe<T> *keyframeNext = nullptr;
|
||||
struct SimpleKeyframe<T> *keyframeCurrent = nullptr;
|
||||
|
||||
// Find current and next keyframe(s)
|
||||
auto itKey = this->keyframes.begin();
|
||||
while(itKey != this->keyframes.end()) {
|
||||
if(itKey->time > newTime) {
|
||||
keyframeNext = &(*itKey);
|
||||
break;
|
||||
}
|
||||
keyframeCurrent = &(*itKey);
|
||||
++itKey;
|
||||
}
|
||||
|
||||
// Update values
|
||||
if(keyframeCurrent != nullptr && keyframeNext == nullptr) {
|
||||
// "End of animation"
|
||||
*this->modifies = keyframeCurrent->value;
|
||||
} else if(keyframeNext != nullptr) {
|
||||
T oldValue;
|
||||
float_t oldTime;
|
||||
|
||||
if(keyframeCurrent == nullptr) {
|
||||
// "Start of animation"
|
||||
oldValue = keyframeCurrent->value;
|
||||
oldTime = keyframeCurrent->time;
|
||||
} else {
|
||||
// "Mid animation"
|
||||
oldTime = this->time;
|
||||
oldValue = *this->modifies;
|
||||
}
|
||||
|
||||
// Slerp between keyframes
|
||||
float_t keyframeDelta = this->easing(
|
||||
(newTime - oldTime) / (keyframeNext->time - oldTime)
|
||||
);
|
||||
*this->modifies = oldValue + (
|
||||
(keyframeNext->value - oldValue) * keyframeDelta
|
||||
);
|
||||
}
|
||||
|
||||
// Update time.
|
||||
this->time = newTime;
|
||||
|
||||
// Has the animation finished?
|
||||
if(newTime < this->duration) return;
|
||||
|
||||
// Do we need to loop?
|
||||
if(this->loop) {
|
||||
this->time = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Animation end.
|
||||
this->finished = true;
|
||||
this->eventAnimationEnd.invoke();
|
||||
}
|
||||
|
||||
void clear() override {
|
||||
Animation::clear();
|
||||
this->keyframes.clear();
|
||||
}
|
||||
};
|
||||
}
|
27
src/dawn/display/animation/TiledSpriteAnimation.hpp
Normal file
27
src/dawn/display/animation/TiledSpriteAnimation.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2022 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "SimpleAnimation.hpp"
|
||||
#include "scene/components/Components.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
struct TiledSpriteAnimation : public SimpleAnimation<int32_t> {
|
||||
public:
|
||||
int32_t frame = 0;
|
||||
TiledSprite *sprite = nullptr;
|
||||
|
||||
TiledSpriteAnimation(TiledSprite *sprite) :
|
||||
SimpleAnimation(&frame),
|
||||
sprite(sprite)
|
||||
{
|
||||
}
|
||||
|
||||
void tick(float_t delta) override {
|
||||
SimpleAnimation::tick(delta);
|
||||
this->sprite->setTile(frame);
|
||||
}
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user