From 98e955aad96bbd4652c68e44a65fc66bc2c06756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Untersch=C3=BCtz?= Date: Sun, 4 Jan 2026 19:43:09 +0100 Subject: [PATCH] Add analog joystick support with fine control adjustments, improve prediction smoothing, reduce correction thresholds, and enhance lobby and overlay UI responsiveness. --- cmd/client/game_render.go | 32 ++++++--- cmd/client/main.go | 1 + cmd/client/prediction.go | 12 +++- cmd/client/web/game.js | 7 ++ cmd/client/web/index.html | 20 +++--- cmd/client/web/style.css | 140 +++++++++++++++++++++++++++++++------- 6 files changed, 164 insertions(+), 48 deletions(-) diff --git a/cmd/client/game_render.go b/cmd/client/game_render.go index c45abd9..db4c98d 100644 --- a/cmd/client/game_render.go +++ b/cmd/client/game_render.go @@ -39,11 +39,22 @@ func (g *Game) UpdateGame() { joyDir := 0.0 if g.joyActive { diffX := g.joyStickX - g.joyBaseX - if diffX < -20 { - joyDir = -1 + maxDist := 60.0 // Muss mit handleTouchInput() übereinstimmen + + // Analoger Wert zwischen -1.0 und 1.0 + joyDir = diffX / maxDist + + // Clamp zwischen -1 und 1 + if joyDir < -1.0 { + joyDir = -1.0 } - if diffX > 20 { - joyDir = 1 + if joyDir > 1.0 { + joyDir = 1.0 + } + + // Deadzone für bessere Kontrolle + if joyDir > -0.15 && joyDir < 0.15 { + joyDir = 0 } } @@ -52,10 +63,11 @@ func (g *Game) UpdateGame() { // Input State zusammenbauen input := InputState{ Sequence: g.inputSequence, - Left: keyLeft || joyDir == -1, - Right: keyRight || joyDir == 1, + Left: keyLeft || joyDir < -0.1, + Right: keyRight || joyDir > 0.1, Jump: keyJump || g.btnJumpActive, Down: keyDown || isJoyDown, + JoyX: joyDir, // Analoge X-Achse speichern } g.btnJumpActive = false @@ -72,8 +84,8 @@ func (g *Game) UpdateGame() { // Lokale Physik sofort anwenden (Prediction) g.ApplyInput(input) - // Sanfte Korrektur anwenden (20% pro Frame) - const smoothingFactor = 0.2 + // Sanfte Korrektur anwenden (schnellere Interpolation für weniger Ruckeln) + const smoothingFactor = 0.4 // Erhöht von 0.2 auf 0.4 if g.correctionX != 0 || g.correctionY != 0 { g.predictedX += g.correctionX * smoothingFactor g.predictedY += g.correctionY * smoothingFactor @@ -81,8 +93,8 @@ func (g *Game) UpdateGame() { g.correctionX *= (1.0 - smoothingFactor) g.correctionY *= (1.0 - smoothingFactor) - // Korrektur beenden wenn sehr klein - if g.correctionX*g.correctionX+g.correctionY*g.correctionY < 0.01 { + // Korrektur beenden wenn sehr klein (kleinerer Threshold) + if g.correctionX*g.correctionX+g.correctionY*g.correctionY < 0.25 { g.correctionX = 0 g.correctionY = 0 } diff --git a/cmd/client/main.go b/cmd/client/main.go index 3b5d541..ff97511 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -49,6 +49,7 @@ type InputState struct { Right bool Jump bool Down bool + JoyX float64 // Analoger Joystick-Wert (-1.0 bis 1.0) } // --- GAME STRUCT --- diff --git a/cmd/client/prediction.go b/cmd/client/prediction.go index 64c6624..f70bb11 100644 --- a/cmd/client/prediction.go +++ b/cmd/client/prediction.go @@ -7,7 +7,7 @@ import ( // ApplyInput wendet einen Input auf den vorhergesagten Zustand an func (g *Game) ApplyInput(input InputState) { - // Horizontale Bewegung + // Horizontale Bewegung mit analogem Joystick moveX := 0.0 if input.Left { moveX = -1.0 @@ -15,6 +15,12 @@ func (g *Game) ApplyInput(input InputState) { moveX = 1.0 } + // Wenn Joystick benutzt wird, überschreibe moveX mit analogem Wert + if input.JoyX != 0 { + moveX = input.JoyX + } + + // Geschwindigkeit skaliert mit Joystick-Intensität speed := config.RunSpeed + (moveX * 4.0) g.predictedX += speed @@ -107,8 +113,8 @@ func (g *Game) ReconcileWithServer(serverState game.PlayerState) { diffX := replayX - g.predictedX diffY := replayY - g.predictedY - // Nur korrigieren wenn Differenz signifikant (> 5 Pixel) - const threshold = 5.0 + // Nur korrigieren wenn Differenz signifikant (reduzierter Threshold für weniger Ruckeln) + const threshold = 2.0 // Reduziert von 5.0 auf 2.0 if diffX*diffX+diffY*diffY > threshold*threshold { // Speichere Korrektur für sanfte Interpolation g.correctionX = diffX diff --git a/cmd/client/web/game.js b/cmd/client/web/game.js index 32a6be8..631ac8d 100644 --- a/cmd/client/web/game.js +++ b/cmd/client/web/game.js @@ -34,6 +34,13 @@ function setUIState(newState) { screen.classList.add('hidden'); }); if (loadingScreen) loadingScreen.style.display = 'none'; + + // Scroll alle overlay-screens nach oben + setTimeout(() => { + document.querySelectorAll('.overlay-screen:not(.hidden)').forEach(screen => { + screen.scrollTop = 0; + }); + }, 10); // Manage Canvas and Overlays based on state switch(newState) { diff --git a/cmd/client/web/index.html b/cmd/client/web/index.html index dbd3f1d..3c7e72c 100644 --- a/cmd/client/web/index.html +++ b/cmd/client/web/index.html @@ -232,17 +232,19 @@

LOBBY

-
-

Raum-Code:

-
- XXXXX +
+
+

Spieler im Raum:

+
+
Warte auf Spieler...
+
-
-
-

Spieler im Raum:

-
-
Warte auf Spieler...
+
+

Raum-Code:

+
+ XXXXX +
diff --git a/cmd/client/web/style.css b/cmd/client/web/style.css index 66422d4..1fd58dc 100644 --- a/cmd/client/web/style.css +++ b/cmd/client/web/style.css @@ -44,33 +44,121 @@ input[type=range]{width:100%;max-width:300px} #mute-btn:hover{background:rgba(255,255,255,.2);border-color:#fff} #rotate-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:#222;z-index:99999;color:#fff;flex-direction:column;align-items:center;justify-content:center;text-align:center} .icon{font-size:60px;margin-bottom:20px} -/* Mobile First - Base Styles bereits für Mobile optimiert */ -@media screen and (max-width: 768px) { - h1 { font-size: 20px; margin: 5px 0 15px; } - button { font-size: 12px; padding: 12px 20px; margin: 8px; } - .big-btn { font-size: 14px; padding: 14px 25px; } - input[type=text] { font-size: 12px; padding: 10px; max-width: 280px; } - .info-box { max-width: 280px; padding: 10px; font-size: 10px; } - .info-box p { font-size: 11px; } - .overlay-screen { padding: 10px; } - #startScreen { flex-direction: column; gap: 20px; } - .start-left, .start-right { max-width: 90%; flex: 1; } - .center-box { max-width: 90%; } - .leaderboard-box { max-width: 90%; padding: 10px; } - .hall-of-fame-box { max-height: 200px; max-width: 90%; } - .legal-bar { gap: 8px; } - .legal-btn { font-size: 8px; padding: 6px 10px; } - .back-btn { font-size: 10px; padding: 8px 16px; } + +/* Viewport-basierte dynamische Skalierung für Mobile */ +@media (min-width:1024px){ + h1{font-size:48px;margin-bottom:40px} + button{font-size:22px;padding:20px 40px} + input[type=text]{width:350px;font-size:20px;padding:15px} + .info-box{max-width:500px} + .info-box p{font-size:16px} + .info-title{font-size:14px} + .hall-of-fame-box{max-height:400px} } -@media screen and (max-width: 480px) { - h1 { font-size: 16px; line-height: 1.2; } - button { font-size: 10px; padding: 10px 16px; margin: 6px; } - .big-btn { font-size: 12px; padding: 12px 20px; } - input[type=text] { font-size: 11px; padding: 8px; max-width: 240px; } - .overlay-screen { padding: 8px; } - body, html { font-size: 11px; } +@media (max-height:500px){ + #startScreen{ + flex-direction:row; + align-items:center; + justify-content:center; + gap:20px; + padding:10px; + overflow-y:auto; + } + .start-left{ + width:45%; + max-width:none; + align-items:center; + text-align:center; + margin:0; + } + .start-right{ + width:45%; + max-width:none; + height:auto; + margin:0; + } + h1{font-size:18px;margin:0 0 10px 0;line-height:1.2} + .info-box{display:none} + .legal-bar{margin-top:5px;gap:10px} + button{padding:8px 12px;font-size:10px;margin:5px} + .hall-of-fame-box{max-height:180px} + .hall-of-fame-box h3{font-size:10px;margin-bottom:5px} + + /* Game Over Screen */ + #gameOverScreen{justify-content:center;align-items:center;padding:10px;overflow-y:auto} + #gameOverScreen h1{font-size:16px;margin:5px 0} + #gameOverScreen p{font-size:10px;margin:5px 0} + #finalScore{font-size:18px!important} + + /* Coop Menu */ + #coopMenu .center-box{max-width:90%;padding:10px} + #coopMenu h1{font-size:16px;margin:5px 0} + #coopMenu input{width:200px} + + /* Lobby Screen */ + #lobbyScreen .center-box{max-width:95%;padding:5px} + #lobbyScreen h1{font-size:14px;margin:3px 0} + #lobbyContent{gap:10px!important} + #lobbyContent>div{flex:1!important;max-width:48%!important} + #lobbyScreen p{font-size:9px!important;margin:3px 0!important} + #lobbyRoomCode{font-size:14px!important;padding:6px!important;letter-spacing:1px!important} + #lobbyPlayerList{padding:8px!important;min-height:60px!important;font-size:9px!important;max-height:100px!important} + #lobbyTeamName{padding:5px!important;font-size:10px!important} + #lobbyTeamNameBox{margin:5px 0!important;max-width:90%!important} + #lobbyHostControls{margin:5px 0!important} + #lobbyStatus{font-size:9px!important;margin:5px 0!important} + #currentTeamName{font-size:9px!important} + #teamNameDisplay{font-size:9px!important} + + /* Settings Menu */ + #settingsMenu .center-box{max-width:90%;padding:10px} + #settingsMenu h1{font-size:16px;margin:5px 0} + .settings-group{gap:10px;margin:10px 0} + .setting-item{gap:5px;flex-direction:column;align-items:center} + .setting-item label{font-size:10px} + .setting-item span{font-size:10px} + input[type=range]{max-width:200px;height:20px} + + /* Leaderboard Menu */ + #leaderboardMenu .center-box{max-width:90%;padding:10px} + #leaderboardMenu h1{font-size:16px;margin:5px 0} + + /* My Codes Menu */ + #myCodesMenu .center-box{max-width:90%;padding:10px} + #myCodesMenu h1{font-size:16px;margin:5px 0} + + /* Impressum & Datenschutz */ + #impressumMenu .center-box,#datenschutzMenu .center-box{max-width:95%;padding:10px} + #impressumMenu h1,#datenschutzMenu h1{font-size:14px;margin:5px 0} + #impressumMenu .leaderboard-box,#datenschutzMenu .leaderboard-box{ + max-height:250px!important; + padding:10px!important; + font-size:9px!important; + line-height:1.3!important; + } + #impressumMenu h3,#datenschutzMenu h3{font-size:10px!important;margin:10px 0 5px 0!important} + #impressumMenu h4,#datenschutzMenu h4{font-size:9px!important;margin:8px 0 3px 0!important} + #impressumMenu p,#datenschutzMenu p{font-size:9px!important;margin:3px 0!important} + #impressumMenu ul,#datenschutzMenu ul{margin:3px 0!important;padding-left:15px!important} + #impressumMenu li,#datenschutzMenu li{font-size:9px!important;margin:2px 0!important} + + /* Common */ + input{padding:5px;font-size:12px;width:150px;margin-bottom:5px} + .leaderboard-box{max-height:120px;padding:8px;margin-top:5px;font-size:10px} + .leaderboard-item{font-size:9px;padding:3px 0} + .leaderboard-rank,.leaderboard-name,.leaderboard-score{font-size:9px} + .big-btn{padding:8px 12px;font-size:10px;margin:5px} + .back-btn{padding:8px 12px;font-size:10px;margin-top:10px} + .center-box{max-width:90%;padding:10px} + .legal-btn{padding:6px 10px;font-size:8px;margin:3px} + p{font-size:10px;margin:5px 0} + + /* Overlay Screens */ + .overlay-screen{padding:10px;overflow-y:auto} } -@media screen and (orientation:portrait){#rotate-overlay{display:flex}#game-container{display:none!important}} -@media (min-width:1024px){h1{font-size:48px}button{font-size:22px;padding:20px 40px}input[type=text]{max-width:450px;font-size:20px;padding:15px}.info-box{max-width:500px}.hall-of-fame-box{max-height:400px}} +@media screen and (orientation:portrait){ + #rotate-overlay{display:flex} + #game-container{display:none!important} +}