Private
Public Access
1
0

Add analog joystick support with fine control adjustments, improve prediction smoothing, reduce correction thresholds, and enhance lobby and overlay UI responsiveness.
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 2m23s

This commit is contained in:
Sebastian Unterschütz
2026-01-04 19:43:09 +01:00
parent 6606d67a21
commit 98e955aad9
6 changed files with 164 additions and 48 deletions

View File

@@ -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
}

View File

@@ -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 ---

View File

@@ -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

View File

@@ -35,6 +35,13 @@ function setUIState(newState) {
});
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) {
case UIState.LOADING:

View File

@@ -232,20 +232,22 @@
<div class="center-box">
<h1>LOBBY</h1>
<div style="margin: 20px 0;">
<p style="font-size: 14px; color: #aaa;">Raum-Code:</p>
<div style="background: rgba(0,0,0,0.6); border: 4px solid #ffcc00; padding: 20px; font-size: 32px; color: #ffcc00; letter-spacing: 4px;">
<span id="lobbyRoomCode">XXXXX</span>
</div>
</div>
<div style="margin: 20px 0; width: 100%; max-width: 400px;">
<div id="lobbyContent" style="display: flex; gap: 20px; width: 100%; justify-content: center; align-items: flex-start;">
<div style="flex: 1; max-width: 400px;">
<p style="font-size: 14px; color: #aaa; margin-bottom: 10px;">Spieler im Raum:</p>
<div id="lobbyPlayerList" style="background: rgba(0,0,0,0.5); border: 2px solid #666; padding: 15px; min-height: 100px; font-family: sans-serif; font-size: 14px;">
<div style="color: #888;">Warte auf Spieler...</div>
</div>
</div>
<div style="flex: 1; max-width: 300px;">
<p style="font-size: 14px; color: #aaa; margin-bottom: 10px;">Raum-Code:</p>
<div style="background: rgba(0,0,0,0.6); border: 4px solid #ffcc00; padding: 20px; font-size: 32px; color: #ffcc00; letter-spacing: 4px; text-align: center;">
<span id="lobbyRoomCode">XXXXX</span>
</div>
</div>
</div>
<div id="lobbyTeamNameBox" class="hidden" style="margin: 20px 0; width: 100%; max-width: 400px;">
<p style="font-size: 14px; color: #aaa; margin-bottom: 10px;">Team-Name (nur Host):</p>
<input type="text" id="lobbyTeamName" placeholder="TEAM-NAME EINGEBEN" maxlength="15" style="text-transform:uppercase; width: 100%; padding: 10px; font-size: 16px;">

View File

@@ -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}
}