Private
Public Access
1
0

Integrate shared physics engine for player movement and collision handling, refine 20 TPS gameplay logic, and enhance client prediction with server-reconciliation updates.
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 7m51s

This commit is contained in:
Sebastian Unterschütz
2026-01-06 21:37:32 +01:00
parent 23d42d42e7
commit 023996229a
13 changed files with 685 additions and 251 deletions

View File

@@ -1,11 +1,12 @@
package main
import (
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/config"
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/physics"
)
// ApplyInput wendet einen Input auf den vorhergesagten Zustand an
// Nutzt die gemeinsame Physik-Engine aus pkg/physics
func (g *Game) ApplyInput(input InputState) {
// Horizontale Bewegung mit analogem Joystick
moveX := 0.0
@@ -20,39 +21,42 @@ func (g *Game) ApplyInput(input InputState) {
moveX = input.JoyX
}
// Geschwindigkeit skaliert mit Joystick-Intensität
// Bewegung relativ zum Scroll (symmetrisch)
speed := config.RunSpeed + (moveX * config.PlayerSpeed)
g.predictedX += speed
// Gravitation
g.predictedVY += config.Gravity
if g.predictedVY > config.MaxFall {
g.predictedVY = config.MaxFall
// Physik-State vorbereiten
state := physics.PlayerPhysicsState{
X: g.predictedX,
Y: g.predictedY,
VX: g.predictedVX,
VY: g.predictedVY,
OnGround: g.predictedGround,
OnWall: g.predictedOnWall,
}
// Fast Fall
if input.Down {
g.predictedVY = config.FastFall
// Physik-Input vorbereiten
physicsInput := physics.PhysicsInput{
InputX: moveX,
Jump: input.Jump,
Down: input.Down,
}
// Sprung
if input.Jump && g.predictedGround {
g.predictedVY = -config.JumpVelocity
g.predictedGround = false
// Kollisions-Checker vorbereiten
g.stateMutex.Lock()
collisionChecker := &physics.ClientCollisionChecker{
World: g.world,
ActiveChunks: g.gameState.WorldChunks,
MovingPlatforms: g.gameState.MovingPlatforms,
}
g.stateMutex.Unlock()
// Vertikale Bewegung
g.predictedY += g.predictedVY
// Gemeinsame Physik anwenden (1:1 wie Server)
physics.ApplyPhysics(&state, physicsInput, g.currentSpeed, collisionChecker, physics.DefaultPlayerConstants())
// Einfache Boden-Kollision (hardcoded für jetzt)
if g.predictedY >= 540 {
g.predictedY = 540
g.predictedVY = 0
g.predictedGround = true
} else {
g.predictedGround = false
}
// Ergebnis zurückschreiben
g.predictedX = state.X
g.predictedY = state.Y
g.predictedVX = state.VX
g.predictedVY = state.VY
g.predictedGround = state.OnGround
g.predictedOnWall = state.OnWall
}
// ReconcileWithServer gleicht lokale Prediction mit Server-State ab
@@ -70,14 +74,15 @@ func (g *Game) ReconcileWithServer(serverState game.PlayerState) {
}
}
// Temporäre Position für Replay
// Temporäre Position für Replay (jetzt MIT Y-Achse)
replayX := serverState.X
replayY := serverState.Y
replayVX := serverState.VX
replayVY := serverState.VY
replayGround := serverState.OnGround
replayOnWall := serverState.OnWall
// Replay alle noch nicht bestätigten Inputs
// Replay alle noch nicht bestätigten Inputs mit VOLLER Physik
if len(g.pendingInputs) > 0 {
for seq := g.lastServerSeq + 1; seq <= g.inputSequence; seq++ {
if input, ok := g.pendingInputs[seq]; ok {
@@ -85,12 +90,14 @@ func (g *Game) ReconcileWithServer(serverState game.PlayerState) {
oldX, oldY := g.predictedX, g.predictedY
oldVX, oldVY := g.predictedVX, g.predictedVY
oldGround := g.predictedGround
oldOnWall := g.predictedOnWall
g.predictedX = replayX
g.predictedY = replayY
g.predictedVX = replayVX
g.predictedVY = replayVY
g.predictedGround = replayGround
g.predictedOnWall = replayOnWall
g.ApplyInput(input)
@@ -99,6 +106,7 @@ func (g *Game) ReconcileWithServer(serverState game.PlayerState) {
replayVX = g.predictedVX
replayVY = g.predictedVY
replayGround = g.predictedGround
replayOnWall = g.predictedOnWall
// Zurücksetzen
g.predictedX = oldX
@@ -106,25 +114,44 @@ func (g *Game) ReconcileWithServer(serverState game.PlayerState) {
g.predictedVX = oldVX
g.predictedVY = oldVY
g.predictedGround = oldGround
g.predictedOnWall = oldOnWall
}
}
}
// Berechne Differenz zwischen aktueller Prediction und Server-Replay
// Berechne Differenz zwischen Client-Prediction und Server-Replay (X und Y)
diffX := replayX - g.predictedX
diffY := replayY - g.predictedY
dist := diffX*diffX + diffY*diffY
// Nur korrigieren wenn Differenz signifikant
// Bei 20 TPS größerer Threshold wegen größerer normaler Abweichungen
const threshold = 5.0 // Erhöht für 20 TPS (war 2.0)
if diffX*diffX+diffY*diffY > threshold*threshold {
// Speichere Korrektur für sanfte Interpolation
g.correctionX = diffX
g.correctionY = diffY
// Speichere Korrektur-Magnitude für Debug
g.correctionX = diffX
g.correctionY = diffY
// Bei sehr kleinen Abweichungen (<2px): Sofort korrigieren um Drift zu vermeiden
if dist < 4.0 { // 2px threshold
g.predictedX = replayX
g.predictedY = replayY
} else if dist > 100*100 {
// Bei sehr großen Abweichungen (>100px): Sofort korrigieren (Teleport/Respawn)
g.predictedX = replayX
g.predictedY = replayY
g.correctionCount++
} else if dist > 1.0 {
// Bei normalen Abweichungen: Sanfte Interpolation
// Bei 20 TPS: Aggressivere Interpolation
interpFactor := 0.5 // 50% pro Tick
if dist > 50*50 {
interpFactor = 0.8 // 80% bei großen Abweichungen
}
g.predictedX += diffX * interpFactor
g.predictedY += diffY * interpFactor
g.correctionCount++
}
// Velocity und Ground immer sofort übernehmen
// Velocity und Ground Status vom Server übernehmen
g.predictedVX = replayVX
g.predictedVY = replayVY
g.predictedGround = replayGround
g.predictedOnWall = replayOnWall
}