186 lines
6.1 KiB
Go
186 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"image/color"
|
|
"math"
|
|
"math/rand"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
|
)
|
|
|
|
// GroundTile repräsentiert ein Boden-Segment
|
|
type GroundTile struct {
|
|
X float64
|
|
DirtVariants []DirtPatch
|
|
Stones []Stone
|
|
}
|
|
|
|
// DirtPatch ist ein Dirt-Fleck im Boden
|
|
type DirtPatch struct {
|
|
OffsetX float64
|
|
OffsetY float64
|
|
Width float64
|
|
Height float64
|
|
Color color.RGBA
|
|
}
|
|
|
|
// Stone ist ein Stein auf dem Boden
|
|
type Stone struct {
|
|
X float64
|
|
Y float64
|
|
Size float64
|
|
Color color.RGBA
|
|
Shape int // 0=rund, 1=eckig
|
|
}
|
|
|
|
// GroundCache speichert generierte Tiles
|
|
var groundCache = make(map[int]GroundTile)
|
|
|
|
// ClearGroundCache leert den Cache (z.B. bei Änderungen)
|
|
func ClearGroundCache() {
|
|
groundCache = make(map[int]GroundTile)
|
|
}
|
|
|
|
// GenerateGroundTile generiert ein prozedurales Boden-Segment (gecacht)
|
|
func GenerateGroundTile(tileIdx int) GroundTile {
|
|
// Prüfe Cache
|
|
if cached, exists := groundCache[tileIdx]; exists {
|
|
return cached
|
|
}
|
|
|
|
// Deterministischer Seed basierend auf Tile-Index
|
|
rng := rand.New(rand.NewSource(int64(tileIdx * 12345)))
|
|
|
|
tile := GroundTile{
|
|
X: float64(tileIdx) * 128.0,
|
|
DirtVariants: make([]DirtPatch, 0),
|
|
Stones: make([]Stone, 0),
|
|
}
|
|
|
|
// Zufällige Dirt-Patches generieren (15-25 pro Tile, kompakt)
|
|
numDirt := 15 + rng.Intn(10)
|
|
maxDirtHeight := 100.0 // Kompakte Erde-Tiefe (100px)
|
|
for i := 0; i < numDirt; i++ {
|
|
darkness := uint8(70 + rng.Intn(40)) // Verschiedene Brauntöne
|
|
tile.DirtVariants = append(tile.DirtVariants, DirtPatch{
|
|
OffsetX: rng.Float64() * 128,
|
|
OffsetY: rng.Float64()*maxDirtHeight + 20, // Nur im sichtbaren Bereich
|
|
Width: 10 + rng.Float64()*30,
|
|
Height: 10 + rng.Float64()*25,
|
|
Color: color.RGBA{darkness, darkness - 10, darkness - 20, 255},
|
|
})
|
|
}
|
|
|
|
// Steine IN der Erde generieren (20-30 pro Tile, nur eckig, kompakt)
|
|
numStones := 20 + rng.Intn(10)
|
|
for i := 0; i < numStones; i++ {
|
|
tile.Stones = append(tile.Stones, Stone{
|
|
X: rng.Float64() * 128,
|
|
Y: rng.Float64()*maxDirtHeight + 20, // Nur im sichtbaren Bereich
|
|
Size: 4 + rng.Float64()*8, // Verschiedene Größen
|
|
Color: color.RGBA{100 + uint8(rng.Intn(50)), 100 + uint8(rng.Intn(50)), 100 + uint8(rng.Intn(50)), 255},
|
|
Shape: 1, // Nur eckig (keine runden Steine mehr)
|
|
})
|
|
}
|
|
|
|
// In Cache speichern
|
|
groundCache[tileIdx] = tile
|
|
return tile
|
|
}
|
|
|
|
// RenderGround rendert den Boden mit Bewegung
|
|
func (g *Game) RenderGround(screen *ebiten.Image, cameraX float64, viewScale float64) {
|
|
// Tatsächliche Canvas-Größe verwenden
|
|
canvasW, canvasH := screen.Size()
|
|
|
|
// Gameplay-Boden-Position (wo Spieler laufen) - mit echter Canvas-Höhe!
|
|
gameFloorY := float32(GetFloorYFromHeight(canvasH))
|
|
|
|
// Erde-Tiefe - kompakt (100px) damit Gameplay optimal Platz hat
|
|
maxDirtDepth := float32(100.0)
|
|
|
|
// Berechne wie viel Erde sichtbar sein soll (bis zum Bildschirmrand)
|
|
remainingSpace := float32(canvasH) - gameFloorY
|
|
visibleDirtHeight := maxDirtDepth
|
|
if remainingSpace < maxDirtDepth {
|
|
visibleDirtHeight = remainingSpace
|
|
}
|
|
|
|
// 1. Basis Gras-Schicht (volle Canvas-Breite, nur dünne Grasnarbe)
|
|
vector.DrawFilledRect(screen, 0, gameFloorY, float32(canvasW), 20, ColGrass, false)
|
|
|
|
// 2. Dirt-Schicht (Basis, volle Canvas-Breite, nur sichtbarer Bereich)
|
|
if visibleDirtHeight > 20 {
|
|
vector.DrawFilledRect(screen, 0, gameFloorY+20, float32(canvasW), visibleDirtHeight-20, ColDirt, false)
|
|
}
|
|
|
|
// 3. Prozedurale Dirt-Patches und Steine (bewegen sich mit Kamera)
|
|
// Berechne welche Tiles sichtbar sind (basierend auf Canvas-Breite)
|
|
tileWidth := 128.0
|
|
startTile := int(math.Floor(cameraX / tileWidth))
|
|
endTile := int(math.Ceil((cameraX + float64(canvasW)/viewScale) / tileWidth))
|
|
|
|
// Tiles rendern
|
|
for tileIdx := startTile; tileIdx <= endTile; tileIdx++ {
|
|
tile := GenerateGroundTile(tileIdx)
|
|
|
|
// Dirt-Patches rendern
|
|
for _, dirt := range tile.DirtVariants {
|
|
worldX := tile.X + dirt.OffsetX
|
|
screenX := float32((worldX - cameraX) * viewScale)
|
|
screenY := gameFloorY + float32(dirt.OffsetY)
|
|
|
|
// Nur rendern wenn im sichtbaren Bereich (Canvas-Breite verwenden)
|
|
if screenX+float32(dirt.Width) > 0 && screenX < float32(canvasW) {
|
|
vector.DrawFilledRect(screen, screenX, screenY, float32(dirt.Width), float32(dirt.Height), dirt.Color, false)
|
|
}
|
|
}
|
|
|
|
// Steine rendern (in der Erde)
|
|
for _, stone := range tile.Stones {
|
|
worldX := tile.X + stone.X
|
|
screenX := float32((worldX - cameraX) * viewScale)
|
|
screenY := gameFloorY + float32(stone.Y)
|
|
|
|
// Nur rendern wenn im sichtbaren Bereich (Canvas-Breite verwenden)
|
|
if screenX > -20 && screenX < float32(canvasW)+20 {
|
|
if stone.Shape == 0 {
|
|
// Runder Stein
|
|
vector.DrawFilledCircle(screen, screenX, screenY, float32(stone.Size/2), stone.Color, false)
|
|
// Highlight für 3D-Effekt
|
|
highlightCol := color.RGBA{
|
|
uint8(math.Min(float64(stone.Color.R)+40, 255)),
|
|
uint8(math.Min(float64(stone.Color.G)+40, 255)),
|
|
uint8(math.Min(float64(stone.Color.B)+40, 255)),
|
|
200,
|
|
}
|
|
vector.DrawFilledCircle(screen, screenX-float32(stone.Size*0.15), screenY-float32(stone.Size*0.15), float32(stone.Size/4), highlightCol, false)
|
|
} else {
|
|
// Eckiger Stein
|
|
vector.DrawFilledRect(screen, screenX-float32(stone.Size/2), screenY-float32(stone.Size/2), float32(stone.Size), float32(stone.Size), stone.Color, false)
|
|
// Schatten für 3D-Effekt
|
|
shadowCol := color.RGBA{
|
|
uint8(float64(stone.Color.R) * 0.6),
|
|
uint8(float64(stone.Color.G) * 0.6),
|
|
uint8(float64(stone.Color.B) * 0.6),
|
|
150,
|
|
}
|
|
vector.DrawFilledRect(screen, screenX-float32(stone.Size/2)+2, screenY-float32(stone.Size/2)+2, float32(stone.Size), float32(stone.Size), shadowCol, false)
|
|
// Original drüber
|
|
vector.DrawFilledRect(screen, screenX-float32(stone.Size/2), screenY-float32(stone.Size/2), float32(stone.Size), float32(stone.Size), stone.Color, false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cache aufräumen (nur Tiles außerhalb des Sichtbereichs entfernen)
|
|
if len(groundCache) > 100 {
|
|
for idx := range groundCache {
|
|
if idx < startTile-10 || idx > endTile+10 {
|
|
delete(groundCache, idx)
|
|
}
|
|
}
|
|
}
|
|
}
|