forked from NaiJi/project-kyoku
All simple curve flying animation
This commit is contained in:
parent
7dc4ef2bb8
commit
192e371d2f
|
@ -30,7 +30,7 @@ private:
|
|||
|
||||
std::unique_ptr<Game> _game;
|
||||
|
||||
void startGameLoop();
|
||||
void exec();
|
||||
};
|
||||
|
||||
#endif // APPLICATION_H
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,13 +170,15 @@ 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))
|
||||
{
|
||||
window.draw(*(*note_to_draw));
|
||||
if ((*note_to_draw)->sprite())
|
||||
window.draw(*(*note_to_draw));
|
||||
++note_to_draw;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue