forked from NaiJi/project-kyoku
Encapsulate timeline and implement logic on it
This commit is contained in:
parent
47277ee754
commit
367316b327
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -28,14 +28,14 @@ void DebugHelper::update(const microsec µseconds)
|
||||||
_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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
6
note.cpp
6
note.cpp
|
@ -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
2
note.h
|
@ -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;
|
||||||
|
|
|
@ -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 µseconds)
|
||||||
|
{
|
||||||
|
checkCurrentActiveNote(microseconds);
|
||||||
|
checkForNextActiveNote(microseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timeline::checkCurrentActiveNote(const microsec µseconds)
|
||||||
|
{
|
||||||
|
if (_active_note && !_active_note->isActive(microseconds))
|
||||||
|
{
|
||||||
|
_active_note = nullptr;
|
||||||
|
++_top_note;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timeline::checkForNextActiveNote(const microsec µseconds)
|
||||||
|
{
|
||||||
|
if (!_active_note && (*_top_note)->isActive(microseconds))
|
||||||
|
{
|
||||||
|
_active_note = *_top_note;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Note* Timeline::getActiveNote() const noexcept
|
||||||
|
{
|
||||||
|
return _active_note;
|
||||||
|
}
|
|
@ -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 µseconds);
|
||||||
|
void checkForNextActiveNote(const microsec µseconds);
|
||||||
|
|
||||||
|
/* 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
|
Loading…
Reference in New Issue