Private
Public Access
1
0
Files
EscapeFromTeacher/cmd/client/ground_system.go
Sebastian Unterschütz fc909fc2bc
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Has been cancelled
Increase number of underground stones per tile, limit to rectangular shapes only
2026-01-06 21:48:52 +01:00

176 lines
5.8 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 (20-30 pro Tile, über die ganze Höhe)
numDirt := 20 + rng.Intn(10)
dirtHeight := 5000.0 // Gesamte Dirt-Höhe bis tief in die Erde
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()*dirtHeight + 20, // Über die ganze Dirt-Schicht verteilt
Width: 10 + rng.Float64()*30,
Height: 10 + rng.Float64()*25,
Color: color.RGBA{darkness, darkness - 10, darkness - 20, 255},
})
}
// Steine IN der Erde generieren (30-50 pro Tile, nur eckig, tief verteilt)
numStones := 30 + rng.Intn(20)
for i := 0; i < numStones; i++ {
tile.Stones = append(tile.Stones, Stone{
X: rng.Float64() * 128,
Y: rng.Float64()*dirtHeight + 20, // Tief in der Erde verteilt
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) {
// Tatsächliche Canvas-Größe verwenden
canvasW, _ := screen.Size()
// Boden bleibt an fester Position (RefFloorY) - wichtig für Spielphysik!
// Erweitere Boden nach unten weit über Canvas-Rand hinaus (5000 Pixel tief)
floorY := float32(RefFloorY)
floorH := float32(5000) // Tief in die Erde
// 1. Basis Gras-Schicht (volle Canvas-Breite, nur dünne Grasnarbe)
vector.DrawFilledRect(screen, 0, floorY, float32(canvasW), 20, ColGrass, false)
// 2. Dirt-Schicht (Basis, volle Canvas-Breite, tief nach unten)
vector.DrawFilledRect(screen, 0, floorY+20, float32(canvasW), floorH-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)) / 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)
screenY := float32(RefFloorY) + 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 (auf dem Gras)
for _, stone := range tile.Stones {
worldX := tile.X + stone.X
screenX := float32(worldX - cameraX)
screenY := float32(RefFloorY) + 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)
}
}
}
}