fix: make choice options the only clickable area in dialogue choices

This commit is contained in:
vottozi 2026-04-17 02:04:36 +06:00
parent 2494a44e4a
commit be4dcd4cf9
8 changed files with 112 additions and 29 deletions

View File

@ -696,6 +696,13 @@ namespace ZL
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight); int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my); handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
if (dialogueSystem.blocksGameplayInput()) {
dialogueSystem.handlePointerMoved(
static_cast<float>(mx),
Environment::projectionHeight - static_cast<float>(my)
);
}
if (rightMouseDown) { if (rightMouseDown) {
int dx = event.motion.x - lastMouseX; int dx = event.motion.x - lastMouseX;
int dy = event.motion.y - lastMouseY; int dy = event.motion.y - lastMouseY;
@ -732,7 +739,7 @@ namespace ZL
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) { if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
switch (event.key.keysym.sym) { switch (event.key.keysym.sym) {
case SDLK_f: case SDLK_f:
dialogueSystem.startDialogue("test_cutscene_pan_dialogue_silent"); dialogueSystem.startDialogue("test_choice_dialogue");
break; break;
case SDLK_e: case SDLK_e:

View File

@ -84,6 +84,12 @@ bool DialogueOverlay::init(Renderer& renderer, const std::string& zipFile) {
return ok; return ok;
} }
void DialogueOverlay::update(const PresentationModel& model, int deltaMs) {
if (model.mode != PresentationMode::Choice) {
hoveredChoiceIndex = -1;
}
}
void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) { void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
if (model.mode == PresentationMode::Hidden) { if (model.mode == PresentationMode::Hidden) {
lastChoiceRects.clear(); lastChoiceRects.clear();
@ -170,12 +176,9 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
lastChoiceRects.push_back(rect); lastChoiceRects.push_back(rect);
choiceQuads[i].rebuild(rect); choiceQuads[i].rebuild(rect);
const bool isSelected = static_cast<int>(i) == model.selectedChoice; const bool isHighlighted = static_cast<int>(i) == hoveredChoiceIndex || static_cast<int>(i) == model.selectedChoice;
std::shared_ptr<Texture> choiceTexture = choiceMainTexture; std::shared_ptr<Texture> choiceTexture = (model.choices[i].kind == ChoiceKind::Optional) ? choiceOptionalTexture : choiceMainTexture;
if (model.choices[i].kind == ChoiceKind::Optional) { if (isHighlighted) {
choiceTexture = choiceOptionalTexture;
}
if (isSelected) {
choiceTexture = choiceSelectedTexture; choiceTexture = choiceSelectedTexture;
} }
drawQuad(renderer, choiceQuads[i], choiceTexture); drawQuad(renderer, choiceQuads[i], choiceTexture);
@ -187,10 +190,10 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
for (size_t i = 0; i < model.choices.size(); ++i) { for (size_t i = 0; i < model.choices.size(); ++i) {
const UiRect& rect = lastChoiceRects[i]; const UiRect& rect = lastChoiceRects[i];
const bool isSelected = static_cast<int>(i) == model.selectedChoice; const bool isHighlighted = static_cast<int>(i) == hoveredChoiceIndex || static_cast<int>(i) == model.selectedChoice;
const std::array<float, 4> color = (model.choices[i].kind == ChoiceKind::Optional) const std::array<float, 4> color = (model.choices[i].kind == ChoiceKind::Optional)
? std::array<float, 4>{0.82f, 0.82f, 0.82f, 1.0f} ? std::array<float, 4>{0.82f, 0.82f, 0.82f, 1.0f}
: std::array<float, 4>{ 1.0f, 0.93f, 0.65f, 1.0f }; : std::array<float, 4>{ 1.0f, 0.93f, 0.65f, 1.0f };
choiceRenderer->drawText( choiceRenderer->drawText(
wrapText(model.choices[i].text, 52), wrapText(model.choices[i].text, 52),
@ -198,7 +201,7 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
rect.y + 9.0f, rect.y + 9.0f,
1.0f, 1.0f,
false, false,
isSelected ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color isHighlighted ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color
); );
} }
} }
@ -417,21 +420,48 @@ void DialogueOverlay::drawCutscene(Renderer& renderer, const PresentationModel&
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }
bool DialogueOverlay::handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex) const { void DialogueOverlay::handlePointerDown(float x, float y, const PresentationModel& model) {
if (model.mode == PresentationMode::Choice) {
handlePointerMoved(x, y, model);
return;
}
}
void DialogueOverlay::handlePointerMoved(float x, float y, const PresentationModel& model) {
if (model.mode == PresentationMode::Choice) {
hoveredChoiceIndex = -1;
for (size_t i = 0; i < lastChoiceRects.size(); ++i) {
if (rectContains(lastChoiceRects[i], x, y)) {
hoveredChoiceIndex = static_cast<int>(i);
break;
}
}
return;
}
hoveredChoiceIndex = -1;
}
bool DialogueOverlay::handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex, bool& outAdvanceDialogue) {
outChoiceIndex = -1; outChoiceIndex = -1;
outAdvanceDialogue = false;
if (model.mode == PresentationMode::Choice) { if (model.mode == PresentationMode::Choice) {
for (size_t i = 0; i < lastChoiceRects.size(); ++i) { for (size_t i = 0; i < lastChoiceRects.size(); ++i) {
if (lastChoiceRects[i].contains(x, y)) { if (rectContains(lastChoiceRects[i], x, y)) {
outChoiceIndex = static_cast<int>(i); outChoiceIndex = static_cast<int>(i);
return true; return true;
} }
} }
return lastDialogueAdvanceRect.contains(x, y); return false;
} }
if (model.mode == PresentationMode::Dialogue) { if (model.mode == PresentationMode::Dialogue) {
return lastDialogueAdvanceRect.contains(x, y); if (lastDialogueAdvanceRect.contains(x, y)) {
outAdvanceDialogue = true;
return true;
}
return false;
} }
if (model.mode == PresentationMode::Cutscene) { if (model.mode == PresentationMode::Cutscene) {
@ -494,4 +524,8 @@ std::string DialogueOverlay::wrapText(const std::string& input, size_t maxLineLe
return output; return output;
} }
bool DialogueOverlay::rectContains(const UiRect& rect, float x, float y) {
return x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
}
} // namespace ZL::Dialogue } // namespace ZL::Dialogue

View File

@ -15,11 +15,12 @@ namespace ZL::Dialogue {
class DialogueOverlay { class DialogueOverlay {
public: public:
bool init(Renderer& renderer, const std::string& zipFile = ""); bool init(Renderer& renderer, const std::string& zipFile = "");
void update(const PresentationModel& model, int deltaMs);
void draw(Renderer& renderer, const PresentationModel& model); void draw(Renderer& renderer, const PresentationModel& model);
// Coordinates are expected in the game's UI projection space void handlePointerDown(float x, float y, const PresentationModel& model);
// Returns true only when the click should advance/select. void handlePointerMoved(float x, float y, const PresentationModel& model);
bool handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex) const; bool handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex, bool& outAdvanceDialogue);
private: private:
struct TexturedQuad { struct TexturedQuad {
@ -60,6 +61,8 @@ private:
mutable UiRect lastCutsceneAdvanceRect{}; mutable UiRect lastCutsceneAdvanceRect{};
mutable bool cutsceneAdvanceEnabled = false; mutable bool cutsceneAdvanceEnabled = false;
int hoveredChoiceIndex = -1;
std::unique_ptr<TextRenderer> nameRenderer; std::unique_ptr<TextRenderer> nameRenderer;
std::unique_ptr<TextRenderer> bodyRenderer; std::unique_ptr<TextRenderer> bodyRenderer;
std::unique_ptr<TextRenderer> choiceRenderer; std::unique_ptr<TextRenderer> choiceRenderer;
@ -80,6 +83,7 @@ private:
void drawQuad(Renderer& renderer, const TexturedQuad& quad, const std::shared_ptr<Texture>& texture) const; void drawQuad(Renderer& renderer, const TexturedQuad& quad, const std::shared_ptr<Texture>& texture) const;
static std::string wrapText(const std::string& input, size_t maxLineLength); static std::string wrapText(const std::string& input, size_t maxLineLength);
static bool rectContains(const UiRect& rect, float x, float y);
static float lerpFloat(float a, float b, float t); static float lerpFloat(float a, float b, float t);
static ResolvedViewport resolveViewportPose( static ResolvedViewport resolveViewportPose(

View File

@ -185,10 +185,26 @@ void DialogueRuntime::moveSelection(int delta) {
} }
const int count = static_cast<int>(visibleChoices.size()); const int count = static_cast<int>(visibleChoices.size());
selectedChoice = (selectedChoice + delta) % count; if (selectedChoice < 0 || selectedChoice >= count) {
if (selectedChoice < 0) { selectedChoice = (delta >= 0) ? 0 : (count - 1);
selectedChoice += count;
} }
else {
selectedChoice = (selectedChoice + delta) % count;
if (selectedChoice < 0) {
selectedChoice += count;
}
}
presentation.selectedChoice = selectedChoice;
}
void DialogueRuntime::selectChoice(int index) {
if (mode != Mode::WaitingForChoice || visibleChoices.empty()) {
return;
}
if (index < 0 || index >= static_cast<int>(visibleChoices.size())) {
return;
}
selectedChoice = index;
presentation.selectedChoice = selectedChoice; presentation.selectedChoice = selectedChoice;
} }
@ -346,14 +362,14 @@ void DialogueRuntime::presentChoices(const Node& node) {
} }
mode = Mode::WaitingForChoice; mode = Mode::WaitingForChoice;
selectedChoice = 0; selectedChoice = -1;
presentation.mode = PresentationMode::Choice; presentation.mode = PresentationMode::Choice;
presentation.speaker = node.speaker; presentation.speaker = node.speaker;
presentation.fullText = node.text; presentation.fullText = node.text;
presentation.visibleText = node.text; presentation.visibleText = node.text;
presentation.portraitPath = node.portrait; presentation.portraitPath = node.portrait;
presentation.backgroundPath.clear(); presentation.backgroundPath.clear();
presentation.selectedChoice = 0; presentation.selectedChoice = -1;
presentation.revealCompleted = true; presentation.revealCompleted = true;
presentation.showCutsceneSubtitle = false; presentation.showCutsceneSubtitle = false;
presentation.cutsceneCamera = {}; presentation.cutsceneCamera = {};

View File

@ -26,6 +26,7 @@ public:
void confirmAdvance(); void confirmAdvance();
void moveSelection(int delta); void moveSelection(int delta);
void selectChoice(int index);
const PresentationModel& getPresentation() const { return presentation; } const PresentationModel& getPresentation() const { return presentation; }

View File

@ -66,24 +66,43 @@ bool DialogueSystem::handleKeyDown(SDL_Keycode key) {
} }
} }
void DialogueSystem::handlePointerDown(float x, float y) {
if (!runtime.isActive()) {
return;
}
overlay.handlePointerDown(x, y, runtime.getPresentation());
}
void DialogueSystem::handlePointerMoved(float x, float y) {
if (!runtime.isActive()) {
return;
}
overlay.handlePointerMoved(x, y, runtime.getPresentation());
}
bool DialogueSystem::handlePointerReleased(float x, float y) { bool DialogueSystem::handlePointerReleased(float x, float y) {
if (!runtime.isActive()) { if (!runtime.isActive()) {
return false; return false;
} }
int choiceIndex = -1; int choiceIndex = -1;
bool advanceDialogue = false;
const PresentationModel& model = runtime.getPresentation(); const PresentationModel& model = runtime.getPresentation();
if (!overlay.handlePointerReleased(x, y, model, choiceIndex)) { if (!overlay.handlePointerReleased(x, y, model, choiceIndex, advanceDialogue)) {
return false; return false;
} }
if (choiceIndex >= 0) { if (choiceIndex >= 0) {
while (runtime.getPresentation().selectedChoice != choiceIndex) { runtime.selectChoice(choiceIndex);
runtime.moveSelection(1); runtime.confirmAdvance();
} return true;
}
if (advanceDialogue) {
runtime.confirmAdvance();
return true;
} }
runtime.confirmAdvance();
return true; return true;
} }

View File

@ -27,6 +27,8 @@ public:
void draw(Renderer& renderer); void draw(Renderer& renderer);
bool handleKeyDown(SDL_Keycode key); bool handleKeyDown(SDL_Keycode key);
void handlePointerDown(float x, float y);
void handlePointerMoved(float x, float y);
bool handlePointerReleased(float x, float y); bool handlePointerReleased(float x, float y);
bool startDialogue(const std::string& dialogueId); bool startDialogue(const std::string& dialogueId);

View File

@ -173,7 +173,7 @@ struct PresentationModel {
std::string portraitPath; std::string portraitPath;
std::string backgroundPath; std::string backgroundPath;
std::vector<PresentedChoice> choices; std::vector<PresentedChoice> choices;
int selectedChoice = 0; int selectedChoice = -1;
bool revealCompleted = true; bool revealCompleted = true;
bool showCutsceneSubtitle = false; bool showCutsceneSubtitle = false;