Merge remote-tracking branch 'origin/main' into ShipSpawn

This commit is contained in:
anime committed 2026-03-02 17:49:09 +06:00
commit 78046e5e2d
37 files changed
+1174 -744

No files matched your search

+70 -94
View File
@@ -1,100 +1,76 @@
<!doctypehtml> <!DOCTYPE html>
<html lang=en-us> <html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Space Game</title>
<style>
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
overflow: hidden; background-color: #000;
position: fixed; /* Предотвращает pull-to-refresh на Android */
}
#canvas {
display: block;
position: absolute;
top: 0; left: 0;
width: 100vw; height: 100vh;
border: none;
}
/* Кнопка Fullscreen */
#fs-button {
position: absolute;
top: 10px; right: 10px;
padding: 10px;
z-index: 10;
background: rgba(255,255,255,0.3);
color: white; border: 1px solid white;
cursor: pointer;
font-family: sans-serif;
border-radius: 5px;
}
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
</style>
</head>
<body>
<button id="fs-button">Fullscreen</button>
<div id="status">Downloading...</div>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<head> <script>
<meta charset=utf-8> var statusElement = document.getElementById("status");
<meta content="text/html; charset=utf-8" http-equiv=Content-Type> var canvas = document.getElementById("canvas");
<title>Space Game</title>
<style> var Module = {
body { canvas: canvas,
font-family: arial; setStatus: function(text) {
margin: 0; statusElement.innerHTML = text;
overflow: hidden; statusElement.style.display = text ? 'block' : 'none';
padding: 0;
min-height: 100vh;
background-color: #000;
} }
};
.emscripten { // Кнопка Fullscreen
padding-right: 0; document.getElementById('fs-button').addEventListener('click', function() {
margin-left: auto; if (!document.fullscreenElement) {
margin-right: auto; document.documentElement.requestFullscreen().catch(e => {
display: block console.error(`Error attempting to enable full-screen mode: ${e.message}`);
});
} else {
document.exitFullscreen();
} }
});
div.emscripten { // Обработка ориентации
text-align: center window.addEventListener("orientationchange", function() {
} // Chrome на Android обновляет innerWidth/Height не мгновенно.
// Ждем завершения анимации поворота.
setTimeout(() => {
// В Emscripten это вызовет ваш onWindowResized в C++
window.dispatchEvent(new Event('resize'));
}, 200);
});
div.emscripten_border { </script>
border: 1px solid #000 <script async src="space-game001.js"></script>
} </body>
</html>
canvas.emscripten {
border: 0 none;
background-color: #000;
width: 90%;
height: 100vh;
}
@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0)
}
to {
-webkit-transform: rotate(360deg)
}
}
@-moz-keyframes rotation {
from {
-moz-transform: rotate(0)
}
to {
-moz-transform: rotate(360deg)
}
}
@-o-keyframes rotation {
from {
-o-transform: rotate(0)
}
to {
-o-transform: rotate(360deg)
}
}
@keyframes rotation {
from {
transform: rotate(0)
}
to {
transform: rotate(360deg)
}
}
#status {
display: none;
}
#progress {
height: 20px;
width: 300px
}
</style>
</head>
<body>
<div class=emscripten id=status></div>
<div class=emscripten><progress hidden id=progress max=100 value=0></progress></div>
<div class=emscripten_border><canvas class=emscripten id=canvas oncontextmenu=event.preventDefault()
tabindex=-1></canvas></div>
<script>var statusElement = document.getElementById("status"), progressElement = document.getElementById("progress"), spinnerElement = document.getElementById("spinner"), Module = { print: function () { var e = document.getElementById("output"); return e && (e.value = ""), function (t) { arguments.length > 1 && (t = Array.prototype.slice.call(arguments).join(" ")), console.log(t), e && (e.value += t + "\n", e.scrollTop = e.scrollHeight) } }(), canvas: (() => { var e = document.getElementById("canvas"); return e.addEventListener("webglcontextlost", (e => { alert("WebGL context lost. You will need to reload the page."), e.preventDefault() }), !1), e })(), setStatus: e => { if (Module.setStatus.last || (Module.setStatus.last = { time: Date.now(), text: "" }), e !== Module.setStatus.last.text) { var t = e.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/), n = Date.now(); t && n - Module.setStatus.last.time < 30 || (Module.setStatus.last.time = n, Module.setStatus.last.text = e, t ? (e = t[1], progressElement.value = 100 * parseInt(t[2]), progressElement.max = 100 * parseInt(t[4]), progressElement.hidden = !1, spinnerElement.hidden = !1) : (progressElement.value = null, progressElement.max = null, progressElement.hidden = !0, e || (spinnerElement.style.display = "none")), statusElement.innerHTML = e) } }, totalDependencies: 0, monitorRunDependencies: e => { this.totalDependencies = Math.max(this.totalDependencies, e), Module.setStatus(e ? "Preparing... (" + (this.totalDependencies - e) + "/" + this.totalDependencies + ")" : "All downloads complete.") } }; Module.setStatus("Downloading..."), window.onerror = e => { Module.setStatus("Exception thrown, see JavaScript console"), spinnerElement.style.display = "none", Module.setStatus = e => { e && console.error("[post-exception status] " + e) } }</script>
<script async src=space-game001.js></script>
</body>
</html>
Binary file not shown.
Binary file not shown.
Binary file not shown.
+83 -91
View File
@@ -1,93 +1,85 @@
{ {
"root": { "root": {
"type": "LinearLayout", "type": "LinearLayout",
"orientation": "vertical", "orientation": "vertical",
"align": "center", "vertical_align": "center",
"x": 0, "horizontal_align": "center",
"y": 0, "spacing": 10,
"width": 1920, "x": 0,
"height": 1080, "y": 0,
"background": { "width": "match_parent",
"color": [0, 0, 0, 0.7] "height": "match_parent",
}, "children": [
"children": [ {
{ "type": "Button",
"type": "Button", "name": "gameOverText",
"name": "gameOverText", "width": 327,
"x": 476.5, "height": 26,
"y": 500, "textures": {
"width": 327, "normal": "resources/game_over/MissionFailed.png",
"height": 26, "hover": "resources/game_over/MissionFailed.png",
"textures": { "pressed": "resources/game_over/MissionFailed.png"
"normal": "resources/game_over/MissionFailed.png", }
"hover": "resources/game_over/MissionFailed.png", },
"pressed": "resources/game_over/MissionFailed.png" {
} "type": "Button",
}, "name": "underlineBtn",
{ "width": 168,
"type": "Button", "height": 44,
"name": "underlineBtn", "textures": {
"x": 556, "normal": "resources/game_over/Container.png",
"y": 465, "hover": "resources/game_over/Container.png",
"width": 168, "pressed": "resources/game_over/Container.png"
"height": 44, }
"textures": { },
"normal": "resources/game_over/Container.png", {
"hover": "resources/game_over/Container.png", "type": "Button",
"pressed": "resources/game_over/Container.png" "name": "finalscore",
} "width": 87,
}, "height": 9,
{ "textures": {
"type": "Button", "normal": "resources/game_over/FinalScore.png",
"name": "finalscore", "hover": "resources/game_over/FinalScore.png",
"x": 596.5, "pressed": "resources/game_over/FinalScore.png"
"y": 436, }
"width": 87, },
"height": 9, {
"textures": { "type": "TextView",
"normal": "resources/game_over/FinalScore.png", "name": "scoreText",
"hover": "resources/game_over/FinalScore.png", "width": 600,
"pressed": "resources/game_over/FinalScore.png" "height": 80,
} "text": "0",
}, "fontSize": 36,
{ "color": [
"type": "TextView", 0,
"name": "scoreText", 217,
"x": 350, 255,
"y": 356, 1
"width": 600, ],
"height": 80, "align": "center"
"text": "0", },
"fontSize": 36, {
"color": [0, 217, 255, 1], "type": "Button",
"align": "center" "name": "restartButton",
}, "width": 382,
{ "height": 56,
"type": "Button", "textures": {
"name": "restartButton", "normal": "resources/game_over/Filledbuttons.png",
"x": 449, "hover": "resources/game_over/Filledbuttons.png",
"y": 308, "pressed": "resources/game_over/Filledbuttons.png"
"width": 382, }
"height": 56, },
"textures": { {
"normal": "resources/game_over/Filledbuttons.png", "type": "Button",
"hover": "resources/game_over/Filledbuttons.png", "name": "gameOverExitButton",
"pressed": "resources/game_over/Filledbuttons.png" "width": 382,
} "height": 56,
}, "textures": {
{ "normal": "resources/game_over/Secondarybutton.png",
"type": "Button", "hover": "resources/game_over/Secondarybutton.png",
"name": "gameOverExitButton", "pressed": "resources/game_over/Secondarybutton.png"
"x": 449, }
"y": 240, }
"width": 382, ]
"height": 56, }
"textures": {
"normal": "resources/game_over/Secondarybutton.png",
"hover": "resources/game_over/Secondarybutton.png",
"pressed": "resources/game_over/Secondarybutton.png"
}
}
]
}
} }
+93
View File
@@ -0,0 +1,93 @@
{
"root": {
"type": "LinearLayout",
"orientation": "vertical",
"align": "center",
"x": 0,
"y": 0,
"width": 1920,
"height": 1080,
"background": {
"color": [0, 0, 0, 0.7]
},
"children": [
{
"type": "Button",
"name": "gameOverText",
"x": 476.5,
"y": 500,
"width": 327,
"height": 26,
"textures": {
"normal": "resources/game_over/MissionFailed.png",
"hover": "resources/game_over/MissionFailed.png",
"pressed": "resources/game_over/MissionFailed.png"
}
},
{
"type": "Button",
"name": "underlineBtn",
"x": 556,
"y": 465,
"width": 168,
"height": 44,
"textures": {
"normal": "resources/game_over/Container.png",
"hover": "resources/game_over/Container.png",
"pressed": "resources/game_over/Container.png"
}
},
{
"type": "Button",
"name": "finalscore",
"x": 596.5,
"y": 436,
"width": 87,
"height": 9,
"textures": {
"normal": "resources/game_over/FinalScore.png",
"hover": "resources/game_over/FinalScore.png",
"pressed": "resources/game_over/FinalScore.png"
}
},
{
"type": "TextView",
"name": "scoreText",
"x": 350,
"y": 356,
"width": 600,
"height": 80,
"text": "0",
"fontSize": 36,
"color": [0, 217, 255, 1],
"align": "center"
},
{
"type": "Button",
"name": "restartButton",
"x": 449,
"y": 308,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/game_over/Filledbuttons.png",
"hover": "resources/game_over/Filledbuttons.png",
"pressed": "resources/game_over/Filledbuttons.png"
}
},
{
"type": "Button",
"name": "gameOverExitButton",
"x": 449,
"y": 240,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/game_over/Secondarybutton.png",
"hover": "resources/game_over/Secondarybutton.png",
"pressed": "resources/game_over/Secondarybutton.png"
}
}
]
}
}
+58 -118
View File
@@ -1,141 +1,81 @@
{ {
"root": { "root": {
"type": "FrameLayout", "type": "LinearLayout",
"x": 0, "orientation": "vertical",
"y": 0, "vertical_align": "center",
"width": 1280, "horizontal_align": "center",
"height": 720, "spacing": 10,
"children": [ "x": 0,
{ "y": 0,
"type": "LinearLayout", "width": "match_parent",
"name": "settingsButtons", "height": "match_parent",
"orientation": "vertical", "children": [
"spacing": 10, {
"x": 0, "type": "Button",
"y": 0, "name": "titleBtn",
"width": 300, "width": 254,
"height": 300, "height": 35,
"children": [ "textures": {
{
"type": "Button",
"name": "langButton",
"x": 1100,
"y": 580,
"width": 142,
"height": 96,
"textures": {
"normal": "resources/main_menu/lang.png",
"hover": "resources/main_menu/lang.png",
"pressed": "resources/main_menu/lang.png"
}
},
{
"type": "Button",
"name": "titleBtn",
"x": 473,
"y": 500,
"width": 254,
"height": 35,
"textures": {
"normal": "resources/main_menu/title.png", "normal": "resources/main_menu/title.png",
"hover": "resources/main_menu/title.png", "hover": "resources/main_menu/title.png",
"pressed": "resources/main_menu/title.png" "pressed": "resources/main_menu/title.png"
} }
}, },
{ {
"type": "Button", "type": "Button",
"name": "underlineBtn", "name": "underlineBtn",
"x": 516, "width": 168,
"y": 465, "height": 44,
"width": 168, "textures": {
"height": 44,
"textures": {
"normal": "resources/main_menu/line.png", "normal": "resources/main_menu/line.png",
"hover": "resources/main_menu/line.png", "hover": "resources/main_menu/line.png",
"pressed": "resources/main_menu/line.png" "pressed": "resources/main_menu/line.png"
} }
}, },
{ {
"type": "Button", "type": "Button",
"name": "subtitleBtn", "name": "subtitleBtn",
"x": 528, "width": 144,
"y": 455, "height": 11,
"width": 144, "textures": {
"height": 11,
"textures": {
"normal": "resources/main_menu/subtitle.png", "normal": "resources/main_menu/subtitle.png",
"hover": "resources/main_menu/subtitle.png", "hover": "resources/main_menu/subtitle.png",
"pressed": "resources/main_menu/subtitle.png" "pressed": "resources/main_menu/subtitle.png"
} }
}, },
{ {
"type": "Button", "type": "Button",
"name": "singleButton", "name": "singleButton",
"x": 409, "width": 382,
"y": 360, "height": 56,
"width": 382, "textures": {
"height": 56,
"textures": {
"normal": "resources/main_menu/single.png", "normal": "resources/main_menu/single.png",
"hover": "resources/main_menu/single.png", "hover": "resources/main_menu/single.png",
"pressed": "resources/main_menu/single.png" "pressed": "resources/main_menu/single.png"
} }
}, },
{ {
"type": "Button", "type": "Button",
"name": "multiplayerButton", "name": "multiplayerButton",
"x": 409, "width": 382,
"y": 289, "height": 56,
"width": 382, "textures": {
"height": 56,
"textures": {
"normal": "resources/main_menu/multi.png", "normal": "resources/main_menu/multi.png",
"hover": "resources/main_menu/multi.png", "hover": "resources/main_menu/multi.png",
"pressed": "resources/main_menu/multi.png" "pressed": "resources/main_menu/multi.png"
} }
}, },
{ {
"type": "Button", "type": "Button",
"name": "multiplayerButton2", "name": "versionLabel",
"x": 409, "width": 81,
"y": 218, "height": 9,
"width": 382, "textures": {
"height": 56,
"textures": {
"normal": "resources/main_menu/multi.png",
"hover": "resources/main_menu/multi.png",
"pressed": "resources/main_menu/multi.png"
}
},
{
"type": "Button",
"name": "exitButton",
"x": 409,
"y": 147,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/main_menu/exit.png",
"hover": "resources/main_menu/exit.png",
"pressed": "resources/main_menu/exit.png"
}
},
{
"type": "Button",
"name": "versionLabel",
"x": 559.5,
"y": 99,
"width": 81,
"height": 9,
"textures": {
"normal": "resources/main_menu/version.png", "normal": "resources/main_menu/version.png",
"hover": "resources/main_menu/version.png", "hover": "resources/main_menu/version.png",
"pressed": "resources/main_menu/version.png" "pressed": "resources/main_menu/version.png"
}
} }
]
} }
] ]
}
} }
}
+57 -62
View File
@@ -1,64 +1,59 @@
{ {
"root": { "root": {
"name": "shipSelectionRoot", "type": "LinearLayout",
"type": "node", "orientation": "vertical",
"children": [ "vertical_align": "center",
"horizontal_align": "center",
{ "spacing": 10,
"type": "TextField", "x": 0,
"name": "nicknameInput", "y": 0,
"x": 400, "width": "match_parent",
"y": 150, "height": "match_parent",
"width": 400, "children": [
"height": 50, {
"placeholder": "Enter your nickname", "type": "LinearLayout",
"fontPath": "resources/fonts/DroidSans.ttf", "orientation": "horizontal",
"fontSize": 16, "vertical_align": "center",
"maxLength": 256, "horizontal_align": "center",
"color": [122, 156, 198, 1], "spacing": 10,
"placeholderColor": [122, 156, 198, 1], "width": "match_parent",
"backgroundColor": [15, 29, 51, 1], "height": 260,
"borderColor": [15, 29, 51, 1] "children": [
}, {
{ "type": "Button",
"type": "Button", "name": "spaceshipButton",
"name": "spaceshipButton", "width": 256,
"x": 300, "height": 256,
"y": 320, "textures": {
"width": 200, "normal": "resources/multiplayer_menu/ship_fighter.png",
"height": 80, "hover": "resources/multiplayer_menu/ship_fighter_pressed.png",
"textures": { "pressed": "resources/multiplayer_menu/ship_fighter_pressed.png"
"normal": "resources/multiplayer_menu/JoinServer.png", }
"hover": "resources/multiplayer_menu/JoinServer.png", },
"pressed": "resources/multiplayer_menu/JoinServer.png" {
"type": "Button",
"name": "cargoshipButton",
"width": 256,
"height": 256,
"textures": {
"normal": "resources/multiplayer_menu/ship_cargo.png",
"hover": "resources/multiplayer_menu/ship_cargo_pressed.png",
"pressed": "resources/multiplayer_menu/ship_cargo_pressed.png"
}
}
]
},
{
"type": "Button",
"name": "backButton",
"width": 382,
"height": 56,
"textures": {
"normal": "resources/multiplayer_menu/Backbutton.png",
"hover": "resources/multiplayer_menu/Backbutton.png",
"pressed": "resources/multiplayer_menu/Backbutton.png"
}
} }
}, ]
{ }
"type": "Button", }
"name": "cargoshipButton",
"x": 700,
"y": 320,
"width": 200,
"height": 80,
"textures": {
"normal": "resources/multiplayer_menu/JoinServer.png",
"hover": "resources/multiplayer_menu/JoinServer.png",
"pressed": "resources/multiplayer_menu/JoinServer.png"
}
},
{
"type": "Button",
"name": "backButton",
"x": 449,
"y": 280,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/multiplayer_menu/Backbutton.png",
"hover": "resources/multiplayer_menu/Backbutton.png",
"pressed": "resources/multiplayer_menu/Backbutton.png"
}
}
]
}
}
+51 -188
View File
@@ -1,194 +1,57 @@
{ {
"root": { "root": {
"type": "FrameLayout", "type": "FrameLayout",
"x": 0, "x": 0,
"y": 0, "y": 0,
"width": 1280, "width": "match_parent",
"height": 720, "height": "match_parent",
"children": [ "children": [
{
"type": "FrameLayout",
"name": "leftPanel",
"x": 100,
"y": 100,
"width": 320,
"height": 400,
"children": [
{ {
"type": "LinearLayout", "type": "Button",
"name": "mainButtons", "name": "shootButton",
"orientation": "vertical", "x": 0,
"spacing": 10, "y": 0,
"x": 0, "width": 150,
"y": 0, "height": 150,
"width": 300, "horizontal_gravity": "right",
"height": 300, "vertical_gravity": "bottom",
"children": [ "textures": {
{ "normal": "resources/shoot_normal.png",
"type": "Button", "hover": "resources/shoot_hover.png",
"name": "playButton", "pressed": "resources/shoot_pressed.png"
"x": -1000, }
"y": 500, },
"width": 200, {
"height": 50, "type": "Button",
"animations": { "name": "shootButton2",
"buttonsExit": { "x": 0,
"repeat": false, "y": 0,
"steps": [ "width": 150,
{ "height": 150,
"type": "move", "horizontal_gravity": "left",
"to": [ "vertical_gravity": "bottom",
-400, "textures": {
0 "normal": "resources/shoot_normal.png",
], "hover": "resources/shoot_hover.png",
"duration": 1.0, "pressed": "resources/shoot_pressed.png"
"easing": "easein" }
} },
] {
} "type": "Slider",
}, "name": "velocitySlider",
"textures": { "x": 10,
"normal": "./resources/sand2.png", "y": 200,
"hover": "./resources/sand2.png", "width": 80,
"pressed": "./resources/sand2.png" "height": 300,
} "value": 0.0,
}, "orientation": "vertical",
{ "horizontal_gravity": "right",
"type": "Button", "vertical_gravity": "bottom",
"name": "settingsButton", "textures": {
"x": -1000, "track": "resources/velocitySliderTexture.png",
"y": 400, "knob": "resources/velocitySliderButton.png"
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 0.5
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
},
{
"type": "Button",
"name": "exitButton",
"x": -1000,
"y": 300,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 1.0
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
},
"bgScroll": {
"repeat": true,
"steps": [
{
"type": "move",
"to": [
1280,
0
],
"duration": 5.0,
"easing": "linear"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
} }
]
} }
] ]
},
{
"type": "Slider",
"name": "velocitySlider",
"x": 1140,
"y": 300,
"width": 50,
"height": 300,
"value": 0.0,
"orientation": "vertical",
"textures": {
"track": "resources/velocitySliderTexture.png",
"knob": "resources/velocitySliderButton.png"
}
},
{
"type": "Button",
"name": "shootButton",
"x": 100,
"y": 100,
"width": 100,
"height": 100,
"textures": {
"normal": "resources/shoot_normal.png",
"hover": "resources/shoot_hover.png",
"pressed": "resources/shoot_pressed.png"
}
},
{
"type": "Button",
"name": "shootButton2",
"x": 1000,
"y": 100,
"width": 100,
"height": 100,
"textures": {
"normal": "resources/shoot_normal.png",
"hover": "resources/shoot_hover.png",
"pressed": "resources/shoot_pressed.png"
}
},
{
"type": "TextView",
"name": "velocityText",
"x": 10,
"y": 10,
"width": 200,
"height": 40,
"text": "Velocity: 0",
"fontSize": 24,
"color": [1.0, 1.0, 1.0, 1.0],
"centered": false
}
]
} }
} }
+194
View File
@@ -0,0 +1,194 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": 1280,
"height": 720,
"children": [
{
"type": "FrameLayout",
"name": "leftPanel",
"x": 100,
"y": 100,
"width": 320,
"height": 400,
"children": [
{
"type": "LinearLayout",
"name": "mainButtons",
"orientation": "vertical",
"spacing": 10,
"x": 0,
"y": 0,
"width": 300,
"height": 300,
"children": [
{
"type": "Button",
"name": "playButton",
"x": -1000,
"y": 500,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
},
{
"type": "Button",
"name": "settingsButton",
"x": -1000,
"y": 400,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 0.5
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
},
{
"type": "Button",
"name": "exitButton",
"x": -1000,
"y": 300,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 1.0
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
},
"bgScroll": {
"repeat": true,
"steps": [
{
"type": "move",
"to": [
1280,
0
],
"duration": 5.0,
"easing": "linear"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
}
]
}
]
},
{
"type": "Slider",
"name": "velocitySlider",
"x": 1140,
"y": 300,
"width": 50,
"height": 300,
"value": 0.0,
"orientation": "vertical",
"textures": {
"track": "resources/velocitySliderTexture.png",
"knob": "resources/velocitySliderButton.png"
}
},
{
"type": "Button",
"name": "shootButton",
"x": 100,
"y": 100,
"width": 100,
"height": 100,
"textures": {
"normal": "resources/shoot_normal.png",
"hover": "resources/shoot_hover.png",
"pressed": "resources/shoot_pressed.png"
}
},
{
"type": "Button",
"name": "shootButton2",
"x": 1000,
"y": 100,
"width": 100,
"height": 100,
"textures": {
"normal": "resources/shoot_normal.png",
"hover": "resources/shoot_hover.png",
"pressed": "resources/shoot_pressed.png"
}
},
{
"type": "TextView",
"name": "velocityText",
"x": 10,
"y": 10,
"width": 200,
"height": 40,
"text": "Velocity: 0",
"fontSize": 24,
"color": [1.0, 1.0, 1.0, 1.0],
"centered": false
}
]
}
}
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.
BIN
View File
Binary file not shown.
+6 -8
View File
@@ -62,7 +62,7 @@ struct Projectile {
uint64_t spawnMs = 0; uint64_t spawnMs = 0;
Eigen::Vector3f pos; Eigen::Vector3f pos;
Eigen::Vector3f vel; Eigen::Vector3f vel;
float lifeMs = 5000.0f; float lifeMs = PROJECTILE_LIFE;
}; };
struct BoxDestroyedInfo { struct BoxDestroyedInfo {
@@ -699,11 +699,12 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
} }
} }
// --- Tick: box-projectile collisions --- // --- Tick: box-projectile collisions ---
{ {
std::lock_guard<std::mutex> bm(g_boxes_mutex); std::lock_guard<std::mutex> bm(g_boxes_mutex);
const float projectileHitRadius = 5.0f;
const float boxCollisionRadius = 2.0f;
std::vector<std::pair<size_t, size_t>> boxProjectileCollisions; std::vector<std::pair<size_t, size_t>> boxProjectileCollisions;
@@ -749,9 +750,6 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
std::lock_guard<std::mutex> bm(g_boxes_mutex); std::lock_guard<std::mutex> bm(g_boxes_mutex);
std::lock_guard<std::mutex> lm(g_sessions_mutex); std::lock_guard<std::mutex> lm(g_sessions_mutex);
const float shipCollisionRadius = 15.0f;
const float boxCollisionRadius = 2.0f;
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
if (g_serverBoxes[bi].destroyed) continue; if (g_serverBoxes[bi].destroyed) continue;
@@ -840,8 +838,8 @@ std::vector<ServerBox> generateServerBoxes(int count) {
std::random_device rd; std::random_device rd;
std::mt19937 gen(rd()); std::mt19937 gen(rd());
const float MIN_COORD = -100.0f; const float MIN_COORD = -1000.0f;
const float MAX_COORD = 100.0f; const float MAX_COORD = 1000.0f;
const float MIN_DISTANCE = 3.0f; const float MIN_DISTANCE = 3.0f;
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE; const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
const int MAX_ATTEMPTS = 1000; const int MAX_ATTEMPTS = 1000;
+20
View File
@@ -36,5 +36,25 @@ ClientState Environment::shipState;
const float Environment::CONST_Z_NEAR = 5.f; const float Environment::CONST_Z_NEAR = 5.f;
const float Environment::CONST_Z_FAR = 5000.f; const float Environment::CONST_Z_FAR = 5000.f;
float Environment::projectionWidth = 1280.0f;
float Environment::projectionHeight = 720.0f;
void Environment::computeProjectionDimensions()
{
if (width <= 0 || height <= 0) return;
const float refShortSide = 720.0f;
float aspect = (float)width / (float)height;
if (width >= height) {
// Landscape: fix height to 720, scale width to preserve aspect
projectionHeight = refShortSide;
projectionWidth = refShortSide * aspect;
} else {
// Portrait: fix width to 720, scale height to preserve aspect
projectionWidth = refShortSide;
projectionHeight = refShortSide / aspect;
}
}
} // namespace ZL } // namespace ZL
+8 -1
View File
@@ -35,8 +35,15 @@ public:
static const float CONST_Z_NEAR; static const float CONST_Z_NEAR;
static const float CONST_Z_FAR; static const float CONST_Z_FAR;
// Virtual projection dimensions used for all 2D/UI rendering.
// These maintain the screen's actual aspect ratio but normalize the
// height to 720 (landscape) or width to 720 (portrait), giving a
// consistent coordinate space regardless of physical screen resolution.
static float projectionWidth;
static float projectionHeight;
// Call this once at startup and whenever the window is resized.
static void computeProjectionDimensions();
}; };
} // namespace ZL } // namespace ZL
+46 -42
View File
@@ -84,6 +84,8 @@ namespace ZL
void Game::setup() { void Game::setup() {
glContext = SDL_GL_CreateContext(ZL::Environment::window); glContext = SDL_GL_CreateContext(ZL::Environment::window);
Environment::computeProjectionDimensions();
ZL::BindOpenGlFunctions(); ZL::BindOpenGlFunctions();
ZL::CheckGlError(); ZL::CheckGlError();
renderer.InitOpenGL(); renderer.InitOpenGL();
@@ -100,7 +102,7 @@ namespace ZL
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE)); loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
#endif #endif
loadingMesh.data = CreateRect2D({ Environment::width * 0.5, Environment::height * 0.5 }, { Environment::width * 0.5, Environment::height*0.5 }, 3); loadingMesh.data = CreateRect2D({ Environment::projectionWidth * 0.5f, Environment::projectionHeight * 0.5f }, { Environment::projectionWidth * 0.5f, Environment::projectionHeight * 0.5f }, 3);
loadingMesh.RefreshVBO(); loadingMesh.RefreshVBO();
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
@@ -142,17 +144,14 @@ namespace ZL
Environment::shipState.nickname = nickname; Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType; Environment::shipState.shipType = shipType;
networkClient = std::make_unique<LocalClient>(); auto localClient = new LocalClient;
ClientState st = Environment::shipState;
st.id = localClient->GetClientId();
localClient->setLocalPlayerState(st);
networkClient = std::unique_ptr<INetworkClient>(localClient);
networkClient->Connect("", 0); networkClient->Connect("", 0);
#ifndef NETWORK
auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
if (localClient) {
ZL::ClientState st = Environment::shipState;
st.id = localClient->GetClientId();
localClient->setLocalPlayerState(st);
}
#endif
lastTickCount = 0; lastTickCount = 0;
spaceGameStarted = 1; spaceGameStarted = 1;
}; };
@@ -161,8 +160,7 @@ namespace ZL
Environment::shipState.nickname = nickname; Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType; Environment::shipState.shipType = shipType;
networkClient = std::make_unique<LocalClient>();
#ifdef NETWORK
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
networkClient = std::make_unique<WebSocketClientEmscripten>(); networkClient = std::make_unique<WebSocketClientEmscripten>();
networkClient->Connect("localhost", 8081); networkClient->Connect("localhost", 8081);
@@ -170,19 +168,6 @@ namespace ZL
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext()); networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
networkClient->Connect("localhost", 8081); networkClient->Connect("localhost", 8081);
#endif #endif
#else
networkClient->Connect("", 0);
#endif
#ifndef NETWORK
auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
if (localClient) {
ZL::ClientState st = Environment::shipState;
st.id = localClient->GetClientId();
localClient->setLocalPlayerState(st);
}
#endif
if (networkClient) { if (networkClient) {
std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType); std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType);
@@ -265,8 +250,8 @@ namespace ZL
renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName); renderer.EnableVertexAttribArray(vTexCoordName);
float width = Environment::width; float width = Environment::projectionWidth;
float height = Environment::height; float height = Environment::projectionHeight;
renderer.PushProjectionMatrix( renderer.PushProjectionMatrix(
0, width, 0, width,
@@ -351,16 +336,19 @@ namespace ZL
if (event.type == SDL_QUIT) { if (event.type == SDL_QUIT) {
Environment::exitGameLoop = true; Environment::exitGameLoop = true;
} }
#if SDL_VERSION_ATLEAST(2,0,5)
else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
// Обновляем размеры и сбрасываем кеш текстов, т.к. меши хранятся в пикселях // Обновляем размеры и сбрасываем кеш текстов, т.к. меши хранятся в пикселях
Environment::width = event.window.data1; Environment::width = event.window.data1;
Environment::height = event.window.data2; Environment::height = event.window.data2;
std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl; Environment::computeProjectionDimensions();
menuManager.uiManager.updateAllLayouts();
std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl;
space.clearTextRendererCache(); space.clearTextRendererCache();
} }
#endif
#ifdef __ANDROID__ #ifdef __ANDROID__
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) { if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
Environment::exitGameLoop = true; Environment::exitGameLoop = true;
@@ -369,22 +357,38 @@ namespace ZL
#ifdef __ANDROID__ #ifdef __ANDROID__
if (event.type == SDL_FINGERDOWN) { if (event.type == SDL_FINGERDOWN) {
int mx = static_cast<int>(event.tfinger.x * Environment::width); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::height); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleDown(mx, my); handleDown(mx, my);
} }
else if (event.type == SDL_FINGERUP) { else if (event.type == SDL_FINGERUP) {
int mx = static_cast<int>(event.tfinger.x * Environment::width); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::height); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleUp(mx, my); handleUp(mx, my);
} }
else if (event.type == SDL_FINGERMOTION) { else if (event.type == SDL_FINGERMOTION) {
int mx = static_cast<int>(event.tfinger.x * Environment::width); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::height); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleMotion(mx, my); handleMotion(mx, my);
} }
#else #else
if (event.type == SDL_MOUSEBUTTONDOWN) {
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
// Преобразуем экранные пиксели в проекционные единицы
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(mx, my);
else handleUp(mx, my);
}
else if (event.type == SDL_MOUSEMOTION) {
int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
handleMotion(mx, my);
}
/*if (event.type == SDL_MOUSEBUTTONDOWN) {
int mx = event.button.x; int mx = event.button.x;
int my = event.button.y; int my = event.button.y;
handleDown(mx, my); handleDown(mx, my);
@@ -398,7 +402,7 @@ namespace ZL
int mx = event.motion.x; int mx = event.motion.x;
int my = event.motion.y; int my = event.motion.y;
handleMotion(mx, my); handleMotion(mx, my);
} }*/
if (event.type == SDL_MOUSEWHEEL) { if (event.type == SDL_MOUSEWHEEL) {
static const float zoomstep = 2.0f; static const float zoomstep = 2.0f;
@@ -480,7 +484,7 @@ namespace ZL
void Game::handleDown(int mx, int my) void Game::handleDown(int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::height - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseDown(uiX, uiY); menuManager.uiManager.onMouseDown(uiX, uiY);
@@ -508,7 +512,7 @@ namespace ZL
void Game::handleUp(int mx, int my) void Game::handleUp(int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::height - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseUp(uiX, uiY); menuManager.uiManager.onMouseUp(uiX, uiY);
@@ -523,7 +527,7 @@ namespace ZL
void Game::handleMotion(int mx, int my) void Game::handleMotion(int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::height - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseMove(uiX, uiY); menuManager.uiManager.onMouseMove(uiX, uiY);
+6 -4
View File
@@ -96,6 +96,7 @@ namespace ZL {
} }
}); });
uiManager.setButtonCallback("shootButton", [this](const std::string& name) { uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
onFirePressed(); onFirePressed();
}); });
@@ -103,6 +104,7 @@ namespace ZL {
onFirePressed(); onFirePressed();
}); });
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
int newVel = roundf(value * 10); int newVel = roundf(value * 10);
if (newVel > 2) if (newVel > 2)
{ {
@@ -183,8 +185,8 @@ namespace ZL {
} }
}); });
uiManager.setButtonCallback("multiplayerButton2", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) { /*uiManager.setButtonCallback("multiplayerButton2", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) {
/*std::cerr << "Multiplayer button pressed → opening multiplayer menu\n"; std::cerr << "Multiplayer button pressed → opening multiplayer menu\n";
uiManager.startAnimationOnNode("playButton", "buttonsExit"); uiManager.startAnimationOnNode("playButton", "buttonsExit");
uiManager.startAnimationOnNode("settingsButton", "buttonsExit"); uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
@@ -219,7 +221,7 @@ namespace ZL {
} }
else { else {
std::cerr << "Failed to load multiplayer menu\n"; std::cerr << "Failed to load multiplayer menu\n";
}*/ }
std::cerr << "Single button pressed: " << name << " -> open ship selection UI\n"; std::cerr << "Single button pressed: " << name << " -> open ship selection UI\n";
if (!shipSelectionRoot) { if (!shipSelectionRoot) {
std::cerr << "Failed to load ship selection UI\n"; std::cerr << "Failed to load ship selection UI\n";
@@ -255,7 +257,7 @@ namespace ZL {
uiManager.setButtonCallback("exitButton", [](const std::string& name) { uiManager.setButtonCallback("exitButton", [](const std::string& name) {
std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n"; std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n";
Environment::exitGameLoop = true; Environment::exitGameLoop = true;
}); });*/
} }
void MenuManager::showGameOver(int score) void MenuManager::showGameOver(int score)
+1 -1
View File
@@ -41,7 +41,7 @@ namespace ZL {
} }
void Projectile::rebuildMesh(Renderer&) { void Projectile::rebuildMesh(Renderer&) {
float half = size * 0.5f; float half = 10 * size * 0.5f;
mesh.data.PositionData.clear(); mesh.data.PositionData.clear();
mesh.data.TexCoordData.clear(); mesh.data.TexCoordData.clear();
+3
View File
@@ -3,6 +3,7 @@
#include "render/Renderer.h" #include "render/Renderer.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"
#include <memory> #include <memory>
#include "SparkEmitter.h"
namespace ZL { namespace ZL {
@@ -19,6 +20,8 @@ namespace ZL {
Vector3f getPosition() const { return pos; } Vector3f getPosition() const { return pos; }
void deactivate() { active = false; } void deactivate() { active = false; }
SparkEmitter projectileEmitter;
private: private:
Vector3f pos; Vector3f pos;
Vector3f vel; Vector3f vel;
+56 -38
View File
@@ -159,15 +159,15 @@ namespace ZL
// В пределах экрана? // В пределах экрана?
// (можно оставить, можно клампить) // (можно оставить, можно клампить)
float sx = (ndc.x() * 0.5f + 0.5f) * Environment::width; float sx = (ndc.x() * 0.5f + 0.5f) * Environment::projectionWidth;
float sy = (ndc.y() * 0.5f + 0.5f) * Environment::height; float sy = (ndc.y() * 0.5f + 0.5f) * Environment::projectionHeight;
outX = sx; outX = sx;
outY = sy; outY = sy;
// Можно отсеять те, что вне: // Можно отсеять те, что вне:
if (sx < -200 || sx > Environment::width + 200) return false; if (sx < -200 || sx > Environment::projectionWidth + 200) return false;
if (sy < -200 || sy > Environment::height + 200) return false; if (sy < -200 || sy > Environment::projectionHeight + 200) return false;
return true; return true;
} }
@@ -296,12 +296,12 @@ namespace ZL
cubemapTexture = std::make_shared<Texture>( cubemapTexture = std::make_shared<Texture>(
std::array<TextureDataStruct, 6>{ std::array<TextureDataStruct, 6>{
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE) CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE)
}); });
@@ -335,6 +335,7 @@ namespace ZL
cargo.AssignFrom(cargoBase); cargo.AssignFrom(cargoBase);
cargo.RefreshVBO(); cargo.RefreshVBO();
//projectileTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/spark2.png", CONST_ZIP_FILE));
//Boxes //Boxes
boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE)); boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE));
@@ -362,7 +363,7 @@ namespace ZL
} }
crosshairCfgLoaded = loadCrosshairConfig("resources/config/crosshair_config.json"); crosshairCfgLoaded = loadCrosshairConfig("resources/config/crosshair_config.json");
std::cerr << "[Crosshair] loaded=" << crosshairCfgLoaded std::cout << "[Crosshair] loaded=" << crosshairCfgLoaded
<< " enabled=" << crosshairCfg.enabled << " enabled=" << crosshairCfg.enabled
<< " w=" << Environment::width << " h=" << Environment::height << " w=" << Environment::width << " h=" << Environment::height
<< " alpha=" << crosshairCfg.alpha << " alpha=" << crosshairCfg.alpha
@@ -490,13 +491,25 @@ namespace ZL
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderer.shaderManager.PushShader("default");
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
for (const auto& p : projectiles) { for (const auto& p : projectiles) {
if (p && p->isActive()) { if (p && p->isActive()) {
p->draw(renderer); p->draw(renderer);
p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
} }
} }
glDisable(GL_BLEND);
projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader();
//projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
if (shipAlive) { if (shipAlive) {
renderer.PushMatrix(); renderer.PushMatrix();
@@ -859,8 +872,8 @@ namespace ZL
// если ничего не изменилось — не трогаем VBO // если ничего не изменилось — не трогаем VBO
if (crosshairMeshValid && if (crosshairMeshValid &&
crosshairLastW == Environment::width && crosshairLastW == Environment::projectionWidth &&
crosshairLastH == Environment::height && crosshairLastH == Environment::projectionHeight &&
std::abs(crosshairLastAlpha - crosshairCfg.alpha) < 1e-6f && std::abs(crosshairLastAlpha - crosshairCfg.alpha) < 1e-6f &&
std::abs(crosshairLastThickness - crosshairCfg.thicknessPx) < 1e-6f && std::abs(crosshairLastThickness - crosshairCfg.thicknessPx) < 1e-6f &&
std::abs(crosshairLastGap - crosshairCfg.gapPx) < 1e-6f && std::abs(crosshairLastGap - crosshairCfg.gapPx) < 1e-6f &&
@@ -869,18 +882,18 @@ namespace ZL
return; return;
} }
crosshairLastW = Environment::width; crosshairLastW = Environment::projectionWidth;
crosshairLastH = Environment::height; crosshairLastH = Environment::projectionHeight;
crosshairLastAlpha = crosshairCfg.alpha; crosshairLastAlpha = crosshairCfg.alpha;
crosshairLastThickness = crosshairCfg.thicknessPx; crosshairLastThickness = crosshairCfg.thicknessPx;
crosshairLastGap = crosshairCfg.gapPx; crosshairLastGap = crosshairCfg.gapPx;
crosshairLastScaleMul = crosshairCfg.scaleMul; crosshairLastScaleMul = crosshairCfg.scaleMul;
float cx = Environment::width * 0.5f; float cx = Environment::projectionWidth * 0.5f;
float cy = Environment::height * 0.5f; float cy = Environment::projectionHeight * 0.5f;
// масштаб от reference (стандартно: по высоте) // масштаб от reference (стандартно: по высоте)
float scale = (crosshairCfg.refH > 0) ? (Environment::height / (float)crosshairCfg.refH) : 1.0f; float scale = (crosshairCfg.refH > 0) ? (Environment::projectionHeight / (float)crosshairCfg.refH) : 1.0f;
scale *= crosshairCfg.scaleMul; scale *= crosshairCfg.scaleMul;
float thickness = crosshairCfg.thicknessPx * scale; float thickness = crosshairCfg.thicknessPx * scale;
@@ -940,7 +953,7 @@ namespace ZL
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderer.shaderManager.PushShader("defaultColor"); renderer.shaderManager.PushShader("defaultColor");
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f); renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
@@ -1124,7 +1137,7 @@ namespace ZL
// Lead Indicator // Lead Indicator
// скорость пули (как в fireProjectiles) // скорость пули (как в fireProjectiles)
const float projectileSpeed = 60.0f; const float projectileSpeed = PROJECTILE_VELOCITY;
// позиция вылета // позиция вылета
Vector3f shooterPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f }; Vector3f shooterPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f };
@@ -1187,15 +1200,15 @@ namespace ZL
// 4) Настройки стиля // 4) Настройки стиля
Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный
float thickness = 10.0f; // толщина линий (px) float thickness = 2.0f; // толщина линий (px)
float z = 0.0f; // 2D слой float z = 0.0f; // 2D слой
// 5) Если цель в кадре: рисуем скобки // 5) Если цель в кадре: рисуем скобки
if (onScreen) if (onScreen)
{ {
// перевод NDC -> экран (в пикселях) // перевод NDC -> экран (в пикселях)
float sx = (ndcX * 0.5f + 0.5f) * Environment::width; float sx = (ndcX * 0.5f + 0.5f) * Environment::projectionWidth;
float sy = (ndcY * 0.5f + 0.5f) * Environment::height; float sy = (ndcY * 0.5f + 0.5f) * Environment::projectionHeight;
// анимация “снаружи внутрь” // анимация “снаружи внутрь”
// targetAcquireAnim растёт к 1, быстро (похоже на захват) // targetAcquireAnim растёт к 1, быстро (похоже на захват)
@@ -1234,7 +1247,7 @@ namespace ZL
glClear(GL_DEPTH_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT);
renderer.shaderManager.PushShader("defaultColor"); renderer.shaderManager.PushShader("defaultColor");
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f); renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
@@ -1248,8 +1261,8 @@ namespace ZL
float leadNdcX, leadNdcY, leadNdcZ, leadClipW; float leadNdcX, leadNdcY, leadNdcZ, leadClipW;
if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) { if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) {
if (leadNdcX >= -1 && leadNdcX <= 1 && leadNdcY >= -1 && leadNdcY <= 1) { if (leadNdcX >= -1 && leadNdcX <= 1 && leadNdcY >= -1 && leadNdcY <= 1) {
float lx = (leadNdcX * 0.5f + 0.5f) * Environment::width; float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
float ly = (leadNdcY * 0.5f + 0.5f) * Environment::height; float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
float distLead = (Environment::shipState.position - leadWorld).norm(); float distLead = (Environment::shipState.position - leadWorld).norm();
float r = 30.0f / (distLead * 0.01f + 1.0f); float r = 30.0f / (distLead * 0.01f + 1.0f);
@@ -1323,8 +1336,8 @@ namespace ZL
float edgeNdcX = dirX * k; float edgeNdcX = dirX * k;
float edgeNdcY = dirY * k; float edgeNdcY = dirY * k;
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::width; float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::height; float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
float bob = std::sin(t * 6.0f) * 6.0f; float bob = std::sin(t * 6.0f) * 6.0f;
edgeX += dirX * bob; edgeX += dirX * bob;
@@ -1367,7 +1380,7 @@ namespace ZL
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderer.shaderManager.PushShader("defaultColor"); renderer.shaderManager.PushShader("defaultColor");
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f); renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
@@ -1540,22 +1553,25 @@ namespace ZL
} }
} }
std::vector<Vector3f> projCameraPoints;
for (const auto& p : projectiles) { for (const auto& p : projectiles) {
if (p && p->isActive()) { if (p && p->isActive()) {
Vector3f worldPos = p->getPosition(); Vector3f worldPos = p->getPosition();
Vector3f rel = worldPos - Environment::shipState.position; Vector3f rel = worldPos - Environment::shipState.position;
Vector3f camPos = Environment::inverseShipMatrix * rel; Vector3f camPos = Environment::inverseShipMatrix * rel;
projCameraPoints.push_back(camPos); p->projectileEmitter.setEmissionPoints({ camPos });
p->projectileEmitter.emit();
p->projectileEmitter.update(static_cast<float>(delta));
} }
} }
/*
if (!projCameraPoints.empty()) { if (!projCameraPoints.empty()) {
projectileEmitter.setEmissionPoints(projCameraPoints); projectileEmitter.setEmissionPoints(projCameraPoints);
projectileEmitter.emit(); projectileEmitter.emit();
} }
else { else {
projectileEmitter.setEmissionPoints(std::vector<Vector3f>()); projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
} }*/
std::vector<Vector3f> shipCameraPoints; std::vector<Vector3f> shipCameraPoints;
for (const auto& lp : shipLocalEmissionPoints) { for (const auto& lp : shipLocalEmissionPoints) {
@@ -1567,7 +1583,7 @@ namespace ZL
} }
sparkEmitter.update(static_cast<float>(delta)); sparkEmitter.update(static_cast<float>(delta));
projectileEmitter.update(static_cast<float>(delta)); //projectileEmitter.update(static_cast<float>(delta));
explosionEmitter.update(static_cast<float>(delta)); explosionEmitter.update(static_cast<float>(delta));
if (showExplosion) { if (showExplosion) {
@@ -1696,8 +1712,8 @@ namespace ZL
Vector3f{ 1.5f, 0.9f - 6.f, 5.0f } Vector3f{ 1.5f, 0.9f - 6.f, 5.0f }
}; };
const float projectileSpeed = 60.0f; const float projectileSpeed = PROJECTILE_VELOCITY;
const float lifeMs = 50000.0f; const float lifeMs = PROJECTILE_LIFE;
const float size = 0.5f; const float size = 0.5f;
Vector3f localForward = { 0,0,-1 }; Vector3f localForward = { 0,0,-1 };
@@ -1710,6 +1726,7 @@ namespace ZL
for (auto& p : projectiles) { for (auto& p : projectiles) {
if (!p->isActive()) { if (!p->isActive()) {
p->init(worldPos, worldVel, lifeMs, size, projectileTexture, renderer); p->init(worldPos, worldVel, lifeMs, size, projectileTexture, renderer);
p->projectileEmitter = SparkEmitter(projectileEmitter);
break; break;
} }
} }
@@ -1721,8 +1738,8 @@ namespace ZL
if (networkClient) { if (networkClient) {
auto pending = networkClient->getPendingProjectiles(); auto pending = networkClient->getPendingProjectiles();
if (!pending.empty()) { if (!pending.empty()) {
const float projectileSpeed = 60.0f; const float projectileSpeed = PROJECTILE_VELOCITY;
const float lifeMs = 5000.0f; const float lifeMs = PROJECTILE_LIFE;
const float size = 0.5f; const float size = 0.5f;
for (const auto& pi : pending) { for (const auto& pi : pending) {
const std::vector<Vector3f> localOffsets = { const std::vector<Vector3f> localOffsets = {
@@ -1747,6 +1764,7 @@ namespace ZL
for (auto& p : projectiles) { for (auto& p : projectiles) {
if (!p->isActive()) { if (!p->isActive()) {
p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer); p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
p->projectileEmitter = SparkEmitter(projectileEmitter);
break; break;
} }
} }
+1 -1
View File
@@ -96,7 +96,7 @@ namespace ZL {
std::shared_ptr<Texture> projectileTexture; std::shared_ptr<Texture> projectileTexture;
float projectileCooldownMs = 500.0f; float projectileCooldownMs = 500.0f;
int64_t lastProjectileFireTime = 0; int64_t lastProjectileFireTime = 0;
int maxProjectiles = 32; int maxProjectiles = 500;
std::vector<Vector3f> shipLocalEmissionPoints; std::vector<Vector3f> shipLocalEmissionPoints;
+17
View File
@@ -25,6 +25,23 @@ namespace ZL {
sparkQuad.data = VertexDataStruct(); sparkQuad.data = VertexDataStruct();
} }
SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom)
: particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints),
lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate),
isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions),
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(copyFrom.drawDataDirty),
sparkQuad(copyFrom.sparkQuad), texture(copyFrom.texture),
maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize),
biasX(copyFrom.biasX), speedRange(copyFrom.speedRange),
zSpeedRange(copyFrom.zSpeedRange),
scaleRange(copyFrom.scaleRange),
lifeTimeRange(copyFrom.lifeTimeRange),
shaderProgramName(copyFrom.shaderProgramName),
configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace)
{
}
SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate) SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate)
: emissionPoints(positions), emissionRate(rate), isActive(true), : emissionPoints(positions), emissionRate(rate), isActive(true),
drawDataDirty(true), maxParticles(positions.size() * 100), drawDataDirty(true), maxParticles(positions.size() * 100),
+2 -1
View File
@@ -41,7 +41,7 @@ namespace ZL {
float biasX; float biasX;
// Ranges (used when config supplies intervals) // Ranges (used when config supplies intervals)
struct FloatRange { float min; float max; }; struct FloatRange { float min=0; float max=0; };
FloatRange speedRange; // XY speed FloatRange speedRange; // XY speed
FloatRange zSpeedRange; // Z speed FloatRange zSpeedRange; // Z speed
FloatRange scaleRange; FloatRange scaleRange;
@@ -55,6 +55,7 @@ namespace ZL {
public: public:
SparkEmitter(); SparkEmitter();
SparkEmitter(const SparkEmitter& copyFrom);
SparkEmitter(const std::vector<Vector3f>& positions, float rate = 100.0f); SparkEmitter(const std::vector<Vector3f>& positions, float rate = 100.0f);
SparkEmitter(const std::vector<Vector3f>& positions, SparkEmitter(const std::vector<Vector3f>& positions,
std::shared_ptr<Texture> tex, std::shared_ptr<Texture> tex,
+247 -40
View File
@@ -184,21 +184,89 @@ namespace ZL {
std::shared_ptr<UiNode> parseNode(const json& j, Renderer& renderer, const std::string& zipFile) { std::shared_ptr<UiNode> parseNode(const json& j, Renderer& renderer, const std::string& zipFile) {
auto node = std::make_shared<UiNode>(); auto node = std::make_shared<UiNode>();
if (j.contains("type") && j["type"].is_string()) node->type = j["type"].get<std::string>();
if (j.contains("name") && j["name"].is_string()) node->name = j["name"].get<std::string>();
if (j.contains("x")) node->rect.x = j["x"].get<float>(); // 1. Определяем тип контейнера и ориентацию
if (j.contains("y")) node->rect.y = j["y"].get<float>(); std::string typeStr = j.value("type", "FrameLayout"); // По умолчанию FrameLayout
if (j.contains("width")) node->rect.w = j["width"].get<float>(); if (typeStr == "LinearLayout") {
if (j.contains("height")) node->rect.h = j["height"].get<float>(); node->layoutType = LayoutType::Linear;
}
else {
node->layoutType = LayoutType::Frame;
}
if (j.contains("orientation") && j["orientation"].is_string()) node->orientation = j["orientation"].get<std::string>(); if (j.contains("name")) node->name = j["name"].get<std::string>();
if (j.contains("spacing")) node->spacing = j["spacing"].get<float>();
if (node->type == "Button") { // 2. Читаем размеры во временные "локальные" поля
// Это критически важно: мы не пишем сразу в screenRect,
// так как LinearLayout их пересчитает.
node->localX = j.value("x", 0.0f);
node->localY = j.value("y", 0.0f);
if (j.contains("width")) {
if (j["width"].is_string() && j["width"] == "match_parent") {
node->width = -1.0f; // Наш маркер для match_parent
}
else {
node->width = j["width"].get<float>();
}
}
else
{
node->width = 0.0f;
}
if (j.contains("height")) {
if (j["height"].is_string() && j["height"] == "match_parent") {
node->height = -1.0f; // Наш маркер для match_parent
}
else {
node->height = j["height"].get<float>();
}
}
else
{
node->height = 0.0f;
}
// 3. Параметры компоновки
if (j.contains("orientation")) {
std::string orient = j["orientation"].get<std::string>();
node->orientation = (orient == "horizontal") ? Orientation::Horizontal : Orientation::Vertical;
}
node->spacing = j.value("spacing", 0.0f);
if (j.contains("horizontal_align")) {
std::string halign = j["horizontal_align"];
if (halign == "center") node->layoutSettings.hAlign = HorizontalAlign::Center;
else if (halign == "right") node->layoutSettings.hAlign = HorizontalAlign::Right;
}
if (j.contains("vertical_align")) {
std::string valign = j["vertical_align"];
if (valign == "center") node->layoutSettings.vAlign = VerticalAlign::Center;
else if (valign == "bottom") node->layoutSettings.vAlign = VerticalAlign::Bottom;
}
if (j.contains("horizontal_gravity")) {
std::string hg = j["horizontal_gravity"].get<std::string>();
if (hg == "right") node->layoutSettings.hGravity = HorizontalGravity::Right;
else node->layoutSettings.hGravity = HorizontalGravity::Left;
}
// Читаем Vertical Gravity
if (j.contains("vertical_gravity")) {
std::string vg = j["vertical_gravity"].get<std::string>();
if (vg == "bottom") node->layoutSettings.vGravity = VerticalGravity::Bottom;
else node->layoutSettings.vGravity = VerticalGravity::Top;
}
// Подготавливаем базовый rect для компонентов (кнопок и т.д.)
// На этапе парсинга мы даем им "желаемый" размер
UiRect initialRect = { node->localX, node->localY, node->width, node->height };
if (typeStr == "Button") {
auto btn = std::make_shared<UiButton>(); auto btn = std::make_shared<UiButton>();
btn->name = node->name; btn->name = node->name;
btn->rect = node->rect; btn->rect = initialRect;
if (!j.contains("textures") || !j["textures"].is_object()) { if (!j.contains("textures") || !j["textures"].is_object()) {
std::cerr << "UiManager: Button '" << btn->name << "' missing textures" << std::endl; std::cerr << "UiManager: Button '" << btn->name << "' missing textures" << std::endl;
@@ -225,10 +293,10 @@ namespace ZL {
node->button = btn; node->button = btn;
} }
else if (node->type == "Slider") { else if (typeStr == "Slider") {
auto s = std::make_shared<UiSlider>(); auto s = std::make_shared<UiSlider>();
s->name = node->name; s->name = node->name;
s->rect = node->rect; s->rect = initialRect;
if (!j.contains("textures") || !j["textures"].is_object()) { if (!j.contains("textures") || !j["textures"].is_object()) {
std::cerr << "UiManager: Slider '" << s->name << "' missing textures" << std::endl; std::cerr << "UiManager: Slider '" << s->name << "' missing textures" << std::endl;
@@ -261,10 +329,10 @@ namespace ZL {
node->slider = s; node->slider = s;
} }
else if (node->type == "TextField") { else if (typeStr == "TextField") {
auto tf = std::make_shared<UiTextField>(); auto tf = std::make_shared<UiTextField>();
tf->name = node->name; tf->name = node->name;
tf->rect = node->rect; tf->rect = initialRect;
if (j.contains("placeholder")) tf->placeholder = j["placeholder"].get<std::string>(); if (j.contains("placeholder")) tf->placeholder = j["placeholder"].get<std::string>();
if (j.contains("fontPath")) tf->fontPath = j["fontPath"].get<std::string>(); if (j.contains("fontPath")) tf->fontPath = j["fontPath"].get<std::string>();
@@ -331,11 +399,11 @@ namespace ZL {
} }
} }
if (node->type == "TextView") { if (typeStr == "TextView") {
auto tv = std::make_shared<UiTextView>(); auto tv = std::make_shared<UiTextView>();
tv->name = node->name;
tv->rect = node->rect;
tv->name = node->name;
tv->rect = initialRect;
if (j.contains("text")) tv->text = j["text"].get<std::string>(); if (j.contains("text")) tv->text = j["text"].get<std::string>();
if (j.contains("fontPath")) tv->fontPath = j["fontPath"].get<std::string>(); if (j.contains("fontPath")) tv->fontPath = j["fontPath"].get<std::string>();
if (j.contains("fontSize")) tv->fontSize = j["fontSize"].get<int>(); if (j.contains("fontSize")) tv->fontSize = j["fontSize"].get<int>();
@@ -400,6 +468,7 @@ namespace ZL {
throw std::runtime_error("Failed to load UI file: " + path); throw std::runtime_error("Failed to load UI file: " + path);
} }
root = parseNode(j["root"], renderer, zipFile); root = parseNode(j["root"], renderer, zipFile);
return root; return root;
@@ -407,7 +476,14 @@ namespace ZL {
void UiManager::replaceRoot(std::shared_ptr<UiNode> newRoot) { void UiManager::replaceRoot(std::shared_ptr<UiNode> newRoot) {
root = newRoot; root = newRoot;
layoutNode(root); layoutNode(
root,
0.0f, 0.0f, // parentX, parentY (экран начинается с 0,0)
Environment::projectionWidth, // parentW
Environment::projectionHeight, // parentH
root->localX, // finalLocalX
root->localY // finalLocalY
);
buttons.clear(); buttons.clear();
sliders.clear(); sliders.clear();
textViews.clear(); textViews.clear();
@@ -431,39 +507,170 @@ namespace ZL {
replaceRoot(newRoot); replaceRoot(newRoot);
} }
void UiManager::layoutNode(const std::shared_ptr<UiNode>& node) { void UiManager::layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY) {
for (auto& child : node->children) {
child->rect.x += node->rect.x;
child->rect.y += node->rect.y;
}
if (node->type == "LinearLayout") { node->screenRect.w = (node->width < 0) ? parentW : node->width;
std::string orient = node->orientation; node->screenRect.h = (node->height < 0) ? parentH : node->height;
std::transform(orient.begin(), orient.end(), orient.begin(), ::tolower);
float cursorX = node->rect.x; // ТЕПЕРЬ используем переданные координаты, а не node->localX напрямую
float cursorY = node->rect.y; node->screenRect.x = parentX + finalLocalX;
for (auto& child : node->children) { node->screenRect.y = parentY + finalLocalY;
if (orient == "horizontal") {
child->rect.x = cursorX; float currentW = node->screenRect.w;
child->rect.y = node->rect.y; float currentH = node->screenRect.h;
cursorX += child->rect.w + node->spacing;
if (node->layoutType == LayoutType::Linear) {
float totalContentWidth = 0;
float totalContentHeight = 0;
// Предварительный расчет занимаемого места всеми детьми
for (size_t i = 0; i < node->children.size(); ++i) {
if (node->orientation == Orientation::Vertical) {
totalContentHeight += node->children[i]->height;
if (i < node->children.size() - 1) totalContentHeight += node->spacing;
} }
else { else {
child->rect.x = node->rect.x; totalContentWidth += node->children[i]->width;
child->rect.y = cursorY; if (i < node->children.size() - 1) totalContentWidth += node->spacing;
cursorY += child->rect.h + node->spacing;
} }
layoutNode(child); }
float startX = 0;
float startY = currentH;
if (node->orientation == Orientation::Vertical) {
if (node->layoutSettings.vAlign == VerticalAlign::Center) {
startY = (currentH + totalContentHeight) / 2.0f;
}
else if (node->layoutSettings.vAlign == VerticalAlign::Bottom) {
startY = totalContentHeight;
}
}
// Горизонтальное выравнивание всего блока
if (node->orientation == Orientation::Horizontal) {
if (node->layoutSettings.hAlign == HorizontalAlign::Center) {
startX = (currentW - totalContentWidth) / 2.0f;
}
else if (node->layoutSettings.hAlign == HorizontalAlign::Right) {
startX = currentW - totalContentWidth;
}
}
float cursorX = startX;
float cursorY = startY;
for (auto& child : node->children) {
float childW = (child->width < 0) ? currentW : child->width;
float childH = (child->height < 0) ? currentH : child->height;
if (node->orientation == Orientation::Vertical) {
cursorY -= childH; // используем вычисленный childH
float childX = 0;
float freeSpaceX = currentW - childW;
if (node->layoutSettings.hAlign == HorizontalAlign::Center) childX = freeSpaceX / 2.0f;
else if (node->layoutSettings.hAlign == HorizontalAlign::Right) childX = freeSpaceX;
child->localX = childX;
child->localY = cursorY;
cursorY -= node->spacing;
}
else {
child->localX = cursorX;
// Вертикальное выравнивание внутри "строки" (Cross-axis alignment)
float childY = 0;
float freeSpaceY = currentH - childH;
if (node->layoutSettings.vAlign == VerticalAlign::Center) {
childY = freeSpaceY / 2.0f;
}
else if (node->layoutSettings.vAlign == VerticalAlign::Top) {
childY = freeSpaceY; // Прижимаем к верхнему краю (т.к. Y растет вверх)
}
else if (node->layoutSettings.vAlign == VerticalAlign::Bottom) {
childY = 0; // Прижимаем к нижнему краю
}
child->localY = childY;
// Сдвигаем курсор вправо для следующего элемента
cursorX += childW + node->spacing;
}
layoutNode(child, node->screenRect.x, node->screenRect.y, currentW, currentH, child->localX, child->localY);
} }
} }
else { else {
for (auto& child : node->children) { for (auto& child : node->children) {
layoutNode(child); float childW = (child->width < 0) ? currentW : child->width;
float childH = (child->height < 0) ? currentH : child->height;
float fLX = child->localX;
float fLY = child->localY;
if (child->layoutSettings.hGravity == HorizontalGravity::Right) {
fLX = currentW - childW - child->localX;
}
if (child->layoutSettings.vGravity == VerticalGravity::Top) {
fLY = currentH - childH - child->localY;
}
// Передаем рассчитанные fLX, fLY в рекурсию
layoutNode(child, node->screenRect.x, node->screenRect.y, currentW, currentH, fLX, fLY);
} }
} }
// Обновляем меши визуальных компонентов
syncComponentRects(node);
}
void UiManager::syncComponentRects(const std::shared_ptr<UiNode>& node) {
if (!node) return;
// 1. Обновляем кнопку
if (node->button) {
node->button->rect = node->screenRect;
// Если у кнопки есть анимационные смещения, они учитываются внутри buildMesh
// или при рендеринге через Uniform-переменные матрицы модели.
node->button->buildMesh();
}
// 2. Обновляем слайдер
if (node->slider) {
node->slider->rect = node->screenRect;
node->slider->buildTrackMesh();
node->slider->buildKnobMesh();
}
// 3. Обновляем текстовое поле (TextView)
if (node->textView) {
node->textView->rect = node->screenRect;
// Если в TextView реализован кэш меша для текста, его нужно обновить здесь
// node->textView->rebuildText();
}
// 4. Обновляем поле ввода (TextField)
if (node->textField) {
node->textField->rect = node->screenRect;
// Аналогично для курсора и фонового меша
}
}
void UiManager::updateAllLayouts() {
if (!root) return;
// Запускаем расчет от корня, передавая размеры экрана как "родительские"
layoutNode(
root,
0.0f, 0.0f, // parentX, parentY (экран начинается с 0,0)
Environment::projectionWidth, // parentW
Environment::projectionHeight, // parentH
root->localX, // finalLocalX
root->localY // finalLocalY
);
} }
void UiManager::collectButtonsAndSliders(const std::shared_ptr<UiNode>& node) { void UiManager::collectButtonsAndSliders(const std::shared_ptr<UiNode>& node) {
@@ -657,7 +864,7 @@ namespace ZL {
} }
void UiManager::draw(Renderer& renderer) { void UiManager::draw(Renderer& renderer) {
renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1); renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, -1, 1);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
+66 -5
View File
@@ -31,6 +31,48 @@ namespace ZL {
Pressed Pressed
}; };
enum class LayoutType {
Frame, // Позиционирование по X, Y
Linear // Автоматическое позиционирование
};
enum class Orientation {
Vertical,
Horizontal
};
enum class HorizontalAlign {
Left,
Center,
Right
};
enum class VerticalAlign {
Top,
Center,
Bottom
};
enum class HorizontalGravity {
Left,
Right
};
enum class VerticalGravity {
Bottom, // Обычно в OpenGL Y растет вверх, так что низ - это 0
Top
};
// В структуру или класс, отвечающий за LinearLayout (вероятно, это свойства UiNode)
struct LayoutSettings {
HorizontalAlign hAlign = HorizontalAlign::Left;
VerticalAlign vAlign = VerticalAlign::Top;
HorizontalGravity hGravity = HorizontalGravity::Left;
VerticalGravity vGravity = VerticalGravity::Top;
};
struct UiButton { struct UiButton {
std::string name; std::string name;
UiRect rect; UiRect rect;
@@ -111,21 +153,38 @@ namespace ZL {
}; };
struct UiNode { struct UiNode {
std::string type;
UiRect rect;
std::string name; std::string name;
LayoutType layoutType = LayoutType::Frame;
Orientation orientation = Orientation::Vertical;
float spacing = 0.0f;
LayoutSettings layoutSettings;
// Внутренние вычисленные координаты для OpenGL
// Именно их мы передаем в Vertex Buffer при buildMesh()
UiRect screenRect;
// Данные из JSON (желаемые размеры и смещения)
float localX = 0;
float localY = 0;
float width = 0;
float height = 0;
// Иерархия
std::vector<std::shared_ptr<UiNode>> children; std::vector<std::shared_ptr<UiNode>> children;
// Компоненты (только один из них обычно активен для ноды)
std::shared_ptr<UiButton> button; std::shared_ptr<UiButton> button;
std::shared_ptr<UiSlider> slider; std::shared_ptr<UiSlider> slider;
std::shared_ptr<UiTextView> textView; std::shared_ptr<UiTextView> textView;
std::shared_ptr<UiTextField> textField; std::shared_ptr<UiTextField> textField;
std::string orientation = "vertical";
float spacing = 0.0f;
// Анимации
struct AnimStep { struct AnimStep {
std::string type; std::string type;
float toX = 0.0f; float toX = 0.0f;
float toY = 0.0f; float toY = 0.0f;
float toScale = 1.0f; // Полезно добавить для UI
float durationMs = 0.0f; float durationMs = 0.0f;
std::string easing = "linear"; std::string easing = "linear";
}; };
@@ -200,9 +259,11 @@ namespace ZL {
bool startAnimationOnNode(const std::string& nodeName, const std::string& animName); bool startAnimationOnNode(const std::string& nodeName, const std::string& animName);
bool stopAnimationOnNode(const std::string& nodeName, const std::string& animName); bool stopAnimationOnNode(const std::string& nodeName, const std::string& animName);
bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb); bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb);
void updateAllLayouts();
private: private:
void layoutNode(const std::shared_ptr<UiNode>& node); void layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY);
void syncComponentRects(const std::shared_ptr<UiNode>& node);
void collectButtonsAndSliders(const std::shared_ptr<UiNode>& node); void collectButtonsAndSliders(const std::shared_ptr<UiNode>& node);
struct ActiveAnim { struct ActiveAnim {
+45 -22
View File
@@ -44,20 +44,34 @@ EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void
return EM_TRUE; return EM_TRUE;
} }
// Resize the canvas, notify SDL, and push a synthetic SDL_WINDOWEVENT_RESIZED static void applyResize(int logicalW, int logicalH) {
// so Game::update()'s existing handler updates Environment::width/height and clears caches. // Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
static void applyResize(int w, int h) { double dpr = emscripten_get_device_pixel_ratio();
if (w <= 0 || h <= 0) return;
// Resize the actual WebGL canvas — without this the rendered pixels stay at // Вычисляем реальные физические пиксели
// the original size no matter what Environment::width/height say. int physicalW = static_cast<int>(logicalW * dpr);
emscripten_set_canvas_element_size("#canvas", w, h); int physicalH = static_cast<int>(logicalH * dpr);
if (ZL::Environment::window)
SDL_SetWindowSize(ZL::Environment::window, w, h); // Устанавливаем размер внутреннего буфера канваса
emscripten_set_canvas_element_size("#canvas", physicalW, physicalH);
// Сообщаем SDL о новом размере.
// ВАЖНО: SDL2 в Emscripten ожидает здесь именно физические пиксели
// для корректной работы последующих вызовов glViewport.
if (ZL::Environment::window) {
SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH);
}
// Обновляем ваши внутренние переменные окружения
ZL::Environment::width = physicalW;
ZL::Environment::height = physicalH;
// Пушим событие, чтобы движок пересчитал матрицы проекции
SDL_Event e = {}; SDL_Event e = {};
e.type = SDL_WINDOWEVENT; e.type = SDL_WINDOWEVENT;
e.window.event = SDL_WINDOWEVENT_RESIZED; e.window.event = SDL_WINDOWEVENT_RESIZED;
e.window.data1 = w; e.window.data1 = physicalW;
e.window.data2 = h; e.window.data2 = physicalH;
SDL_PushEvent(&e); SDL_PushEvent(&e);
} }
@@ -69,17 +83,11 @@ EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*u
} }
EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) { EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) {
if (e->isFullscreen) { // Вместо window.innerWidth, попробуйте запросить размер целевого элемента
// e->screenWidth/screenHeight comes from screen.width/screen.height in JS, // так как после перехода в FS именно он растягивается на весь экран.
// which on mobile browsers returns physical pixels (e.g. 2340x1080), double clientW, clientH;
// causing the canvas to extend far off-screen. window.innerWidth/innerHeight emscripten_get_element_css_size("#canvas", &clientW, &clientH);
// always gives CSS logical pixels and is correct on both desktop and mobile. applyResize(clientW, clientH);
int w = EM_ASM_INT({ return window.innerWidth; });
int h = EM_ASM_INT({ return window.innerHeight; });
applyResize(w, h);
}
// Exiting fullscreen: the browser fires a window resize event next,
// which onWindowResized handles automatically.
return EM_FALSE; return EM_FALSE;
} }
@@ -226,6 +234,21 @@ int main(int argc, char *argv[]) {
// Keep Environment::width/height in sync when the canvas is resized. // Keep Environment::width/height in sync when the canvas is resized.
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized); emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized);
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged); emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged);
// 2. ИНИЦИАЛИЗАЦИЯ РАЗМЕРОВ:
// Получаем реальные размеры окна браузера на момент запуска
int canvasW = EM_ASM_INT({ return window.innerWidth; });
int canvasH = EM_ASM_INT({ return window.innerHeight; });
// Вызываем вашу функцию — она сама применит DPR, выставит физический размер
// канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции.
applyResize(canvasW, canvasH);
// 3. Создаем игру и вызываем setup (теперь проекция уже будет знать верный size)
g_game = new ZL::Game();
g_game->setup();
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
emscripten_set_main_loop(MainLoop, 0, 1); emscripten_set_main_loop(MainLoop, 0, 1);
#else #else
+8
View File
@@ -27,6 +27,14 @@ constexpr long long SERVER_DELAY = 0; //ms
constexpr long long CLIENT_DELAY = 500; //ms constexpr long long CLIENT_DELAY = 500; //ms
constexpr long long CUTOFF_TIME = 5000; //ms constexpr long long CUTOFF_TIME = 5000; //ms
constexpr float PROJECTILE_VELOCITY = 600.f;
constexpr float PROJECTILE_LIFE = 15000.f; //ms
const float projectileHitRadius = 1.5f * 5;
const float boxCollisionRadius = 2.0f * 5;
const float shipCollisionRadius = 15.0f * 5;
const float npcCollisionRadius = 5.0f * 5;
uint32_t fnv1a_hash(const std::string& data); uint32_t fnv1a_hash(const std::string& data);
struct ClientState { struct ClientState {
+3 -8
View File
@@ -21,8 +21,8 @@ namespace ZL {
std::random_device rd; std::random_device rd;
std::mt19937 gen(rd()); std::mt19937 gen(rd());
const float MIN_COORD = -100.0f; const float MIN_COORD = -1000.0f;
const float MAX_COORD = 100.0f; const float MAX_COORD = 1000.0f;
const float MIN_DISTANCE = 3.0f; const float MIN_DISTANCE = 3.0f;
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE; const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
const int MAX_ATTEMPTS = 1000; const int MAX_ATTEMPTS = 1000;
@@ -68,7 +68,7 @@ namespace ZL {
Eigen::Vector3f LocalClient::generateRandomPosition() { Eigen::Vector3f LocalClient::generateRandomPosition() {
std::random_device rd; std::random_device rd;
std::mt19937 gen(rd()); std::mt19937 gen(rd());
std::uniform_real_distribution<> distrib(-500.0, 500.0); std::uniform_real_distribution<> distrib(-5000.0, 5000.0);
return Eigen::Vector3f( return Eigen::Vector3f(
(float)distrib(gen), (float)distrib(gen),
@@ -238,11 +238,6 @@ namespace ZL {
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>( auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count(); std::chrono::system_clock::now().time_since_epoch()).count();
const float projectileHitRadius = 1.5f;
const float boxCollisionRadius = 2.0f;
const float shipCollisionRadius = 15.0f;
const float npcCollisionRadius = 5.0f;
std::vector<std::pair<size_t, size_t>> boxProjectileCollisions; std::vector<std::pair<size_t, size_t>> boxProjectileCollisions;
for (size_t bi = 0; bi < serverBoxes.size(); ++bi) { for (size_t bi = 0; bi < serverBoxes.size(); ++bi) {
+4 -3
View File
@@ -362,9 +362,10 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
// 4. Рендеринг // 4. Рендеринг
r->shaderManager.PushShader(shaderName); r->shaderManager.PushShader(shaderName);
// Матрица проекции (экрана) // Матрица проекции — используем виртуальные проекционные размеры,
float W = (float)Environment::width; // чтобы координаты текста были независимы от физического разрешения экрана.
float H = (float)Environment::height; float W = Environment::projectionWidth;
float H = Environment::projectionHeight;
Eigen::Matrix4f proj = Eigen::Matrix4f::Identity(); Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
proj(0, 0) = 2.0f / W; proj(0, 0) = 2.0f / W;
proj(1, 1) = 2.0f / H; proj(1, 1) = 2.0f / H;