fix game
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Has been cancelled
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Has been cancelled
This commit is contained in:
132
cmd/client/atlas.go
Normal file
132
cmd/client/atlas.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"log"
|
||||
"sort"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
|
||||
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
|
||||
)
|
||||
|
||||
const (
|
||||
// 2px Padding verhindert Linear-Filter-Bleeding an Sprite-Grenzen
|
||||
atlasPadding = 2
|
||||
// Maximale Atlas-Größe; 2048 deckt alle aktuellen Sprites locker ab
|
||||
atlasMaxSize = 2048
|
||||
)
|
||||
|
||||
// buildAtlas packt alle Sprite-Bilder in eine einzige GPU-Textur (Texture Atlas).
|
||||
// Ebiten erkennt zusammenhängende DrawImage-Calls auf derselben Quell-Textur
|
||||
// und fasst sie in einen einzigen GPU-Draw-Call zusammen ("internal batching").
|
||||
//
|
||||
// Bilder vom Typ "background" und Bilder die größer als atlasMaxSize/2 sind
|
||||
// bleiben als eigenständige Texturen und werden direkt in das Ergebnis übernommen.
|
||||
//
|
||||
// Alle anderen Sprites werden per Shelf-Packing-Algorithmus in den Atlas gepackt.
|
||||
// Der Rückgabewert ist eine neue Map, in der jeder Eintrag entweder ein
|
||||
// atlas.SubImage()-Ausschnitt oder das originale Einzelbild ist.
|
||||
func buildAtlas(images map[string]*ebiten.Image, manifest game.AssetManifest) map[string]*ebiten.Image {
|
||||
result := make(map[string]*ebiten.Image, len(images))
|
||||
|
||||
type spriteEntry struct {
|
||||
id string
|
||||
w int
|
||||
h int
|
||||
}
|
||||
|
||||
var topack []spriteEntry
|
||||
standalone := make(map[string]*ebiten.Image)
|
||||
|
||||
for id, img := range images {
|
||||
if img == nil {
|
||||
continue
|
||||
}
|
||||
def, ok := manifest.Assets[id]
|
||||
if ok && def.Type == "background" {
|
||||
standalone[id] = img
|
||||
continue
|
||||
}
|
||||
w, h := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
if w > atlasMaxSize/2 || h > atlasMaxSize/2 {
|
||||
standalone[id] = img
|
||||
continue
|
||||
}
|
||||
topack = append(topack, spriteEntry{id: id, w: w, h: h})
|
||||
}
|
||||
|
||||
if len(topack) == 0 {
|
||||
for id, img := range standalone {
|
||||
result[id] = img
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Shelf-Packing: nach Höhe absteigend sortieren → dichtere Packung
|
||||
sort.Slice(topack, func(i, j int) bool {
|
||||
return topack[i].h > topack[j].h
|
||||
})
|
||||
|
||||
type placement struct {
|
||||
id string
|
||||
rect image.Rectangle
|
||||
}
|
||||
var placements []placement
|
||||
|
||||
curX := atlasPadding
|
||||
curY := atlasPadding
|
||||
shelfH := 0
|
||||
atlasW := 0
|
||||
atlasH := 0
|
||||
|
||||
for _, s := range topack {
|
||||
pw := s.w + atlasPadding
|
||||
ph := s.h + atlasPadding
|
||||
|
||||
// Neue Zeile (Shelf) wenn kein Platz mehr in aktueller Zeile
|
||||
if curX+pw > atlasMaxSize {
|
||||
curX = atlasPadding
|
||||
curY += shelfH + atlasPadding
|
||||
shelfH = 0
|
||||
}
|
||||
|
||||
// Passt nicht mehr in den Atlas → als Standalone behalten
|
||||
if curY+ph > atlasMaxSize {
|
||||
standalone[s.id] = images[s.id]
|
||||
continue
|
||||
}
|
||||
|
||||
r := image.Rect(curX, curY, curX+s.w, curY+s.h)
|
||||
placements = append(placements, placement{id: s.id, rect: r})
|
||||
|
||||
if s.h > shelfH {
|
||||
shelfH = s.h
|
||||
}
|
||||
if curX+pw > atlasW {
|
||||
atlasW = curX + pw
|
||||
}
|
||||
if curY+s.h+atlasPadding > atlasH {
|
||||
atlasH = curY + s.h + atlasPadding
|
||||
}
|
||||
curX += pw
|
||||
}
|
||||
|
||||
// Atlas-Textur anlegen und Sprites hineinzeichnen
|
||||
atlas := ebiten.NewImage(atlasW, atlasH)
|
||||
for _, p := range placements {
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Translate(float64(p.rect.Min.X), float64(p.rect.Min.Y))
|
||||
atlas.DrawImage(images[p.id], op)
|
||||
result[p.id] = atlas.SubImage(p.rect).(*ebiten.Image)
|
||||
}
|
||||
|
||||
for id, img := range standalone {
|
||||
result[id] = img
|
||||
}
|
||||
|
||||
log.Printf("🗺️ Texture Atlas: %dx%d px | %d Sprites gepackt | %d standalone",
|
||||
atlasW, atlasH, len(placements), len(standalone))
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -371,6 +371,19 @@ func (g *Game) DrawGame(screen *ebiten.Image) {
|
||||
effectiveCamX := g.camX / viewScale
|
||||
g.RenderGround(screen, effectiveCamX)
|
||||
|
||||
// Prediction-Snapshot VOR dem stateMutex holen (verhindert Deadlock:
|
||||
// DrawGame würde sonst stateMutex→predictionMutex halten, während
|
||||
// ReconcileWithServer predictionMutex→stateMutex hält)
|
||||
g.predictionMutex.Lock()
|
||||
snapPrevX := g.prevPredictedX
|
||||
snapPrevY := g.prevPredictedY
|
||||
snapPredX := g.predictedX
|
||||
snapPredY := g.predictedY
|
||||
snapOffX := g.correctionOffsetX
|
||||
snapOffY := g.correctionOffsetY
|
||||
snapPhysTime := g.lastPhysicsTime
|
||||
g.predictionMutex.Unlock()
|
||||
|
||||
// State Locken für Datenzugriff
|
||||
g.stateMutex.Lock()
|
||||
defer g.stateMutex.Unlock()
|
||||
@@ -434,15 +447,13 @@ func (g *Game) DrawGame(screen *ebiten.Image) {
|
||||
// Für lokalen Spieler: Client-Prediction mit Interpolation zwischen Physics-Steps
|
||||
// Physics läuft bei 20/sec, Draw bei 60fps → alpha interpoliert dazwischen
|
||||
if p.Name == g.playerName {
|
||||
g.predictionMutex.Lock()
|
||||
// Interpolations-Alpha: wie weit sind wir zwischen letztem und nächstem Physics-Step?
|
||||
alpha := float64(time.Since(g.lastPhysicsTime)) / float64(50*time.Millisecond)
|
||||
alpha := float64(time.Since(snapPhysTime)) / 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()
|
||||
posX = snapPrevX + (snapPredX-snapPrevX)*alpha + snapOffX
|
||||
posY = snapPrevY + (snapPredY-snapPrevY)*alpha + snapOffY
|
||||
}
|
||||
|
||||
// Wähle Sprite basierend auf Sprung-Status
|
||||
|
||||
Reference in New Issue
Block a user