All simple curve flying animation

This commit is contained in:
NaiJi ✨ 2021-06-11 19:58:44 +03:00
parent 7dc4ef2bb8
commit 192e371d2f
13 changed files with 164 additions and 45 deletions

View File

@ -30,7 +30,7 @@ private:
std::unique_ptr<Game> _game;
void startGameLoop();
void exec();
};
#endif // APPLICATION_H

View File

@ -13,7 +13,8 @@ public:
_perfect_offset(perfect_offset) {}
virtual ~Note() = default;
virtual bool isActive(microsec music_offset) const = 0;
virtual bool isActive(const microsec& music_offset) const = 0;
virtual void update(const microsec& music_offset) = 0;
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const = 0;
virtual microsec offset() const

View File

@ -10,7 +10,7 @@
using microsec = sf::Int64;
template<typename GRADE, typename = std::enable_if_t<std::is_enum<GRADE>::value>>
template<typename Grade, typename = std::enable_if_t<std::is_enum<Grade>::value>>
class PrecisionEvaluator
{
public:
@ -34,7 +34,7 @@ public:
&& music_play_offset < _end_handling_offset;
}
inline GRADE calculatePrecision(microsec odds) const noexcept
inline Grade calculatePrecision(microsec odds) const noexcept
{
microsec shift_from_perfect = std::abs(odds - offset());
@ -45,7 +45,7 @@ public:
break;
}
return static_cast<GRADE>(raw_grade);
return static_cast<Grade>(raw_grade);
}
private:
@ -53,9 +53,9 @@ private:
microsec _start_handling_offset;
microsec _end_handling_offset;
/* Amount of values in enum instanced as GRADES
/* Amount of values in enum instanced as GradeS
* represents capacity of _intervals.
* So, for each V value in GRADES enum, _intervals[V]
* So, for each V value in GradeS enum, _intervals[V]
* should return time shift from V - 1.
* V0 is PERFECT SCORE and the last V represents the worst
* grades which is death of note by expiration */

View File

@ -13,7 +13,7 @@ public:
virtual ~Timeline() = default;
virtual void update() = 0;
virtual void init() = 0;
virtual void run() = 0;
virtual void clear() = 0;
virtual microsec currentMusicOffset() const = 0;

View File

@ -24,11 +24,12 @@ Application::~Application()
void Application::run()
{
_game_window.display();
_game->run();
startGameLoop();
exec();
}
void Application::startGameLoop()
void Application::exec()
{
sf::Clock timer;
sf::Time time_since_last_update = sf::Time::Zero;
@ -54,6 +55,9 @@ void Application::input()
sf::Event event;
while (_game_window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
_game_window.close();
_game->input(event);
}
}

View File

@ -8,9 +8,7 @@ ClassicGame::ClassicGame() :
_timeline(std::make_unique<ClassicTimeline>()),
_view_manager(std::make_unique<ClassicViewManager>())
{
_timeline->fetchVisibleNotes(_view_manager);
_timeline->init();
_font.loadFromFile("VeraMono.ttf");
_keys_to_buttons =
{
{sf::Keyboard::Up, Button::UP}, // Load from settings
@ -55,7 +53,8 @@ ClassicGame::~ClassicGame()
void ClassicGame::run()
{
_timeline->fetchVisibleNotes(_view_manager);
_timeline->run();
}
void ClassicGame::input(const sf::Event& event)
@ -66,6 +65,7 @@ void ClassicGame::input(const sf::Event& event)
switch (event.type)
{
default:
return;
break;
case sf::Event::KeyPressed:
{
@ -83,8 +83,26 @@ void ClassicGame::input(const sf::Event& event)
auto note = _timeline->getActiveNote();
if (!_timeline->isExpired(note))
(*note)->input(ClassicInputType(timestamp, new_action));
if (!_timeline->isExpired(note) || (*note)->state() != ClassicNote::State::DEAD)
{
auto grade = (*note)->input(ClassicInputType(timestamp, new_action));
sf::Text new_grade;
new_grade.setFillColor(sf::Color::White);
new_grade.setPosition((*note)->getCoordinates().x, (*note)->getCoordinates().y - 40);
switch (grade)
{
case ClassicNote::Grade::PERFECT:
new_grade.setString("PERFECT"); break;
case ClassicNote::Grade::GOOD:
new_grade.setString("GOOD"); break;
case ClassicNote::Grade::BAD:
new_grade.setString("BAD"); break;
}
new_grade.setFont(_font);
_grades.emplace_back(new_grade);
}
}
Action ClassicGame::getActionKeyPressed(Button button) const
@ -101,9 +119,24 @@ void ClassicGame::update()
{
_timeline->update();
_timeline->fetchVisibleNotes(_view_manager);
for (auto& grade : _grades)
{
if (grade.getFillColor().a > 20)
{
grade.setFillColor(sf::Color(255, 255, 255, grade.getFillColor().a - 20));
}
}
_grades.remove_if([](const auto& grade) { return grade.getFillColor().a <= 20; });
}
void ClassicGame::draw(sf::RenderWindow& window) const
{
_timeline->drawVisibleNotes(window);
for (auto& grade : _grades)
{
window.draw(grade);
}
}

View File

@ -2,7 +2,8 @@
#define CLASSICGAME_H
#include <map>
#include <list>
#include <SFML/Graphics/Text.hpp>
#include "game.h"
#include "classicactions.h"
@ -31,6 +32,9 @@ private:
std::unique_ptr<ClassicTimeline> _timeline;
std::unique_ptr<ClassicViewManager> _view_manager;
sf::Font _font;
std::list<sf::Text> _grades;
};
#endif // CLASSICGAME_H

View File

@ -8,22 +8,43 @@ ClassicNote::ClassicNote(const std::vector<microsec>& intervals, microsec perfec
Note(perfect_offset),
_coordinates(coord),
_evaluator(intervals, _perfect_offset),
_action(action)
_action(action),
_appearance_time(0)
{}
bool ClassicNote::isActive(microsec music_offset) const
bool ClassicNote::isActive(const microsec& music_offset) const
{
return _evaluator.isActive(music_offset);
}
static int getPt( int n1 , int n2 , float perc )
{
int diff = n2 - n1;
return n1 + ( diff * perc );
}
void ClassicNote::update(const microsec& music_offset)
{
auto update_time = music_offset - _appearance_time;
auto i = update_time / _trail_path_percent / 100;
int xa = getPt( 720./2. , 1280./2. , i );
int ya = getPt( 0 , 720./2. , i );
int xb = getPt( 1280./2. , _coordinates.x , i );
int yb = getPt( 720./2. , _coordinates.y , i );
_sprite->setTrailCoordinates(getPt( xa , xb , i ), getPt( ya , yb , i ));
}
void ClassicNote::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
target.draw(*_sprite, states);
}
ClassicNote::GRADE ClassicNote::input(ClassicInputType&& input_data)
auto ClassicNote::input(ClassicInputType&& input_data) -> Grade
{
auto grade = ClassicNote::GRADE::BAD;
auto grade = ClassicNote::Grade::BAD;
if (input_data == _action)
{
@ -31,7 +52,7 @@ ClassicNote::GRADE ClassicNote::input(ClassicInputType&& input_data)
}
std::cout << "User input: " << static_cast<int>(grade) << "\n";
_state = State::DEAD;
return grade;
}
@ -40,19 +61,35 @@ Action ClassicNote::action() const
return _action;
}
auto ClassicNote::state() const -> State
{
return _state;
}
void ClassicNote::setState(State next_state)
{
_state = next_state;
}
std::shared_ptr<ClassicSprite> ClassicNote::sprite() const noexcept
{
return _sprite;
}
void ClassicNote::saveAppearanceTime(const microsec &offset)
{
_appearance_time = offset;
_trail_path_percent = ((_perfect_offset - _appearance_time) * 0.01);
}
void ClassicNote::setSprite(const std::shared_ptr<ClassicSprite>& sprite) noexcept
{
_sprite = sprite;
if (_sprite)
_sprite->setCoordinates(_coordinates.x, _coordinates.y);
_sprite->setCoordinates(_coordinates.x, _coordinates.y, 720/2, 50);
}
inline const Coordinates& ClassicNote::getCoordinates() const noexcept
const Coordinates& ClassicNote::getCoordinates() const noexcept
{
return _coordinates;
}

View File

@ -23,31 +23,46 @@ class ClassicNote : public Note
{
public:
enum class GRADE
enum class Grade
{
PERFECT,
GOOD,
BAD
};
enum class State
{
FLYING,
DYING,
DEAD
};
explicit ClassicNote(const std::vector<microsec>& intervals, microsec perfect_offset,
Action action, const Coordinates& coord);
virtual ~ClassicNote() = default;
virtual bool isActive(microsec music_offset) const override;
virtual bool isActive(const microsec& music_offset) const override;
virtual void update(const microsec &music_offset) override;
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
GRADE input(ClassicInputType&& input_data);
Grade input(ClassicInputType&& input_data);
Action action() const;
State state() const;
void setState(State next_state);
std::shared_ptr<ClassicSprite> sprite() const noexcept;
void saveAppearanceTime(const microsec& offset);
void setSprite(const std::shared_ptr<ClassicSprite>& sprite) noexcept;
inline const Coordinates& getCoordinates() const noexcept;
const Coordinates& getCoordinates() const noexcept;
private:
const Coordinates _coordinates;
const PrecisionEvaluator<GRADE> _evaluator;
const PrecisionEvaluator<Grade> _evaluator;
const Action _action;
State _state = State::FLYING;
std::shared_ptr<ClassicSprite> _sprite;
microsec _appearance_time;
float _trail_path_percent; //100% for sprite falling trajectory
};

View File

@ -2,15 +2,23 @@
#include <SFML/Graphics/RenderTarget.hpp>
ClassicSprite::ClassicSprite(const sf::RectangleShape& shape) :
_shape(shape)
_shape(shape),
_trail(shape)
{}
void ClassicSprite::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
target.draw(_shape, states);
target.draw(_trail, states);
}
void ClassicSprite::setCoordinates(float x, float y) noexcept
void ClassicSprite::setCoordinates(float x, float y, float trail_x, float trail_y) noexcept
{
_shape.setPosition(x, y);
_trail.setPosition(trail_x, trail_y);
}
void ClassicSprite::setTrailCoordinates(float trail_x, float trail_y) noexcept
{
_trail.setPosition(trail_x, trail_y);
}

View File

@ -9,8 +9,10 @@ public:
ClassicSprite(const sf::RectangleShape& shape);
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
void setCoordinates(float x, float y) noexcept;
void setCoordinates(float x, float y, float trail_x, float trail_y) noexcept;
void setTrailCoordinates(float trail_x, float trail_y) noexcept;
private:
sf::RectangleShape _shape;
sf::RectangleShape _trail;
};

View File

@ -11,20 +11,20 @@ ClassicTimeline::ClassicTimeline()
// Length is 1:14
// I calculated that the time between beats is about 1412162 microseconds
std::string song_filename = "/home/naiji/METEOR.flac";
std::string song_filename = "METEOR.flac";
_music.openFromFile(song_filename);
_music.setVolume(10);
_timeline.reserve(1000);
microsec starting_beat_offset = 372162;
microsec starting_beat_offset = 352162;
int amount_of_beats = 209;
microsec interval = 1412162;
microsec note_input_offset = 412162;
microsec bpm_iterator = starting_beat_offset;
microsec bpm_end = starting_beat_offset + (interval * amount_of_beats);
_visibility_offset = note_input_offset * 6;
_visibility_offset = note_input_offset * 8;
_timeline.emplace_back(new ClassicNote({note_input_offset}, bpm_iterator, Action::PRESS_DOWN, {90, 90}));
bpm_iterator += interval;
@ -35,10 +35,13 @@ ClassicTimeline::ClassicTimeline()
_timeline.emplace_back(new ClassicNote({note_input_offset}, bpm_iterator, Action::PRESS_LEFT, {290, 90}));
bpm_iterator += interval;
float x = 90.;
while (bpm_iterator < bpm_end)
{
_timeline.emplace_back(new ClassicNote({note_input_offset}, bpm_iterator, Action::PRESS_UP, {390, 390}));
_timeline.emplace_back(new ClassicNote({note_input_offset}, bpm_iterator, Action::PRESS_UP, {x, 390.}));
bpm_iterator += interval;
x += 70;
}
expire(_last_visible_note);
@ -46,7 +49,7 @@ ClassicTimeline::ClassicTimeline()
_top_note = _timeline.begin();
}
void ClassicTimeline::init()
void ClassicTimeline::run()
{
_music.play();
}
@ -113,7 +116,7 @@ microsec ClassicTimeline::currentMusicOffset() const
return _music.getPlayingOffset().asMicroseconds();
}
void ClassicTimeline::discardExpiredNotes(const std::unique_ptr<ClassicViewManager> &view_manager, const microsec &music_offset)
void ClassicTimeline::discardExpiredNotes(const std::unique_ptr<ClassicViewManager> &view_manager)
{
if (_top_note == _timeline.begin())
return;
@ -137,15 +140,25 @@ bool ClassicTimeline::isVisiblyClose(const Iterator &iterator, const microsec &m
void ClassicTimeline::fetchVisibleNotes(const std::unique_ptr<ClassicViewManager>& view_manager)
{
microsec music_offset = currentMusicOffset();
discardExpiredNotes(view_manager, music_offset);
const microsec music_offset = currentMusicOffset();
discardExpiredNotes(view_manager);
Iterator note_iterator = _top_note;
while (isVisiblyClose(note_iterator, music_offset))
{
ClassicNote* note = *note_iterator;
if (!note->sprite())
{
note->saveAppearanceTime(music_offset);
view_manager->initNoteSprite(note);
}
if (note->state() == ClassicNote::State::DEAD)
{
view_manager->resetNoteSprite(note);
}
else
note->update(music_offset);
++note_iterator;
}
@ -157,12 +170,14 @@ void ClassicTimeline::drawVisibleNotes(sf::RenderWindow &window) const
{
bool no_visible_notes = isExpired(_last_visible_note)
|| _top_note > _last_visible_note;
if (no_visible_notes)
return;
Iterator note_to_draw = _top_note;
while (note_to_draw != (_last_visible_note))
{
if ((*note_to_draw)->sprite())
window.draw(*(*note_to_draw));
++note_to_draw;
}

View File

@ -14,7 +14,7 @@ public:
explicit ClassicTimeline();
virtual ~ClassicTimeline();
virtual void update() override;
virtual void init() override;
virtual void run() override;
virtual void clear() override;
virtual microsec currentMusicOffset() const override;
@ -41,7 +41,7 @@ private:
void checkCurrentActiveNote(const microsec &music_offset);
void checkForNextActiveNote(const microsec &music_offset);
void discardExpiredNotes(const std::unique_ptr<ClassicViewManager>& view_manager, const microsec &music_offset);
void discardExpiredNotes(const std::unique_ptr<ClassicViewManager>& view_manager);
bool isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const;
/* Difference between top and active note is that