scenes: infraestructura de la capa scenes:: (scene, timeline, sprite mover, frame animator, palette fade, surface handle, registry)
This commit is contained in:
36
source/scenes/frame_animator.cpp
Normal file
36
source/scenes/frame_animator.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "scenes/frame_animator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace scenes {
|
||||
|
||||
FrameAnimator::FrameAnimator(int num_frames, int frame_ms, bool loop)
|
||||
: num_frames_(std::max(1, num_frames)),
|
||||
frame_ms_(std::max(1, frame_ms)),
|
||||
loop_(loop) {}
|
||||
|
||||
void FrameAnimator::tick(int delta_ms) {
|
||||
if (finished_) return;
|
||||
elapsed_ms_ += delta_ms;
|
||||
while (elapsed_ms_ >= frame_ms_) {
|
||||
elapsed_ms_ -= frame_ms_;
|
||||
++current_frame_;
|
||||
if (current_frame_ >= num_frames_) {
|
||||
if (loop_) {
|
||||
current_frame_ = 0;
|
||||
} else {
|
||||
current_frame_ = num_frames_ - 1;
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrameAnimator::reset() {
|
||||
current_frame_ = 0;
|
||||
elapsed_ms_ = 0;
|
||||
finished_ = false;
|
||||
}
|
||||
|
||||
} // namespace scenes
|
||||
34
source/scenes/frame_animator.hpp
Normal file
34
source/scenes/frame_animator.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
namespace scenes {
|
||||
|
||||
// Cicla per un conjunt de frames numerats (0..num_frames-1) avançant un
|
||||
// frame cada `frame_ms` mil·lisegons. No carrega ni dibuixa cap sprite —
|
||||
// només el caller sap quins frames dibuixar a partir de `frame()`.
|
||||
//
|
||||
// Usat per animacions periòdiques amb frames subsamplejats: palmeres,
|
||||
// camell, aigua, torxes, Sam caminant amb `(i/5) % fr` del codi original.
|
||||
class FrameAnimator {
|
||||
public:
|
||||
FrameAnimator() = default;
|
||||
FrameAnimator(int num_frames, int frame_ms, bool loop = true);
|
||||
|
||||
void tick(int delta_ms);
|
||||
|
||||
int frame() const { return current_frame_; }
|
||||
bool done() const { return !loop_ && finished_; }
|
||||
int numFrames() const { return num_frames_; }
|
||||
|
||||
void reset();
|
||||
void setFrameMs(int frame_ms) { frame_ms_ = frame_ms; }
|
||||
|
||||
private:
|
||||
int num_frames_{1};
|
||||
int frame_ms_{100};
|
||||
bool loop_{true};
|
||||
int current_frame_{0};
|
||||
int elapsed_ms_{0};
|
||||
bool finished_{false};
|
||||
};
|
||||
|
||||
} // namespace scenes
|
||||
28
source/scenes/palette_fade.cpp
Normal file
28
source/scenes/palette_fade.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "scenes/palette_fade.hpp"
|
||||
|
||||
namespace scenes {
|
||||
|
||||
void PaletteFade::startFadeOut() {
|
||||
JD8_FadeStartOut();
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void PaletteFade::startFadeTo(JD8_Palette target) {
|
||||
JD8_FadeStartToPal(target);
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void PaletteFade::tick(int /*delta_ms*/) {
|
||||
if (!active_) return;
|
||||
// El fade té 32 passos interns. Amb un tick per frame (~16ms)
|
||||
// dura ~512ms — el mateix temps que la versió bloquejant original.
|
||||
// Si en el futur volem fer-lo genuinament time-based (p.ex. "fade
|
||||
// de 500ms exactes independent del framerate") podem convertir la
|
||||
// màquina d'estats de jdraw8 a time-based ací sense tocar cap altre
|
||||
// call site.
|
||||
if (JD8_FadeTickStep()) {
|
||||
active_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace scenes
|
||||
30
source/scenes/palette_fade.hpp
Normal file
30
source/scenes/palette_fade.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/jail/jdraw8.hpp"
|
||||
|
||||
namespace scenes {
|
||||
|
||||
// Embolcall fi damunt de la màquina d'estats de fade de jdraw8
|
||||
// (`JD8_FadeStart*` / `JD8_FadeTickStep`). Exposa una API time-based
|
||||
// però internament avança un pas del fade per cada crida a `tick()`.
|
||||
// La raó de tindre-ho com a classe a banda: que una escena no puga
|
||||
// cridar accidentalment a `JD8_FadeOut`/`JD8_FadeToPal` (els shims
|
||||
// bloquejants vells) i que el `done()` siga consultable com la resta
|
||||
// dels helpers.
|
||||
class PaletteFade {
|
||||
public:
|
||||
PaletteFade() = default;
|
||||
|
||||
void startFadeOut();
|
||||
void startFadeTo(JD8_Palette target);
|
||||
|
||||
void tick(int delta_ms);
|
||||
|
||||
bool active() const { return active_; }
|
||||
bool done() const { return !active_; }
|
||||
|
||||
private:
|
||||
bool active_{false};
|
||||
};
|
||||
|
||||
} // namespace scenes
|
||||
37
source/scenes/scene.hpp
Normal file
37
source/scenes/scene.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
// Interfície base per a una escena (cinemàtica, menú, banner, etc.) del
|
||||
// joc. Una escena és una unitat autònoma amb un `tick(delta_ms)` no
|
||||
// bloquejant. El Director la fa avançar cada frame fins que `done()` és
|
||||
// cert, i llavors consulta `nextState()` per decidir la següent.
|
||||
//
|
||||
// Contracte:
|
||||
// - `tick(delta_ms)` no pot bloquejar ni cridar JD8_Flip — el caller
|
||||
// s'encarrega de fer el flip després del tick.
|
||||
// - `done()` es consulta just després de cada tick.
|
||||
// - Els assets són propietat de l'escena (normalment via SurfaceHandle)
|
||||
// i s'alliberen al destructor.
|
||||
// - `onEnter()` es crida una vegada just abans del primer tick. És el
|
||||
// moment bo per a arrancar música, disparar un fade-in, etc.
|
||||
|
||||
namespace scenes {
|
||||
|
||||
class Scene {
|
||||
public:
|
||||
virtual ~Scene() = default;
|
||||
|
||||
virtual void onEnter() {}
|
||||
|
||||
virtual void tick(int delta_ms) = 0;
|
||||
|
||||
virtual bool done() const = 0;
|
||||
|
||||
// Valor retornat al caller quan l'escena acaba — equivalent al int
|
||||
// que retornaven les velles funcions `Go()` de ModuleSequence:
|
||||
// 1 = continuar amb la següent escena segons info::ctx
|
||||
// 0 = entrar al gameplay (ModuleGame)
|
||||
// -1 = eixir del joc
|
||||
virtual int nextState() const { return 1; }
|
||||
};
|
||||
|
||||
} // namespace scenes
|
||||
20
source/scenes/scene_registry.cpp
Normal file
20
source/scenes/scene_registry.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "scenes/scene_registry.hpp"
|
||||
|
||||
namespace scenes {
|
||||
|
||||
SceneRegistry& SceneRegistry::instance() {
|
||||
static SceneRegistry inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
void SceneRegistry::registerScene(int state_key, Factory factory) {
|
||||
factories_[state_key] = std::move(factory);
|
||||
}
|
||||
|
||||
std::unique_ptr<Scene> SceneRegistry::tryCreate(int state_key) const {
|
||||
const auto it = factories_.find(state_key);
|
||||
if (it == factories_.end()) return nullptr;
|
||||
return it->second();
|
||||
}
|
||||
|
||||
} // namespace scenes
|
||||
37
source/scenes/scene_registry.hpp
Normal file
37
source/scenes/scene_registry.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "scenes/scene.hpp"
|
||||
|
||||
namespace scenes {
|
||||
|
||||
// Mapa de `state_key` (actualment = `info::ctx.num_piramide`) a factory
|
||||
// d'escena. Permet que el dispatch de `gameFiberEntry` provi primer una
|
||||
// Scene nova i caiga al vell `ModuleSequence::Go()` si encara no està
|
||||
// migrada.
|
||||
//
|
||||
// Registre inicial: `Director::init()` cridarà `instance()` i afegirà
|
||||
// una entrada per cada escena ja portada. A mesura que vagen caient, les
|
||||
// línies del registre creixen i les funcions `doX()` del modulesequence
|
||||
// desapareixen.
|
||||
class SceneRegistry {
|
||||
public:
|
||||
using Factory = std::function<std::unique_ptr<Scene>()>;
|
||||
|
||||
static SceneRegistry& instance();
|
||||
|
||||
void registerScene(int state_key, Factory factory);
|
||||
|
||||
// Retorna `nullptr` si no hi ha cap escena registrada per a aquest
|
||||
// state. El caller hauria de caure al path legacy en aquest cas.
|
||||
std::unique_ptr<Scene> tryCreate(int state_key) const;
|
||||
|
||||
private:
|
||||
SceneRegistry() = default;
|
||||
std::unordered_map<int, Factory> factories_;
|
||||
};
|
||||
|
||||
} // namespace scenes
|
||||
46
source/scenes/sprite_mover.cpp
Normal file
46
source/scenes/sprite_mover.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "scenes/sprite_mover.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace scenes {
|
||||
|
||||
void SpriteMover::moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn ease) {
|
||||
x0_ = x0;
|
||||
y0_ = y0;
|
||||
x1_ = x1;
|
||||
y1_ = y1;
|
||||
duration_ms_ = std::max(0, duration_ms);
|
||||
elapsed_ms_ = 0;
|
||||
ease_ = ease ? ease : Easing::linear;
|
||||
cur_x_ = x0;
|
||||
cur_y_ = y0;
|
||||
}
|
||||
|
||||
void SpriteMover::setPosition(int x, int y) {
|
||||
cur_x_ = x;
|
||||
cur_y_ = y;
|
||||
x0_ = x1_ = x;
|
||||
y0_ = y1_ = y;
|
||||
duration_ms_ = 0;
|
||||
elapsed_ms_ = 0;
|
||||
}
|
||||
|
||||
void SpriteMover::tick(int delta_ms) {
|
||||
if (duration_ms_ <= 0) {
|
||||
cur_x_ = x1_;
|
||||
cur_y_ = y1_;
|
||||
return;
|
||||
}
|
||||
elapsed_ms_ = std::min(elapsed_ms_ + delta_ms, duration_ms_);
|
||||
const float t = static_cast<float>(elapsed_ms_) / static_cast<float>(duration_ms_);
|
||||
const float eased = ease_(t);
|
||||
cur_x_ = Easing::lerpInt(x0_, x1_, eased);
|
||||
cur_y_ = Easing::lerpInt(y0_, y1_, eased);
|
||||
}
|
||||
|
||||
float SpriteMover::progress() const {
|
||||
if (duration_ms_ <= 0) return 1.0f;
|
||||
return static_cast<float>(elapsed_ms_) / static_cast<float>(duration_ms_);
|
||||
}
|
||||
|
||||
} // namespace scenes
|
||||
39
source/scenes/sprite_mover.hpp
Normal file
39
source/scenes/sprite_mover.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "utils/easing.hpp"
|
||||
|
||||
namespace scenes {
|
||||
|
||||
// Interpola una posició 2D entre dos punts durant un temps donat amb
|
||||
// una funció d'easing. No toca cap surface — el caller llegix x()/y()
|
||||
// i fa el blit ell mateix. Pensat per a scrolls, sprites que entren/
|
||||
// ixen per pantalla, i moviments d'objectes interpolats.
|
||||
class SpriteMover {
|
||||
public:
|
||||
using EaseFn = float (*)(float);
|
||||
|
||||
SpriteMover() = default;
|
||||
|
||||
// Arrenca un moviment nou. Si ja n'hi havia un en curs, es descarta.
|
||||
void moveTo(int x0, int y0, int x1, int y1, int duration_ms,
|
||||
EaseFn ease = Easing::linear);
|
||||
|
||||
// Posicionament immediat (útil per a "teleportar" entre moviments).
|
||||
void setPosition(int x, int y);
|
||||
|
||||
void tick(int delta_ms);
|
||||
|
||||
int x() const { return cur_x_; }
|
||||
int y() const { return cur_y_; }
|
||||
bool done() const { return elapsed_ms_ >= duration_ms_; }
|
||||
float progress() const; // 0..1 sense easing aplicat
|
||||
|
||||
private:
|
||||
int x0_{0}, y0_{0}, x1_{0}, y1_{0};
|
||||
int duration_ms_{0};
|
||||
int elapsed_ms_{0};
|
||||
int cur_x_{0}, cur_y_{0};
|
||||
EaseFn ease_{Easing::linear};
|
||||
};
|
||||
|
||||
} // namespace scenes
|
||||
31
source/scenes/surface_handle.cpp
Normal file
31
source/scenes/surface_handle.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "scenes/surface_handle.hpp"
|
||||
|
||||
namespace scenes {
|
||||
|
||||
SurfaceHandle::SurfaceHandle(const char* file)
|
||||
: surface_(JD8_LoadSurface(file)) {}
|
||||
|
||||
SurfaceHandle::~SurfaceHandle() {
|
||||
if (surface_) JD8_FreeSurface(surface_);
|
||||
}
|
||||
|
||||
SurfaceHandle::SurfaceHandle(SurfaceHandle&& other) noexcept
|
||||
: surface_(other.surface_) {
|
||||
other.surface_ = nullptr;
|
||||
}
|
||||
|
||||
SurfaceHandle& SurfaceHandle::operator=(SurfaceHandle&& other) noexcept {
|
||||
if (this != &other) {
|
||||
if (surface_) JD8_FreeSurface(surface_);
|
||||
surface_ = other.surface_;
|
||||
other.surface_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SurfaceHandle::reset(const char* file) {
|
||||
if (surface_) JD8_FreeSurface(surface_);
|
||||
surface_ = file ? JD8_LoadSurface(file) : nullptr;
|
||||
}
|
||||
|
||||
} // namespace scenes
|
||||
38
source/scenes/surface_handle.hpp
Normal file
38
source/scenes/surface_handle.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/jail/jdraw8.hpp"
|
||||
|
||||
namespace scenes {
|
||||
|
||||
// Wrapper RAII damunt de `JD8_Surface`. Allibera automàticament amb
|
||||
// `JD8_FreeSurface` al destructor. Move-only per evitar dobles alliberaments.
|
||||
// Converteix implícitament a `JD8_Surface` per a poder passar-lo
|
||||
// directament a `JD8_Blit*` sense haver de cridar `.get()`.
|
||||
class SurfaceHandle {
|
||||
public:
|
||||
SurfaceHandle() = default;
|
||||
explicit SurfaceHandle(const char* file);
|
||||
~SurfaceHandle();
|
||||
|
||||
SurfaceHandle(const SurfaceHandle&) = delete;
|
||||
SurfaceHandle& operator=(const SurfaceHandle&) = delete;
|
||||
|
||||
SurfaceHandle(SurfaceHandle&& other) noexcept;
|
||||
SurfaceHandle& operator=(SurfaceHandle&& other) noexcept;
|
||||
|
||||
// Allibera la surface actual (si n'hi ha) i carrega una nova.
|
||||
// Usat per escenes que recarreguen assets a mitja cinemàtica
|
||||
// (p.ex. doSecreta que passa de tomba1 a tomba2).
|
||||
void reset(const char* file);
|
||||
|
||||
// Conversió implícita per al confort d'ús: JD8_Blit(handle)
|
||||
// en lloc de JD8_Blit(handle.get()).
|
||||
operator JD8_Surface() const { return surface_; }
|
||||
JD8_Surface get() const { return surface_; }
|
||||
bool valid() const { return surface_ != nullptr; }
|
||||
|
||||
private:
|
||||
JD8_Surface surface_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace scenes
|
||||
85
source/scenes/timeline.cpp
Normal file
85
source/scenes/timeline.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "scenes/timeline.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace scenes {
|
||||
|
||||
Timeline& Timeline::step(int duration_ms, StepFn fn) {
|
||||
Step s;
|
||||
s.duration_ms = duration_ms;
|
||||
s.continuous = std::move(fn);
|
||||
steps_.push_back(std::move(s));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Timeline& Timeline::once(OnceFn fn) {
|
||||
Step s;
|
||||
s.duration_ms = 0;
|
||||
s.oneshot = std::move(fn);
|
||||
steps_.push_back(std::move(s));
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Timeline::flushOneShots() {
|
||||
while (current_ < steps_.size() && steps_[current_].duration_ms == 0) {
|
||||
auto& s = steps_[current_];
|
||||
if (!s.entered) {
|
||||
s.entered = true;
|
||||
if (s.oneshot) s.oneshot();
|
||||
}
|
||||
++current_;
|
||||
elapsed_in_step_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::tick(int delta_ms) {
|
||||
if (skipped_) return;
|
||||
flushOneShots();
|
||||
if (current_ >= steps_.size()) return;
|
||||
|
||||
auto& s = steps_[current_];
|
||||
if (!s.entered) {
|
||||
s.entered = true;
|
||||
// Primer tick dins del pas: cridem amb progress=0 si hi ha callback.
|
||||
if (s.continuous) s.continuous(0.0f);
|
||||
}
|
||||
|
||||
elapsed_in_step_ += delta_ms;
|
||||
if (elapsed_in_step_ >= s.duration_ms) {
|
||||
// Tancament del pas: una crida final amb progress=1.
|
||||
if (s.continuous) s.continuous(1.0f);
|
||||
++current_;
|
||||
elapsed_in_step_ = 0;
|
||||
// Pot ser que el següent pas siga una cadena de one-shots.
|
||||
flushOneShots();
|
||||
} else if (s.continuous) {
|
||||
const float p = static_cast<float>(elapsed_in_step_) /
|
||||
static_cast<float>(std::max(1, s.duration_ms));
|
||||
s.continuous(p);
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::skip() {
|
||||
skipped_ = true;
|
||||
current_ = steps_.size();
|
||||
}
|
||||
|
||||
void Timeline::reset() {
|
||||
for (auto& s : steps_) s.entered = false;
|
||||
current_ = 0;
|
||||
elapsed_in_step_ = 0;
|
||||
skipped_ = false;
|
||||
}
|
||||
|
||||
bool Timeline::done() const {
|
||||
return skipped_ || current_ >= steps_.size();
|
||||
}
|
||||
|
||||
float Timeline::currentProgress() const {
|
||||
if (current_ >= steps_.size()) return 1.0f;
|
||||
const auto& s = steps_[current_];
|
||||
if (s.duration_ms <= 0) return 0.0f;
|
||||
return static_cast<float>(elapsed_in_step_) / static_cast<float>(s.duration_ms);
|
||||
}
|
||||
|
||||
} // namespace scenes
|
||||
57
source/scenes/timeline.hpp
Normal file
57
source/scenes/timeline.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace scenes {
|
||||
|
||||
// Timeline declaratiu de passos seqüencials. Cada pas té una duració en
|
||||
// ms i un callback. Exemple d'ús:
|
||||
//
|
||||
// timeline_
|
||||
// .once([this] { JD8_ClearScreen(0); fade_.startFadeTo(pal); })
|
||||
// .step(5000) // espera pura
|
||||
// .step(1000, [this](float p) { /*...*/ }) // animat amb progress
|
||||
// .once([this] { JA_FadeOutMusic(250); });
|
||||
//
|
||||
// `tick(delta_ms)` avança el temps. Els passos one-shot s'executen al
|
||||
// moment d'entrar-hi i avancen immediatament. Els passos amb duració
|
||||
// criden el seu callback cada tick amb el progress [0..1] i passen al
|
||||
// següent quan s'exhaureix el temps. `skip()` marca tota la timeline
|
||||
// com a acabada (no executa res més) — útil per als "polsa una tecla
|
||||
// per a saltar la cinemàtica".
|
||||
class Timeline {
|
||||
public:
|
||||
using StepFn = std::function<void(float progress_0_1)>;
|
||||
using OnceFn = std::function<void()>;
|
||||
|
||||
Timeline& step(int duration_ms, StepFn fn = nullptr);
|
||||
Timeline& once(OnceFn fn);
|
||||
|
||||
void tick(int delta_ms);
|
||||
void skip();
|
||||
void reset();
|
||||
|
||||
bool done() const;
|
||||
int currentStepIndex() const { return static_cast<int>(current_); }
|
||||
float currentProgress() const;
|
||||
|
||||
private:
|
||||
struct Step {
|
||||
int duration_ms{0}; // 0 = one-shot
|
||||
StepFn continuous;
|
||||
OnceFn oneshot;
|
||||
bool entered{false};
|
||||
};
|
||||
|
||||
// Avança els one-shots consecutius des de `current_` fins a trobar
|
||||
// un pas amb duració > 0 o l'endoll de la llista.
|
||||
void flushOneShots();
|
||||
|
||||
std::vector<Step> steps_;
|
||||
std::size_t current_{0};
|
||||
int elapsed_in_step_{0};
|
||||
bool skipped_{false};
|
||||
};
|
||||
|
||||
} // namespace scenes
|
||||
Reference in New Issue
Block a user