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; | ||||||
|  | |||||||
							
								
								
									
										66
									
								
								timeline.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								timeline.cpp
									
									
									
									
									
										Normal 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 µ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; | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								timeline.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								timeline.h
									
									
									
									
									
										Normal 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 µ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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user