Compare commits
8
Commits
7e5aa22fee
...
78046e5e2d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78046e5e2d | ||
|
|
26233f934f | ||
|
|
5fcb1d1234 | ||
|
|
70a617b688 | ||
|
|
bb0f584bf6 | ||
|
|
6b4b549b3c | ||
|
|
ffbecbbcde | ||
|
|
2728ca9646 |
No files matched your search
@@ -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.
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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": {
|
"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"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.
+145
-12
@@ -23,6 +23,15 @@ namespace http = beast::http;
|
|||||||
namespace websocket = beast::websocket;
|
namespace websocket = beast::websocket;
|
||||||
namespace net = boost::asio;
|
namespace net = boost::asio;
|
||||||
using tcp = net::ip::tcp;
|
using tcp = net::ip::tcp;
|
||||||
|
static constexpr float kWorldZOffset = 45000.0f;
|
||||||
|
static const Eigen::Vector3f kWorldOffset(0.0f, 0.0f, kWorldZOffset);
|
||||||
|
|
||||||
|
static constexpr float kShipRadius = 15.0f;
|
||||||
|
static constexpr float kSpawnShipMargin = 25.0f;
|
||||||
|
static constexpr float kSpawnBoxMargin = 15.0f;
|
||||||
|
static constexpr float kSpawnZJitter = 60.0f;
|
||||||
|
|
||||||
|
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
|
||||||
|
|
||||||
struct DeathInfo {
|
struct DeathInfo {
|
||||||
int targetId = -1;
|
int targetId = -1;
|
||||||
@@ -53,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 {
|
||||||
@@ -92,6 +101,10 @@ class Session : public std::enable_shared_from_this<Session> {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
ClientStateInterval timedClientStates;
|
ClientStateInterval timedClientStates;
|
||||||
|
bool joined_ = false;
|
||||||
|
|
||||||
|
bool hasReservedSpawn_ = false;
|
||||||
|
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
|
||||||
|
|
||||||
std::string nickname = "Player";
|
std::string nickname = "Player";
|
||||||
int shipType = 0;
|
int shipType = 0;
|
||||||
@@ -102,6 +115,9 @@ public:
|
|||||||
|
|
||||||
int get_id() const { return id_; }
|
int get_id() const { return id_; }
|
||||||
|
|
||||||
|
bool hasSpawnReserved() const { return hasReservedSpawn_; }
|
||||||
|
const Eigen::Vector3f& reservedSpawn() const { return reservedSpawn_; }
|
||||||
|
|
||||||
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
|
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
|
||||||
if (timedClientStates.canFetchClientStateAtTime(targetTime)) {
|
if (timedClientStates.canFetchClientStateAtTime(targetTime)) {
|
||||||
outState = timedClientStates.fetchClientStateAtTime(targetTime);
|
outState = timedClientStates.fetchClientStateAtTime(targetTime);
|
||||||
@@ -189,7 +205,11 @@ public:
|
|||||||
timer->expires_after(std::chrono::milliseconds(100));
|
timer->expires_after(std::chrono::milliseconds(100));
|
||||||
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
|
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
|
||||||
if (!ec) {
|
if (!ec) {
|
||||||
self->send_message("ID:" + std::to_string(self->id_));
|
auto now_tp = std::chrono::system_clock::now();
|
||||||
|
uint64_t now_ms = static_cast<uint64_t>(
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
|
||||||
|
|
||||||
|
self->send_message("ID:" + std::to_string(self->id_) + ":" + std::to_string(now_ms));
|
||||||
self->do_read();
|
self->do_read();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -281,7 +301,45 @@ private:
|
|||||||
|
|
||||||
this->nickname = nick;
|
this->nickname = nick;
|
||||||
this->shipType = sType;
|
this->shipType = sType;
|
||||||
|
this->joined_ = true;
|
||||||
|
|
||||||
|
auto now_tp = std::chrono::system_clock::now();
|
||||||
|
uint64_t now_ms = static_cast<uint64_t>(
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
|
||||||
|
|
||||||
|
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_);
|
||||||
|
this->hasReservedSpawn_ = true;
|
||||||
|
this->reservedSpawn_ = spawnPos;
|
||||||
|
|
||||||
|
ClientState st;
|
||||||
|
st.id = id_;
|
||||||
|
st.position = spawnPos;
|
||||||
|
st.rotation = Eigen::Matrix3f::Identity();
|
||||||
|
st.currentAngularVelocity = Eigen::Vector3f::Zero();
|
||||||
|
st.velocity = 0.0f;
|
||||||
|
st.selectedVelocity = 0;
|
||||||
|
st.discreteMag = 0.0f;
|
||||||
|
st.discreteAngle = -1;
|
||||||
|
st.lastUpdateServerTime = now_tp;
|
||||||
|
st.nickname = this->nickname;
|
||||||
|
st.shipType = this->shipType;
|
||||||
|
|
||||||
|
timedClientStates.add_state(st);
|
||||||
|
|
||||||
|
this->send_message(
|
||||||
|
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string eventMsg =
|
||||||
|
"EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||||
|
for (auto& session : g_sessions) {
|
||||||
|
if (session->get_id() == id_) continue;
|
||||||
|
session->send_message(eventMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
|
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
|
||||||
|
|
||||||
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
|
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
|
||||||
@@ -298,12 +356,16 @@ private:
|
|||||||
for (auto& session : g_sessions) {
|
for (auto& session : g_sessions) {
|
||||||
if (session->get_id() == this->id_) continue;
|
if (session->get_id() == this->id_) continue;
|
||||||
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
|
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
|
||||||
// Отправляем именно новому клиенту
|
|
||||||
this->send_message(otherInfo);
|
this->send_message(otherInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (type == "UPD") {
|
else if (type == "UPD") {
|
||||||
|
if (!joined_) {
|
||||||
|
std::cout << "Server: Ignoring UPD before JOIN from " << id_ << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> gd(g_dead_mutex);
|
std::lock_guard<std::mutex> gd(g_dead_mutex);
|
||||||
if (g_dead_players.find(id_) != g_dead_players.end()) {
|
if (g_dead_players.find(id_) != g_dead_players.end()) {
|
||||||
@@ -338,7 +400,13 @@ private:
|
|||||||
|
|
||||||
ClientState st;
|
ClientState st;
|
||||||
st.id = id_;
|
st.id = id_;
|
||||||
st.position = Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
|
||||||
|
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_);
|
||||||
|
st.position = spawnPos;
|
||||||
|
|
||||||
|
this->hasReservedSpawn_ = true;
|
||||||
|
this->reservedSpawn_ = spawnPos;
|
||||||
|
|
||||||
st.rotation = Eigen::Matrix3f::Identity();
|
st.rotation = Eigen::Matrix3f::Identity();
|
||||||
st.currentAngularVelocity = Eigen::Vector3f::Zero();
|
st.currentAngularVelocity = Eigen::Vector3f::Zero();
|
||||||
st.velocity = 0.0f;
|
st.velocity = 0.0f;
|
||||||
@@ -350,7 +418,9 @@ private:
|
|||||||
st.shipType = this->shipType;
|
st.shipType = this->shipType;
|
||||||
|
|
||||||
timedClientStates.add_state(st);
|
timedClientStates.add_state(st);
|
||||||
|
this->send_message(
|
||||||
|
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
|
||||||
|
);
|
||||||
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
|
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
|
||||||
broadcastToAll(respawnMsg);
|
broadcastToAll(respawnMsg);
|
||||||
|
|
||||||
@@ -436,6 +506,71 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId)
|
||||||
|
{
|
||||||
|
static thread_local std::mt19937 rng{ std::random_device{}() };
|
||||||
|
|
||||||
|
std::scoped_lock lock(g_boxes_mutex, g_sessions_mutex, g_dead_mutex);
|
||||||
|
|
||||||
|
auto isSafe = [&](const Eigen::Vector3f& pWorld) -> bool
|
||||||
|
{
|
||||||
|
for (const auto& box : g_serverBoxes) {
|
||||||
|
if (box.destroyed) continue;
|
||||||
|
|
||||||
|
Eigen::Vector3f boxWorld = box.position + kWorldOffset;
|
||||||
|
float minDist = kShipRadius + box.collisionRadius + kSpawnBoxMargin;
|
||||||
|
|
||||||
|
if ((pWorld - boxWorld).squaredNorm() < minDist * minDist)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& s : g_sessions) {
|
||||||
|
int pid = s->get_id();
|
||||||
|
if (pid == forPlayerId) continue;
|
||||||
|
if (g_dead_players.count(pid)) continue;
|
||||||
|
|
||||||
|
Eigen::Vector3f otherPos;
|
||||||
|
if (!s->timedClientStates.timedStates.empty()) {
|
||||||
|
otherPos = s->timedClientStates.timedStates.back().position;
|
||||||
|
}
|
||||||
|
else if (s->hasSpawnReserved()) {
|
||||||
|
otherPos = s->reservedSpawn();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float minDist = (kShipRadius * 2.0f) + kSpawnShipMargin;
|
||||||
|
if ((pWorld - otherPos).squaredNorm() < minDist * minDist)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const float radii[] = { 150.f, 250.f, 400.f, 650.f, 1000.f, 1600.f };
|
||||||
|
|
||||||
|
for (float r : radii) {
|
||||||
|
std::uniform_real_distribution<float> dxy(-r, r);
|
||||||
|
std::uniform_real_distribution<float> dz(-kSpawnZJitter, kSpawnZJitter);
|
||||||
|
|
||||||
|
for (int attempt = 0; attempt < 250; ++attempt) {
|
||||||
|
Eigen::Vector3f cand(
|
||||||
|
dxy(rng),
|
||||||
|
dxy(rng),
|
||||||
|
kWorldZOffset + dz(rng)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isSafe(cand))
|
||||||
|
return cand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int a = (forPlayerId % 10);
|
||||||
|
int b = ((forPlayerId / 10) % 10);
|
||||||
|
return Eigen::Vector3f(600.0f + a * 100.0f, -600.0f + b * 100.0f, kWorldZOffset);
|
||||||
|
}
|
||||||
|
|
||||||
void broadcastToAll(const std::string& message) {
|
void broadcastToAll(const std::string& message) {
|
||||||
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||||
for (const auto& session : g_sessions) {
|
for (const auto& session : g_sessions) {
|
||||||
@@ -564,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;
|
||||||
|
|
||||||
@@ -614,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;
|
||||||
|
|
||||||
@@ -705,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;
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
+72
-42
@@ -16,6 +16,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef NETWORK
|
#ifdef NETWORK
|
||||||
|
#include "network/WebSocketClientBase.h"
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
#include "network/WebSocketClientEmscripten.h"
|
#include "network/WebSocketClientEmscripten.h"
|
||||||
#else
|
#else
|
||||||
@@ -83,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();
|
||||||
@@ -99,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
|
||||||
@@ -141,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;
|
||||||
};
|
};
|
||||||
@@ -160,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);
|
||||||
@@ -169,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);
|
||||||
@@ -264,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,
|
||||||
@@ -350,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;
|
||||||
@@ -368,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);
|
||||||
@@ -397,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;
|
||||||
@@ -443,6 +448,31 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
networkClient->Poll();
|
networkClient->Poll();
|
||||||
|
#ifdef NETWORK
|
||||||
|
auto* wsBase = dynamic_cast<ZL::WebSocketClientBase*>(networkClient.get());
|
||||||
|
if (wsBase) {
|
||||||
|
auto spawns = wsBase->getPendingSpawns();
|
||||||
|
for (auto& st : spawns) {
|
||||||
|
if (st.id == wsBase->getClientId()) {
|
||||||
|
// применяем к локальному кораблю
|
||||||
|
ZL::Environment::shipState.position = st.position;
|
||||||
|
ZL::Environment::shipState.rotation = st.rotation;
|
||||||
|
|
||||||
|
// обнуляем движение чтобы не было рывков
|
||||||
|
ZL::Environment::shipState.currentAngularVelocity = Eigen::Vector3f::Zero();
|
||||||
|
ZL::Environment::shipState.velocity = 0.0f;
|
||||||
|
ZL::Environment::shipState.selectedVelocity = 0;
|
||||||
|
ZL::Environment::shipState.discreteMag = 0.0f;
|
||||||
|
ZL::Environment::shipState.discreteAngle = -1;
|
||||||
|
|
||||||
|
std::cout << "Game: Applied SPAWN at "
|
||||||
|
<< st.position.x() << ", "
|
||||||
|
<< st.position.y() << ", "
|
||||||
|
<< st.position.z() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
mainThreadHandler.processMainThreadTasks();
|
mainThreadHandler.processMainThreadTasks();
|
||||||
|
|
||||||
@@ -454,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);
|
||||||
|
|
||||||
@@ -482,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);
|
||||||
|
|
||||||
@@ -497,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
@@ -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
@@ -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,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
@@ -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
@@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -190,7 +190,28 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (msg.rfind("SPAWN:", 0) == 0) {
|
||||||
|
// SPAWN:playerId:serverTime:<14 полей>
|
||||||
|
if (parts.size() >= 3 + 14) {
|
||||||
|
try {
|
||||||
|
int pid = std::stoi(parts[1]);
|
||||||
|
uint64_t serverTime = std::stoull(parts[2]);
|
||||||
|
|
||||||
|
ClientState st;
|
||||||
|
st.id = pid;
|
||||||
|
std::chrono::system_clock::time_point tp{ std::chrono::milliseconds(serverTime) };
|
||||||
|
st.lastUpdateServerTime = tp;
|
||||||
|
|
||||||
|
// данные начинаются с parts[3]
|
||||||
|
st.handle_full_sync(parts, 3);
|
||||||
|
|
||||||
|
pendingSpawns_.push_back(st);
|
||||||
|
std::cout << "Client: SPAWN received for player " << pid << std::endl;
|
||||||
|
}
|
||||||
|
catch (...) {}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (msg.rfind("EVENT:", 0) == 0) {
|
if (msg.rfind("EVENT:", 0) == 0) {
|
||||||
//auto parts = split(msg, ':');
|
//auto parts = split(msg, ':');
|
||||||
if (parts.size() < 5) return; // EVENT:ID:TYPE:TIME:DATA...
|
if (parts.size() < 5) return; // EVENT:ID:TYPE:TIME:DATA...
|
||||||
@@ -334,6 +355,12 @@ namespace ZL {
|
|||||||
copy.swap(pendingBoxDestructions_);
|
copy.swap(pendingBoxDestructions_);
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ClientState> WebSocketClientBase::getPendingSpawns() {
|
||||||
|
std::vector<ClientState> copy;
|
||||||
|
copy.swap(pendingSpawns_);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -22,6 +22,7 @@ namespace ZL {
|
|||||||
std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
|
std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
|
||||||
int clientId = -1;
|
int clientId = -1;
|
||||||
int64_t timeOffset = 0;
|
int64_t timeOffset = 0;
|
||||||
|
std::vector<ClientState> pendingSpawns_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
int GetClientId() const override { return clientId; }
|
int GetClientId() const override { return clientId; }
|
||||||
@@ -48,6 +49,8 @@ namespace ZL {
|
|||||||
std::vector<DeathInfo> getPendingDeaths() override;
|
std::vector<DeathInfo> getPendingDeaths() override;
|
||||||
std::vector<int> getPendingRespawns() override;
|
std::vector<int> getPendingRespawns() override;
|
||||||
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
||||||
|
std::vector<ClientState> getPendingSpawns();
|
||||||
|
int getClientId() const { return clientId; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in new issue
Block a user