Encapsulate timeline and implement logic on it

This commit is contained in:
NaiJi ✨ 2021-04-08 16:34:03 +03:00
parent 47277ee754
commit 367316b327
8 changed files with 144 additions and 74 deletions

View File

@ -1,4 +1,5 @@
#include "application.h" #include "application.h"
#include "note.h"
#include <SFML/Graphics/Color.hpp> #include <SFML/Graphics/Color.hpp>
#include <SFML/Window/Event.hpp> #include <SFML/Window/Event.hpp>
@ -17,27 +18,7 @@ Application::Application() :
void Application::run() void Application::run()
{ {
// BPM of METEOR is 170.
// 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 = "/home/naiji/METEOR.flac";
microsec starting_beat_offset = 372162;
int amount_of_beats = 209;
microsec time_between_beats = 1412162;
microsec note_input_offset = 412162;
sf::Int64 iter = starting_beat_offset + (time_between_beats * amount_of_beats);
Note::setPrecisionQualifier(note_input_offset / 2);
while (iter > 0)
{
Note note(iter, note_input_offset);
_timeline.push(note);
iter -= time_between_beats;
}
// // // // // // // //
_music.openFromFile(song_filename); _music.openFromFile(song_filename);
_music.play(); _music.play();
@ -163,48 +144,31 @@ void Application::onKeyPressed(const sf::Keyboard::Key &key)
const auto arrow = keyToArrow(key); const auto arrow = keyToArrow(key);
if (arrow != Note::Arrow::NONE) if (arrow != Note::Arrow::NONE)
{ { // TODO: SHIT BLOCK.
_debug.spawnGreenPulse(); _debug.spawnGreenPulse();
const auto note = _timeline.getActiveNote();
if (!_timeline.empty()) if (note)
{ {
const auto current_note = _timeline.top(); // This is obscure. Active note on timeline gets received by last ::update() call.
const auto grade_result = current_note.onTap(arrow, _music.getPlayingOffset().asMicroseconds()); // there can be 100-200 microseconds delay between onKeyPressed and update...
_grade = makeGradeString(grade_result.rating); // Also the problem is that we get music offset by CURRENT music time,
// when active note is activated by music time of last ::update call
// anyway gotta think on it, smh smh smh
const auto music_offset = _music.getPlayingOffset().asMicroseconds();
const auto tap_result = note->onTap(arrow, music_offset);
_grade = makeGradeString(tap_result.rating);
} }
} }
} }
void Application::update() void Application::update()
{ {
const auto microseconds = _music.getPlayingOffset().asMicroseconds(); const auto music_offset = _music.getPlayingOffset().asMicroseconds();
// To Do: Here we notice when next note becomes active and ready for user input. _timeline.update(music_offset);
// Here I explicitly calculate its birth time for now. _debug.update(music_offset);
if (!_timeline.empty() && _timeline.top().offset() - 412162 <= microseconds)
{
_debug.spawnBluePulse();
}
// To do: Actual note offset should pulse only once and the note shouldn't die right after it, if (_grade.getFillColor().a > 0) // TODO: Encapsulate
// because there is also "after pulse" offset, like, you know, player can be a little late
if (!_timeline.empty() && _timeline.top().offset() <= microseconds)
{
_timeline.pop();
_debug.spawnRedPulse();
}
// To do: Here should be the end of "after pulse" time. When user fucked up all the time and the
// note dies with "Missed" grade.
//
// if ( . . . _timeline.top().offset() + 412162 <= microseconds)
// {
//
// }
_debug.update(microseconds);
if (_grade.getFillColor().a > 0)
{ {
const auto alpha = _grade.getFillColor().a - 20; const auto alpha = _grade.getFillColor().a - 20;
_grade.setFillColor(sf::Color(255, 255, 255, alpha < 0 ? 0 : alpha)); _grade.setFillColor(sf::Color(255, 255, 255, alpha < 0 ? 0 : alpha));
@ -215,7 +179,7 @@ void Application::update()
void Application::draw() void Application::draw()
{ {
_game_window.clear(); _game_window.clear();
_debug.drawOn(_game_window); _game_window.draw(_debug);
_game_window.draw(_grade); _game_window.draw(_grade);
_game_window.display(); _game_window.display();
} }

View File

@ -5,10 +5,8 @@
#include <SFML/System/Clock.hpp> #include <SFML/System/Clock.hpp>
#include <SFML/Window/Keyboard.hpp> #include <SFML/Window/Keyboard.hpp>
#include <stack>
#include "debughelper.h" #include "debughelper.h"
#include "note.h" #include "timeline.h"
class Application class Application
{ {
@ -23,12 +21,10 @@ private:
sf::RenderWindow _game_window; sf::RenderWindow _game_window;
sf::Music _music; sf::Music _music;
std::stack<Note> _timeline;
sf::Int64 _time_since_last_tick;
sf::Int64 _last_stamp;
sf::Font _font; sf::Font _font;
sf::Text _grade; sf::Text _grade;
Timeline _timeline;
DebugHelper _debug; DebugHelper _debug;
void startGameLoop(); void startGameLoop();

View File

@ -28,14 +28,14 @@ void DebugHelper::update(const microsec &microseconds)
_blue_pulse.fade(); _blue_pulse.fade();
} }
void DebugHelper::drawOn(sf::RenderWindow &game_window) const void DebugHelper::draw(sf::RenderTarget& target, sf::RenderStates states) const
{ {
if (_toggled) if (_toggled)
{ {
_red_pulse.drawOn(game_window); target.draw(_green_pulse, states);
_green_pulse.drawOn(game_window); target.draw(_red_pulse, states);
_blue_pulse.drawOn(game_window); target.draw(_blue_pulse, states);
game_window.draw(_time_print); target.draw(_time_print, states);
} }
} }
@ -83,7 +83,7 @@ void DebugHelper::Pulse::fade()
_pulse_shape.setFillColor(fill_color); _pulse_shape.setFillColor(fill_color);
} }
void DebugHelper::Pulse::drawOn(sf::RenderWindow &game_window) const void DebugHelper::Pulse::draw(sf::RenderTarget& target, sf::RenderStates states) const
{ {
game_window.draw(_pulse_shape); target.draw(_pulse_shape, states);
} }

View File

@ -8,14 +8,14 @@
using microsec = sf::Int64; using microsec = sf::Int64;
class DebugHelper class DebugHelper : public sf::Drawable
{ {
public: public:
DebugHelper(bool init = true); DebugHelper(bool init = true);
void toggle(); void toggle();
void update(const microsec& microseconds); void update(const microsec& microseconds);
void drawOn(sf::RenderWindow &game_window) const; virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
void spawnGreenPulse(); void spawnGreenPulse();
void spawnRedPulse(); void spawnRedPulse();
@ -26,14 +26,14 @@ private:
sf::Font _font; sf::Font _font;
sf::Text _time_print; sf::Text _time_print;
class Pulse class Pulse : public sf::Drawable
{ {
public: public:
Pulse(sf::Vector2f position, sf::Color fill_color); Pulse(sf::Vector2f position, sf::Color fill_color);
void appear(); void appear();
void fade(); void fade();
void drawOn(sf::RenderWindow &game_window) const; virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
private: private:
sf::RectangleShape _pulse_shape; sf::RectangleShape _pulse_shape;

View File

@ -2,8 +2,8 @@
#include <cmath> #include <cmath>
Note::Note(microsec offset, microsec life_span_offset, Note::Arrow type) : Note::Note(microsec offset, microsec life_span_offset, Note::Arrow type) :
_offset(offset), _offset(offset), // TODO: Move to struct NoteData
_start_handling_offset(_offset + life_span_offset), _start_handling_offset(_offset + life_span_offset), // so Note::Note(NoteData&& data) : . . .
_end_handling_offset(_offset - life_span_offset), _end_handling_offset(_offset - life_span_offset),
_type(type) _type(type)
{} {}
@ -50,7 +50,7 @@ bool Note::isActive(microsec music_play_offset) const noexcept
&& music_play_offset < _end_handling_offset; && music_play_offset < _end_handling_offset;
} }
void Note::setPrecisionQualifier(microsec qualifier) void Note::resetPrecisionQualifier(microsec qualifier)
{ {
_precision_qualifier = qualifier; _precision_qualifier = qualifier;
} }

2
note.h
View File

@ -47,7 +47,7 @@ public:
NoteGrade onTap(Arrow arrow_type, microsec tap_time_stamp) const; NoteGrade onTap(Arrow arrow_type, microsec tap_time_stamp) const;
bool isActive(microsec music_play_offset) const noexcept; bool isActive(microsec music_play_offset) const noexcept;
static void setPrecisionQualifier(microsec qualifier); static void resetPrecisionQualifier(microsec qualifier = 500000);
private: private:
coordinates _position; coordinates _position;

66
timeline.cpp Normal file
View File

@ -0,0 +1,66 @@
#include "timeline.h"
#include "note.h"
Timeline::Timeline()
{
// BPM of METEOR is 170.
// Length is 1:14
// I calculated that the time between beats is about 1412162 microseconds
microsec starting_beat_offset = 372162;
int amount_of_beats = 209;
microsec time_between_beats = 1412162;
microsec note_input_offset = 412162;
microsec interval = starting_beat_offset + (time_between_beats * amount_of_beats);
Note::resetPrecisionQualifier(note_input_offset / 2);
while (interval > 0)
{
_timeline.emplace_back(new Note(interval, note_input_offset));
interval -= time_between_beats;
}
_active_note = nullptr;
_top_note = _timeline.begin();
}
Timeline::~Timeline()
{
for (auto note : _timeline)
delete note;
_timeline.clear();
_top_note = _timeline.end();
_active_note = nullptr;
Note::resetPrecisionQualifier();
}
void Timeline::update(const microsec &microseconds)
{
checkCurrentActiveNote(microseconds);
checkForNextActiveNote(microseconds);
}
void Timeline::checkCurrentActiveNote(const microsec &microseconds)
{
if (_active_note && !_active_note->isActive(microseconds))
{
_active_note = nullptr;
++_top_note;
}
}
void Timeline::checkForNextActiveNote(const microsec &microseconds)
{
if (!_active_note && (*_top_note)->isActive(microseconds))
{
_active_note = *_top_note;
}
}
const Note* Timeline::getActiveNote() const noexcept
{
return _active_note;
}

44
timeline.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef TIMELINE_H
#define TIMELINE_H
#include <SFML/Config.hpp>
#include <vector>
#include <memory>
using microsec = sf::Int64;
class Note;
class Timeline
{
public:
explicit Timeline();
~Timeline();
void update(const microsec& microseconds);
const Note* getActiveNote() const noexcept;
private:
std::vector<Note*> _timeline;
std::vector<Note*>::iterator _top_note;
Note* _active_note;
void checkCurrentActiveNote(const microsec &microseconds);
void checkForNextActiveNote(const microsec &microseconds);
/* Difference between top and active note is that
* top note is the note handling input right now
* OR it's the closest note from current music offset
* position, not necessarily active. A note stops being top only
* after dying or being tapped by player, even if it's already
* past her perfect offset.
*
* Meanwhile active note is the note which is currently handling
* player input for grade.
*
* An active note is always top note but a top note
* is not always active note.
* */
};
#endif // TIMELINE_H