Working on UI improvement
This commit is contained in:
1 parent
0da1ae124c
commit
80af0e61ae
43 files changed
+889
-64
No files matched your search
@@ -1,12 +1,13 @@
|
||||
//precisionmediump float;
|
||||
uniform sampler2D Texture;
|
||||
uniform float uAlpha;
|
||||
varying vec2 texCoord;
|
||||
|
||||
void main()
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(Texture,texCoord).rgba;
|
||||
//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);
|
||||
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
precision mediump float;
|
||||
uniform sampler2D Texture;
|
||||
uniform float uAlpha;
|
||||
varying vec2 texCoord;
|
||||
|
||||
void main()
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(Texture,texCoord).rgba;
|
||||
//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);
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"spacing": 0,
|
||||
"width": "match_parent",
|
||||
"height": 600,
|
||||
"x" : 300,
|
||||
"children": [
|
||||
{
|
||||
"type": "StaticImage",
|
||||
@@ -20,7 +21,13 @@
|
||||
"width": 440,
|
||||
"height": 134,
|
||||
"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",
|
||||
|
||||
@@ -5,42 +5,31 @@
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"children": [
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "inventory_button",
|
||||
"x": 50.0,
|
||||
"y": 50.0,
|
||||
"width": 150.0,
|
||||
"height": 60.0,
|
||||
"text": "Inventory",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "quest_journal_button",
|
||||
"x": 220.0,
|
||||
"y": 50.0,
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
{
|
||||
"type": "LinearLayout",
|
||||
"orientation": "vertical",
|
||||
"vertical_align": "center",
|
||||
"horizontal_align": "center",
|
||||
"spacing": 0,
|
||||
"width": "match_parent",
|
||||
"height": 600,
|
||||
"y" : 300,
|
||||
"children": [
|
||||
{
|
||||
"type": "StaticImage",
|
||||
"name": "hint2",
|
||||
"width": 440,
|
||||
"height": 134,
|
||||
"horizontal_gravity": "center",
|
||||
"texture": "resources/w/ui/img/Hint2.png",
|
||||
"pulse": {
|
||||
"minScale": 0.92,
|
||||
"maxScale": 1.08,
|
||||
"periodMs": 1500
|
||||
},
|
||||
"fadeIn": { "durationMs": 600 }
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ namespace ZL {
|
||||
using std::max;
|
||||
#endif
|
||||
|
||||
constexpr float DEFAULT_ZOOM = 24.f;
|
||||
constexpr float DEFAULT_ZOOM = 6.f;
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
|
||||
+52
-2
@@ -369,6 +369,24 @@ namespace ZL
|
||||
locations["uni_interior"]->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->scriptEngine.callLocationEnterCallback();
|
||||
|
||||
@@ -552,6 +570,8 @@ namespace ZL
|
||||
|
||||
updateDarklandsFlash(delta);
|
||||
|
||||
menuManager.uiManager.update(static_cast<float>(delta));
|
||||
|
||||
if (currentLocation)
|
||||
{
|
||||
currentLocation->update(delta);
|
||||
@@ -661,6 +681,10 @@ namespace ZL
|
||||
if (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) {
|
||||
@@ -722,8 +746,10 @@ namespace ZL
|
||||
break;
|
||||
|
||||
case SDLK_l:
|
||||
x = x - 1;
|
||||
std::cout << "current x: " << x << std::endl;
|
||||
//x = x - 1;
|
||||
//std::cout << "current x: " << x << std::endl;
|
||||
std::cout << "Azimuth: " << currentLocation->cameraAzimuth << std::endl;
|
||||
std::cout << "Inclination: " << currentLocation->cameraInclination << std::endl;
|
||||
break;
|
||||
|
||||
case SDLK_c:
|
||||
@@ -826,6 +852,10 @@ namespace ZL
|
||||
// movement threshold was crossed.
|
||||
currentLocation->lastMouseX = eventX;
|
||||
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;
|
||||
if (newZoom < zoomMin) newZoom = zoomMin;
|
||||
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()
|
||||
@@ -1004,6 +1041,19 @@ namespace ZL
|
||||
if (currentLocation) {
|
||||
// Forwarded for dialogue hover and (when cameraDragging) camera rotation.
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,11 @@ namespace ZL {
|
||||
float pinchStartDistance = 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;
|
||||
|
||||
int64_t getSyncTimeMs();
|
||||
|
||||
@@ -1519,6 +1519,7 @@ namespace ZL
|
||||
targetInteractNpc = nullptr;
|
||||
targetInteractNpcIndex = -1;
|
||||
targetTeleportZone = nullptr;
|
||||
if (onPlayerFloorWalk) onPlayerFloorWalk();
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
+6
-3
@@ -51,12 +51,11 @@ namespace ZL
|
||||
|
||||
std::vector<InteractiveObject> interactiveObjects;
|
||||
|
||||
|
||||
std::unique_ptr<Character> player;
|
||||
std::vector<std::unique_ptr<Character>> npcs;
|
||||
|
||||
float cameraAzimuth = 0.0f;
|
||||
float cameraInclination = M_PI * 30.f / 180.f;
|
||||
float cameraAzimuth = -2.35;
|
||||
float cameraInclination = 1.1036;//M_PI * 30.f / 180.f;
|
||||
|
||||
std::vector<PathFinder> navigationMaps;
|
||||
PathFinder* navigation = nullptr;
|
||||
@@ -87,6 +86,10 @@ namespace ZL
|
||||
// Read by draw functions and raycast — do not write from Location code.
|
||||
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
|
||||
// request the transition without coupling Location to Game directly.
|
||||
std::function<bool()> requestDarklandsTransition;
|
||||
|
||||
+123
-1
@@ -34,7 +34,16 @@ namespace ZL {
|
||||
inventory = &inv;
|
||||
|
||||
//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);
|
||||
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() {
|
||||
visibleQuestIds.clear();
|
||||
auto quests = questJournal.getVisibleQuests();
|
||||
|
||||
+38
-1
@@ -16,7 +16,18 @@ namespace ZL {
|
||||
enum class GameState {
|
||||
Gameplay,
|
||||
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 {
|
||||
@@ -38,6 +49,16 @@ namespace ZL {
|
||||
bool isInventoryOpen() const { return state == GameState::Inventory; }
|
||||
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:
|
||||
Renderer& renderer;
|
||||
|
||||
@@ -45,11 +66,27 @@ namespace ZL {
|
||||
void enterGameplay();
|
||||
void refreshQuestJournalUi();
|
||||
void selectQuestByIndex(int index);
|
||||
void refreshItemPickupHud();
|
||||
void setupStep5Callbacks();
|
||||
|
||||
GameState state = GameState::Gameplay;
|
||||
Inventory* inventory = nullptr;
|
||||
|
||||
bool tutorialPhonePickedUp = false;
|
||||
bool tutorialJournalPickedUp = false;
|
||||
bool tutorialPhoneScreenOpened = false;
|
||||
bool tutorialJournalScreenOpened = false;
|
||||
|
||||
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> questJournalRoot;
|
||||
|
||||
|
||||
+68
-6
@@ -373,9 +373,17 @@ namespace ZL {
|
||||
void UiStaticImage::draw(Renderer& renderer) const {
|
||||
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.RenderUniform1f("uAlpha", alpha);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(mesh);
|
||||
if (alpha < 1.0f) {
|
||||
renderer.RenderUniform1f("uAlpha", 1.0f); // restore for subsequent draws
|
||||
}
|
||||
}
|
||||
|
||||
void UiTextField::draw(Renderer& renderer) const {
|
||||
@@ -504,6 +512,8 @@ namespace ZL {
|
||||
btn->texDisabled = loadTex("disabled");
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -616,6 +626,8 @@ namespace ZL {
|
||||
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>();
|
||||
}
|
||||
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>();
|
||||
if (!tb->textRenderer->init(renderer, tb->fontPath, tb->fontSize, zipFile)) {
|
||||
@@ -679,6 +691,23 @@ namespace ZL {
|
||||
}
|
||||
|
||||
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") {
|
||||
@@ -777,6 +806,7 @@ namespace ZL {
|
||||
textViews.clear();
|
||||
textFields.clear();
|
||||
staticImages.clear();
|
||||
pulsingNodes.clear();
|
||||
collectButtonsAndSliders(root);
|
||||
|
||||
nodeActiveAnims.clear();
|
||||
@@ -1020,6 +1050,12 @@ namespace ZL {
|
||||
}
|
||||
if (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);
|
||||
}
|
||||
@@ -1128,6 +1164,7 @@ namespace ZL {
|
||||
prev.textViews = textViews;
|
||||
prev.textFields = textFields;
|
||||
prev.staticImages = staticImages;
|
||||
prev.pulsingNodes = pulsingNodes;
|
||||
prev.pressedButtons = pressedButtons;
|
||||
prev.pressedTextButtons = pressedTextButtons;
|
||||
prev.pressedSliders = pressedSliders;
|
||||
@@ -1190,6 +1227,7 @@ namespace ZL {
|
||||
textViews = s.textViews;
|
||||
textFields = s.textFields;
|
||||
staticImages = s.staticImages;
|
||||
pulsingNodes = s.pulsingNodes;
|
||||
pressedButtons = s.pressedButtons;
|
||||
pressedTextButtons = s.pressedTextButtons;
|
||||
pressedSliders = s.pressedSliders;
|
||||
@@ -1233,6 +1271,7 @@ namespace ZL {
|
||||
renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, -1, 1);
|
||||
renderer.PushMatrix();
|
||||
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 =
|
||||
[&](const std::shared_ptr<UiNode>& node) {
|
||||
@@ -1301,6 +1340,29 @@ namespace ZL {
|
||||
void UiManager::update(float deltaMs) {
|
||||
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::function<void()>> pendingCallbacks;
|
||||
|
||||
@@ -1490,7 +1552,7 @@ namespace ZL {
|
||||
for (auto& b : buttons) {
|
||||
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;
|
||||
}
|
||||
else {
|
||||
@@ -1501,7 +1563,7 @@ namespace ZL {
|
||||
for (auto& tb : textButtons) {
|
||||
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;
|
||||
}
|
||||
else {
|
||||
@@ -1534,7 +1596,7 @@ namespace ZL {
|
||||
for (auto& b : buttons) {
|
||||
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;
|
||||
pressedButtons[fingerId] = b;
|
||||
if (b->onPress) b->onPress(b->name);
|
||||
@@ -1546,7 +1608,7 @@ namespace ZL {
|
||||
for (auto& tb : textButtons) {
|
||||
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;
|
||||
pressedTextButtons[fingerId] = tb;
|
||||
if (tb->onPress) tb->onPress(tb->name);
|
||||
@@ -1593,7 +1655,7 @@ namespace ZL {
|
||||
if (btnIt != pressedButtons.end()) {
|
||||
auto b = btnIt->second;
|
||||
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 (contains) {
|
||||
clicked.push_back(b);
|
||||
@@ -1609,7 +1671,7 @@ namespace ZL {
|
||||
if (tbIt != pressedTextButtons.end()) {
|
||||
auto tb = tbIt->second;
|
||||
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 (contains) {
|
||||
clickedText.push_back(tb);
|
||||
|
||||
@@ -93,6 +93,17 @@ namespace ZL {
|
||||
|
||||
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&)> onPress; // fires on touch/mouse down
|
||||
|
||||
@@ -139,6 +150,17 @@ namespace ZL {
|
||||
ButtonState state = ButtonState::Normal;
|
||||
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
|
||||
std::string text;
|
||||
std::string fontPath = "resources/fonts/DroidSans.ttf";
|
||||
@@ -209,6 +231,11 @@ namespace ZL {
|
||||
|
||||
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 draw(Renderer& renderer) const;
|
||||
};
|
||||
@@ -236,6 +263,13 @@ namespace ZL {
|
||||
|
||||
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;
|
||||
|
||||
@@ -322,6 +356,13 @@ namespace ZL {
|
||||
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);
|
||||
@@ -404,6 +445,7 @@ namespace ZL {
|
||||
std::vector<std::shared_ptr<UiTextView>> textViews;
|
||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||
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::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<UiTextField>> textFields;
|
||||
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<UiTextButton>> pressedTextButtons;
|
||||
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
||||
|
||||
@@ -104,6 +104,7 @@ bool DialogueSystem::handlePointerReleased(float x, float y) {
|
||||
|
||||
if (advanceDialogue) {
|
||||
runtime.confirmAdvance();
|
||||
if (onDialogueAdvancedCallback) onDialogueAdvancedCallback();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -143,4 +144,8 @@ void DialogueSystem::stopDialogue() {
|
||||
runtime.stop();
|
||||
}
|
||||
|
||||
void DialogueSystem::setOnDialogueAdvanced(std::function<void()> cb) {
|
||||
onDialogueAdvancedCallback = std::move(cb);
|
||||
}
|
||||
|
||||
} // namespace ZL::Dialogue
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
void setOnDialogueLineStarted(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 setOnDialogueAdvanced(std::function<void()> cb);
|
||||
void stopDialogue();
|
||||
|
||||
bool isActive() const { return runtime.isActive(); }
|
||||
@@ -39,6 +40,7 @@ private:
|
||||
DialogueDatabase database;
|
||||
DialogueRuntime runtime;
|
||||
DialogueOverlay overlay;
|
||||
std::function<void()> onDialogueAdvancedCallback;
|
||||
};
|
||||
|
||||
} // namespace ZL::Dialogue
|
||||
@@ -7,6 +7,7 @@ namespace ZL {
|
||||
void Inventory::addItem(const Item& item) {
|
||||
items.push_back(item);
|
||||
std::cout << "Item added to inventory: " << item.name << std::endl;
|
||||
if (onItemAdded) onItemAdded(item.id);
|
||||
}
|
||||
|
||||
void Inventory::removeItem(const std::string& itemId) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@@ -28,6 +29,9 @@ namespace ZL {
|
||||
bool hasItem(const std::string& itemId) const;
|
||||
void clear() { items.clear(); }
|
||||
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
|
||||
Reference in new issue
Block a user