fix game
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 8m20s
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 8m20s
This commit is contained in:
@@ -104,7 +104,22 @@ func (g *Game) UpdateGame() {
|
||||
// Kamera hart setzen
|
||||
g.camX = targetCam
|
||||
|
||||
// --- 6. PARTIKEL UPDATEN ---
|
||||
// --- 6. CORRECTION OFFSET ABKLINGEN ---
|
||||
// Der visuelle Offset sorgt dafür dass Server-Korrekturen sanft und unsichtbar sind.
|
||||
// Decay: 0.85 pro Frame → ~5 Frames zum Halbieren bei 60fps (≈80ms)
|
||||
const correctionDecay = 0.85
|
||||
g.predictionMutex.Lock()
|
||||
g.correctionOffsetX *= correctionDecay
|
||||
g.correctionOffsetY *= correctionDecay
|
||||
if g.correctionOffsetX*g.correctionOffsetX < 0.09 {
|
||||
g.correctionOffsetX = 0
|
||||
}
|
||||
if g.correctionOffsetY*g.correctionOffsetY < 0.09 {
|
||||
g.correctionOffsetY = 0
|
||||
}
|
||||
g.predictionMutex.Unlock()
|
||||
|
||||
// --- 7. PARTIKEL UPDATEN ---
|
||||
g.UpdateParticles(1.0 / 60.0) // Delta time: ~16ms
|
||||
|
||||
// --- 7. PARTIKEL SPAWNEN (State Changes Detection) ---
|
||||
@@ -335,12 +350,12 @@ func (g *Game) DrawGame(screen *ebiten.Image) {
|
||||
vy := p.VY
|
||||
onGround := p.OnGround
|
||||
|
||||
// Für lokalen Spieler: Verwende Client-Prediction Position
|
||||
// Die Reconciliation wird in ReconcileWithServer() (connection_*.go) gemacht
|
||||
// Für lokalen Spieler: Verwende Client-Prediction + visuellen Korrektur-Offset
|
||||
// correctionOffset sorgt dafür dass Server-Korrekturen sanft aussehen
|
||||
if p.Name == g.playerName {
|
||||
g.predictionMutex.Lock()
|
||||
posX = g.predictedX
|
||||
posY = g.predictedY
|
||||
posX = g.predictedX + g.correctionOffsetX
|
||||
posY = g.predictedY + g.correctionOffsetY
|
||||
g.predictionMutex.Unlock()
|
||||
}
|
||||
|
||||
|
||||
@@ -98,9 +98,14 @@ type Game struct {
|
||||
lastRecvSeq uint32 // Letzte empfangene Server-Sequenznummer (für Out-of-Order-Erkennung)
|
||||
lastInputTime time.Time // Letzter Input-Send (für 20 TPS Throttling)
|
||||
|
||||
// Smooth Correction
|
||||
correctionX float64 // Verbleibende Korrektur in X
|
||||
correctionY float64 // Verbleibende Korrektur in Y
|
||||
// Smooth Correction (Debug-Info)
|
||||
correctionX float64 // Letzte Korrektur-Magnitude X
|
||||
correctionY float64 // Letzte Korrektur-Magnitude Y
|
||||
|
||||
// Visueller Korrektur-Offset: Physics springt sofort, Display folgt sanft
|
||||
// display_pos = predicted + correctionOffset (blendet harte Korrekturen aus)
|
||||
correctionOffsetX float64
|
||||
correctionOffsetY float64
|
||||
|
||||
// Particle System
|
||||
particles []Particle
|
||||
@@ -866,50 +871,25 @@ func (g *Game) SendInputWithSequence(input InputState) {
|
||||
|
||||
myID := g.getMyPlayerID()
|
||||
|
||||
// Inputs als einzelne Commands senden
|
||||
if input.Left {
|
||||
g.publishInput(game.ClientInput{
|
||||
PlayerID: myID,
|
||||
Type: "LEFT_DOWN",
|
||||
Sequence: input.Sequence,
|
||||
})
|
||||
}
|
||||
if input.Right {
|
||||
g.publishInput(game.ClientInput{
|
||||
PlayerID: myID,
|
||||
Type: "RIGHT_DOWN",
|
||||
Sequence: input.Sequence,
|
||||
})
|
||||
}
|
||||
if input.Jump {
|
||||
g.publishInput(game.ClientInput{
|
||||
PlayerID: myID,
|
||||
Type: "JUMP",
|
||||
Sequence: input.Sequence,
|
||||
})
|
||||
// Jump Sound abspielen
|
||||
g.audio.PlayJump()
|
||||
}
|
||||
if input.Down {
|
||||
g.publishInput(game.ClientInput{
|
||||
PlayerID: myID,
|
||||
Type: "DOWN",
|
||||
Sequence: input.Sequence,
|
||||
})
|
||||
}
|
||||
// Kompletten Input-State in einer einzigen Nachricht senden.
|
||||
// Vorteile gegenüber mehreren Event-Nachrichten:
|
||||
// - Kein stuck-Input durch verlorene oder falsch sortierte Pakete
|
||||
// - Server hat immer den vollständigen Zustand nach einem Paket
|
||||
// - Weniger Nachrichten pro Frame
|
||||
g.publishInput(game.ClientInput{
|
||||
PlayerID: myID,
|
||||
Type: "STATE",
|
||||
Sequence: input.Sequence,
|
||||
InputLeft: input.Left,
|
||||
InputRight: input.Right,
|
||||
InputJump: input.Jump,
|
||||
InputDown: input.Down,
|
||||
InputJoyX: input.JoyX,
|
||||
})
|
||||
|
||||
// Wenn weder Links noch Rechts, sende STOP
|
||||
if !input.Left && !input.Right {
|
||||
g.publishInput(game.ClientInput{
|
||||
PlayerID: myID,
|
||||
Type: "LEFT_UP",
|
||||
Sequence: input.Sequence,
|
||||
})
|
||||
g.publishInput(game.ClientInput{
|
||||
PlayerID: myID,
|
||||
Type: "RIGHT_UP",
|
||||
Sequence: input.Sequence,
|
||||
})
|
||||
// Jump-Sound lokal abspielen
|
||||
if input.Jump {
|
||||
g.audio.PlayJump()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,37 +128,48 @@ func (g *Game) ReconcileWithServer(serverState game.PlayerState) {
|
||||
g.correctionX = diffX
|
||||
g.correctionY = diffY
|
||||
|
||||
// === NEUE KORREKTUR-LOGIK MIT TOLERANZ ===
|
||||
// === SMOOTH CORRECTION MIT VISUELLEM OFFSET ===
|
||||
//
|
||||
// Strategie: Physics springt sofort zur richtigen Position (korrekte Physik),
|
||||
// aber der angezeigte Wert (predictedX/Y + correctionOffsetX/Y) springt NICHT –
|
||||
// er driftet sanft über ~80ms zur Physics-Position hin.
|
||||
//
|
||||
// Dadurch sehen Spieler keinen "Pushback/Teleport" mehr, selbst wenn der Server
|
||||
// eine größere Korrektur schickt.
|
||||
|
||||
const (
|
||||
// Toleranzen für Client-Abweichungen
|
||||
acceptTolerance = 400.0 // 20px - Client-Wert wird akzeptiert, keine Korrektur
|
||||
warnTolerance = 2500.0 // 50px - Warnung, nur noch Monitoring
|
||||
criticalTolerance = 10000.0 // 100px - kritisch (Teleport/Respawn/Desync)
|
||||
acceptTolerance = 400.0 // ~20px: keine Korrektur
|
||||
criticalTolerance = 2500.0 // ~50px: sanfte Korrektur
|
||||
)
|
||||
|
||||
if dist < acceptTolerance {
|
||||
// Bei kleinen Abweichungen (<20px): Client-Position akzeptieren, KEINE Korrektur
|
||||
// Server nimmt Client-Wert an
|
||||
// (Client-seitige Prediction bleibt unverändert)
|
||||
} else if dist < warnTolerance {
|
||||
// Bei mittleren Abweichungen (20-50px): Nur Monitoring, KEINE Korrektur
|
||||
// Der Unterschied wird toleriert - Client-Position wird akzeptiert
|
||||
// Server überwacht weiterhin, greift aber nicht ein
|
||||
// Kleine Abweichung (<20px): Client-Position beibehalten, kein Eingriff nötig
|
||||
|
||||
} else if dist < criticalTolerance {
|
||||
// Bei großen Abweichungen (50-100px): Sanfte Korrektur
|
||||
interpFactor := 0.3 // Nur 30% Korrektur
|
||||
g.predictedX += diffX * interpFactor
|
||||
g.predictedY += diffY * interpFactor
|
||||
// Mittlere Abweichung (20-50px): sanfte Physics-Korrektur (25%)
|
||||
// Visueller Offset kompensiert die Bewegung → kein sichtbarer Sprung
|
||||
factor := 0.25
|
||||
corrX := diffX * factor
|
||||
corrY := diffY * factor
|
||||
g.correctionOffsetX -= corrX // Offset gegenläufig zur Physics-Korrektur
|
||||
g.correctionOffsetY -= corrY
|
||||
g.predictedX += corrX
|
||||
g.predictedY += corrY
|
||||
g.correctionCount++
|
||||
|
||||
} else {
|
||||
// Bei kritischen Abweichungen (>100px): Sofort korrigieren (Teleport/Respawn/Desync)
|
||||
// Große Abweichung (>50px): Physics springt komplett zur Replay-Position.
|
||||
// Visueller Offset hält das Bild an der alten Stelle – Spieler sieht keinen Teleport.
|
||||
g.correctionOffsetX += g.predictedX - replayX // = -(replayX - predictedX) = -diffX
|
||||
g.correctionOffsetY += g.predictedY - replayY
|
||||
g.predictedX = replayX
|
||||
g.predictedY = replayY
|
||||
g.correctionCount++
|
||||
}
|
||||
|
||||
// Velocity und Ground Status vom Server übernehmen
|
||||
// Velocity und Ground-State immer vom Replay übernehmen.
|
||||
// Replay-Werte kommen aus korrekter Server-State + Pending-Input-Replay
|
||||
// und sind physikalisch genauer als reine Client-Prediction über Zeit.
|
||||
g.predictedVX = replayVX
|
||||
g.predictedVY = replayVY
|
||||
g.predictedGround = replayGround
|
||||
|
||||
Reference in New Issue
Block a user