Implement core game functionalities: client prediction, coin collection, scoring, game state synchronization, and player management.
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
@@ -24,53 +25,47 @@ func (g *Game) UpdateGame() {
|
||||
// --- 2. TOUCH INPUT HANDLING ---
|
||||
g.handleTouchInput()
|
||||
|
||||
// --- 3. INPUTS ZUSAMMENFÜHREN & SENDEN ---
|
||||
|
||||
if g.connected {
|
||||
// A) BEWEGUNG (Links/Rechts)
|
||||
// Joystick auswerten (-1 bis 1)
|
||||
joyDir := 0.0
|
||||
if g.joyActive {
|
||||
diffX := g.joyStickX - g.joyBaseX
|
||||
if diffX < -20 {
|
||||
joyDir = -1
|
||||
} // Nach Links gezogen
|
||||
if diffX > 20 {
|
||||
joyDir = 1
|
||||
} // Nach Rechts gezogen
|
||||
// --- 3. INPUT STATE ERSTELLEN ---
|
||||
joyDir := 0.0
|
||||
if g.joyActive {
|
||||
diffX := g.joyStickX - g.joyBaseX
|
||||
if diffX < -20 {
|
||||
joyDir = -1
|
||||
}
|
||||
|
||||
// Senden: Keyboard ODER Joystick
|
||||
if keyLeft || joyDir == -1 {
|
||||
g.SendCommand("LEFT_DOWN")
|
||||
} else if keyRight || joyDir == 1 {
|
||||
g.SendCommand("RIGHT_DOWN")
|
||||
} else {
|
||||
// Wenn weder Links noch Rechts gedrückt ist, senden wir STOP.
|
||||
g.SendCommand("LEFT_UP")
|
||||
g.SendCommand("RIGHT_UP")
|
||||
}
|
||||
|
||||
// B) NACH UNTEN (Fast Fall)
|
||||
// Joystick weit nach unten gezogen?
|
||||
isJoyDown := false
|
||||
if g.joyActive && (g.joyStickY-g.joyBaseY) > 40 {
|
||||
isJoyDown = true
|
||||
}
|
||||
|
||||
if keyDown || isJoyDown {
|
||||
g.SendCommand("DOWN")
|
||||
}
|
||||
|
||||
// C) SPRINGEN
|
||||
// Keyboard ODER Touch-Button
|
||||
if keyJump || g.btnJumpActive {
|
||||
g.SendCommand("JUMP")
|
||||
g.btnJumpActive = false // Reset (Tap to jump)
|
||||
if diffX > 20 {
|
||||
joyDir = 1
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. KAMERA LOGIK ---
|
||||
isJoyDown := g.joyActive && (g.joyStickY-g.joyBaseY) > 40
|
||||
|
||||
// Input State zusammenbauen
|
||||
input := InputState{
|
||||
Sequence: g.inputSequence,
|
||||
Left: keyLeft || joyDir == -1,
|
||||
Right: keyRight || joyDir == 1,
|
||||
Jump: keyJump || g.btnJumpActive,
|
||||
Down: keyDown || isJoyDown,
|
||||
}
|
||||
g.btnJumpActive = false
|
||||
|
||||
// --- 4. CLIENT PREDICTION ---
|
||||
if g.connected {
|
||||
// Sequenznummer erhöhen
|
||||
g.inputSequence++
|
||||
input.Sequence = g.inputSequence
|
||||
|
||||
// Input speichern für später Reconciliation
|
||||
g.pendingInputs[input.Sequence] = input
|
||||
|
||||
// Lokale Physik sofort anwenden (Prediction)
|
||||
g.ApplyInput(input)
|
||||
|
||||
// Input an Server senden
|
||||
g.SendInputWithSequence(input)
|
||||
}
|
||||
|
||||
// --- 5. KAMERA LOGIK ---
|
||||
g.stateMutex.Lock()
|
||||
defer g.stateMutex.Unlock()
|
||||
|
||||
@@ -176,9 +171,15 @@ func (g *Game) DrawGame(screen *ebiten.Image) {
|
||||
for _, activeChunk := range g.gameState.WorldChunks {
|
||||
chunkDef, exists := g.world.ChunkLibrary[activeChunk.ChunkID]
|
||||
if !exists {
|
||||
log.Printf("⚠️ Chunk '%s' nicht in Library gefunden!", activeChunk.ChunkID)
|
||||
continue
|
||||
}
|
||||
|
||||
// DEBUG: Chunk-Details loggen (nur einmal)
|
||||
if len(chunkDef.Objects) == 0 {
|
||||
log.Printf("⚠️ Chunk '%s' hat 0 Objekte! Width=%d", activeChunk.ChunkID, chunkDef.Width)
|
||||
}
|
||||
|
||||
for _, obj := range chunkDef.Objects {
|
||||
// Asset zeichnen
|
||||
g.DrawAsset(screen, obj.AssetID, activeChunk.X+obj.X, obj.Y)
|
||||
@@ -186,20 +187,36 @@ func (g *Game) DrawGame(screen *ebiten.Image) {
|
||||
}
|
||||
|
||||
// 3. Spieler
|
||||
// MyID ohne Lock holen (wir haben bereits den stateMutex)
|
||||
myID := ""
|
||||
for id, p := range g.gameState.Players {
|
||||
g.DrawAsset(screen, "player", p.X, p.Y)
|
||||
if p.Name == g.playerName {
|
||||
myID = id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for id, p := range g.gameState.Players {
|
||||
// Für lokalen Spieler: Verwende vorhergesagte Position
|
||||
posX, posY := p.X, p.Y
|
||||
if id == myID && g.connected {
|
||||
posX = g.predictedX
|
||||
posY = g.predictedY
|
||||
}
|
||||
|
||||
g.DrawAsset(screen, "player", posX, posY)
|
||||
|
||||
// Name Tag
|
||||
name := p.Name
|
||||
if name == "" {
|
||||
name = id
|
||||
}
|
||||
text.Draw(screen, name, basicfont.Face7x13, int(p.X-g.camX), int(p.Y-25), ColText)
|
||||
text.Draw(screen, name, basicfont.Face7x13, int(posX-g.camX), int(posY-25), ColText)
|
||||
|
||||
// DEBUG: Rote Hitbox
|
||||
if def, ok := g.world.Manifest.Assets["player"]; ok {
|
||||
hx := float32(p.X + def.DrawOffX + def.Hitbox.OffsetX - g.camX)
|
||||
hy := float32(p.Y + def.DrawOffY + def.Hitbox.OffsetY)
|
||||
hx := float32(posX + def.DrawOffX + def.Hitbox.OffsetX - g.camX)
|
||||
hy := float32(posY + def.DrawOffY + def.Hitbox.OffsetY)
|
||||
vector.StrokeRect(screen, hx, hy, float32(def.Hitbox.W), float32(def.Hitbox.H), 2, color.RGBA{255, 0, 0, 255}, false)
|
||||
}
|
||||
}
|
||||
@@ -211,6 +228,25 @@ func (g *Game) DrawGame(screen *ebiten.Image) {
|
||||
} else if g.gameState.Status == "RUNNING" {
|
||||
dist := fmt.Sprintf("Distance: %.0f m", g.camX/64.0)
|
||||
text.Draw(screen, dist, basicfont.Face7x13, ScreenWidth-150, 30, ColText)
|
||||
|
||||
// Score anzeigen
|
||||
for _, p := range g.gameState.Players {
|
||||
if p.Name == g.playerName {
|
||||
scoreStr := fmt.Sprintf("Score: %d", p.Score)
|
||||
text.Draw(screen, scoreStr, basicfont.Face7x13, ScreenWidth-150, 50, ColText)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if g.gameState.Status == "GAMEOVER" {
|
||||
// Game Over Screen mit allen Scores
|
||||
text.Draw(screen, "GAME OVER", basicfont.Face7x13, ScreenWidth/2-50, 100, color.RGBA{255, 0, 0, 255})
|
||||
|
||||
y := 150
|
||||
for _, p := range g.gameState.Players {
|
||||
scoreMsg := fmt.Sprintf("%s: %d pts", p.Name, p.Score)
|
||||
text.Draw(screen, scoreMsg, basicfont.Face7x13, ScreenWidth/2-80, y, color.White)
|
||||
y += 20
|
||||
}
|
||||
}
|
||||
|
||||
// 5. DEBUG: TODES-LINIE
|
||||
@@ -282,6 +318,9 @@ func (g *Game) DrawAsset(screen *ebiten.Image, assetID string, worldX, worldY fl
|
||||
if img != nil {
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
|
||||
// Filter für bessere Skalierung (besonders bei großen Sprites)
|
||||
op.Filter = ebiten.FilterLinear
|
||||
|
||||
// Skalieren
|
||||
op.GeoM.Scale(def.Scale, def.Scale)
|
||||
|
||||
@@ -291,8 +330,11 @@ func (g *Game) DrawAsset(screen *ebiten.Image, assetID string, worldX, worldY fl
|
||||
screenY+def.DrawOffY,
|
||||
)
|
||||
|
||||
// Farbe anwenden
|
||||
op.ColorScale.ScaleWithColor(def.Color.ToRGBA())
|
||||
// Farbe anwenden (nur wenn explizit gesetzt)
|
||||
// Wenn Color leer ist (R=G=B=A=0), nicht anwenden (Bild bleibt original)
|
||||
if def.Color.R != 0 || def.Color.G != 0 || def.Color.B != 0 || def.Color.A != 0 {
|
||||
op.ColorScale.ScaleWithColor(def.Color.ToRGBA())
|
||||
}
|
||||
|
||||
screen.DrawImage(img, op)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user