Working on UI improvement

This commit is contained in:
Vladislav Khorev 2026-05-27 17:30:41 +03:00
parent 0da1ae124c
commit 80af0e61ae
43 changed files with 889 additions and 64 deletions

View File

@ -1,5 +1,6 @@
//precisionmediump float; //precisionmediump float;
uniform sampler2D Texture; uniform sampler2D Texture;
uniform float uAlpha;
varying vec2 texCoord; varying vec2 texCoord;
void main() void main()
@ -7,6 +8,6 @@ void main()
vec4 color = texture2D(Texture,texCoord).rgba; vec4 color = texture2D(Texture,texCoord).rgba;
//gl_FragColor = vec4(color.rgb*0.1 + vec3(0.9, 0.9, 0.9), 1.0); //gl_FragColor = vec4(color.rgb*0.1 + vec3(0.9, 0.9, 0.9), 1.0);
gl_FragColor = color; gl_FragColor = vec4(color.rgb, color.a * uAlpha);
} }

View File

@ -1,5 +1,6 @@
precision mediump float; precision mediump float;
uniform sampler2D Texture; uniform sampler2D Texture;
uniform float uAlpha;
varying vec2 texCoord; varying vec2 texCoord;
void main() void main()
@ -7,6 +8,6 @@ void main()
vec4 color = texture2D(Texture,texCoord).rgba; vec4 color = texture2D(Texture,texCoord).rgba;
//gl_FragColor = vec4(color.rgb*0.1 + vec3(0.9, 0.9, 0.9), 1.0); //gl_FragColor = vec4(color.rgb*0.1 + vec3(0.9, 0.9, 0.9), 1.0);
gl_FragColor = color; gl_FragColor = vec4(color.rgb, color.a * uAlpha);
} }

View File

@ -0,0 +1,43 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 0,
"width": "match_parent",
"height": 600,
"children": [
{
"type": "Button",
"name": "phoneButton",
"width": 229,
"height": 229,
"textures": {
"normal": "resources/w/ui/img/Phone001_State=Default.png",
"hover": "resources/w/ui/img/Phone001_State=Selected.png",
"pressed": "resources/w/ui/img/Phone001_State=Tap.png"
}
},
{
"type": "Button",
"name": "journalButton",
"width": 229,
"height": 229,
"textures": {
"normal": "resources/w/ui/img/Phone001_State=Default.png",
"hover": "resources/w/ui/img/Phone001_State=Selected.png",
"pressed": "resources/w/ui/img/Phone001_State=Tap.png"
}
}
]
}]
}
}

View File

@ -13,6 +13,7 @@
"spacing": 0, "spacing": 0,
"width": "match_parent", "width": "match_parent",
"height": 600, "height": 600,
"x" : 300,
"children": [ "children": [
{ {
"type": "StaticImage", "type": "StaticImage",
@ -20,7 +21,13 @@
"width": 440, "width": 440,
"height": 134, "height": 134,
"horizontal_gravity": "center", "horizontal_gravity": "center",
"texture": "resources/w/ui/img/Hint2.png" "texture": "resources/w/ui/img/Hint1.png",
"pulse": {
"minScale": 0.92,
"maxScale": 1.08,
"periodMs": 1500
},
"fadeIn": { "durationMs": 600 }
}, },
{ {
"type": "StaticImage", "type": "StaticImage",

View File

@ -5,42 +5,31 @@
"width": "match_parent", "width": "match_parent",
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
{ {
"type": "TextButton", "type": "LinearLayout",
"name": "inventory_button", "orientation": "vertical",
"x": 50.0, "vertical_align": "center",
"y": 50.0, "horizontal_align": "center",
"width": 150.0, "spacing": 0,
"height": 60.0, "width": "match_parent",
"text": "Inventory", "height": 600,
"fontSize": 24, "y" : 300,
"fontPath": "resources/fonts/DroidSans.ttf", "children": [
"textCentered": true, {
"color": [1.0, 1.0, 1.0, 1.0], "type": "StaticImage",
"textures": { "name": "hint2",
"normal": "resources/w/red.png", "width": 440,
"hover": "resources/w/red.png", "height": 134,
"pressed": "resources/w/red.png" "horizontal_gravity": "center",
} "texture": "resources/w/ui/img/Hint2.png",
}, "pulse": {
{ "minScale": 0.92,
"type": "TextButton", "maxScale": 1.08,
"name": "quest_journal_button", "periodMs": 1500
"x": 220.0, },
"y": 50.0, "fadeIn": { "durationMs": 600 }
"width": 170.0, }
"height": 60.0, ]
"text": "Quests", }]
"fontSize": 24,
"fontPath": "resources/fonts/DroidSans.ttf",
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0],
"textures": {
"normal": "resources/w/red.png",
"hover": "resources/w/red.png",
"pressed": "resources/w/red.png"
}
}
]
} }
} }

View File

@ -0,0 +1,35 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 0,
"width": "match_parent",
"height": 600,
"y" : 300,
"children": [
{
"type": "StaticImage",
"name": "hint3",
"width": 440,
"height": 134,
"horizontal_gravity": "center",
"texture": "resources/w/ui/img/Hint3.png",
"pulse": {
"minScale": 0.92,
"maxScale": 1.08,
"periodMs": 1500
},
"fadeIn": { "durationMs": 600 }
}
]
}]
}
}

View File

@ -0,0 +1,35 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 0,
"width": "match_parent",
"height": 600,
"y" : 300,
"children": [
{
"type": "StaticImage",
"name": "hint4",
"width": 440,
"height": 134,
"horizontal_gravity": "center",
"texture": "resources/w/ui/img/Hint4.png",
"pulse": {
"minScale": 0.92,
"maxScale": 1.08,
"periodMs": 1500
},
"fadeIn": { "durationMs": 600 }
}
]
}]
}
}

View File

@ -0,0 +1,35 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 0,
"width": "match_parent",
"height": 600,
"y" : 300,
"children": [
{
"type": "StaticImage",
"name": "hint5",
"width": 440,
"height": 134,
"horizontal_gravity": "center",
"texture": "resources/w/ui/img/Hint5.png",
"pulse": {
"minScale": 0.92,
"maxScale": 1.08,
"periodMs": 1500
},
"fadeIn": { "durationMs": 600 }
}
]
}]
}
}

View File

@ -0,0 +1,57 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 0,
"width": "match_parent",
"height": 600,
"y" : -250,
"x" : 300,
"children": [
{
"type": "StaticImage",
"name": "hint6a",
"width": 440,
"height": 134,
"horizontal_gravity": "center",
"texture": "resources/w/ui/img/Hint6a.png",
"pulse": {
"minScale": 0.92,
"maxScale": 1.08,
"periodMs": 1500
},
"fadeIn": {
"durationMs": 600
}
}
]
},
{
"type": "Button",
"name": "phoneButton",
"horizontal_gravity": "right",
"vertical_gravity": "top",
"x": -46,
"y" :-46,
"width": 229,
"height": 229,
"clickZoneWidth": 104,
"clickZoneHeight": 104,
"textures": {
"normal": "resources/w/ui/img/Phone001_State=Default.png",
"hover": "resources/w/ui/img/Phone001_State=Selected.png",
"pressed": "resources/w/ui/img/Phone001_State=Tap.png"
}
}
]
}
}

View File

@ -0,0 +1,31 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 0,
"width": "match_parent",
"height": 600,
"children": [
{
"type": "Button",
"name": "phoneButton",
"width": 229,
"height": 229,
"textures": {
"normal": "resources/w/ui/img/Phone001_State=Default.png",
"hover": "resources/w/ui/img/Phone001_State=Selected.png",
"pressed": "resources/w/ui/img/Phone001_State=Tap.png"
}
}
]
}]
}
}

View File

@ -0,0 +1,82 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "horizontal",
"vertical_align": "center",
"horizontal_align": "right",
"spacing": 16,
"width": "match_parent",
"height": 600,
"y" : -200,
"children": [
{
"type": "StaticImage",
"name": "hint6a",
"width": 440,
"height": 134,
"horizontal_gravity": "center",
"texture": "resources/w/ui/img/Hint6a.png",
"pulse": {
"minScale": 0.92,
"maxScale": 1.08,
"periodMs": 1500
},
"fadeIn": { "durationMs": 600 }
},
{
"type": "StaticImage",
"name": "hint6b",
"width": 440,
"height": 134,
"horizontal_gravity": "center",
"texture": "resources/w/ui/img/Hint6b.png",
"pulse": {
"minScale": 0.92,
"maxScale": 1.08,
"periodMs": 1500
},
"fadeIn": { "durationMs": 600 }
}
]
},{
"type": "Button",
"name": "phoneButton",
"width": 229,
"height": 229,
"x": 74,
"y" :-46,
"clickZoneWidth": 104,
"clickZoneHeight": 104,
"horizontal_gravity": "right",
"vertical_gravity": "top",
"textures": {
"normal": "resources/w/ui/img/Phone001_State=Default.png",
"hover": "resources/w/ui/img/Phone001_State=Selected.png",
"pressed": "resources/w/ui/img/Phone001_State=Tap.png"
}
},
{
"type": "Button",
"name": "journalButton",
"width": 163,
"height": 163,
"x": -13,
"y" :-13,
"clickZoneWidth": 89,
"clickZoneHeight": 89,
"horizontal_gravity": "right",
"vertical_gravity": "top",
"textures": {
"normal": "resources/w/ui/img/Journal001_State=Default.png",
"hover": "resources/w/ui/img/Journal001_State=Selected.png",
"pressed": "resources/w/ui/img/Journal001_State=Tap.png"
}
}]
}
}

View File

@ -0,0 +1,54 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 0,
"width": "match_parent",
"height": 600,
"y" : -250,
"children": [
{
"type": "StaticImage",
"name": "hint6b",
"width": 440,
"height": 134,
"horizontal_gravity": "center",
"texture": "resources/w/ui/img/Hint6b.png",
"pulse": {
"minScale": 0.92,
"maxScale": 1.08,
"periodMs": 1500
},
"fadeIn": { "durationMs": 600 }
}
]
},
{
"type": "Button",
"name": "journalButton",
"horizontal_gravity": "right",
"vertical_gravity": "top",
"x": -13,
"y" :-13,
"width": 163,
"height": 163,
"clickZoneWidth": 89,
"clickZoneHeight": 89,
"horizontal_gravity": "right",
"vertical_gravity": "top",
"textures": {
"normal": "resources/w/ui/img/Journal001_State=Default.png",
"hover": "resources/w/ui/img/Journal001_State=Selected.png",
"pressed": "resources/w/ui/img/Journal001_State=Tap.png"
}
}]
}
}

View File

@ -0,0 +1,31 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 0,
"width": "match_parent",
"height": 600,
"children": [
{
"type": "Button",
"name": "journalButton",
"width": 229,
"height": 229,
"textures": {
"normal": "resources/w/ui/img/Phone001_State=Default.png",
"hover": "resources/w/ui/img/Phone001_State=Selected.png",
"pressed": "resources/w/ui/img/Phone001_State=Tap.png"
}
}
]
}]
}
}

BIN
resources/w/ui/img/Hint1.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/ui/img/Hint2.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/ui/img/Hint3.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Hint4.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Hint5.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Hint6a.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Hint6b.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Journal001_State=Default.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Journal001_State=Selected.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Journal001_State=Tap.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Phone001_State=Default.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Phone001_State=Selected.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/Phone001_State=Tap.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/PhoneTest001.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/w/ui/img/QuestJournal001.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,25 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"vertical_align": "center",
"horizontal_align": "center",
"children": [
{
"type": "Button",
"name": "journalExitButton",
"horizontal_gravity": "center",
"vertical_gravity": "center",
"width": 1125,
"height": 520,
"textures": {
"normal": "resources/w/ui/img/QuestJournal001.png",
"hover": "resources/w/ui/img/QuestJournal001.png",
"pressed": "resources/w/ui/img/QuestJournal001.png"
}
}
]
}
}

View File

@ -0,0 +1,25 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"vertical_align": "center",
"horizontal_align": "center",
"children": [
{
"type": "Button",
"name": "phoneExitButton",
"horizontal_gravity": "center",
"vertical_gravity": "center",
"width": 1232,
"height": 627,
"textures": {
"normal": "resources/w/ui/img/PhoneTest001.png",
"hover": "resources/w/ui/img/PhoneTest001.png",
"pressed": "resources/w/ui/img/PhoneTest001.png"
}
}
]
}
}

View File

@ -15,7 +15,7 @@ namespace ZL {
using std::max; using std::max;
#endif #endif
constexpr float DEFAULT_ZOOM = 24.f; constexpr float DEFAULT_ZOOM = 6.f;
class Environment { class Environment {
public: public:

View File

@ -369,6 +369,24 @@ namespace ZL
locations["uni_interior"]->onTeleport = teleportCallback; locations["uni_interior"]->onTeleport = teleportCallback;
locations["location_dorm"]->onTeleport = teleportCallback; locations["location_dorm"]->onTeleport = teleportCallback;
// Wire tutorial advance callbacks for all locations.
// advanceTutorialStep() guards against double-advancing, so sharing is safe.
for (auto& [name, loc] : locations) {
loc->dialogueSystem.setOnDialogueAdvanced([this]() {
menuManager.advanceTutorialStep();
});
loc->onPlayerFloorWalk = [this]() {
if (menuManager.tutorialStep == TutorialStep::Step2) {
menuManager.advanceTutorialStep();
}
};
}
// Wire inventory item-pickup callback for tutorial step4/5 item tracking.
inventory.onItemAdded = [this](const std::string& itemId) {
menuManager.onItemPickedUp(itemId);
};
currentLocation = locations["location_dorm"]; currentLocation = locations["location_dorm"];
currentLocation->scriptEngine.callLocationEnterCallback(); currentLocation->scriptEngine.callLocationEnterCallback();
@ -552,6 +570,8 @@ namespace ZL
updateDarklandsFlash(delta); updateDarklandsFlash(delta);
menuManager.uiManager.update(static_cast<float>(delta));
if (currentLocation) if (currentLocation)
{ {
currentLocation->update(delta); currentLocation->update(delta);
@ -661,6 +681,10 @@ namespace ZL
if (Environment::zoom < zoomstep) { if (Environment::zoom < zoomstep) {
Environment::zoom = zoomstep; Environment::zoom = zoomstep;
} }
// Tutorial step3 → step4: any mouse-wheel scroll counts as "zoom gesture".
if (menuManager.tutorialStep == TutorialStep::Step3) {
menuManager.advanceTutorialStep();
}
} }
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) { if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
@ -722,8 +746,10 @@ namespace ZL
break; break;
case SDLK_l: case SDLK_l:
x = x - 1; //x = x - 1;
std::cout << "current x: " << x << std::endl; //std::cout << "current x: " << x << std::endl;
std::cout << "Azimuth: " << currentLocation->cameraAzimuth << std::endl;
std::cout << "Inclination: " << currentLocation->cameraInclination << std::endl;
break; break;
case SDLK_c: case SDLK_c:
@ -826,6 +852,10 @@ namespace ZL
// movement threshold was crossed. // movement threshold was crossed.
currentLocation->lastMouseX = eventX; currentLocation->lastMouseX = eventX;
currentLocation->lastMouseY = eventY; currentLocation->lastMouseY = eventY;
// Snapshot current angles so we can measure how far the user rotates.
dragStartAzimuth = currentLocation->cameraAzimuth;
dragStartInclination = currentLocation->cameraInclination;
} }
} }
@ -881,6 +911,13 @@ namespace ZL
static const float zoomMin = 2.0f; static const float zoomMin = 2.0f;
if (newZoom < zoomMin) newZoom = zoomMin; if (newZoom < zoomMin) newZoom = zoomMin;
Environment::zoom = newZoom; Environment::zoom = newZoom;
// Tutorial step3 → step4: detect a significant pinch-zoom (≥ 2 zoom units).
if (menuManager.tutorialStep == TutorialStep::Step3) {
if (std::abs(Environment::zoom - pinchStartZoom) >= 2.0f) {
menuManager.advanceTutorialStep();
}
}
} }
void Game::endPinch() void Game::endPinch()
@ -1004,6 +1041,19 @@ namespace ZL
if (currentLocation) { if (currentLocation) {
// Forwarded for dialogue hover and (when cameraDragging) camera rotation. // Forwarded for dialogue hover and (when cameraDragging) camera rotation.
currentLocation->handleMotion(fingerId, eventX, eventY, mx, my); currentLocation->handleMotion(fingerId, eventX, eventY, mx, my);
// Tutorial step1 → step2: detect a significant camera rotation on BOTH axes.
// ~0.15 rad (≈8.6°) per axis ensures the user intentionally panned in 2D,
// not just nudged a single axis by accident.
static constexpr float TUTORIAL_ROTATION_THRESHOLD = 0.15f;
if (cameraDragging
&& menuManager.tutorialStep == TutorialStep::Step1) {
float deltaAz = std::abs(currentLocation->cameraAzimuth - dragStartAzimuth);
float deltaInc = std::abs(currentLocation->cameraInclination - dragStartInclination);
if (deltaAz >= TUTORIAL_ROTATION_THRESHOLD && deltaInc >= TUTORIAL_ROTATION_THRESHOLD) {
menuManager.advanceTutorialStep();
}
}
} }
} }

View File

@ -87,6 +87,11 @@ namespace ZL {
float pinchStartDistance = 0.0f; float pinchStartDistance = 0.0f;
float pinchStartZoom = 0.0f; float pinchStartZoom = 0.0f;
// Tutorial: azimuth and inclination captured at the start of each camera drag,
// used to measure how far the user has rotated before advancing the tutorial step.
float dragStartAzimuth = 0.0f;
float dragStartInclination = 0.0f;
std::unique_ptr<AudioPlayerAsync> audioPlayer; std::unique_ptr<AudioPlayerAsync> audioPlayer;
int64_t getSyncTimeMs(); int64_t getSyncTimeMs();

View File

@ -1519,6 +1519,7 @@ namespace ZL
targetInteractNpc = nullptr; targetInteractNpc = nullptr;
targetInteractNpcIndex = -1; targetInteractNpcIndex = -1;
targetTeleportZone = nullptr; targetTeleportZone = nullptr;
if (onPlayerFloorWalk) onPlayerFloorWalk();
} }
} }
else { else {

View File

@ -51,12 +51,11 @@ namespace ZL
std::vector<InteractiveObject> interactiveObjects; std::vector<InteractiveObject> interactiveObjects;
std::unique_ptr<Character> player; std::unique_ptr<Character> player;
std::vector<std::unique_ptr<Character>> npcs; std::vector<std::unique_ptr<Character>> npcs;
float cameraAzimuth = 0.0f; float cameraAzimuth = -2.35;
float cameraInclination = M_PI * 30.f / 180.f; float cameraInclination = 1.1036;//M_PI * 30.f / 180.f;
std::vector<PathFinder> navigationMaps; std::vector<PathFinder> navigationMaps;
PathFinder* navigation = nullptr; PathFinder* navigation = nullptr;
@ -87,6 +86,10 @@ namespace ZL
// Read by draw functions and raycast — do not write from Location code. // Read by draw functions and raycast — do not write from Location code.
bool isDarklands = false; bool isDarklands = false;
// Called when the player successfully taps the ground and a floor walk target is set.
// Used by the tutorial system to detect the "tap to walk" gesture.
std::function<void()> onPlayerFloorWalk;
// Set by Game after setup(). Lua and onDeathAnimComplete call this to // Set by Game after setup(). Lua and onDeathAnimComplete call this to
// request the transition without coupling Location to Game directly. // request the transition without coupling Location to Game directly.
std::function<bool()> requestDarklandsTransition; std::function<bool()> requestDarklandsTransition;

View File

@ -34,7 +34,16 @@ namespace ZL {
inventory = &inv; inventory = &inv;
//hudRoot = loadUiFromFile("resources/config2/hud.json", renderer, zipFile); //hudRoot = loadUiFromFile("resources/config2/hud.json", renderer, zipFile);
hudRoot = loadUiFromFile("resources/w/ui/hud_step0.json", renderer, zipFile); hudRoot = loadUiFromFile("resources/w/ui/hud_step0.json", renderer, zipFile);
hudStep1Root = loadUiFromFile("resources/w/ui/hud_step1.json", renderer, zipFile);
hudStep2Root = loadUiFromFile("resources/w/ui/hud_step2.json", renderer, zipFile);
hudStep3Root = loadUiFromFile("resources/w/ui/hud_step3.json", renderer, zipFile);
hudStep4Root = loadUiFromFile("resources/w/ui/hud_step4.json", renderer, zipFile);
hudStep5aRoot = loadUiFromFile("resources/w/ui/hud_step5a.json", renderer, zipFile);
hudStep5bRoot = loadUiFromFile("resources/w/ui/hud_step5b.json", renderer, zipFile);
hudStep5abRoot = loadUiFromFile("resources/w/ui/hud_step5ab.json", renderer, zipFile);
phoneScreenRoot = loadUiFromFile("resources/w/ui/screen_phone.json", renderer, zipFile);
journalScreenRoot= loadUiFromFile("resources/w/ui/screen_journal.json", renderer, zipFile);
inventoryRoot = loadUiFromFile("resources/config2/ui_inventory.json", renderer, zipFile); inventoryRoot = loadUiFromFile("resources/config2/ui_inventory.json", renderer, zipFile);
questJournalRoot = loadUiFromFile("resources/config2/ui_quest_journal.json", renderer, zipFile); questJournalRoot = loadUiFromFile("resources/config2/ui_quest_journal.json", renderer, zipFile);
@ -124,6 +133,119 @@ namespace ZL {
} }
} }
void MenuManager::openPhoneScreen() {
state = GameState::PhoneScreen;
tutorialPhoneScreenOpened = true;
// Hide the phone hint on the current HUD so it stays hidden when we return.
uiManager.setNodeVisible("hint6a", false);
uiManager.pushMenuFromSavedRoot(phoneScreenRoot);
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
closePhoneScreen();
});
}
void MenuManager::closePhoneScreen() {
state = GameState::Gameplay;
uiManager.popMenu();
}
void MenuManager::openJournalScreen() {
state = GameState::JournalScreen;
tutorialJournalScreenOpened = true;
// Hide the journal hint on the current HUD so it stays hidden when we return.
uiManager.setNodeVisible("hint6b", false);
uiManager.pushMenuFromSavedRoot(journalScreenRoot);
uiManager.setButtonCallback("journalExitButton", [this](const std::string&) {
closeJournalScreen();
});
}
void MenuManager::closeJournalScreen() {
state = GameState::Gameplay;
uiManager.popMenu();
}
// Registers phoneButton / journalButton callbacks on the current HUD root
// and hides any hints that have already been completed.
// Called after every replaceRoot during step 5.
void MenuManager::setupStep5Callbacks() {
if (uiManager.findButton("phoneButton")) {
uiManager.setButtonCallback("phoneButton", [this](const std::string&) {
openPhoneScreen();
});
}
if (uiManager.findButton("journalButton")) {
uiManager.setButtonCallback("journalButton", [this](const std::string&) {
openJournalScreen();
});
}
if (tutorialPhoneScreenOpened) uiManager.setNodeVisible("hint6a", false);
if (tutorialJournalScreenOpened) uiManager.setNodeVisible("hint6b", false);
}
void MenuManager::advanceTutorialStep() {
std::shared_ptr<UiNode> nextRoot;
switch (tutorialStep) {
case TutorialStep::Step0:
tutorialStep = TutorialStep::Step1;
nextRoot = hudStep1Root;
break;
case TutorialStep::Step1:
tutorialStep = TutorialStep::Step2;
nextRoot = hudStep2Root;
break;
case TutorialStep::Step2:
tutorialStep = TutorialStep::Step3;
nextRoot = hudStep3Root;
break;
case TutorialStep::Step3:
tutorialStep = TutorialStep::Step4;
nextRoot = hudStep4Root;
break;
default:
return; // Step4/Step5 transitions are driven by onItemPickedUp
}
if (state == GameState::Gameplay && nextRoot) {
uiManager.replaceRoot(nextRoot);
}
}
void MenuManager::onItemPickedUp(const std::string& itemId) {
if (tutorialStep != TutorialStep::Step4 && tutorialStep != TutorialStep::Step5) {
return;
}
if (itemId == "phone") tutorialPhonePickedUp = true;
if (itemId == "journal") tutorialJournalPickedUp = true;
if (tutorialStep == TutorialStep::Step4) {
tutorialStep = TutorialStep::Step5;
}
refreshItemPickupHud();
}
void MenuManager::refreshItemPickupHud() {
if (state != GameState::Gameplay) return;
std::shared_ptr<UiNode> nextRoot;
if (tutorialPhonePickedUp && tutorialJournalPickedUp) {
nextRoot = hudStep5abRoot;
} else if (tutorialPhonePickedUp) {
nextRoot = hudStep5aRoot;
} else if (tutorialJournalPickedUp) {
nextRoot = hudStep5bRoot;
}
if (nextRoot) {
uiManager.replaceRoot(nextRoot);
// Register item-screen buttons and re-apply any already-completed hint visibility.
setupStep5Callbacks();
}
}
void MenuManager::refreshQuestJournalUi() { void MenuManager::refreshQuestJournalUi() {
visibleQuestIds.clear(); visibleQuestIds.clear();
auto quests = questJournal.getVisibleQuests(); auto quests = questJournal.getVisibleQuests();

View File

@ -16,7 +16,18 @@ namespace ZL {
enum class GameState { enum class GameState {
Gameplay, Gameplay,
Inventory, Inventory,
QuestJournal QuestJournal,
PhoneScreen,
JournalScreen
};
enum class TutorialStep {
Step0, // Dialogue hint: "click to advance"
Step1, // Camera rotation hint
Step2, // Floor tap / walk hint
Step3, // Pinch-zoom hint
Step4, // Pick-up item hint
Step5, // Post-pickup reaction (sub-state: which items were collected)
}; };
class MenuManager { class MenuManager {
@ -38,6 +49,16 @@ namespace ZL {
bool isInventoryOpen() const { return state == GameState::Inventory; } bool isInventoryOpen() const { return state == GameState::Inventory; }
bool isQuestJournalOpen() const { return state == GameState::QuestJournal; } bool isQuestJournalOpen() const { return state == GameState::QuestJournal; }
void openPhoneScreen();
void closePhoneScreen();
void openJournalScreen();
void closeJournalScreen();
void advanceTutorialStep();
void onItemPickedUp(const std::string& itemId);
TutorialStep tutorialStep = TutorialStep::Step0;
protected: protected:
Renderer& renderer; Renderer& renderer;
@ -45,11 +66,27 @@ namespace ZL {
void enterGameplay(); void enterGameplay();
void refreshQuestJournalUi(); void refreshQuestJournalUi();
void selectQuestByIndex(int index); void selectQuestByIndex(int index);
void refreshItemPickupHud();
void setupStep5Callbacks();
GameState state = GameState::Gameplay; GameState state = GameState::Gameplay;
Inventory* inventory = nullptr; Inventory* inventory = nullptr;
bool tutorialPhonePickedUp = false;
bool tutorialJournalPickedUp = false;
bool tutorialPhoneScreenOpened = false;
bool tutorialJournalScreenOpened = false;
std::shared_ptr<UiNode> hudRoot; std::shared_ptr<UiNode> hudRoot;
std::shared_ptr<UiNode> hudStep1Root;
std::shared_ptr<UiNode> hudStep2Root;
std::shared_ptr<UiNode> hudStep3Root;
std::shared_ptr<UiNode> hudStep4Root;
std::shared_ptr<UiNode> hudStep5aRoot;
std::shared_ptr<UiNode> hudStep5bRoot;
std::shared_ptr<UiNode> hudStep5abRoot;
std::shared_ptr<UiNode> phoneScreenRoot;
std::shared_ptr<UiNode> journalScreenRoot;
std::shared_ptr<UiNode> inventoryRoot; std::shared_ptr<UiNode> inventoryRoot;
std::shared_ptr<UiNode> questJournalRoot; std::shared_ptr<UiNode> questJournalRoot;

View File

@ -373,9 +373,17 @@ namespace ZL {
void UiStaticImage::draw(Renderer& renderer) const { void UiStaticImage::draw(Renderer& renderer) const {
if (!texture) return; if (!texture) return;
const float alpha = (fadeInEnabled && fadeInDurationMs > 0.0f)
? std::clamp(fadeInElapsedMs / fadeInDurationMs, 0.0f, 1.0f)
: 1.0f;
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1f("uAlpha", alpha);
glBindTexture(GL_TEXTURE_2D, texture->getTexID()); glBindTexture(GL_TEXTURE_2D, texture->getTexID());
renderer.DrawVertexRenderStruct(mesh); renderer.DrawVertexRenderStruct(mesh);
if (alpha < 1.0f) {
renderer.RenderUniform1f("uAlpha", 1.0f); // restore for subsequent draws
}
} }
void UiTextField::draw(Renderer& renderer) const { void UiTextField::draw(Renderer& renderer) const {
@ -504,6 +512,8 @@ namespace ZL {
btn->texDisabled = loadTex("disabled"); btn->texDisabled = loadTex("disabled");
btn->border = j.value("border", 0.0f); btn->border = j.value("border", 0.0f);
if (j.contains("clickZoneWidth")) btn->clickZoneWidth = j["clickZoneWidth"].get<float>();
if (j.contains("clickZoneHeight")) btn->clickZoneHeight = j["clickZoneHeight"].get<float>();
node->button = btn; node->button = btn;
} }
@ -616,6 +626,8 @@ namespace ZL {
if (j.contains("color") && j["color"].is_array() && j["color"].size() == 4) { if (j.contains("color") && j["color"].is_array() && j["color"].size() == 4) {
for (int i = 0; i < 4; ++i) tb->color[i] = j["color"][i].get<float>(); for (int i = 0; i < 4; ++i) tb->color[i] = j["color"][i].get<float>();
} }
if (j.contains("clickZoneWidth")) tb->clickZoneWidth = j["clickZoneWidth"].get<float>();
if (j.contains("clickZoneHeight")) tb->clickZoneHeight = j["clickZoneHeight"].get<float>();
tb->textRenderer = std::make_unique<TextRenderer>(); tb->textRenderer = std::make_unique<TextRenderer>();
if (!tb->textRenderer->init(renderer, tb->fontPath, tb->fontSize, zipFile)) { if (!tb->textRenderer->init(renderer, tb->fontPath, tb->fontSize, zipFile)) {
@ -679,6 +691,23 @@ namespace ZL {
} }
node->staticImage = img; node->staticImage = img;
// Optional fade-in on display
if (j.contains("fadeIn") && j["fadeIn"].is_object()) {
const auto& fi = j["fadeIn"];
img->fadeInEnabled = true;
img->fadeInDurationMs = fi.value("durationMs", 1000.0f);
img->fadeInElapsedMs = 0.0f;
}
// Optional pulse-scale animation
if (j.contains("pulse") && j["pulse"].is_object()) {
const auto& p = j["pulse"];
node->pulseEnabled = true;
node->pulseMinScale = p.value("minScale", 0.9f);
node->pulseMaxScale = p.value("maxScale", 1.1f);
node->pulsePeriodMs = p.value("periodMs", 1000.0f);
}
} }
if (typeStr == "TextView") { if (typeStr == "TextView") {
@ -777,6 +806,7 @@ namespace ZL {
textViews.clear(); textViews.clear();
textFields.clear(); textFields.clear();
staticImages.clear(); staticImages.clear();
pulsingNodes.clear();
collectButtonsAndSliders(root); collectButtonsAndSliders(root);
nodeActiveAnims.clear(); nodeActiveAnims.clear();
@ -1020,6 +1050,12 @@ namespace ZL {
} }
if (node->staticImage) { if (node->staticImage) {
staticImages.push_back(node->staticImage); staticImages.push_back(node->staticImage);
if (node->staticImage->fadeInEnabled) {
node->staticImage->fadeInElapsedMs = 0.0f; // restart fade every time UI is shown
}
}
if (node->pulseEnabled && node->staticImage) {
pulsingNodes.push_back(node);
} }
for (auto& c : node->children) collectButtonsAndSliders(c); for (auto& c : node->children) collectButtonsAndSliders(c);
} }
@ -1128,6 +1164,7 @@ namespace ZL {
prev.textViews = textViews; prev.textViews = textViews;
prev.textFields = textFields; prev.textFields = textFields;
prev.staticImages = staticImages; prev.staticImages = staticImages;
prev.pulsingNodes = pulsingNodes;
prev.pressedButtons = pressedButtons; prev.pressedButtons = pressedButtons;
prev.pressedTextButtons = pressedTextButtons; prev.pressedTextButtons = pressedTextButtons;
prev.pressedSliders = pressedSliders; prev.pressedSliders = pressedSliders;
@ -1190,6 +1227,7 @@ namespace ZL {
textViews = s.textViews; textViews = s.textViews;
textFields = s.textFields; textFields = s.textFields;
staticImages = s.staticImages; staticImages = s.staticImages;
pulsingNodes = s.pulsingNodes;
pressedButtons = s.pressedButtons; pressedButtons = s.pressedButtons;
pressedTextButtons = s.pressedTextButtons; pressedTextButtons = s.pressedTextButtons;
pressedSliders = s.pressedSliders; pressedSliders = s.pressedSliders;
@ -1233,6 +1271,7 @@ namespace ZL {
renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, -1, 1); renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, -1, 1);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.RenderUniform1f("uAlpha", 1.0f); // ensure alpha is fully opaque unless a widget overrides it
std::function<void(const std::shared_ptr<UiNode>&)> drawNode = std::function<void(const std::shared_ptr<UiNode>&)> drawNode =
[&](const std::shared_ptr<UiNode>& node) { [&](const std::shared_ptr<UiNode>& node) {
@ -1301,6 +1340,29 @@ namespace ZL {
void UiManager::update(float deltaMs) { void UiManager::update(float deltaMs) {
if (!root) return; if (!root) return;
// Fade-in animations (StaticImage elements with fadeInEnabled == true)
for (auto& img : staticImages) {
if (!img || !img->fadeInEnabled) continue;
if (img->fadeInElapsedMs < img->fadeInDurationMs) {
img->fadeInElapsedMs += deltaMs;
if (img->fadeInElapsedMs > img->fadeInDurationMs)
img->fadeInElapsedMs = img->fadeInDurationMs;
}
}
// Pulse-scale animations (StaticImage nodes with pulseEnabled == true)
for (auto& node : pulsingNodes) {
if (!node) continue;
node->pulseElapsedMs += deltaMs;
if (node->pulseElapsedMs >= node->pulsePeriodMs)
node->pulseElapsedMs = std::fmod(node->pulseElapsedMs, node->pulsePeriodMs);
const float phase = node->pulseElapsedMs / node->pulsePeriodMs;
const float t = (std::sin(phase * 2.0f * static_cast<float>(M_PI)) + 1.0f) * 0.5f;
const float s = node->pulseMinScale + (node->pulseMaxScale - node->pulseMinScale) * t;
node->scaleX = s;
node->scaleY = s;
}
std::vector<std::pair<std::shared_ptr<UiNode>, size_t>> animationsToRemove; std::vector<std::pair<std::shared_ptr<UiNode>, size_t>> animationsToRemove;
std::vector<std::function<void()>> pendingCallbacks; std::vector<std::function<void()>> pendingCallbacks;
@ -1490,7 +1552,7 @@ namespace ZL {
for (auto& b : buttons) { for (auto& b : buttons) {
if (b->state != ButtonState::Disabled) if (b->state != ButtonState::Disabled)
{ {
if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) { if (b->getClickZoneRect().containsConsideringBorder((float)x, (float)y, b->border)) {
if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover; if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover;
} }
else { else {
@ -1501,7 +1563,7 @@ namespace ZL {
for (auto& tb : textButtons) { for (auto& tb : textButtons) {
if (tb->state != ButtonState::Disabled) if (tb->state != ButtonState::Disabled)
{ {
if (tb->rect.containsConsideringBorder((float)x, (float)y, tb->border)) { if (tb->getClickZoneRect().containsConsideringBorder((float)x, (float)y, tb->border)) {
if (tb->state != ButtonState::Pressed) tb->state = ButtonState::Hover; if (tb->state != ButtonState::Pressed) tb->state = ButtonState::Hover;
} }
else { else {
@ -1534,7 +1596,7 @@ namespace ZL {
for (auto& b : buttons) { for (auto& b : buttons) {
if (b->state != ButtonState::Disabled) if (b->state != ButtonState::Disabled)
{ {
if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) { if (b->getClickZoneRect().containsConsideringBorder((float)x, (float)y, b->border)) {
b->state = ButtonState::Pressed; b->state = ButtonState::Pressed;
pressedButtons[fingerId] = b; pressedButtons[fingerId] = b;
if (b->onPress) b->onPress(b->name); if (b->onPress) b->onPress(b->name);
@ -1546,7 +1608,7 @@ namespace ZL {
for (auto& tb : textButtons) { for (auto& tb : textButtons) {
if (tb->state != ButtonState::Disabled) if (tb->state != ButtonState::Disabled)
{ {
if (tb->rect.containsConsideringBorder((float)x, (float)y, tb->border)) { if (tb->getClickZoneRect().containsConsideringBorder((float)x, (float)y, tb->border)) {
tb->state = ButtonState::Pressed; tb->state = ButtonState::Pressed;
pressedTextButtons[fingerId] = tb; pressedTextButtons[fingerId] = tb;
if (tb->onPress) tb->onPress(tb->name); if (tb->onPress) tb->onPress(tb->name);
@ -1593,7 +1655,7 @@ namespace ZL {
if (btnIt != pressedButtons.end()) { if (btnIt != pressedButtons.end()) {
auto b = btnIt->second; auto b = btnIt->second;
if (b) { if (b) {
bool contains = b->rect.contains((float)x, (float)y); bool contains = b->getClickZoneRect().contains((float)x, (float)y);
if (b->state == ButtonState::Pressed) { if (b->state == ButtonState::Pressed) {
if (contains) { if (contains) {
clicked.push_back(b); clicked.push_back(b);
@ -1609,7 +1671,7 @@ namespace ZL {
if (tbIt != pressedTextButtons.end()) { if (tbIt != pressedTextButtons.end()) {
auto tb = tbIt->second; auto tb = tbIt->second;
if (tb) { if (tb) {
bool contains = tb->rect.contains((float)x, (float)y); bool contains = tb->getClickZoneRect().contains((float)x, (float)y);
if (tb->state == ButtonState::Pressed) { if (tb->state == ButtonState::Pressed) {
if (contains) { if (contains) {
clickedText.push_back(tb); clickedText.push_back(tb);

View File

@ -93,6 +93,17 @@ namespace ZL {
VertexRenderStruct mesh; VertexRenderStruct mesh;
// Optional explicit hit-test zone, centred on rect.
// 0 means "use rect.w / rect.h" (default behaviour).
float clickZoneWidth = 0.0f;
float clickZoneHeight = 0.0f;
UiRect getClickZoneRect() const {
float cw = (clickZoneWidth > 0.0f) ? clickZoneWidth : rect.w;
float ch = (clickZoneHeight > 0.0f) ? clickZoneHeight : rect.h;
return { rect.x + (rect.w - cw) * 0.5f, rect.y + (rect.h - ch) * 0.5f, cw, ch };
}
std::function<void(const std::string&)> onClick; std::function<void(const std::string&)> onClick;
std::function<void(const std::string&)> onPress; // fires on touch/mouse down std::function<void(const std::string&)> onPress; // fires on touch/mouse down
@ -139,6 +150,17 @@ namespace ZL {
ButtonState state = ButtonState::Normal; ButtonState state = ButtonState::Normal;
VertexRenderStruct mesh; VertexRenderStruct mesh;
// Optional explicit hit-test zone, centred on rect.
// 0 means "use rect.w / rect.h" (default behaviour).
float clickZoneWidth = 0.0f;
float clickZoneHeight = 0.0f;
UiRect getClickZoneRect() const {
float cw = (clickZoneWidth > 0.0f) ? clickZoneWidth : rect.w;
float ch = (clickZoneHeight > 0.0f) ? clickZoneHeight : rect.h;
return { rect.x + (rect.w - cw) * 0.5f, rect.y + (rect.h - ch) * 0.5f, cw, ch };
}
// Text drawn on top of the button // Text drawn on top of the button
std::string text; std::string text;
std::string fontPath = "resources/fonts/DroidSans.ttf"; std::string fontPath = "resources/fonts/DroidSans.ttf";
@ -209,6 +231,11 @@ namespace ZL {
VertexRenderStruct mesh; VertexRenderStruct mesh;
// Fade-in on first display (triggers each time the containing UI is shown)
bool fadeInEnabled = false;
float fadeInDurationMs = 1000.0f;
float fadeInElapsedMs = 0.0f; // runtime, reset by collectButtonsAndSliders
void buildMesh(); void buildMesh();
void draw(Renderer& renderer) const; void draw(Renderer& renderer) const;
}; };
@ -236,6 +263,13 @@ namespace ZL {
bool visible = true; bool visible = true;
// Pulse-scale animation (StaticImage only; auto-starts when pulseEnabled == true)
bool pulseEnabled = false;
float pulseMinScale = 1.0f;
float pulseMaxScale = 1.1f;
float pulsePeriodMs = 1000.0f;
float pulseElapsedMs = 0.0f; // runtime, not persisted in JSON
// Иерархия // Иерархия
std::vector<std::shared_ptr<UiNode>> children; std::vector<std::shared_ptr<UiNode>> children;
@ -322,6 +356,13 @@ namespace ZL {
tb->animScaleY = 1.0f; tb->animScaleY = 1.0f;
} }
} }
for (auto& n : pulsingNodes) {
if (n) {
n->scaleX = 1.0f;
n->scaleY = 1.0f;
n->pulseElapsedMs = 0.0f;
}
}
} }
std::shared_ptr<UiButton> findButton(const std::string& name); std::shared_ptr<UiButton> findButton(const std::string& name);
@ -404,6 +445,7 @@ namespace ZL {
std::vector<std::shared_ptr<UiTextView>> textViews; std::vector<std::shared_ptr<UiTextView>> textViews;
std::vector<std::shared_ptr<UiTextField>> textFields; std::vector<std::shared_ptr<UiTextField>> textFields;
std::vector<std::shared_ptr<UiStaticImage>> staticImages; std::vector<std::shared_ptr<UiStaticImage>> staticImages;
std::vector<std::shared_ptr<UiNode>> pulsingNodes;
std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims; std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims;
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName) std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName)
@ -422,6 +464,7 @@ namespace ZL {
std::vector<std::shared_ptr<UiTextView>> textViews; std::vector<std::shared_ptr<UiTextView>> textViews;
std::vector<std::shared_ptr<UiTextField>> textFields; std::vector<std::shared_ptr<UiTextField>> textFields;
std::vector<std::shared_ptr<UiStaticImage>> staticImages; std::vector<std::shared_ptr<UiStaticImage>> staticImages;
std::vector<std::shared_ptr<UiNode>> pulsingNodes;
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons; std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
std::map<int64_t, std::shared_ptr<UiTextButton>> pressedTextButtons; std::map<int64_t, std::shared_ptr<UiTextButton>> pressedTextButtons;
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders; std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;

View File

@ -104,6 +104,7 @@ bool DialogueSystem::handlePointerReleased(float x, float y) {
if (advanceDialogue) { if (advanceDialogue) {
runtime.confirmAdvance(); runtime.confirmAdvance();
if (onDialogueAdvancedCallback) onDialogueAdvancedCallback();
return true; return true;
} }
@ -143,4 +144,8 @@ void DialogueSystem::stopDialogue() {
runtime.stop(); runtime.stop();
} }
void DialogueSystem::setOnDialogueAdvanced(std::function<void()> cb) {
onDialogueAdvancedCallback = std::move(cb);
}
} // namespace ZL::Dialogue } // namespace ZL::Dialogue

View File

@ -27,6 +27,7 @@ public:
void setOnDialogueLineStarted(std::function<void(const std::string&)> cb); void setOnDialogueLineStarted(std::function<void(const std::string&)> cb);
void setOnCutsceneLineStarted(std::function<void(const std::string&)> cb); void setOnCutsceneLineStarted(std::function<void(const std::string&)> cb);
void setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb); void setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb);
void setOnDialogueAdvanced(std::function<void()> cb);
void stopDialogue(); void stopDialogue();
bool isActive() const { return runtime.isActive(); } bool isActive() const { return runtime.isActive(); }
@ -39,6 +40,7 @@ private:
DialogueDatabase database; DialogueDatabase database;
DialogueRuntime runtime; DialogueRuntime runtime;
DialogueOverlay overlay; DialogueOverlay overlay;
std::function<void()> onDialogueAdvancedCallback;
}; };
} // namespace ZL::Dialogue } // namespace ZL::Dialogue

View File

@ -7,6 +7,7 @@ namespace ZL {
void Inventory::addItem(const Item& item) { void Inventory::addItem(const Item& item) {
items.push_back(item); items.push_back(item);
std::cout << "Item added to inventory: " << item.name << std::endl; std::cout << "Item added to inventory: " << item.name << std::endl;
if (onItemAdded) onItemAdded(item.id);
} }
void Inventory::removeItem(const std::string& itemId) { void Inventory::removeItem(const std::string& itemId) {

View File

@ -2,6 +2,7 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <functional>
namespace ZL { namespace ZL {
@ -28,6 +29,9 @@ namespace ZL {
bool hasItem(const std::string& itemId) const; bool hasItem(const std::string& itemId) const;
void clear() { items.clear(); } void clear() { items.clear(); }
size_t getCount() const { return items.size(); } size_t getCount() const { return items.size(); }
// Called whenever an item is added; receives the item id.
std::function<void(const std::string&)> onItemAdded;
}; };
} // namespace ZL } // namespace ZL