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
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 2m23s
This commit is contained in:
@@ -39,11 +39,22 @@ func (g *Game) UpdateGame() {
|
|||||||
joyDir := 0.0
|
joyDir := 0.0
|
||||||
if g.joyActive {
|
if g.joyActive {
|
||||||
diffX := g.joyStickX - g.joyBaseX
|
diffX := g.joyStickX - g.joyBaseX
|
||||||
if diffX < -20 {
|
maxDist := 60.0 // Muss mit handleTouchInput() übereinstimmen
|
||||||
joyDir = -1
|
|
||||||
|
// 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 {
|
if joyDir > 1.0 {
|
||||||
joyDir = 1
|
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 State zusammenbauen
|
||||||
input := InputState{
|
input := InputState{
|
||||||
Sequence: g.inputSequence,
|
Sequence: g.inputSequence,
|
||||||
Left: keyLeft || joyDir == -1,
|
Left: keyLeft || joyDir < -0.1,
|
||||||
Right: keyRight || joyDir == 1,
|
Right: keyRight || joyDir > 0.1,
|
||||||
Jump: keyJump || g.btnJumpActive,
|
Jump: keyJump || g.btnJumpActive,
|
||||||
Down: keyDown || isJoyDown,
|
Down: keyDown || isJoyDown,
|
||||||
|
JoyX: joyDir, // Analoge X-Achse speichern
|
||||||
}
|
}
|
||||||
g.btnJumpActive = false
|
g.btnJumpActive = false
|
||||||
|
|
||||||
@@ -72,8 +84,8 @@ func (g *Game) UpdateGame() {
|
|||||||
// Lokale Physik sofort anwenden (Prediction)
|
// Lokale Physik sofort anwenden (Prediction)
|
||||||
g.ApplyInput(input)
|
g.ApplyInput(input)
|
||||||
|
|
||||||
// Sanfte Korrektur anwenden (20% pro Frame)
|
// Sanfte Korrektur anwenden (schnellere Interpolation für weniger Ruckeln)
|
||||||
const smoothingFactor = 0.2
|
const smoothingFactor = 0.4 // Erhöht von 0.2 auf 0.4
|
||||||
if g.correctionX != 0 || g.correctionY != 0 {
|
if g.correctionX != 0 || g.correctionY != 0 {
|
||||||
g.predictedX += g.correctionX * smoothingFactor
|
g.predictedX += g.correctionX * smoothingFactor
|
||||||
g.predictedY += g.correctionY * smoothingFactor
|
g.predictedY += g.correctionY * smoothingFactor
|
||||||
@@ -81,8 +93,8 @@ func (g *Game) UpdateGame() {
|
|||||||
g.correctionX *= (1.0 - smoothingFactor)
|
g.correctionX *= (1.0 - smoothingFactor)
|
||||||
g.correctionY *= (1.0 - smoothingFactor)
|
g.correctionY *= (1.0 - smoothingFactor)
|
||||||
|
|
||||||
// Korrektur beenden wenn sehr klein
|
// Korrektur beenden wenn sehr klein (kleinerer Threshold)
|
||||||
if g.correctionX*g.correctionX+g.correctionY*g.correctionY < 0.01 {
|
if g.correctionX*g.correctionX+g.correctionY*g.correctionY < 0.25 {
|
||||||
g.correctionX = 0
|
g.correctionX = 0
|
||||||
g.correctionY = 0
|
g.correctionY = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type InputState struct {
|
|||||||
Right bool
|
Right bool
|
||||||
Jump bool
|
Jump bool
|
||||||
Down bool
|
Down bool
|
||||||
|
JoyX float64 // Analoger Joystick-Wert (-1.0 bis 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GAME STRUCT ---
|
// --- GAME STRUCT ---
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
// ApplyInput wendet einen Input auf den vorhergesagten Zustand an
|
// ApplyInput wendet einen Input auf den vorhergesagten Zustand an
|
||||||
func (g *Game) ApplyInput(input InputState) {
|
func (g *Game) ApplyInput(input InputState) {
|
||||||
// Horizontale Bewegung
|
// Horizontale Bewegung mit analogem Joystick
|
||||||
moveX := 0.0
|
moveX := 0.0
|
||||||
if input.Left {
|
if input.Left {
|
||||||
moveX = -1.0
|
moveX = -1.0
|
||||||
@@ -15,6 +15,12 @@ func (g *Game) ApplyInput(input InputState) {
|
|||||||
moveX = 1.0
|
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)
|
speed := config.RunSpeed + (moveX * 4.0)
|
||||||
g.predictedX += speed
|
g.predictedX += speed
|
||||||
|
|
||||||
@@ -107,8 +113,8 @@ func (g *Game) ReconcileWithServer(serverState game.PlayerState) {
|
|||||||
diffX := replayX - g.predictedX
|
diffX := replayX - g.predictedX
|
||||||
diffY := replayY - g.predictedY
|
diffY := replayY - g.predictedY
|
||||||
|
|
||||||
// Nur korrigieren wenn Differenz signifikant (> 5 Pixel)
|
// Nur korrigieren wenn Differenz signifikant (reduzierter Threshold für weniger Ruckeln)
|
||||||
const threshold = 5.0
|
const threshold = 2.0 // Reduziert von 5.0 auf 2.0
|
||||||
if diffX*diffX+diffY*diffY > threshold*threshold {
|
if diffX*diffX+diffY*diffY > threshold*threshold {
|
||||||
// Speichere Korrektur für sanfte Interpolation
|
// Speichere Korrektur für sanfte Interpolation
|
||||||
g.correctionX = diffX
|
g.correctionX = diffX
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ function setUIState(newState) {
|
|||||||
screen.classList.add('hidden');
|
screen.classList.add('hidden');
|
||||||
});
|
});
|
||||||
if (loadingScreen) loadingScreen.style.display = 'none';
|
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
|
// Manage Canvas and Overlays based on state
|
||||||
switch(newState) {
|
switch(newState) {
|
||||||
|
|||||||
@@ -232,17 +232,19 @@
|
|||||||
<div class="center-box">
|
<div class="center-box">
|
||||||
<h1>LOBBY</h1>
|
<h1>LOBBY</h1>
|
||||||
|
|
||||||
<div style="margin: 20px 0;">
|
<div id="lobbyContent" style="display: flex; gap: 20px; width: 100%; justify-content: center; align-items: flex-start;">
|
||||||
<p style="font-size: 14px; color: #aaa;">Raum-Code:</p>
|
<div style="flex: 1; max-width: 400px;">
|
||||||
<div style="background: rgba(0,0,0,0.6); border: 4px solid #ffcc00; padding: 20px; font-size: 32px; color: #ffcc00; letter-spacing: 4px;">
|
<p style="font-size: 14px; color: #aaa; margin-bottom: 10px;">Spieler im Raum:</p>
|
||||||
<span id="lobbyRoomCode">XXXXX</span>
|
<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>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin: 20px 0; width: 100%; max-width: 400px;">
|
<div style="flex: 1; max-width: 300px;">
|
||||||
<p style="font-size: 14px; color: #aaa; margin-bottom: 10px;">Spieler im Raum:</p>
|
<p style="font-size: 14px; color: #aaa; margin-bottom: 10px;">Raum-Code:</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="background: rgba(0,0,0,0.6); border: 4px solid #ffcc00; padding: 20px; font-size: 32px; color: #ffcc00; letter-spacing: 4px; text-align: center;">
|
||||||
<div style="color: #888;">Warte auf Spieler...</div>
|
<span id="lobbyRoomCode">XXXXX</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -44,33 +44,121 @@ input[type=range]{width:100%;max-width:300px}
|
|||||||
#mute-btn:hover{background:rgba(255,255,255,.2);border-color:#fff}
|
#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}
|
#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}
|
.icon{font-size:60px;margin-bottom:20px}
|
||||||
/* Mobile First - Base Styles bereits für Mobile optimiert */
|
|
||||||
@media screen and (max-width: 768px) {
|
/* Viewport-basierte dynamische Skalierung für Mobile */
|
||||||
h1 { font-size: 20px; margin: 5px 0 15px; }
|
@media (min-width:1024px){
|
||||||
button { font-size: 12px; padding: 12px 20px; margin: 8px; }
|
h1{font-size:48px;margin-bottom:40px}
|
||||||
.big-btn { font-size: 14px; padding: 14px 25px; }
|
button{font-size:22px;padding:20px 40px}
|
||||||
input[type=text] { font-size: 12px; padding: 10px; max-width: 280px; }
|
input[type=text]{width:350px;font-size:20px;padding:15px}
|
||||||
.info-box { max-width: 280px; padding: 10px; font-size: 10px; }
|
.info-box{max-width:500px}
|
||||||
.info-box p { font-size: 11px; }
|
.info-box p{font-size:16px}
|
||||||
.overlay-screen { padding: 10px; }
|
.info-title{font-size:14px}
|
||||||
#startScreen { flex-direction: column; gap: 20px; }
|
.hall-of-fame-box{max-height:400px}
|
||||||
.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; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 480px) {
|
@media (max-height:500px){
|
||||||
h1 { font-size: 16px; line-height: 1.2; }
|
#startScreen{
|
||||||
button { font-size: 10px; padding: 10px 16px; margin: 6px; }
|
flex-direction:row;
|
||||||
.big-btn { font-size: 12px; padding: 12px 20px; }
|
align-items:center;
|
||||||
input[type=text] { font-size: 11px; padding: 8px; max-width: 240px; }
|
justify-content:center;
|
||||||
.overlay-screen { padding: 8px; }
|
gap:20px;
|
||||||
body, html { font-size: 11px; }
|
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 screen and (orientation:portrait){
|
||||||
@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}}
|
#rotate-overlay{display:flex}
|
||||||
|
#game-container{display:none!important}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user