forked from NaiJi/project-kyoku
Rework Timeline to work fine with scrolling and note insertion
This commit is contained in:
parent
ebf736a0bb
commit
581e1fe6a4
|
@ -13,6 +13,7 @@ public:
|
|||
virtual void input(PlayerInput&& inputdata) = 0;
|
||||
virtual void update(UpdateData&& updatedata) = 0;
|
||||
virtual void draw() const = 0;
|
||||
virtual void recalculate(const microsec& timestamp) = 0;
|
||||
|
||||
inline void setBPMSections(const std::set<BPMSection, BPMSectionCompt>& sections) noexcept
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ public:
|
|||
_perfect_offset(perfect_offset) {}
|
||||
virtual ~Note() = default;
|
||||
|
||||
virtual bool isActive() const = 0;
|
||||
virtual bool isActive(const microsec& offset) const = 0;
|
||||
virtual void update(const microsec& music_offset) = 0;
|
||||
virtual void draw() const = 0;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <set>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "tools/mathutils.h"
|
||||
#include "core/note.h"
|
||||
|
@ -18,15 +19,46 @@ public:
|
|||
|
||||
typedef typename std::set<TNote*>::const_iterator Iterator;
|
||||
|
||||
void recalculate(const microsec& offset)
|
||||
{
|
||||
_current_offset = offset;
|
||||
expire(_first_visible_note);
|
||||
expire(_last_visible_note);
|
||||
expire(_top_note);
|
||||
|
||||
if (!_timeline.empty())
|
||||
{
|
||||
Iterator head_iterator = _timeline.begin();
|
||||
|
||||
while (!isExpired(head_iterator))
|
||||
{
|
||||
if ((*head_iterator)->offset() >= offset)
|
||||
{
|
||||
Iterator pre_head = head_iterator;
|
||||
--pre_head;
|
||||
|
||||
_top_note = !isExpired(pre_head) && (*pre_head)->isActive(offset)
|
||||
? pre_head
|
||||
: head_iterator;
|
||||
break;
|
||||
}
|
||||
|
||||
++head_iterator;
|
||||
}
|
||||
|
||||
if (isExpired(_top_note))
|
||||
_top_note = _timeline.begin();
|
||||
}
|
||||
|
||||
fetchVisibleNotes();
|
||||
}
|
||||
|
||||
void setNotes(const std::set<TNote*, NotePtrCompt>& notes, const microsec& visibility)
|
||||
{
|
||||
_visibility_offset = visibility;
|
||||
_timeline = std::move(notes);
|
||||
|
||||
_top_note = _timeline.begin();
|
||||
expire(_first_visible_note);
|
||||
expire(_last_visible_note);
|
||||
expire(_active_note);
|
||||
recalculate(_current_offset);
|
||||
|
||||
if (isExpired(_top_note))
|
||||
return;
|
||||
|
@ -37,13 +69,16 @@ public:
|
|||
void insertNote(TNote* note)
|
||||
{
|
||||
_top_note = _timeline.insert(note).first;
|
||||
recalculate(_current_offset);
|
||||
update(_current_offset);
|
||||
}
|
||||
|
||||
void insertNotes(const std::set<TNote*, NotePtrCompt>& notes)
|
||||
{
|
||||
_timeline.insert(notes.begin(), notes.end());
|
||||
recalculate(_current_offset);
|
||||
update(_current_offset);
|
||||
|
||||
}
|
||||
|
||||
inline void clear()
|
||||
|
@ -57,13 +92,7 @@ public:
|
|||
void update(const microsec& offset)
|
||||
{
|
||||
_current_offset = offset;
|
||||
|
||||
if (isExpired(_top_note))
|
||||
return;
|
||||
|
||||
checkTopNote(_current_offset);
|
||||
checkCurrentActiveNote();
|
||||
checkForNextActiveNote();
|
||||
updateTopNote(_current_offset);
|
||||
updateVisibleSprites(_current_offset);
|
||||
}
|
||||
|
||||
|
@ -83,9 +112,6 @@ public:
|
|||
|
||||
void findLastVisibleNote(const microsec& music_offset)
|
||||
{
|
||||
if (isExpired(_top_note))
|
||||
return;
|
||||
|
||||
Iterator note_iterator = _top_note;
|
||||
while (!isExpired(note_iterator) && isVisiblyClose(note_iterator, music_offset))
|
||||
{
|
||||
|
@ -113,15 +139,34 @@ public:
|
|||
{
|
||||
auto note = *note_iterator;
|
||||
if (note->shouldRemove())
|
||||
{
|
||||
++_first_visible_note;
|
||||
}
|
||||
|
||||
++note_iterator;
|
||||
}
|
||||
}
|
||||
|
||||
inline Iterator getActiveNote() noexcept
|
||||
Iterator getActiveNote(const microsec& music_offset) noexcept
|
||||
{
|
||||
return _active_note;
|
||||
Iterator return_note = _timeline.end();
|
||||
|
||||
auto note_iterator = _top_note;
|
||||
while (!isExpired(note_iterator))
|
||||
{
|
||||
const auto& note = *note_iterator;
|
||||
if (note->isActive(music_offset))
|
||||
{
|
||||
return_note = note_iterator;
|
||||
break;
|
||||
}
|
||||
else if (note->offset() > music_offset)
|
||||
break;
|
||||
|
||||
++note_iterator;
|
||||
}
|
||||
|
||||
return return_note;
|
||||
}
|
||||
|
||||
inline Iterator getNoteBy(const microsec& music_offset) noexcept
|
||||
|
@ -133,12 +178,12 @@ public:
|
|||
});
|
||||
}
|
||||
|
||||
inline bool isExpired(const Iterator& iterator) const
|
||||
inline bool isExpired(const Iterator& iterator) const noexcept
|
||||
{
|
||||
return iterator == _timeline.end();
|
||||
}
|
||||
|
||||
inline void expire(Iterator& iterator)
|
||||
inline void expire(Iterator& iterator) noexcept
|
||||
{
|
||||
iterator = _timeline.end();
|
||||
}
|
||||
|
@ -148,6 +193,16 @@ private:
|
|||
microsec _visibility_offset;
|
||||
microsec _current_offset;
|
||||
|
||||
inline void updateTopNote(const microsec& music_offset) noexcept
|
||||
{
|
||||
if ((*_top_note)->offset() < music_offset //
|
||||
&& _top_note == _first_visible_note // Maybe simplify
|
||||
&& !(*_top_note)->isActive(music_offset)) //
|
||||
{
|
||||
++_top_note;
|
||||
}
|
||||
}
|
||||
|
||||
void updateVisibleSprites(const microsec& music_offset)
|
||||
{
|
||||
if (nothingToDraw())
|
||||
|
@ -160,40 +215,7 @@ private:
|
|||
});
|
||||
}
|
||||
|
||||
void checkCurrentActiveNote()
|
||||
{
|
||||
if (isExpired(_active_note))
|
||||
return;
|
||||
|
||||
auto note = *_active_note;
|
||||
|
||||
if (!note->isActive())
|
||||
{
|
||||
expire(_active_note);
|
||||
++_top_note;
|
||||
}
|
||||
}
|
||||
|
||||
void checkTopNote(const microsec& offset)
|
||||
{
|
||||
if (isExpired(_top_note) || !isExpired(_active_note))
|
||||
return;
|
||||
|
||||
while ((*_top_note)->offset() < offset)
|
||||
++_top_note;
|
||||
}
|
||||
|
||||
void checkForNextActiveNote()
|
||||
{
|
||||
if (!isExpired(_active_note))
|
||||
return;
|
||||
|
||||
auto top_note = *_top_note;
|
||||
if (top_note->isActive())
|
||||
_active_note = _top_note;
|
||||
}
|
||||
|
||||
inline bool isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const
|
||||
inline bool isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const noexcept
|
||||
{
|
||||
return ((*iterator)->offset() - _visibility_offset) <= music_offset;
|
||||
}
|
||||
|
@ -203,22 +225,7 @@ private:
|
|||
return isExpired(_first_visible_note);
|
||||
}
|
||||
|
||||
/* 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.
|
||||
* */
|
||||
|
||||
Iterator _top_note;
|
||||
Iterator _active_note;
|
||||
Iterator _last_visible_note;
|
||||
Iterator _first_visible_note;
|
||||
};
|
||||
|
|
|
@ -12,23 +12,20 @@ ClassicEditor::ClassicEditor(std::shared_ptr<ClassicGraphicsManager>&& manager)
|
|||
|
||||
std::set<MockClassicNote*, NotePtrCompt> _set = {};
|
||||
|
||||
for (int i = 1; i < 5; ++i)
|
||||
{
|
||||
NoteInitializer init;
|
||||
init.context = &_context;
|
||||
init.intervals = {};
|
||||
init.perfect_offset = basic_offset + (500000 * i);
|
||||
NoteInitializer init;
|
||||
init.context = &_context;
|
||||
init.intervals = {};
|
||||
init.perfect_offset = basic_offset + (500000 * 20);
|
||||
|
||||
ElementInitializer elem_init;
|
||||
elem_init.type = _selected_type;
|
||||
elem_init.coordinates = Coordinates{ 700 - (i * 120), 550 };
|
||||
elem_init.falling_curve_interpolation = {};
|
||||
ElementInitializer elem_init;
|
||||
elem_init.type = _selected_type;
|
||||
elem_init.coordinates = Coordinates{ 700 - (5 * 120), 550 };
|
||||
elem_init.falling_curve_interpolation = {};
|
||||
|
||||
MockArrowNoteInitializer mock_init;
|
||||
mock_init.elements = {elem_init};
|
||||
mock_init.initializer = init;
|
||||
_set.insert(new MockClassicNote(std::move(mock_init)));
|
||||
}
|
||||
MockArrowNoteInitializer mock_init;
|
||||
mock_init.elements = {elem_init};
|
||||
mock_init.initializer = init;
|
||||
_set.insert(new MockClassicNote(std::move(mock_init)));
|
||||
|
||||
_timeline.setNotes(_set, 1648648);
|
||||
}
|
||||
|
@ -89,6 +86,11 @@ void ClassicEditor::draw() const
|
|||
});
|
||||
}
|
||||
|
||||
void ClassicEditor::recalculate(const microsec& timestamp)
|
||||
{
|
||||
_timeline.recalculate(timestamp);
|
||||
}
|
||||
|
||||
void ClassicEditor::selectNoteType(Type type) noexcept
|
||||
{
|
||||
_selected_type = type;
|
||||
|
|
|
@ -17,6 +17,7 @@ public:
|
|||
virtual void input(PlayerInput&& inputdata) override;
|
||||
virtual void update(UpdateData&& updatedata) override;
|
||||
virtual void draw() const override;
|
||||
virtual void recalculate(const microsec& timestamp) override;
|
||||
|
||||
void selectNoteType(Type type) noexcept;
|
||||
|
||||
|
|
|
@ -30,10 +30,9 @@ MockClassicNote::MockClassicNote(MockArrowNoteInitializer&& init) :
|
|||
}
|
||||
}
|
||||
|
||||
bool MockClassicNote::isActive() const
|
||||
bool MockClassicNote::isActive(const microsec& offset) const
|
||||
{
|
||||
return _state != State::DEAD
|
||||
&& _state != State::NONE;
|
||||
return offset == Note::offset();
|
||||
}
|
||||
|
||||
bool MockClassicNote::isInGame() const
|
||||
|
@ -44,21 +43,20 @@ bool MockClassicNote::isInGame() const
|
|||
|
||||
bool MockClassicNote::shouldRemove() const
|
||||
{
|
||||
return _state == State::DEAD;
|
||||
return _state == State::DEAD
|
||||
|| _state == State::NONE;
|
||||
}
|
||||
|
||||
void MockClassicNote::putToGame(const microsec &music_offset)
|
||||
{
|
||||
_state = State::FLYING;
|
||||
|
||||
std::cout << "Put to game " << this << ": " << music_offset << '\n';
|
||||
_state = State::FLYING; (void)music_offset;
|
||||
|
||||
for (auto& element : _elements)
|
||||
{
|
||||
element.sprite = _context->graphics_manager->getSprite(element.type);
|
||||
element.sprite->setCoordinates(element.coordinates);
|
||||
element.sprite->setTrailCoordinates(Coordinates(0.f, 9.f));
|
||||
element.animations[_state]->launch(element.sprite, music_offset, offset());
|
||||
element.animations[_state]->launch(element.sprite, offset() - 1648648, offset());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
explicit MockClassicNote(MockArrowNoteInitializer&& init);
|
||||
virtual ~MockClassicNote() override = default;
|
||||
|
||||
virtual bool isActive() const override final;
|
||||
virtual bool isActive(const microsec& offset) const override final;
|
||||
virtual bool isInGame() const override final;
|
||||
virtual bool shouldRemove() const override final;
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ ClassicArrowNote::ClassicArrowNote(ArrowNoteInitializer&& init) :
|
|||
// Animations will be injected into note.
|
||||
_elements[i].animations[State::NONE] = nullptr;
|
||||
_elements[i].animations[State::FLYING] = std::make_shared<ClassicFlyingAnimationScenario>();
|
||||
_elements[i].animations[State::ACTIVE] = _elements[i].animations[State::FLYING];
|
||||
_elements[i].animations[State::DYING] = std::make_shared<ClassicDyingAnimationScenario>();
|
||||
_elements[i].animations[State::DEAD] = nullptr;
|
||||
}
|
||||
|
@ -100,9 +99,11 @@ void ClassicArrowNote::update(const microsec& music_offset)
|
|||
break;
|
||||
|
||||
case State::FLYING:
|
||||
if (_evaluator.isActive(music_offset)) {
|
||||
_state = State::ACTIVE;
|
||||
|
||||
if (!_evaluator.isActive(music_offset) && music_offset > offset())
|
||||
{
|
||||
_state = State::DYING;
|
||||
for (auto& element : _elements)
|
||||
element.animations[_state]->launch(element.sprite, music_offset, offset());
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -110,15 +111,6 @@ void ClassicArrowNote::update(const microsec& music_offset)
|
|||
if (_elements[0].animations[_state]->isDone())
|
||||
_state = State::DEAD;
|
||||
break;
|
||||
|
||||
case State::ACTIVE:
|
||||
if (!_evaluator.isActive(music_offset))
|
||||
{
|
||||
_state = State::DYING;
|
||||
for (auto& element : _elements)
|
||||
element.animations[_state]->launch(element.sprite, music_offset, offset());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto& element : _elements)
|
||||
|
@ -138,7 +130,7 @@ bool ClassicArrowNote::allElementsPressed() const
|
|||
bool ClassicArrowNote::isPressedAs(sf::Keyboard::Key key) const
|
||||
{
|
||||
return std::any_of(_elements.begin(), _elements.end(),
|
||||
[key=key](const auto& element)
|
||||
[key](const auto& element)
|
||||
{
|
||||
return key == element.pressed_as;
|
||||
});
|
||||
|
|
|
@ -73,7 +73,7 @@ void ClassicGame::input(PlayerInput&& inputdata)
|
|||
|
||||
case sf::Event::KeyPressed:
|
||||
{
|
||||
auto note_it = _timeline.getActiveNote();
|
||||
auto note_it = _timeline.getActiveNote(inputdata.timestamp);
|
||||
|
||||
if (!_timeline.isExpired(note_it))
|
||||
{
|
||||
|
|
|
@ -14,15 +14,15 @@ ClassicNote::ClassicNote(NoteInitializer &&init) :
|
|||
_context(init.context)
|
||||
{}
|
||||
|
||||
bool ClassicNote::isActive() const
|
||||
bool ClassicNote::isActive(const microsec& offset) const
|
||||
{
|
||||
return _state == State::ACTIVE;
|
||||
return _evaluator.isActive(offset)
|
||||
&& _state != State::DYING;
|
||||
}
|
||||
|
||||
bool ClassicNote::isInGame() const
|
||||
{
|
||||
return _state == State::FLYING
|
||||
|| _state == State::ACTIVE
|
||||
|| _state == State::DYING;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ public:
|
|||
NONE,
|
||||
|
||||
FLYING,
|
||||
ACTIVE,
|
||||
DYING,
|
||||
DEAD
|
||||
};
|
||||
|
@ -32,7 +31,7 @@ public:
|
|||
explicit ClassicNote(NoteInitializer&& init);
|
||||
virtual ~ClassicNote() override = default;
|
||||
|
||||
virtual bool isActive() const override final;
|
||||
virtual bool isActive(const microsec& offset) const override final;
|
||||
virtual bool isInGame() const override final;
|
||||
virtual bool shouldRemove() const override final;
|
||||
|
||||
|
|
|
@ -23,9 +23,6 @@ EditorState::~EditorState()
|
|||
|
||||
void EditorState::input(const sf::Event& event)
|
||||
{
|
||||
if (event.key.code == sf::Keyboard::Space && event.type == sf::Event::KeyReleased)
|
||||
_music.isPaused() ? _music.play() : _music.pause();
|
||||
|
||||
_group->input(event);
|
||||
}
|
||||
|
||||
|
@ -156,8 +153,13 @@ void EditorState::enter()
|
|||
|
||||
callbacks.onInput = [&editor, &music](const sf::Event& event)
|
||||
{
|
||||
if (event.type == sf::Event::MouseWheelScrolled)
|
||||
if (event.key.code == sf::Keyboard::Space && event.type == sf::Event::KeyReleased)
|
||||
music.isPaused() ? music.play() : music.pause();
|
||||
else if (event.type == sf::Event::MouseWheelScrolled)
|
||||
{
|
||||
music.moveOffset(event.mouseWheelScroll.delta > 0 ? 500000 : -500000);
|
||||
editor->recalculate(music.fetchOffset());
|
||||
}
|
||||
else
|
||||
editor->input(PlayerInput{music.fetchOffset(), event});
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue