Some improvements, added cutscene stub
This commit is contained in:
1 parent
72e05cdc28
commit
be05643b70
27 files changed
+3480
-40
No files matched your search
@@ -197,6 +197,10 @@ StaticCutsceneDefinition DialogueDatabase::parseCutscene(const json& j) {
|
||||
cutscene.music = j.value("music", "");
|
||||
cutscene.skippable = j.value("skippable", true);
|
||||
cutscene.durationMs = j.value("durationMs", 0);
|
||||
cutscene.fadeOutMs = j.value("fadeOutMs", 0);
|
||||
cutscene.fadeInMs = j.value("fadeInMs", 0);
|
||||
cutscene.endFadeOutMs = j.value("endFadeOutMs", 0);
|
||||
cutscene.endFadeInMs = j.value("endFadeInMs", 0);
|
||||
|
||||
if (j.contains("cameraTrack") && j["cameraTrack"].is_array()) {
|
||||
for (const auto& item : j["cameraTrack"]) {
|
||||
|
||||
@@ -461,7 +461,7 @@ void DialogueOverlay::drawCutscene(Renderer& renderer, const PresentationModel&
|
||||
toUV(srcBR)
|
||||
);
|
||||
|
||||
renderer.RenderUniform1f("uAlpha", std::clamp(layer.alpha, 0.0f, 1.0f));
|
||||
renderer.RenderUniform1f("uAlpha", std::clamp(layer.alpha * model.cutsceneGlobalFadeAlpha, 0.0f, 1.0f));
|
||||
drawQuad(renderer, backgroundQuad, texture);
|
||||
}
|
||||
|
||||
@@ -469,6 +469,25 @@ void DialogueOverlay::drawCutscene(Renderer& renderer, const PresentationModel&
|
||||
renderer.PopProjectionMatrix();
|
||||
renderer.shaderManager.PopShader();
|
||||
|
||||
// Black overlay: fades IN over the game world (phase 1), then fades OUT to reveal cutscene (phase 2).
|
||||
// Image renders at cutsceneGlobalFadeAlpha (0 during phase 1 so game world shows through for the
|
||||
// fade-to-black effect, 1 during phase 2 so it blocks the world while black peels away).
|
||||
if (model.cutsceneBlackAlpha > 0.001f) {
|
||||
const float blackAlpha = model.cutsceneBlackAlpha;
|
||||
renderer.shaderManager.PushShader("cutsceneBlack");
|
||||
renderer.PushProjectionMatrix(0.0f, W, 0.0f, H, -10.0f, 10.0f);
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
|
||||
backgroundQuad.rebuild(screenRect);
|
||||
renderer.RenderUniform1f("uAlpha", blackAlpha);
|
||||
renderer.DrawVertexRenderStruct(backgroundQuad.mesh);
|
||||
|
||||
renderer.PopMatrix();
|
||||
renderer.PopProjectionMatrix();
|
||||
renderer.shaderManager.PopShader();
|
||||
}
|
||||
|
||||
// UI quads over the image: subtitle panel and skip progress hint background.
|
||||
renderer.shaderManager.PushShader(defaultShaderName);
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
|
||||
@@ -33,6 +33,7 @@ bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
|
||||
cutsceneTimerMs = 0;
|
||||
cutsceneElapsedMs = 0;
|
||||
cutsceneTotalDurationMs = 0;
|
||||
cutsceneContentDurationMs = 0;
|
||||
currentCutsceneBackground.clear();
|
||||
presentation = {};
|
||||
presentation.dialogueId = dialogue->id;
|
||||
@@ -40,6 +41,27 @@ bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
|
||||
return enterNode(dialogue->startNode);
|
||||
}
|
||||
|
||||
bool DialogueRuntime::startStandaloneCutscene(const std::string& cutsceneId) {
|
||||
if (!database) {
|
||||
std::cerr << "[dialogue] No database assigned to runtime\n";
|
||||
return false;
|
||||
}
|
||||
const StaticCutsceneDefinition* def = database->findCutscene(cutsceneId);
|
||||
if (!def) {
|
||||
std::cerr << "[dialogue] Cutscene not found: " << cutsceneId << "\n";
|
||||
return false;
|
||||
}
|
||||
activeDialogue = nullptr;
|
||||
activeCutsceneId = cutsceneId;
|
||||
presentation = {};
|
||||
startCutscene(cutsceneId, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
void DialogueRuntime::setOnCutsceneFinished(std::function<void(const std::string&)> cb) {
|
||||
onCutsceneFinished = std::move(cb);
|
||||
}
|
||||
|
||||
void DialogueRuntime::stop() {
|
||||
activeDialogue = nullptr;
|
||||
activeCutscene = nullptr;
|
||||
@@ -52,6 +74,7 @@ void DialogueRuntime::stop() {
|
||||
cutsceneTimerMs = 0;
|
||||
cutsceneElapsedMs = 0;
|
||||
cutsceneTotalDurationMs = 0;
|
||||
cutsceneContentDurationMs = 0;
|
||||
currentCutsceneBackground.clear();
|
||||
mode = Mode::Inactive;
|
||||
presentation = {};
|
||||
@@ -340,6 +363,7 @@ bool DialogueRuntime::enterNode(const std::string& nodeId) {
|
||||
return true;
|
||||
|
||||
case NodeType::CutsceneStart:
|
||||
activeCutsceneId = node.cutsceneId;
|
||||
startCutscene(node.cutsceneId, node.next);
|
||||
return true;
|
||||
}
|
||||
@@ -364,6 +388,8 @@ void DialogueRuntime::presentLine(const Node& node) {
|
||||
presentation.cutsceneSkippable = false;
|
||||
presentation.cutsceneCamera = {};
|
||||
presentation.cutsceneImages.clear();
|
||||
presentation.cutsceneGlobalFadeAlpha = 1.0f;
|
||||
presentation.cutsceneBlackAlpha = 0.0f;
|
||||
|
||||
if (presentation.revealCompleted) {
|
||||
presentation.visibleText = node.text;
|
||||
@@ -410,6 +436,8 @@ void DialogueRuntime::presentChoices(const Node& node) {
|
||||
presentation.cutsceneSkippable = false;
|
||||
presentation.cutsceneCamera = {};
|
||||
presentation.cutsceneImages.clear();
|
||||
presentation.cutsceneGlobalFadeAlpha = 1.0f;
|
||||
presentation.cutsceneBlackAlpha = 0.0f;
|
||||
}
|
||||
|
||||
void DialogueRuntime::startCutscene(const std::string& cutsceneId, const std::string& nextNodeAfterCutscene) {
|
||||
@@ -451,10 +479,11 @@ void DialogueRuntime::startCutscene(const std::string& cutsceneId, const std::st
|
||||
}
|
||||
imageTrackDurationMs = std::max(imageTrackDurationMs, cueEnd);
|
||||
}
|
||||
cutsceneTotalDurationMs = std::max({ activeCutscene->durationMs, computeCameraTrackDurationMs(*activeCutscene), imageTrackDurationMs });
|
||||
if (cutsceneTotalDurationMs <= 0 && activeCutscene->lines.empty()) {
|
||||
cutsceneTotalDurationMs = 3000;
|
||||
cutsceneContentDurationMs = std::max({ activeCutscene->durationMs, computeCameraTrackDurationMs(*activeCutscene), imageTrackDurationMs });
|
||||
if (cutsceneContentDurationMs <= 0 && activeCutscene->lines.empty()) {
|
||||
cutsceneContentDurationMs = 3000;
|
||||
}
|
||||
cutsceneTotalDurationMs = cutsceneContentDurationMs + activeCutscene->endFadeOutMs + activeCutscene->endFadeInMs;
|
||||
refreshCutscenePresentation();
|
||||
|
||||
std::cout << "[CUTSCENE] start id=" << cutsceneId
|
||||
@@ -466,12 +495,18 @@ void DialogueRuntime::startCutscene(const std::string& cutsceneId, const std::st
|
||||
void DialogueRuntime::finishCutscene() {
|
||||
|
||||
std::cout << "[CUTSCENE] finish nextNode=" << pendingNodeAfterCutscene << std::endl;
|
||||
const std::string finishedId = activeCutsceneId;
|
||||
activeCutsceneId.clear();
|
||||
activeCutscene = nullptr;
|
||||
currentCutsceneLine = -1;
|
||||
cutsceneTimerMs = 0;
|
||||
cutsceneElapsedMs = 0;
|
||||
cutsceneTotalDurationMs = 0;
|
||||
cutsceneContentDurationMs = 0;
|
||||
currentCutsceneBackground.clear();
|
||||
if (onCutsceneFinished && !finishedId.empty())
|
||||
onCutsceneFinished(finishedId);
|
||||
|
||||
if (!pendingNodeAfterCutscene.empty()) {
|
||||
const std::string nextNode = pendingNodeAfterCutscene;
|
||||
pendingNodeAfterCutscene.clear();
|
||||
@@ -528,8 +563,11 @@ void DialogueRuntime::advanceCutsceneLine() {
|
||||
|
||||
if (currentCutsceneLine >= static_cast<int>(activeCutscene->lines.size())) {
|
||||
refreshCutscenePresentation();
|
||||
if (cutsceneTotalDurationMs <= 0 || cutsceneElapsedMs >= cutsceneTotalDurationMs) {
|
||||
finishCutscene();
|
||||
if (cutsceneContentDurationMs <= 0 || cutsceneElapsedMs >= cutsceneContentDurationMs) {
|
||||
// Only finish immediately if there's no end transition to play
|
||||
if (activeCutscene->endFadeOutMs <= 0 && activeCutscene->endFadeInMs <= 0) {
|
||||
finishCutscene();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -589,7 +627,7 @@ std::vector<PresentedCutsceneImage> DialogueRuntime::evaluateCutsceneImages() co
|
||||
return result;
|
||||
}
|
||||
|
||||
const int effectiveTotalDuration = (cutsceneTotalDurationMs > 0) ? cutsceneTotalDurationMs : std::max(activeCutscene->durationMs, 1);
|
||||
const int effectiveTotalDuration = (cutsceneContentDurationMs > 0) ? cutsceneContentDurationMs : std::max(activeCutscene->durationMs, 1);
|
||||
const int now = std::max(cutsceneElapsedMs, 0);
|
||||
|
||||
for (size_t i = 0; i < activeCutscene->images.size(); ++i) {
|
||||
@@ -655,6 +693,43 @@ void DialogueRuntime::refreshCutscenePresentation() {
|
||||
presentation.cutsceneImages = evaluateCutsceneImages();
|
||||
presentation.cutsceneSkippable = activeCutscene->skippable;
|
||||
|
||||
const int fadeOutMs = activeCutscene->fadeOutMs;
|
||||
const int fadeInMs = activeCutscene->fadeInMs;
|
||||
const int endFadeOutMs = activeCutscene->endFadeOutMs;
|
||||
const int endFadeInMs = activeCutscene->endFadeInMs;
|
||||
const int endFadeOutStart = cutsceneContentDurationMs;
|
||||
const int endFadeInStart = cutsceneContentDurationMs + endFadeOutMs;
|
||||
|
||||
if (cutsceneElapsedMs < fadeOutMs) {
|
||||
// Start phase 1: game world fading to black
|
||||
presentation.cutsceneGlobalFadeAlpha = 0.0f;
|
||||
presentation.cutsceneBlackAlpha = std::clamp(
|
||||
static_cast<float>(cutsceneElapsedMs) / static_cast<float>(fadeOutMs),
|
||||
0.0f, 1.0f
|
||||
);
|
||||
} else if (cutsceneElapsedMs < endFadeOutStart) {
|
||||
// Content playing (also covers start phase 2 fade-in)
|
||||
presentation.cutsceneGlobalFadeAlpha = 1.0f;
|
||||
const int phase2elapsed = cutsceneElapsedMs - fadeOutMs;
|
||||
presentation.cutsceneBlackAlpha = (fadeInMs > 0)
|
||||
? std::clamp(1.0f - static_cast<float>(phase2elapsed) / static_cast<float>(fadeInMs), 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
} else if (cutsceneElapsedMs < endFadeInStart) {
|
||||
// End phase 1: cutscene fading to black
|
||||
presentation.cutsceneGlobalFadeAlpha = 1.0f;
|
||||
const int elapsed = cutsceneElapsedMs - endFadeOutStart;
|
||||
presentation.cutsceneBlackAlpha = (endFadeOutMs > 0)
|
||||
? std::clamp(static_cast<float>(elapsed) / static_cast<float>(endFadeOutMs), 0.0f, 1.0f)
|
||||
: 1.0f;
|
||||
} else {
|
||||
// End phase 2: game world appearing from black (image hidden, world shows through)
|
||||
presentation.cutsceneGlobalFadeAlpha = 0.0f;
|
||||
const int elapsed = cutsceneElapsedMs - endFadeInStart;
|
||||
presentation.cutsceneBlackAlpha = (endFadeInMs > 0)
|
||||
? std::clamp(1.0f - static_cast<float>(elapsed) / static_cast<float>(endFadeInMs), 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
presentation.choices.clear();
|
||||
presentation.selectedChoice = -1;
|
||||
presentation.revealCompleted = true;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "dialogue/DialogueDatabase.h"
|
||||
#include "external/nlohmann/json.hpp"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -16,6 +17,8 @@ public:
|
||||
void setDatabase(const DialogueDatabase* value);
|
||||
|
||||
bool startDialogue(const std::string& dialogueId);
|
||||
bool startStandaloneCutscene(const std::string& cutsceneId);
|
||||
void setOnCutsceneFinished(std::function<void(const std::string&)> cb);
|
||||
void stop();
|
||||
|
||||
void update(int deltaMs);
|
||||
@@ -46,6 +49,9 @@ private:
|
||||
PlayingCutscene
|
||||
};
|
||||
|
||||
std::function<void(const std::string&)> onCutsceneFinished;
|
||||
std::string activeCutsceneId;
|
||||
|
||||
const DialogueDatabase* database = nullptr;
|
||||
const DialogueDefinition* activeDialogue = nullptr;
|
||||
const StaticCutsceneDefinition* activeCutscene = nullptr;
|
||||
@@ -68,6 +74,7 @@ private:
|
||||
int cutsceneTimerMs = 0;
|
||||
int cutsceneElapsedMs = 0;
|
||||
int cutsceneTotalDurationMs = 0;
|
||||
int cutsceneContentDurationMs = 0;
|
||||
|
||||
std::string currentCutsceneBackground;
|
||||
|
||||
|
||||
@@ -119,6 +119,14 @@ bool DialogueSystem::startDialogue(const std::string& dialogueId) {
|
||||
return runtime.startDialogue(dialogueId);
|
||||
}
|
||||
|
||||
bool DialogueSystem::startCutscene(const std::string& cutsceneId) {
|
||||
return runtime.startStandaloneCutscene(cutsceneId);
|
||||
}
|
||||
|
||||
void DialogueSystem::setOnCutsceneFinished(std::function<void(const std::string&)> cb) {
|
||||
runtime.setOnCutsceneFinished(std::move(cb));
|
||||
}
|
||||
|
||||
void DialogueSystem::stopDialogue() {
|
||||
runtime.stop();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "dialogue/DialogueOverlay.h"
|
||||
#include "dialogue/DialogueRuntime.h"
|
||||
#include <SDL.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace ZL::Dialogue {
|
||||
@@ -21,6 +22,8 @@ public:
|
||||
bool handlePointerReleased(float x, float y);
|
||||
|
||||
bool startDialogue(const std::string& dialogueId);
|
||||
bool startCutscene(const std::string& cutsceneId);
|
||||
void setOnCutsceneFinished(std::function<void(const std::string&)> cb);
|
||||
void stopDialogue();
|
||||
|
||||
bool isActive() const { return runtime.isActive(); }
|
||||
|
||||
@@ -149,6 +149,10 @@ struct StaticCutsceneDefinition {
|
||||
std::string music;
|
||||
bool skippable = true;
|
||||
int durationMs = 0;
|
||||
int fadeOutMs = 0;
|
||||
int fadeInMs = 0;
|
||||
int endFadeOutMs = 0;
|
||||
int endFadeInMs = 0;
|
||||
std::vector<CutsceneCameraSegment> cameraTrack;
|
||||
std::vector<CutsceneImageCue> images;
|
||||
std::vector<CutsceneLine> lines;
|
||||
@@ -195,6 +199,8 @@ struct PresentationModel {
|
||||
|
||||
CutsceneCameraBlendState cutsceneCamera;
|
||||
std::vector<PresentedCutsceneImage> cutsceneImages;
|
||||
float cutsceneGlobalFadeAlpha = 1.0f;
|
||||
float cutsceneBlackAlpha = 0.0f;
|
||||
};
|
||||
|
||||
struct SaveState {
|
||||
|
||||
Reference in new issue
Block a user