fix game
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 7m29s
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 7m29s
This commit is contained in:
@@ -61,23 +61,72 @@ func (g *Game) UpdateGame() {
|
|||||||
// Down: Joystick nach unten ziehen ODER Down-Button
|
// Down: Joystick nach unten ziehen ODER Down-Button
|
||||||
isJoyDown := g.joyActive && (g.joyStickY-g.joyBaseY) > g.joyRadius*0.5
|
isJoyDown := g.joyActive && (g.joyStickY-g.joyBaseY) > g.joyRadius*0.5
|
||||||
|
|
||||||
|
wantsJump := keyJump || g.btnJumpActive
|
||||||
|
g.btnJumpActive = false
|
||||||
|
g.btnDownActive = false
|
||||||
|
|
||||||
|
// Jump Buffer: Sprung-Wunsch für bis zu 6 Physics-Frames (=300ms) speichern
|
||||||
|
if wantsJump {
|
||||||
|
g.jumpBufferFrames = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. INPUT SENDEN (MIT CLIENT PREDICTION) ---
|
||||||
|
// Wichtig: Prediction und Send müssen synchron laufen.
|
||||||
|
// Nur wenn wir senden, speichern wir den Input in pendingInputs.
|
||||||
|
if g.connected && time.Since(g.lastInputTime) >= 50*time.Millisecond {
|
||||||
|
g.lastInputTime = time.Now()
|
||||||
|
|
||||||
|
// Coyote Time: War auf dem Boden, jetzt nicht mehr → Timer setzen
|
||||||
|
g.predictionMutex.Lock()
|
||||||
|
wasOnGround := g.predictedGround
|
||||||
|
g.predictionMutex.Unlock()
|
||||||
|
|
||||||
|
// Coyote Time Countdown
|
||||||
|
if g.coyoteFrames > 0 {
|
||||||
|
g.coyoteFrames--
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jump Buffer Countdown
|
||||||
|
if g.jumpBufferFrames > 0 {
|
||||||
|
g.jumpBufferFrames--
|
||||||
|
}
|
||||||
|
|
||||||
|
// Effektiven Jump bestimmen:
|
||||||
|
// - Direkter Druck, ODER
|
||||||
|
// - Jump-Buffer aktiv UND jetzt auf dem Boden (Sprung kurz vor Landung)
|
||||||
|
g.predictionMutex.Lock()
|
||||||
|
onGround := g.predictedGround
|
||||||
|
g.predictionMutex.Unlock()
|
||||||
|
|
||||||
|
if wasOnGround && !onGround {
|
||||||
|
// Gerade von Kante abgegangen → Coyote Time starten
|
||||||
|
g.coyoteFrames = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
effectiveJump := wantsJump ||
|
||||||
|
(g.jumpBufferFrames > 0 && onGround) ||
|
||||||
|
(wantsJump && g.coyoteFrames > 0)
|
||||||
|
|
||||||
|
if effectiveJump {
|
||||||
|
g.jumpBufferFrames = 0 // Buffer verbraucht
|
||||||
|
}
|
||||||
|
|
||||||
// Input State zusammenbauen
|
// Input State zusammenbauen
|
||||||
input := InputState{
|
input := InputState{
|
||||||
Sequence: g.inputSequence,
|
Sequence: g.inputSequence,
|
||||||
Left: keyLeft || joyDir < -0.1,
|
Left: keyLeft || joyDir < -0.1,
|
||||||
Right: keyRight || joyDir > 0.1,
|
Right: keyRight || joyDir > 0.1,
|
||||||
Jump: keyJump || g.btnJumpActive,
|
Jump: effectiveJump,
|
||||||
Down: keyDown || isJoyDown || g.btnDownActive,
|
Down: keyDown || isJoyDown,
|
||||||
JoyX: joyDir,
|
JoyX: joyDir,
|
||||||
}
|
}
|
||||||
g.btnJumpActive = false
|
|
||||||
g.btnDownActive = false
|
|
||||||
|
|
||||||
// --- 4. INPUT SENDEN (MIT CLIENT PREDICTION) ---
|
|
||||||
// Prediction läuft bei jedem Frame (60 TPS) für flüssige Darstellung.
|
|
||||||
// Netz-Send wird auf ~20/sek gedrosselt um Server-Last zu begrenzen.
|
|
||||||
if g.connected {
|
|
||||||
g.predictionMutex.Lock()
|
g.predictionMutex.Lock()
|
||||||
|
// Position vor Physics-Step merken (für Interpolation)
|
||||||
|
g.prevPredictedX = g.predictedX
|
||||||
|
g.prevPredictedY = g.predictedY
|
||||||
|
g.lastPhysicsTime = time.Now()
|
||||||
|
|
||||||
// Sequenznummer erhöhen
|
// Sequenznummer erhöhen
|
||||||
g.inputSequence++
|
g.inputSequence++
|
||||||
input.Sequence = g.inputSequence
|
input.Sequence = g.inputSequence
|
||||||
@@ -88,9 +137,9 @@ func (g *Game) UpdateGame() {
|
|||||||
// Input für History speichern (für Server-Reconciliation)
|
// Input für History speichern (für Server-Reconciliation)
|
||||||
g.pendingInputs[input.Sequence] = input
|
g.pendingInputs[input.Sequence] = input
|
||||||
|
|
||||||
// Cap: nie mehr als 120 unbestätigte Inputs ansammeln
|
// Cap: nie mehr als 60 unbestätigte Inputs ansammeln (~3 Sek bei 20/sek)
|
||||||
if len(g.pendingInputs) > 120 {
|
if len(g.pendingInputs) > 60 {
|
||||||
oldest := g.inputSequence - 120
|
oldest := g.inputSequence - 60
|
||||||
for seq := range g.pendingInputs {
|
for seq := range g.pendingInputs {
|
||||||
if seq < oldest {
|
if seq < oldest {
|
||||||
delete(g.pendingInputs, seq)
|
delete(g.pendingInputs, seq)
|
||||||
@@ -100,25 +149,21 @@ func (g *Game) UpdateGame() {
|
|||||||
|
|
||||||
g.predictionMutex.Unlock()
|
g.predictionMutex.Unlock()
|
||||||
|
|
||||||
// Netz-Throttle: max 20 Inputs/Sek senden
|
|
||||||
if time.Since(g.lastInputTime) >= 50*time.Millisecond {
|
|
||||||
g.lastInputTime = time.Now()
|
|
||||||
g.SendInputWithSequence(input)
|
g.SendInputWithSequence(input)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// --- 5. KAMERA LOGIK ---
|
// --- 5. KAMERA LOGIK (mit Smoothing) ---
|
||||||
g.stateMutex.Lock()
|
g.stateMutex.Lock()
|
||||||
targetCam := g.gameState.ScrollX
|
targetCam := g.gameState.ScrollX
|
||||||
g.stateMutex.Unlock()
|
g.stateMutex.Unlock()
|
||||||
|
|
||||||
// Negative Kamera verhindern
|
|
||||||
if targetCam < 0 {
|
if targetCam < 0 {
|
||||||
targetCam = 0
|
targetCam = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kamera hart setzen
|
// Sanftes Kamera-Folgen: 20% pro Frame Richtung Ziel (bei 60fps ≈ 95ms Halbwertszeit)
|
||||||
g.camX = targetCam
|
diff := targetCam - g.camX
|
||||||
|
g.camX += diff * 0.2
|
||||||
|
|
||||||
// --- 6. CORRECTION OFFSET ABKLINGEN ---
|
// --- 6. CORRECTION OFFSET ABKLINGEN ---
|
||||||
// Der visuelle Offset sorgt dafür dass Server-Korrekturen sanft und unsichtbar sind.
|
// Der visuelle Offset sorgt dafür dass Server-Korrekturen sanft und unsichtbar sind.
|
||||||
@@ -386,12 +431,17 @@ func (g *Game) DrawGame(screen *ebiten.Image) {
|
|||||||
vy := p.VY
|
vy := p.VY
|
||||||
onGround := p.OnGround
|
onGround := p.OnGround
|
||||||
|
|
||||||
// Für lokalen Spieler: Verwende Client-Prediction + visuellen Korrektur-Offset
|
// Für lokalen Spieler: Client-Prediction mit Interpolation zwischen Physics-Steps
|
||||||
// correctionOffset sorgt dafür dass Server-Korrekturen sanft aussehen
|
// Physics läuft bei 20/sec, Draw bei 60fps → alpha interpoliert dazwischen
|
||||||
if p.Name == g.playerName {
|
if p.Name == g.playerName {
|
||||||
g.predictionMutex.Lock()
|
g.predictionMutex.Lock()
|
||||||
posX = g.predictedX + g.correctionOffsetX
|
// Interpolations-Alpha: wie weit sind wir zwischen letztem und nächstem Physics-Step?
|
||||||
posY = g.predictedY + g.correctionOffsetY
|
alpha := float64(time.Since(g.lastPhysicsTime)) / float64(50*time.Millisecond)
|
||||||
|
if alpha > 1 {
|
||||||
|
alpha = 1
|
||||||
|
}
|
||||||
|
posX = g.prevPredictedX + (g.predictedX-g.prevPredictedX)*alpha + g.correctionOffsetX
|
||||||
|
posY = g.prevPredictedY + (g.predictedY-g.prevPredictedY)*alpha + g.correctionOffsetY
|
||||||
g.predictionMutex.Unlock()
|
g.predictionMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,17 @@ type Game struct {
|
|||||||
lastRecvSeq uint32 // Letzte empfangene Server-Sequenznummer (für Out-of-Order-Erkennung)
|
lastRecvSeq uint32 // Letzte empfangene Server-Sequenznummer (für Out-of-Order-Erkennung)
|
||||||
lastInputTime time.Time // Letzter Input-Send (für 20 TPS Throttling)
|
lastInputTime time.Time // Letzter Input-Send (für 20 TPS Throttling)
|
||||||
|
|
||||||
|
// Interpolation (60fps Draw ↔ 20hz Physics)
|
||||||
|
prevPredictedX float64 // Position vor letztem Physics-Step (für Interpolation)
|
||||||
|
prevPredictedY float64
|
||||||
|
lastPhysicsTime time.Time // Zeitpunkt des letzten Physics-Steps
|
||||||
|
|
||||||
|
// Jump Buffer: Sprung kurz vor Landung speichern → löst beim Aufkommen aus
|
||||||
|
jumpBufferFrames int // Countdown in Physics-Frames (bei 0: kein Buffer)
|
||||||
|
|
||||||
|
// Coyote Time: Sprung kurz nach Abgang von Kante erlauben
|
||||||
|
coyoteFrames int // Countdown in Physics-Frames
|
||||||
|
|
||||||
// Smooth Correction (Debug-Info)
|
// Smooth Correction (Debug-Info)
|
||||||
correctionX float64 // Letzte Korrektur-Magnitude X
|
correctionX float64 // Letzte Korrektur-Magnitude X
|
||||||
correctionY float64 // Letzte Korrektur-Magnitude Y
|
correctionY float64 // Letzte Korrektur-Magnitude Y
|
||||||
@@ -176,6 +187,9 @@ func NewGame() *Game {
|
|||||||
fpsSampleTime: time.Now(),
|
fpsSampleTime: time.Now(),
|
||||||
lastUpdateTime: time.Now(),
|
lastUpdateTime: time.Now(),
|
||||||
|
|
||||||
|
// Interpolation
|
||||||
|
lastPhysicsTime: time.Now(),
|
||||||
|
|
||||||
joyBaseX: 150, joyBaseY: ScreenHeight - 150,
|
joyBaseX: 150, joyBaseY: ScreenHeight - 150,
|
||||||
joyStickX: 150, joyStickY: ScreenHeight - 150,
|
joyStickX: 150, joyStickY: ScreenHeight - 150,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user