Private
Public Access
1
0
Files
EscapeFromTeacher/pkg/physics/physics.go
Sebastian Unterschütz 023996229a
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 7m51s
Integrate shared physics engine for player movement and collision handling, refine 20 TPS gameplay logic, and enhance client prediction with server-reconciliation updates.
2026-01-06 21:37:32 +01:00

257 lines
6.9 KiB
Go

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)
}