Merge remote-tracking branch 'origin/main' into ShipSpawn
This commit is contained in:
commit
78046e5e2d
37 files changed
+1172
-742
No files matched your search
@@ -1,100 +1,76 @@
|
||||
<!doctypehtml>
|
||||
<html lang=en-us>
|
||||
<!DOCTYPE html>
|
||||
<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>
|
||||
<meta charset=utf-8>
|
||||
<meta content="text/html; charset=utf-8" http-equiv=Content-Type>
|
||||
<title>Space Game</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: arial;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
background-color: #000;
|
||||
<script>
|
||||
var statusElement = document.getElementById("status");
|
||||
var canvas = document.getElementById("canvas");
|
||||
|
||||
var Module = {
|
||||
canvas: canvas,
|
||||
setStatus: function(text) {
|
||||
statusElement.innerHTML = text;
|
||||
statusElement.style.display = text ? 'block' : 'none';
|
||||
}
|
||||
};
|
||||
|
||||
.emscripten {
|
||||
padding-right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block
|
||||
// Кнопка Fullscreen
|
||||
document.getElementById('fs-button').addEventListener('click', function() {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen().catch(e => {
|
||||
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 {
|
||||
border: 1px solid #000
|
||||
}
|
||||
|
||||
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>
|
||||
</script>
|
||||
<script async src="space-game001.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,93 +1,85 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"root": {
|
||||
"type": "LinearLayout",
|
||||
"orientation": "vertical",
|
||||
"vertical_align": "center",
|
||||
"horizontal_align": "center",
|
||||
"spacing": 10,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"children": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "gameOverText",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"width": 600,
|
||||
"height": 80,
|
||||
"text": "0",
|
||||
"fontSize": 36,
|
||||
"color": [
|
||||
0,
|
||||
217,
|
||||
255,
|
||||
1
|
||||
],
|
||||
"align": "center"
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "restartButton",
|
||||
"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",
|
||||
"width": 382,
|
||||
"height": 56,
|
||||
"textures": {
|
||||
"normal": "resources/game_over/Secondarybutton.png",
|
||||
"hover": "resources/game_over/Secondarybutton.png",
|
||||
"pressed": "resources/game_over/Secondarybutton.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -1,141 +1,81 @@
|
||||
{
|
||||
"root": {
|
||||
"type": "FrameLayout",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"children": [
|
||||
{
|
||||
"type": "LinearLayout",
|
||||
"name": "settingsButtons",
|
||||
"orientation": "vertical",
|
||||
"spacing": 10,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 300,
|
||||
"children": [
|
||||
{
|
||||
"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": {
|
||||
"type": "LinearLayout",
|
||||
"orientation": "vertical",
|
||||
"vertical_align": "center",
|
||||
"horizontal_align": "center",
|
||||
"spacing": 10,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"children": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "titleBtn",
|
||||
"width": 254,
|
||||
"height": 35,
|
||||
"textures": {
|
||||
"normal": "resources/main_menu/title.png",
|
||||
"hover": "resources/main_menu/title.png",
|
||||
"pressed": "resources/main_menu/title.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "underlineBtn",
|
||||
"x": 516,
|
||||
"y": 465,
|
||||
"width": 168,
|
||||
"height": 44,
|
||||
"textures": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "underlineBtn",
|
||||
"width": 168,
|
||||
"height": 44,
|
||||
"textures": {
|
||||
"normal": "resources/main_menu/line.png",
|
||||
"hover": "resources/main_menu/line.png",
|
||||
"pressed": "resources/main_menu/line.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "subtitleBtn",
|
||||
"x": 528,
|
||||
"y": 455,
|
||||
"width": 144,
|
||||
"height": 11,
|
||||
"textures": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "subtitleBtn",
|
||||
"width": 144,
|
||||
"height": 11,
|
||||
"textures": {
|
||||
"normal": "resources/main_menu/subtitle.png",
|
||||
"hover": "resources/main_menu/subtitle.png",
|
||||
"pressed": "resources/main_menu/subtitle.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "singleButton",
|
||||
"x": 409,
|
||||
"y": 360,
|
||||
"width": 382,
|
||||
"height": 56,
|
||||
"textures": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "singleButton",
|
||||
"width": 382,
|
||||
"height": 56,
|
||||
"textures": {
|
||||
"normal": "resources/main_menu/single.png",
|
||||
"hover": "resources/main_menu/single.png",
|
||||
"pressed": "resources/main_menu/single.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "multiplayerButton",
|
||||
"x": 409,
|
||||
"y": 289,
|
||||
"width": 382,
|
||||
"height": 56,
|
||||
"textures": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "multiplayerButton",
|
||||
"width": 382,
|
||||
"height": 56,
|
||||
"textures": {
|
||||
"normal": "resources/main_menu/multi.png",
|
||||
"hover": "resources/main_menu/multi.png",
|
||||
"pressed": "resources/main_menu/multi.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "multiplayerButton2",
|
||||
"x": 409,
|
||||
"y": 218,
|
||||
"width": 382,
|
||||
"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": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "versionLabel",
|
||||
"width": 81,
|
||||
"height": 9,
|
||||
"textures": {
|
||||
"normal": "resources/main_menu/version.png",
|
||||
"hover": "resources/main_menu/version.png",
|
||||
"pressed": "resources/main_menu/version.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +1,59 @@
|
||||
{
|
||||
"root": {
|
||||
"name": "shipSelectionRoot",
|
||||
"type": "node",
|
||||
"children": [
|
||||
|
||||
{
|
||||
"type": "TextField",
|
||||
"name": "nicknameInput",
|
||||
"x": 400,
|
||||
"y": 150,
|
||||
"width": 400,
|
||||
"height": 50,
|
||||
"placeholder": "Enter your nickname",
|
||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||
"fontSize": 16,
|
||||
"maxLength": 256,
|
||||
"color": [122, 156, 198, 1],
|
||||
"placeholderColor": [122, 156, 198, 1],
|
||||
"backgroundColor": [15, 29, 51, 1],
|
||||
"borderColor": [15, 29, 51, 1]
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "spaceshipButton",
|
||||
"x": 300,
|
||||
"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"
|
||||
"root": {
|
||||
"type": "LinearLayout",
|
||||
"orientation": "vertical",
|
||||
"vertical_align": "center",
|
||||
"horizontal_align": "center",
|
||||
"spacing": 10,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"children": [
|
||||
{
|
||||
"type": "LinearLayout",
|
||||
"orientation": "horizontal",
|
||||
"vertical_align": "center",
|
||||
"horizontal_align": "center",
|
||||
"spacing": 10,
|
||||
"width": "match_parent",
|
||||
"height": 260,
|
||||
"children": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "spaceshipButton",
|
||||
"width": 256,
|
||||
"height": 256,
|
||||
"textures": {
|
||||
"normal": "resources/multiplayer_menu/ship_fighter.png",
|
||||
"hover": "resources/multiplayer_menu/ship_fighter_pressed.png",
|
||||
"pressed": "resources/multiplayer_menu/ship_fighter_pressed.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
@@ -1,194 +1,57 @@
|
||||
{
|
||||
"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": "FrameLayout",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"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": "Button",
|
||||
"name": "shootButton",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 150,
|
||||
"height": 150,
|
||||
"horizontal_gravity": "right",
|
||||
"vertical_gravity": "bottom",
|
||||
"textures": {
|
||||
"normal": "resources/shoot_normal.png",
|
||||
"hover": "resources/shoot_hover.png",
|
||||
"pressed": "resources/shoot_pressed.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "shootButton2",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 150,
|
||||
"height": 150,
|
||||
"horizontal_gravity": "left",
|
||||
"vertical_gravity": "bottom",
|
||||
"textures": {
|
||||
"normal": "resources/shoot_normal.png",
|
||||
"hover": "resources/shoot_hover.png",
|
||||
"pressed": "resources/shoot_pressed.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Slider",
|
||||
"name": "velocitySlider",
|
||||
"x": 10,
|
||||
"y": 200,
|
||||
"width": 80,
|
||||
"height": 300,
|
||||
"value": 0.0,
|
||||
"orientation": "vertical",
|
||||
"horizontal_gravity": "right",
|
||||
"vertical_gravity": "bottom",
|
||||
"textures": {
|
||||
"track": "resources/velocitySliderTexture.png",
|
||||
"knob": "resources/velocitySliderButton.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
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Binary file not shown.
+6
-8
@@ -62,7 +62,7 @@ struct Projectile {
|
||||
uint64_t spawnMs = 0;
|
||||
Eigen::Vector3f pos;
|
||||
Eigen::Vector3f vel;
|
||||
float lifeMs = 5000.0f;
|
||||
float lifeMs = PROJECTILE_LIFE;
|
||||
};
|
||||
|
||||
struct BoxDestroyedInfo {
|
||||
@@ -699,11 +699,12 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Tick: box-projectile collisions ---
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -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> lm(g_sessions_mutex);
|
||||
|
||||
const float shipCollisionRadius = 15.0f;
|
||||
const float boxCollisionRadius = 2.0f;
|
||||
|
||||
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
|
||||
if (g_serverBoxes[bi].destroyed) continue;
|
||||
|
||||
@@ -840,8 +838,8 @@ std::vector<ServerBox> generateServerBoxes(int count) {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
const float MIN_COORD = -100.0f;
|
||||
const float MAX_COORD = 100.0f;
|
||||
const float MIN_COORD = -1000.0f;
|
||||
const float MAX_COORD = 1000.0f;
|
||||
const float MIN_DISTANCE = 3.0f;
|
||||
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
|
||||
const int MAX_ATTEMPTS = 1000;
|
||||
|
||||
@@ -36,5 +36,25 @@ ClientState Environment::shipState;
|
||||
const float Environment::CONST_Z_NEAR = 5.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
|
||||
+8
-1
@@ -35,8 +35,15 @@ public:
|
||||
static const float CONST_Z_NEAR;
|
||||
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
|
||||
+45
-41
@@ -84,6 +84,8 @@ namespace ZL
|
||||
void Game::setup() {
|
||||
glContext = SDL_GL_CreateContext(ZL::Environment::window);
|
||||
|
||||
Environment::computeProjectionDimensions();
|
||||
|
||||
ZL::BindOpenGlFunctions();
|
||||
ZL::CheckGlError();
|
||||
renderer.InitOpenGL();
|
||||
@@ -100,7 +102,7 @@ namespace ZL
|
||||
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
|
||||
#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();
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
@@ -142,17 +144,14 @@ namespace ZL
|
||||
Environment::shipState.nickname = nickname;
|
||||
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);
|
||||
|
||||
#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;
|
||||
spaceGameStarted = 1;
|
||||
};
|
||||
@@ -161,8 +160,7 @@ namespace ZL
|
||||
Environment::shipState.nickname = nickname;
|
||||
Environment::shipState.shipType = shipType;
|
||||
|
||||
networkClient = std::make_unique<LocalClient>();
|
||||
#ifdef NETWORK
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
networkClient = std::make_unique<WebSocketClientEmscripten>();
|
||||
networkClient->Connect("localhost", 8081);
|
||||
@@ -170,19 +168,6 @@ namespace ZL
|
||||
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
|
||||
networkClient->Connect("localhost", 8081);
|
||||
#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) {
|
||||
std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType);
|
||||
@@ -265,8 +250,8 @@ namespace ZL
|
||||
renderer.EnableVertexAttribArray(vPositionName);
|
||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||
|
||||
float width = Environment::width;
|
||||
float height = Environment::height;
|
||||
float width = Environment::projectionWidth;
|
||||
float height = Environment::projectionHeight;
|
||||
|
||||
renderer.PushProjectionMatrix(
|
||||
0, width,
|
||||
@@ -351,16 +336,19 @@ namespace ZL
|
||||
if (event.type == SDL_QUIT) {
|
||||
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::height = event.window.data2;
|
||||
Environment::computeProjectionDimensions();
|
||||
menuManager.uiManager.updateAllLayouts();
|
||||
std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl;
|
||||
|
||||
space.clearTextRendererCache();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
|
||||
Environment::exitGameLoop = true;
|
||||
@@ -369,22 +357,38 @@ namespace ZL
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (event.type == SDL_FINGERDOWN) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::width);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::height);
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleDown(mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERUP) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::width);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::height);
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleUp(mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERMOTION) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::width);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::height);
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleMotion(mx, my);
|
||||
}
|
||||
#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 my = event.button.y;
|
||||
handleDown(mx, my);
|
||||
@@ -398,7 +402,7 @@ namespace ZL
|
||||
int mx = event.motion.x;
|
||||
int my = event.motion.y;
|
||||
handleMotion(mx, my);
|
||||
}
|
||||
}*/
|
||||
|
||||
if (event.type == SDL_MOUSEWHEEL) {
|
||||
static const float zoomstep = 2.0f;
|
||||
@@ -480,7 +484,7 @@ namespace ZL
|
||||
void Game::handleDown(int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::height - my;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
menuManager.uiManager.onMouseDown(uiX, uiY);
|
||||
|
||||
@@ -508,7 +512,7 @@ namespace ZL
|
||||
void Game::handleUp(int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::height - my;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
menuManager.uiManager.onMouseUp(uiX, uiY);
|
||||
|
||||
@@ -523,7 +527,7 @@ namespace ZL
|
||||
void Game::handleMotion(int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::height - my;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
menuManager.uiManager.onMouseMove(uiX, uiY);
|
||||
|
||||
|
||||
+6
-4
@@ -96,6 +96,7 @@ namespace ZL {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
|
||||
onFirePressed();
|
||||
});
|
||||
@@ -103,6 +104,7 @@ namespace ZL {
|
||||
onFirePressed();
|
||||
});
|
||||
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
|
||||
|
||||
int newVel = roundf(value * 10);
|
||||
if (newVel > 2)
|
||||
{
|
||||
@@ -183,8 +185,8 @@ namespace ZL {
|
||||
}
|
||||
});
|
||||
|
||||
uiManager.setButtonCallback("multiplayerButton2", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) {
|
||||
/*std::cerr << "Multiplayer button pressed → opening multiplayer menu\n";
|
||||
/*uiManager.setButtonCallback("multiplayerButton2", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) {
|
||||
std::cerr << "Multiplayer button pressed → opening multiplayer menu\n";
|
||||
|
||||
uiManager.startAnimationOnNode("playButton", "buttonsExit");
|
||||
uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
|
||||
@@ -219,7 +221,7 @@ namespace ZL {
|
||||
}
|
||||
else {
|
||||
std::cerr << "Failed to load multiplayer menu\n";
|
||||
}*/
|
||||
}
|
||||
std::cerr << "Single button pressed: " << name << " -> open ship selection UI\n";
|
||||
if (!shipSelectionRoot) {
|
||||
std::cerr << "Failed to load ship selection UI\n";
|
||||
@@ -255,7 +257,7 @@ namespace ZL {
|
||||
uiManager.setButtonCallback("exitButton", [](const std::string& name) {
|
||||
std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n";
|
||||
Environment::exitGameLoop = true;
|
||||
});
|
||||
});*/
|
||||
}
|
||||
|
||||
void MenuManager::showGameOver(int score)
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ namespace ZL {
|
||||
}
|
||||
|
||||
void Projectile::rebuildMesh(Renderer&) {
|
||||
float half = size * 0.5f;
|
||||
float half = 10 * size * 0.5f;
|
||||
|
||||
mesh.data.PositionData.clear();
|
||||
mesh.data.TexCoordData.clear();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "render/Renderer.h"
|
||||
#include "render/TextureManager.h"
|
||||
#include <memory>
|
||||
#include "SparkEmitter.h"
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@@ -19,6 +20,8 @@ namespace ZL {
|
||||
|
||||
Vector3f getPosition() const { return pos; }
|
||||
void deactivate() { active = false; }
|
||||
|
||||
SparkEmitter projectileEmitter;
|
||||
private:
|
||||
Vector3f pos;
|
||||
Vector3f vel;
|
||||
|
||||
+56
-38
@@ -159,15 +159,15 @@ namespace ZL
|
||||
|
||||
// В пределах экрана?
|
||||
// (можно оставить, можно клампить)
|
||||
float sx = (ndc.x() * 0.5f + 0.5f) * Environment::width;
|
||||
float sy = (ndc.y() * 0.5f + 0.5f) * Environment::height;
|
||||
float sx = (ndc.x() * 0.5f + 0.5f) * Environment::projectionWidth;
|
||||
float sy = (ndc.y() * 0.5f + 0.5f) * Environment::projectionHeight;
|
||||
|
||||
outX = sx;
|
||||
outY = sy;
|
||||
|
||||
// Можно отсеять те, что вне:
|
||||
if (sx < -200 || sx > Environment::width + 200) return false;
|
||||
if (sy < -200 || sy > Environment::height + 200) return false;
|
||||
if (sx < -200 || sx > Environment::projectionWidth + 200) return false;
|
||||
if (sy < -200 || sy > Environment::projectionHeight + 200) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -296,12 +296,12 @@ namespace ZL
|
||||
|
||||
cubemapTexture = std::make_shared<Texture>(
|
||||
std::array<TextureDataStruct, 6>{
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE)
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE)
|
||||
});
|
||||
|
||||
|
||||
@@ -335,6 +335,7 @@ namespace ZL
|
||||
cargo.AssignFrom(cargoBase);
|
||||
cargo.RefreshVBO();
|
||||
|
||||
//projectileTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/spark2.png", CONST_ZIP_FILE));
|
||||
|
||||
//Boxes
|
||||
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");
|
||||
std::cerr << "[Crosshair] loaded=" << crosshairCfgLoaded
|
||||
std::cout << "[Crosshair] loaded=" << crosshairCfgLoaded
|
||||
<< " enabled=" << crosshairCfg.enabled
|
||||
<< " w=" << Environment::width << " h=" << Environment::height
|
||||
<< " alpha=" << crosshairCfg.alpha
|
||||
@@ -490,13 +491,25 @@ namespace ZL
|
||||
glEnable(GL_BLEND);
|
||||
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) {
|
||||
if (p && p->isActive()) {
|
||||
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) {
|
||||
renderer.PushMatrix();
|
||||
@@ -859,8 +872,8 @@ namespace ZL
|
||||
|
||||
// если ничего не изменилось — не трогаем VBO
|
||||
if (crosshairMeshValid &&
|
||||
crosshairLastW == Environment::width &&
|
||||
crosshairLastH == Environment::height &&
|
||||
crosshairLastW == Environment::projectionWidth &&
|
||||
crosshairLastH == Environment::projectionHeight &&
|
||||
std::abs(crosshairLastAlpha - crosshairCfg.alpha) < 1e-6f &&
|
||||
std::abs(crosshairLastThickness - crosshairCfg.thicknessPx) < 1e-6f &&
|
||||
std::abs(crosshairLastGap - crosshairCfg.gapPx) < 1e-6f &&
|
||||
@@ -869,18 +882,18 @@ namespace ZL
|
||||
return;
|
||||
}
|
||||
|
||||
crosshairLastW = Environment::width;
|
||||
crosshairLastH = Environment::height;
|
||||
crosshairLastW = Environment::projectionWidth;
|
||||
crosshairLastH = Environment::projectionHeight;
|
||||
crosshairLastAlpha = crosshairCfg.alpha;
|
||||
crosshairLastThickness = crosshairCfg.thicknessPx;
|
||||
crosshairLastGap = crosshairCfg.gapPx;
|
||||
crosshairLastScaleMul = crosshairCfg.scaleMul;
|
||||
|
||||
float cx = Environment::width * 0.5f;
|
||||
float cy = Environment::height * 0.5f;
|
||||
float cx = Environment::projectionWidth * 0.5f;
|
||||
float cy = Environment::projectionHeight * 0.5f;
|
||||
|
||||
// масштаб от 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;
|
||||
|
||||
float thickness = crosshairCfg.thicknessPx * scale;
|
||||
@@ -940,7 +953,7 @@ namespace ZL
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
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.LoadIdentity();
|
||||
|
||||
@@ -1124,7 +1137,7 @@ namespace ZL
|
||||
|
||||
// Lead Indicator
|
||||
// скорость пули (как в 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 };
|
||||
@@ -1187,15 +1200,15 @@ namespace ZL
|
||||
|
||||
// 4) Настройки стиля
|
||||
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 слой
|
||||
|
||||
// 5) Если цель в кадре: рисуем скобки
|
||||
if (onScreen)
|
||||
{
|
||||
// перевод NDC -> экран (в пикселях)
|
||||
float sx = (ndcX * 0.5f + 0.5f) * Environment::width;
|
||||
float sy = (ndcY * 0.5f + 0.5f) * Environment::height;
|
||||
float sx = (ndcX * 0.5f + 0.5f) * Environment::projectionWidth;
|
||||
float sy = (ndcY * 0.5f + 0.5f) * Environment::projectionHeight;
|
||||
|
||||
// анимация “снаружи внутрь”
|
||||
// targetAcquireAnim растёт к 1, быстро (похоже на захват)
|
||||
@@ -1234,7 +1247,7 @@ namespace ZL
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
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.LoadIdentity();
|
||||
|
||||
@@ -1248,8 +1261,8 @@ namespace ZL
|
||||
float leadNdcX, leadNdcY, leadNdcZ, leadClipW;
|
||||
if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) {
|
||||
if (leadNdcX >= -1 && leadNdcX <= 1 && leadNdcY >= -1 && leadNdcY <= 1) {
|
||||
float lx = (leadNdcX * 0.5f + 0.5f) * Environment::width;
|
||||
float ly = (leadNdcY * 0.5f + 0.5f) * Environment::height;
|
||||
float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
|
||||
float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
|
||||
|
||||
float distLead = (Environment::shipState.position - leadWorld).norm();
|
||||
float r = 30.0f / (distLead * 0.01f + 1.0f);
|
||||
@@ -1323,8 +1336,8 @@ namespace ZL
|
||||
float edgeNdcX = dirX * k;
|
||||
float edgeNdcY = dirY * k;
|
||||
|
||||
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::width;
|
||||
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::height;
|
||||
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
|
||||
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
|
||||
|
||||
float bob = std::sin(t * 6.0f) * 6.0f;
|
||||
edgeX += dirX * bob;
|
||||
@@ -1367,7 +1380,7 @@ namespace ZL
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
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.LoadIdentity();
|
||||
|
||||
@@ -1540,22 +1553,25 @@ namespace ZL
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vector3f> projCameraPoints;
|
||||
|
||||
for (const auto& p : projectiles) {
|
||||
if (p && p->isActive()) {
|
||||
Vector3f worldPos = p->getPosition();
|
||||
Vector3f rel = worldPos - Environment::shipState.position;
|
||||
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()) {
|
||||
projectileEmitter.setEmissionPoints(projCameraPoints);
|
||||
projectileEmitter.emit();
|
||||
}
|
||||
else {
|
||||
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||||
}
|
||||
}*/
|
||||
|
||||
std::vector<Vector3f> shipCameraPoints;
|
||||
for (const auto& lp : shipLocalEmissionPoints) {
|
||||
@@ -1567,7 +1583,7 @@ namespace ZL
|
||||
}
|
||||
|
||||
sparkEmitter.update(static_cast<float>(delta));
|
||||
projectileEmitter.update(static_cast<float>(delta));
|
||||
//projectileEmitter.update(static_cast<float>(delta));
|
||||
|
||||
explosionEmitter.update(static_cast<float>(delta));
|
||||
if (showExplosion) {
|
||||
@@ -1696,8 +1712,8 @@ namespace ZL
|
||||
Vector3f{ 1.5f, 0.9f - 6.f, 5.0f }
|
||||
};
|
||||
|
||||
const float projectileSpeed = 60.0f;
|
||||
const float lifeMs = 50000.0f;
|
||||
const float projectileSpeed = PROJECTILE_VELOCITY;
|
||||
const float lifeMs = PROJECTILE_LIFE;
|
||||
const float size = 0.5f;
|
||||
|
||||
Vector3f localForward = { 0,0,-1 };
|
||||
@@ -1710,6 +1726,7 @@ namespace ZL
|
||||
for (auto& p : projectiles) {
|
||||
if (!p->isActive()) {
|
||||
p->init(worldPos, worldVel, lifeMs, size, projectileTexture, renderer);
|
||||
p->projectileEmitter = SparkEmitter(projectileEmitter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1721,8 +1738,8 @@ namespace ZL
|
||||
if (networkClient) {
|
||||
auto pending = networkClient->getPendingProjectiles();
|
||||
if (!pending.empty()) {
|
||||
const float projectileSpeed = 60.0f;
|
||||
const float lifeMs = 5000.0f;
|
||||
const float projectileSpeed = PROJECTILE_VELOCITY;
|
||||
const float lifeMs = PROJECTILE_LIFE;
|
||||
const float size = 0.5f;
|
||||
for (const auto& pi : pending) {
|
||||
const std::vector<Vector3f> localOffsets = {
|
||||
@@ -1747,6 +1764,7 @@ namespace ZL
|
||||
for (auto& p : projectiles) {
|
||||
if (!p->isActive()) {
|
||||
p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
|
||||
p->projectileEmitter = SparkEmitter(projectileEmitter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ namespace ZL {
|
||||
std::shared_ptr<Texture> projectileTexture;
|
||||
float projectileCooldownMs = 500.0f;
|
||||
int64_t lastProjectileFireTime = 0;
|
||||
int maxProjectiles = 32;
|
||||
int maxProjectiles = 500;
|
||||
std::vector<Vector3f> shipLocalEmissionPoints;
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,23 @@ namespace ZL {
|
||||
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)
|
||||
: emissionPoints(positions), emissionRate(rate), isActive(true),
|
||||
drawDataDirty(true), maxParticles(positions.size() * 100),
|
||||
|
||||
+2
-1
@@ -41,7 +41,7 @@ namespace ZL {
|
||||
float biasX;
|
||||
|
||||
// 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 zSpeedRange; // Z speed
|
||||
FloatRange scaleRange;
|
||||
@@ -55,6 +55,7 @@ namespace ZL {
|
||||
|
||||
public:
|
||||
SparkEmitter();
|
||||
SparkEmitter(const SparkEmitter& copyFrom);
|
||||
SparkEmitter(const std::vector<Vector3f>& positions, float rate = 100.0f);
|
||||
SparkEmitter(const std::vector<Vector3f>& positions,
|
||||
std::shared_ptr<Texture> tex,
|
||||
|
||||
+246
-39
@@ -184,21 +184,89 @@ namespace ZL {
|
||||
|
||||
std::shared_ptr<UiNode> parseNode(const json& j, Renderer& renderer, const std::string& zipFile) {
|
||||
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>();
|
||||
if (j.contains("y")) node->rect.y = j["y"].get<float>();
|
||||
if (j.contains("width")) node->rect.w = j["width"].get<float>();
|
||||
if (j.contains("height")) node->rect.h = j["height"].get<float>();
|
||||
// 1. Определяем тип контейнера и ориентацию
|
||||
std::string typeStr = j.value("type", "FrameLayout"); // По умолчанию FrameLayout
|
||||
if (typeStr == "LinearLayout") {
|
||||
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("spacing")) node->spacing = j["spacing"].get<float>();
|
||||
if (j.contains("name")) node->name = j["name"].get<std::string>();
|
||||
|
||||
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>();
|
||||
btn->name = node->name;
|
||||
btn->rect = node->rect;
|
||||
btn->rect = initialRect;
|
||||
|
||||
if (!j.contains("textures") || !j["textures"].is_object()) {
|
||||
std::cerr << "UiManager: Button '" << btn->name << "' missing textures" << std::endl;
|
||||
@@ -225,10 +293,10 @@ namespace ZL {
|
||||
|
||||
node->button = btn;
|
||||
}
|
||||
else if (node->type == "Slider") {
|
||||
else if (typeStr == "Slider") {
|
||||
auto s = std::make_shared<UiSlider>();
|
||||
s->name = node->name;
|
||||
s->rect = node->rect;
|
||||
s->rect = initialRect;
|
||||
|
||||
if (!j.contains("textures") || !j["textures"].is_object()) {
|
||||
std::cerr << "UiManager: Slider '" << s->name << "' missing textures" << std::endl;
|
||||
@@ -261,10 +329,10 @@ namespace ZL {
|
||||
|
||||
node->slider = s;
|
||||
}
|
||||
else if (node->type == "TextField") {
|
||||
else if (typeStr == "TextField") {
|
||||
auto tf = std::make_shared<UiTextField>();
|
||||
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("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>();
|
||||
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("fontPath")) tv->fontPath = j["fontPath"].get<std::string>();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
root = parseNode(j["root"], renderer, zipFile);
|
||||
|
||||
return root;
|
||||
@@ -407,7 +476,14 @@ namespace ZL {
|
||||
|
||||
void UiManager::replaceRoot(std::shared_ptr<UiNode> 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();
|
||||
sliders.clear();
|
||||
textViews.clear();
|
||||
@@ -432,38 +508,169 @@ namespace ZL {
|
||||
}
|
||||
|
||||
|
||||
void UiManager::layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY) {
|
||||
|
||||
void UiManager::layoutNode(const std::shared_ptr<UiNode>& node) {
|
||||
for (auto& child : node->children) {
|
||||
child->rect.x += node->rect.x;
|
||||
child->rect.y += node->rect.y;
|
||||
}
|
||||
node->screenRect.w = (node->width < 0) ? parentW : node->width;
|
||||
node->screenRect.h = (node->height < 0) ? parentH : node->height;
|
||||
|
||||
if (node->type == "LinearLayout") {
|
||||
std::string orient = node->orientation;
|
||||
std::transform(orient.begin(), orient.end(), orient.begin(), ::tolower);
|
||||
// ТЕПЕРЬ используем переданные координаты, а не node->localX напрямую
|
||||
node->screenRect.x = parentX + finalLocalX;
|
||||
node->screenRect.y = parentY + finalLocalY;
|
||||
|
||||
float cursorX = node->rect.x;
|
||||
float cursorY = node->rect.y;
|
||||
for (auto& child : node->children) {
|
||||
if (orient == "horizontal") {
|
||||
child->rect.x = cursorX;
|
||||
child->rect.y = node->rect.y;
|
||||
cursorX += child->rect.w + node->spacing;
|
||||
float currentW = node->screenRect.w;
|
||||
float currentH = node->screenRect.h;
|
||||
|
||||
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 {
|
||||
child->rect.x = node->rect.x;
|
||||
child->rect.y = cursorY;
|
||||
cursorY += child->rect.h + node->spacing;
|
||||
totalContentWidth += node->children[i]->width;
|
||||
if (i < node->children.size() - 1) totalContentWidth += 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 {
|
||||
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) {
|
||||
@@ -657,7 +864,7 @@ namespace ZL {
|
||||
}
|
||||
|
||||
void UiManager::draw(Renderer& renderer) {
|
||||
renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1);
|
||||
renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, -1, 1);
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
|
||||
|
||||
+66
-5
@@ -31,6 +31,48 @@ namespace ZL {
|
||||
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 {
|
||||
std::string name;
|
||||
UiRect rect;
|
||||
@@ -111,21 +153,38 @@ namespace ZL {
|
||||
};
|
||||
|
||||
struct UiNode {
|
||||
std::string type;
|
||||
UiRect rect;
|
||||
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::shared_ptr<UiButton> button;
|
||||
std::shared_ptr<UiSlider> slider;
|
||||
std::shared_ptr<UiTextView> textView;
|
||||
std::shared_ptr<UiTextField> textField;
|
||||
std::string orientation = "vertical";
|
||||
float spacing = 0.0f;
|
||||
|
||||
// Анимации
|
||||
struct AnimStep {
|
||||
std::string type;
|
||||
float toX = 0.0f;
|
||||
float toY = 0.0f;
|
||||
float toScale = 1.0f; // Полезно добавить для UI
|
||||
float durationMs = 0.0f;
|
||||
std::string easing = "linear";
|
||||
};
|
||||
@@ -200,9 +259,11 @@ namespace ZL {
|
||||
bool startAnimationOnNode(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);
|
||||
void updateAllLayouts();
|
||||
|
||||
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);
|
||||
|
||||
struct ActiveAnim {
|
||||
|
||||
+45
-22
@@ -44,20 +44,34 @@ EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void
|
||||
return EM_TRUE;
|
||||
}
|
||||
|
||||
// Resize the canvas, notify SDL, and push a synthetic SDL_WINDOWEVENT_RESIZED
|
||||
// so Game::update()'s existing handler updates Environment::width/height and clears caches.
|
||||
static void applyResize(int w, int h) {
|
||||
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.
|
||||
emscripten_set_canvas_element_size("#canvas", w, h);
|
||||
if (ZL::Environment::window)
|
||||
SDL_SetWindowSize(ZL::Environment::window, w, h);
|
||||
static void applyResize(int logicalW, int logicalH) {
|
||||
// Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
|
||||
double dpr = emscripten_get_device_pixel_ratio();
|
||||
|
||||
// Вычисляем реальные физические пиксели
|
||||
int physicalW = static_cast<int>(logicalW * dpr);
|
||||
int physicalH = static_cast<int>(logicalH * dpr);
|
||||
|
||||
// Устанавливаем размер внутреннего буфера канваса
|
||||
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 = {};
|
||||
e.type = SDL_WINDOWEVENT;
|
||||
e.window.event = SDL_WINDOWEVENT_RESIZED;
|
||||
e.window.data1 = w;
|
||||
e.window.data2 = h;
|
||||
e.window.data1 = physicalW;
|
||||
e.window.data2 = physicalH;
|
||||
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*/) {
|
||||
if (e->isFullscreen) {
|
||||
// e->screenWidth/screenHeight comes from screen.width/screen.height in JS,
|
||||
// which on mobile browsers returns physical pixels (e.g. 2340x1080),
|
||||
// causing the canvas to extend far off-screen. window.innerWidth/innerHeight
|
||||
// always gives CSS logical pixels and is correct on both desktop and mobile.
|
||||
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.
|
||||
// Вместо window.innerWidth, попробуйте запросить размер целевого элемента
|
||||
// так как после перехода в FS именно он растягивается на весь экран.
|
||||
double clientW, clientH;
|
||||
emscripten_get_element_css_size("#canvas", &clientW, &clientH);
|
||||
applyResize(clientW, clientH);
|
||||
return EM_FALSE;
|
||||
}
|
||||
|
||||
@@ -227,6 +235,21 @@ int main(int argc, char *argv[]) {
|
||||
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized);
|
||||
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);
|
||||
#else
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
|
||||
|
||||
@@ -27,6 +27,14 @@ constexpr long long SERVER_DELAY = 0; //ms
|
||||
constexpr long long CLIENT_DELAY = 500; //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);
|
||||
|
||||
struct ClientState {
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace ZL {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
const float MIN_COORD = -100.0f;
|
||||
const float MAX_COORD = 100.0f;
|
||||
const float MIN_COORD = -1000.0f;
|
||||
const float MAX_COORD = 1000.0f;
|
||||
const float MIN_DISTANCE = 3.0f;
|
||||
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
|
||||
const int MAX_ATTEMPTS = 1000;
|
||||
@@ -68,7 +68,7 @@ namespace ZL {
|
||||
Eigen::Vector3f LocalClient::generateRandomPosition() {
|
||||
std::random_device 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(
|
||||
(float)distrib(gen),
|
||||
@@ -238,11 +238,6 @@ namespace ZL {
|
||||
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
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;
|
||||
|
||||
for (size_t bi = 0; bi < serverBoxes.size(); ++bi) {
|
||||
|
||||
@@ -362,9 +362,10 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
|
||||
// 4. Рендеринг
|
||||
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();
|
||||
proj(0, 0) = 2.0f / W;
|
||||
proj(1, 1) = 2.0f / H;
|
||||
|
||||
Reference in new issue
Block a user