From 32df388c77e3e8697e2cbee19ab8d029a7ea44a1 Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Tue, 16 Dec 2025 09:13:22 -0700 Subject: [PATCH] Fixed lighting --- internal/hero/hero.go | 10 ++--- internal/maps/desert.go | 16 ++++++- internal/maps/map.go | 23 +++++++--- internal/maps/plains.go | 14 +++++- internal/raycast/raycast.go | 2 +- internal/screens/gameplay.go | 86 ++++++++++++++++-------------------- 6 files changed, 85 insertions(+), 66 deletions(-) diff --git a/internal/hero/hero.go b/internal/hero/hero.go index 7bb15ec..35bb814 100644 --- a/internal/hero/hero.go +++ b/internal/hero/hero.go @@ -91,9 +91,7 @@ type Hero struct { ProjectileConfig projectile.ProjectileConfig - // Lighting state - InShadow bool - ShadowIntensity float64 + Brightness float64 } type Config struct { @@ -149,6 +147,7 @@ func New(cfg Config) *Hero { Radius: 4.0, Color: color.NRGBA{R: 255, G: 0, B: 0, A: 255}, }, + Brightness: 1.0, } } @@ -362,9 +361,8 @@ func (h *Hero) Draw(screen *ebiten.Image) { // no color modification for idle } - if h.InShadow { - darkness := 1.0 - h.ShadowIntensity - op.ColorScale.Scale(float32(darkness), float32(darkness), float32(darkness), 1.0) + if h.Brightness < 1.0 { + op.ColorScale.Scale(float32(h.Brightness), float32(h.Brightness), float32(h.Brightness), 1.0) } op.Filter = ebiten.FilterNearest diff --git a/internal/maps/desert.go b/internal/maps/desert.go index df32bd9..772aef2 100644 --- a/internal/maps/desert.go +++ b/internal/maps/desert.go @@ -10,8 +10,20 @@ import ( func CreateDesert(screenWidth, screenHeight float64) *Map { m := NewMap("desert", 2, "Desert", screenWidth, screenHeight) - m.BackgroundColor = color.NRGBA{R: 15, G: 20, B: 40, A: 255} // Dark blue night sky - m.TimeOfDay = Nighttime + m.BackgroundColor = color.NRGBA{R: 15, G: 20, B: 40, A: 255} + m.Lighting = LightConfig{ + AmbientBrightness: 0.1, + Sources: []LightSource{ + { + X: screenWidth - 80, + Y: 80, + Radius: 450, + Color: color.RGBA{R: 200, G: 220, B: 255, A: 255}, + Intensity: 0.5, + ShadowDarkness: 0.7, + }, + }, + } // ground surface m.World.AddSurface(&world.Surface{ diff --git a/internal/maps/map.go b/internal/maps/map.go index becdd12..0e5ef6b 100644 --- a/internal/maps/map.go +++ b/internal/maps/map.go @@ -8,12 +8,18 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) -type TimeOfDay string +type LightSource struct { + X, Y float64 + Radius float64 + Color color.RGBA + Intensity float64 + ShadowDarkness float64 +} -const ( - Daytime TimeOfDay = "day" - Nighttime TimeOfDay = "night" -) +type LightConfig struct { + AmbientBrightness float64 + Sources []LightSource +} type Map struct { ID string @@ -24,7 +30,7 @@ type Map struct { World *world.World Portals []*portal.Portal BackgroundColor color.NRGBA - TimeOfDay TimeOfDay // Day or Night tag + Lighting LightConfig bakedImage *ebiten.Image } @@ -39,7 +45,10 @@ func NewMap(id string, number int, displayName string, width, height float64) *M World: world.NewWorld(), Portals: make([]*portal.Portal, 0), BackgroundColor: color.NRGBA{R: 135, G: 206, B: 235, A: 255}, - TimeOfDay: Daytime, // Default to daytime + Lighting: LightConfig{ + AmbientBrightness: 1.0, + Sources: []LightSource{}, + }, } } diff --git a/internal/maps/plains.go b/internal/maps/plains.go index 256e428..0b103f6 100644 --- a/internal/maps/plains.go +++ b/internal/maps/plains.go @@ -11,7 +11,19 @@ import ( func CreatePlains(screenWidth, screenHeight float64) *Map { m := NewMap("plains", 1, "Plains", screenWidth, screenHeight) m.BackgroundColor = color.NRGBA{R: 135, G: 206, B: 235, A: 255} - m.TimeOfDay = Daytime + m.Lighting = LightConfig{ + AmbientBrightness: 0.3, + Sources: []LightSource{ + { + X: screenWidth - 80, + Y: 80, + Radius: 600, + Color: color.RGBA{R: 255, G: 250, B: 220, A: 255}, + Intensity: 0.7, + ShadowDarkness: 0.5, + }, + }, + } // ground surface groundColor := color.NRGBA{R: 34, G: 139, B: 34, A: 255} diff --git a/internal/raycast/raycast.go b/internal/raycast/raycast.go index cf02a6b..ef604e0 100644 --- a/internal/raycast/raycast.go +++ b/internal/raycast/raycast.go @@ -337,7 +337,7 @@ func (s *System) IsPointInShadow(x, y float64, w *world.World) bool { if ix, iy, ok := intersection(rayToPoint, wall); ok { distanceToIntersection := math.Sqrt((ix-light.X)*(ix-light.X) + (iy-light.Y)*(iy-light.Y)) - if distanceToIntersection < distanceToPoint-5.0 { + if distanceToIntersection < distanceToPoint-10.0 { blocked = true break } diff --git a/internal/screens/gameplay.go b/internal/screens/gameplay.go index 658cd8d..59f83b4 100644 --- a/internal/screens/gameplay.go +++ b/internal/screens/gameplay.go @@ -3,6 +3,7 @@ package screens import ( "fmt" "image/color" + "math" "time" "github.com/hajimehoshi/ebiten/v2" @@ -168,28 +169,14 @@ func (g *GameplayScreen) updateRaycastLights() { return } - screenWidth := g.bounds.Width - - lightX := float64(screenWidth - 80) - lightY := 80.0 - - if currentMap.TimeOfDay == maps.Daytime { - g.raycastSystem.SetShadowIntensity(0.25) + for _, lightSrc := range currentMap.Lighting.Sources { + g.raycastSystem.SetShadowIntensity(lightSrc.ShadowDarkness) g.raycastSystem.AddLight(&raycast.LightSource{ - X: lightX, - Y: lightY, - Radius: 800.0, - Color: color.RGBA{R: 255, G: 250, B: 220, A: 255}, - Intensity: 1.0, - }) - } else { - g.raycastSystem.SetShadowIntensity(0.7) - g.raycastSystem.AddLight(&raycast.LightSource{ - X: lightX, - Y: lightY, - Radius: 500.0, - Color: color.RGBA{R: 200, G: 220, B: 255, A: 255}, - Intensity: 0.7, + X: lightSrc.X, + Y: lightSrc.Y, + Radius: lightSrc.Radius, + Color: lightSrc.Color, + Intensity: lightSrc.Intensity, }) } } @@ -276,10 +263,7 @@ func (g *GameplayScreen) Draw(screen *ebiten.Image) { currentMap.Draw(screen) g.hud.ScreenName = currentMap.DisplayName - // Apply nighttime darkening overlay BEFORE raycasting - if currentMap.TimeOfDay == maps.Nighttime { - g.drawNightOverlay(screen) - } + g.drawAmbientOverlay(screen, currentMap) // Draw raycasting shadows/lighting if g.raycastEnabled != nil && *g.raycastEnabled { @@ -375,32 +359,37 @@ func (g *GameplayScreen) drawSaveNotification(screen *ebiten.Image) { func (g *GameplayScreen) updateHeroLighting(currentMap *maps.Map) { if g.raycastEnabled == nil || !*g.raycastEnabled { - g.hero.InShadow = false - g.hero.ShadowIntensity = 0 + g.hero.Brightness = 1.0 return } - // Check if hero is in shadow - inShadow := g.raycastSystem.IsPointInShadow(g.hero.X, g.hero.Y, g.world) - g.hero.InShadow = inShadow + ambient := currentMap.Lighting.AmbientBrightness + maxDirectLight := 0.0 - if inShadow { - if currentMap.TimeOfDay == maps.Daytime { - g.hero.ShadowIntensity = 0.3 + for _, light := range currentMap.Lighting.Sources { + inShadow := g.raycastSystem.IsPointInShadow(g.hero.X, g.hero.Y, g.world) + + if !inShadow { + maxDirectLight = math.Max(maxDirectLight, light.Intensity) } else { - g.hero.ShadowIntensity = 0.6 + indirect := light.Intensity * (1.0 - light.ShadowDarkness) + maxDirectLight = math.Max(maxDirectLight, indirect) } - } else { - g.hero.ShadowIntensity = 0 } + + g.hero.Brightness = math.Min(1.0, ambient+maxDirectLight) } -func (g *GameplayScreen) drawNightOverlay(screen *ebiten.Image) { - opts := &ebiten.DrawImageOptions{} - opts.ColorScale.Scale(0.4, 0.4, 0.6, 1.0) +func (g *GameplayScreen) drawAmbientOverlay(screen *ebiten.Image, currentMap *maps.Map) { + if currentMap.Lighting.AmbientBrightness >= 1.0 { + return + } + + darkness := 1.0 - currentMap.Lighting.AmbientBrightness + alpha := uint8(darkness * 200) overlay := ebiten.NewImage(int(g.bounds.Width), int(g.bounds.Height)) - overlay.Fill(color.RGBA{R: 0, G: 0, B: 30, A: 120}) + overlay.Fill(color.RGBA{R: 0, G: 0, B: 0, A: alpha}) screen.DrawImage(overlay, &ebiten.DrawImageOptions{}) } @@ -409,16 +398,15 @@ func (g *GameplayScreen) drawCelestialBody(screen *ebiten.Image, currentMap *map return } - cx := float32(g.bounds.Width - 80) - cy := float32(80) - radius := float32(25) + for _, lightSrc := range currentMap.Lighting.Sources { + cx := float32(lightSrc.X) + cy := float32(lightSrc.Y) + radius := float32(25) - if currentMap.TimeOfDay == maps.Daytime { - vector.DrawFilledCircle(screen, cx, cy, radius, color.RGBA{R: 255, G: 230, B: 100, A: 255}, true) - vector.DrawFilledCircle(screen, cx, cy, radius+3, color.RGBA{R: 255, G: 240, B: 150, A: 100}, true) - } else { - vector.DrawFilledCircle(screen, cx, cy, radius, color.RGBA{R: 240, G: 240, B: 255, A: 255}, true) - vector.DrawFilledCircle(screen, cx, cy, radius+3, color.RGBA{R: 200, G: 200, B: 255, A: 80}, true) + vector.DrawFilledCircle(screen, cx, cy, radius, lightSrc.Color, true) + glowColor := lightSrc.Color + glowColor.A = 80 + vector.DrawFilledCircle(screen, cx, cy, radius+3, glowColor, true) } }