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
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 7m51s
This commit is contained in:
256
pkg/physics/physics.go
Normal file
256
pkg/physics/physics.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package physics
|
||||
|
||||
import (
|
||||
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/config"
|
||||
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
|
||||
)
|
||||
|
||||
// PlayerPhysicsState enthält den kompletten Physik-Zustand eines Spielers
|
||||
type PlayerPhysicsState struct {
|
||||
X float64
|
||||
Y float64
|
||||
VX float64
|
||||
VY float64
|
||||
OnGround bool
|
||||
OnWall bool
|
||||
}
|
||||
|
||||
// PhysicsInput enthält alle Inputs für einen Physik-Tick
|
||||
type PhysicsInput struct {
|
||||
InputX float64 // -1.0 bis 1.0 (Links/Rechts)
|
||||
Jump bool
|
||||
Down bool
|
||||
}
|
||||
|
||||
// CollisionChecker ist ein Interface für Kollisionserkennung
|
||||
// Server und Client können unterschiedliche Implementierungen haben
|
||||
type CollisionChecker interface {
|
||||
CheckCollision(x, y, w, h float64) (hit bool, collisionType string)
|
||||
}
|
||||
|
||||
// PlayerConstants enthält alle Spieler-spezifischen Konstanten
|
||||
type PlayerConstants struct {
|
||||
DrawOffX float64
|
||||
DrawOffY float64
|
||||
HitboxOffX float64
|
||||
HitboxOffY float64
|
||||
Width float64
|
||||
Height float64
|
||||
}
|
||||
|
||||
// DefaultPlayerConstants gibt die Standard-Spieler-Konstanten zurück
|
||||
// WICHTIG: Diese Werte müssen EXAKT mit assets.json übereinstimmen!
|
||||
func DefaultPlayerConstants() PlayerConstants {
|
||||
return PlayerConstants{
|
||||
DrawOffX: -56.0, // Aus assets.json "player"
|
||||
DrawOffY: -231.0, // Aus assets.json "player"
|
||||
HitboxOffX: 68.0, // Aus assets.json "player" Hitbox.OffsetX
|
||||
HitboxOffY: 42.0, // Aus assets.json "player" Hitbox.OffsetY
|
||||
Width: 73.0,
|
||||
Height: 184.0,
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyPhysics wendet einen Physik-Tick auf den Spieler an
|
||||
// Diese Funktion wird 1:1 von Server und Client verwendet
|
||||
func ApplyPhysics(
|
||||
state *PlayerPhysicsState,
|
||||
input PhysicsInput,
|
||||
currentSpeed float64,
|
||||
collisionChecker CollisionChecker,
|
||||
playerConst PlayerConstants,
|
||||
) {
|
||||
// --- HORIZONTALE BEWEGUNG MIT KOLLISION ---
|
||||
playerMovement := input.InputX * config.PlayerSpeed
|
||||
speed := currentSpeed + playerMovement
|
||||
nextX := state.X + speed
|
||||
|
||||
// Horizontale Kollisionsprüfung
|
||||
checkX := nextX + playerConst.DrawOffX + playerConst.HitboxOffX
|
||||
checkY := state.Y + playerConst.DrawOffY + playerConst.HitboxOffY
|
||||
|
||||
xHit, xCollisionType := collisionChecker.CheckCollision(
|
||||
checkX,
|
||||
checkY,
|
||||
playerConst.Width,
|
||||
playerConst.Height,
|
||||
)
|
||||
|
||||
// Nur X-Bewegung blockieren wenn es eine Wand/Plattform ist (nicht Obstacle)
|
||||
if xHit && (xCollisionType == "wall" || xCollisionType == "platform") {
|
||||
// Kollision in X-Richtung -> X nicht ändern
|
||||
// state.X bleibt wie es ist
|
||||
} else {
|
||||
// Keine Kollision -> X-Bewegung durchführen
|
||||
state.X = nextX
|
||||
}
|
||||
|
||||
// --- VERTIKALE BEWEGUNG ---
|
||||
// An der Wand: Reduzierte Gravität + Klettern
|
||||
if state.OnWall {
|
||||
state.VY += config.Gravity * 0.3 // 30% Gravität an der Wand
|
||||
if state.VY > config.WallSlideMax {
|
||||
state.VY = config.WallSlideMax
|
||||
}
|
||||
// Hochklettern wenn Bewegung vorhanden
|
||||
if input.InputX != 0 {
|
||||
state.VY = -config.WallClimbSpeed
|
||||
}
|
||||
} else {
|
||||
// Normal: Volle Gravität
|
||||
state.VY += config.Gravity
|
||||
if state.VY > config.MaxFall {
|
||||
state.VY = config.MaxFall
|
||||
}
|
||||
}
|
||||
|
||||
// Fast Fall
|
||||
if input.Down {
|
||||
state.VY = config.FastFall
|
||||
}
|
||||
|
||||
// Sprung
|
||||
if input.Jump && state.OnGround {
|
||||
state.VY = -config.JumpVelocity
|
||||
state.OnGround = false
|
||||
}
|
||||
|
||||
// Vertikale Bewegung mit Kollisionserkennung
|
||||
nextY := state.Y + state.VY
|
||||
|
||||
// Kollision für AKTUELLE Position prüfen (um OnGround richtig zu setzen)
|
||||
checkX = state.X + playerConst.DrawOffX + playerConst.HitboxOffX
|
||||
currentCheckY := state.Y + playerConst.DrawOffY + playerConst.HitboxOffY
|
||||
|
||||
currentHit, currentType := collisionChecker.CheckCollision(
|
||||
checkX,
|
||||
currentCheckY,
|
||||
playerConst.Width,
|
||||
playerConst.Height,
|
||||
)
|
||||
|
||||
// Wenn Spieler aktuell bereits auf dem Boden steht
|
||||
if currentHit && currentType == "platform" && state.VY >= 0 {
|
||||
state.OnGround = true
|
||||
state.VY = 0
|
||||
state.OnWall = false
|
||||
// Y-Position bleibt wo sie ist
|
||||
return
|
||||
}
|
||||
|
||||
// Kollision für NÄCHSTE Position prüfen
|
||||
nextCheckY := nextY + playerConst.DrawOffY + playerConst.HitboxOffY
|
||||
|
||||
hit, collisionType := collisionChecker.CheckCollision(
|
||||
checkX,
|
||||
nextCheckY,
|
||||
playerConst.Width,
|
||||
playerConst.Height,
|
||||
)
|
||||
|
||||
if hit {
|
||||
if collisionType == "wall" {
|
||||
// An der Wand: Position halten (Y nicht ändern)
|
||||
state.VY = 0
|
||||
state.OnWall = true
|
||||
state.OnGround = false
|
||||
} else if collisionType == "obstacle" {
|
||||
// Obstacle: Spieler bewegt sich in Obstacle (Server killt dann)
|
||||
state.Y = nextY
|
||||
state.VY = 0
|
||||
state.OnGround = false
|
||||
state.OnWall = false
|
||||
} else if collisionType == "platform" {
|
||||
// Platform: Blockiert vertikale Bewegung
|
||||
if state.VY > 0 {
|
||||
state.OnGround = true
|
||||
}
|
||||
state.VY = 0
|
||||
state.OnWall = false
|
||||
// Y-Position bleibt unverändert
|
||||
}
|
||||
} else {
|
||||
// Keine Kollision: Bewegung durchführen
|
||||
state.Y = nextY
|
||||
state.OnGround = false
|
||||
state.OnWall = false
|
||||
}
|
||||
}
|
||||
|
||||
// ClientCollisionChecker implementiert CollisionChecker für den Client
|
||||
type ClientCollisionChecker struct {
|
||||
World *game.World
|
||||
ActiveChunks []game.ActiveChunk
|
||||
MovingPlatforms []game.MovingPlatformSync
|
||||
}
|
||||
|
||||
func (c *ClientCollisionChecker) CheckCollision(x, y, w, h float64) (bool, string) {
|
||||
playerRect := game.Rect{
|
||||
OffsetX: x,
|
||||
OffsetY: y,
|
||||
W: w,
|
||||
H: h,
|
||||
}
|
||||
|
||||
// 0. Basis-Boden-Collider (wie Server) - UNTER dem sichtbaren Gras bis tief in die Erde
|
||||
floorCollider := game.Rect{
|
||||
OffsetX: -10000,
|
||||
OffsetY: 540, // Startet bei Y=540 (Gras-Oberfläche)
|
||||
W: 100000000,
|
||||
H: 5000, // Geht tief nach UNTEN (bis Y=5540) - gesamte Erdschicht
|
||||
}
|
||||
if game.CheckRectCollision(playerRect, floorCollider) {
|
||||
return true, "platform"
|
||||
}
|
||||
|
||||
// 1. Statische Colliders aus Chunks
|
||||
for _, activeChunk := range c.ActiveChunks {
|
||||
if chunk, ok := c.World.ChunkLibrary[activeChunk.ChunkID]; ok {
|
||||
for _, obj := range chunk.Objects {
|
||||
if assetDef, ok := c.World.Manifest.Assets[obj.AssetID]; ok {
|
||||
if assetDef.Hitbox.W > 0 && assetDef.Hitbox.H > 0 {
|
||||
colliderRect := game.Rect{
|
||||
OffsetX: activeChunk.X + obj.X + assetDef.DrawOffX + assetDef.Hitbox.OffsetX,
|
||||
OffsetY: obj.Y + assetDef.DrawOffY + assetDef.Hitbox.OffsetY,
|
||||
W: assetDef.Hitbox.W,
|
||||
H: assetDef.Hitbox.H,
|
||||
}
|
||||
|
||||
if game.CheckRectCollision(playerRect, colliderRect) {
|
||||
return true, assetDef.Hitbox.Type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Bewegende Plattformen
|
||||
for _, mp := range c.MovingPlatforms {
|
||||
if assetDef, ok := c.World.Manifest.Assets[mp.AssetID]; ok {
|
||||
if assetDef.Hitbox.W > 0 && assetDef.Hitbox.H > 0 {
|
||||
mpRect := game.Rect{
|
||||
OffsetX: mp.X + assetDef.DrawOffX + assetDef.Hitbox.OffsetX,
|
||||
OffsetY: mp.Y + assetDef.DrawOffY + assetDef.Hitbox.OffsetY,
|
||||
W: assetDef.Hitbox.W,
|
||||
H: assetDef.Hitbox.H,
|
||||
}
|
||||
|
||||
if game.CheckRectCollision(playerRect, mpRect) {
|
||||
return true, "platform"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// ServerCollisionChecker ist ein Wrapper für die Server CheckCollision-Methode
|
||||
type ServerCollisionChecker struct {
|
||||
CheckCollisionFunc func(x, y, w, h float64) (bool, string)
|
||||
}
|
||||
|
||||
func (s *ServerCollisionChecker) CheckCollision(x, y, w, h float64) (bool, string) {
|
||||
return s.CheckCollisionFunc(x, y, w, h)
|
||||
}
|
||||
Reference in New Issue
Block a user