Lighting experiments
This commit is contained in:
16
go.mod
16
go.mod
@@ -3,19 +3,19 @@ module github.com/atridad/LilGuy
|
|||||||
go 1.25.4
|
go 1.25.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/hajimehoshi/ebiten/v2 v2.9.4
|
github.com/hajimehoshi/ebiten/v2 v2.9.6
|
||||||
golang.org/x/image v0.31.0
|
golang.org/x/image v0.34.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.5.0
|
github.com/BurntSushi/toml v1.5.0
|
||||||
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 // indirect
|
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 // indirect
|
||||||
github.com/ebitengine/hideconsole v1.0.0 // indirect
|
github.com/ebitengine/hideconsole v1.0.0 // indirect
|
||||||
github.com/ebitengine/purego v0.9.0 // indirect
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
github.com/go-text/typesetting v0.3.0 // indirect
|
github.com/go-text/typesetting v0.3.1 // indirect
|
||||||
github.com/jezek/xgb v1.1.1 // indirect
|
github.com/jezek/xgb v1.2.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.36.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.29.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
17
go.sum
17
go.sum
@@ -6,25 +6,42 @@ github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj
|
|||||||
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
|
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
|
||||||
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
|
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
|
||||||
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
|
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
|
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
||||||
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
||||||
|
github.com/go-text/typesetting v0.3.1 h1:ESHfFntFnJOigjEeEiTc3OGXqggC1eSAAqHkG9ZB+yA=
|
||||||
|
github.com/go-text/typesetting v0.3.1/go.mod h1:vIRUT25mLQaSh4C8H/lIsKppQz/Gdb8Pu/tNwpi52ts=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||||
|
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8 h1:4KCscI9qYWMGTuz6BpJtbUSRzcBrUSSE0ENMJbNSrFs=
|
||||||
github.com/hajimehoshi/bitmapfont/v4 v4.1.0 h1:eE3qa5Do4qhowZVIHjsrX5pYyyPN6sAFWMsO7QREm3U=
|
github.com/hajimehoshi/bitmapfont/v4 v4.1.0 h1:eE3qa5Do4qhowZVIHjsrX5pYyyPN6sAFWMsO7QREm3U=
|
||||||
github.com/hajimehoshi/bitmapfont/v4 v4.1.0/go.mod h1:/PD+aLjAJ0F2UoQx6hkOfXqWN7BkroDUMr5W+IT1dpE=
|
github.com/hajimehoshi/bitmapfont/v4 v4.1.0/go.mod h1:/PD+aLjAJ0F2UoQx6hkOfXqWN7BkroDUMr5W+IT1dpE=
|
||||||
github.com/hajimehoshi/ebiten/v2 v2.9.4 h1:IlPJpwtksylmmvNhQjv4W2bmCFWXtjY7Z10Esise1bk=
|
github.com/hajimehoshi/ebiten/v2 v2.9.4 h1:IlPJpwtksylmmvNhQjv4W2bmCFWXtjY7Z10Esise1bk=
|
||||||
github.com/hajimehoshi/ebiten/v2 v2.9.4/go.mod h1:DAt4tnkYYpCvu3x9i1X/nK/vOruNXIlYq/tBXxnhrXM=
|
github.com/hajimehoshi/ebiten/v2 v2.9.4/go.mod h1:DAt4tnkYYpCvu3x9i1X/nK/vOruNXIlYq/tBXxnhrXM=
|
||||||
|
github.com/hajimehoshi/ebiten/v2 v2.9.6 h1:uP41hMkfcbfEfgiTlpzhgnTHGAAfbM/v/pNOZkelI78=
|
||||||
|
github.com/hajimehoshi/ebiten/v2 v2.9.6/go.mod h1:DAt4tnkYYpCvu3x9i1X/nK/vOruNXIlYq/tBXxnhrXM=
|
||||||
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
|
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
|
||||||
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||||
|
github.com/jezek/xgb v1.2.0 h1:LzgkD11wOrPnxXEqo588cnjUt4NwMHrFh/tgajo50Q0=
|
||||||
|
github.com/jezek/xgb v1.2.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
|
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
|
||||||
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
|
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
|
||||||
|
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
|
||||||
|
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||||
|
|
||||||
|
"github.com/atridad/LilGuy/internal/maps"
|
||||||
"github.com/atridad/LilGuy/internal/save"
|
"github.com/atridad/LilGuy/internal/save"
|
||||||
"github.com/atridad/LilGuy/internal/screens"
|
"github.com/atridad/LilGuy/internal/screens"
|
||||||
"github.com/atridad/LilGuy/internal/ui/menu"
|
"github.com/atridad/LilGuy/internal/ui/menu"
|
||||||
"github.com/atridad/LilGuy/internal/maps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -105,6 +105,8 @@ type state struct {
|
|||||||
fpsEnabled bool
|
fpsEnabled bool
|
||||||
fpsCap FPSCap
|
fpsCap FPSCap
|
||||||
portalVisibility bool
|
portalVisibility bool
|
||||||
|
raycastEnabled bool
|
||||||
|
raycastDebugMode bool
|
||||||
saveManager *save.Manager
|
saveManager *save.Manager
|
||||||
|
|
||||||
lastAutoSave time.Time
|
lastAutoSave time.Time
|
||||||
@@ -123,6 +125,8 @@ func newState() *state {
|
|||||||
fpsEnabled: false,
|
fpsEnabled: false,
|
||||||
fpsCap: FPSCap60,
|
fpsCap: FPSCap60,
|
||||||
portalVisibility: false,
|
portalVisibility: false,
|
||||||
|
raycastEnabled: true,
|
||||||
|
raycastDebugMode: false,
|
||||||
lastAutoSave: now,
|
lastAutoSave: now,
|
||||||
autoSaveInterval: 30 * time.Second,
|
autoSaveInterval: 30 * time.Second,
|
||||||
}
|
}
|
||||||
@@ -165,16 +169,18 @@ func newState() *state {
|
|||||||
// Initialize screens
|
// Initialize screens
|
||||||
s.splashScreen = screens.NewSplashScreen()
|
s.splashScreen = screens.NewSplashScreen()
|
||||||
s.titleScreen = screens.NewTitleScreen()
|
s.titleScreen = screens.NewTitleScreen()
|
||||||
s.gameplayScreen = screens.NewGameplayScreen(ScreenWidth, ScreenHeight, mapManager, &s.fpsEnabled, &s.portalVisibility)
|
s.gameplayScreen = screens.NewGameplayScreen(ScreenWidth, ScreenHeight, mapManager, &s.fpsEnabled, &s.portalVisibility, &s.raycastEnabled, &s.raycastDebugMode)
|
||||||
s.pauseMenu = menu.NewPauseMenu()
|
s.pauseMenu = menu.NewPauseMenu()
|
||||||
|
|
||||||
// Wire up settings references
|
// Wire up settings references
|
||||||
s.titleScreen.SetFPSMonitor(&s.fpsEnabled)
|
s.titleScreen.SetFPSMonitor(&s.fpsEnabled)
|
||||||
s.titleScreen.SetFPSCap(&s.fpsCap)
|
s.titleScreen.SetFPSCap(&s.fpsCap)
|
||||||
s.titleScreen.SetPortalVisibility(&s.portalVisibility)
|
s.titleScreen.SetPortalVisibility(&s.portalVisibility)
|
||||||
|
s.titleScreen.SetRaycastSettings(&s.raycastEnabled, &s.raycastDebugMode)
|
||||||
s.pauseMenu.SetFPSMonitor(&s.fpsEnabled)
|
s.pauseMenu.SetFPSMonitor(&s.fpsEnabled)
|
||||||
s.pauseMenu.SetFPSCap(&s.fpsCap)
|
s.pauseMenu.SetFPSCap(&s.fpsCap)
|
||||||
s.pauseMenu.SetPortalVisibility(&s.portalVisibility)
|
s.pauseMenu.SetPortalVisibility(&s.portalVisibility)
|
||||||
|
s.pauseMenu.SetRaycastSettings(&s.raycastEnabled, &s.raycastDebugMode)
|
||||||
|
|
||||||
if saveManager != nil {
|
if saveManager != nil {
|
||||||
s.titleScreen.SetHasSaveGame(saveManager.HasSavedGame())
|
s.titleScreen.SetHasSaveGame(saveManager.HasSavedGame())
|
||||||
@@ -282,6 +288,16 @@ func (g *Game) updatePlaying() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle raycasting with L key
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyL) {
|
||||||
|
g.state.gameplayScreen.ToggleRaycast()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle raycasting debug mode with B key
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyB) {
|
||||||
|
g.state.gameplayScreen.ToggleRaycastDebug()
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
delta := now.Sub(g.state.lastTick)
|
delta := now.Sub(g.state.lastTick)
|
||||||
g.state.lastTick = now
|
g.state.lastTick = now
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ type Hero struct {
|
|||||||
lastAnimKey animationKey
|
lastAnimKey animationKey
|
||||||
|
|
||||||
ProjectileConfig projectile.ProjectileConfig
|
ProjectileConfig projectile.ProjectileConfig
|
||||||
|
|
||||||
|
// Lighting state
|
||||||
|
InShadow bool
|
||||||
|
ShadowIntensity float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -358,6 +362,11 @@ func (h *Hero) Draw(screen *ebiten.Image) {
|
|||||||
// no color modification for idle
|
// no color modification for idle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.InShadow {
|
||||||
|
darkness := 1.0 - h.ShadowIntensity
|
||||||
|
op.ColorScale.Scale(float32(darkness), float32(darkness), float32(darkness), 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
op.Filter = ebiten.FilterNearest
|
op.Filter = ebiten.FilterNearest
|
||||||
screen.DrawImage(sprite, op)
|
screen.DrawImage(sprite, op)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import (
|
|||||||
|
|
||||||
func CreateDesert(screenWidth, screenHeight float64) *Map {
|
func CreateDesert(screenWidth, screenHeight float64) *Map {
|
||||||
m := NewMap("desert", 2, "Desert", screenWidth, screenHeight)
|
m := NewMap("desert", 2, "Desert", screenWidth, screenHeight)
|
||||||
m.BackgroundColor = color.NRGBA{R: 155, G: 196, B: 215, A: 255}
|
m.BackgroundColor = color.NRGBA{R: 15, G: 20, B: 40, A: 255} // Dark blue night sky
|
||||||
|
m.TimeOfDay = Nighttime
|
||||||
|
|
||||||
// ground surface
|
// ground surface
|
||||||
m.World.AddSurface(&world.Surface{
|
m.World.AddSurface(&world.Surface{
|
||||||
@@ -22,6 +23,49 @@ func CreateDesert(screenWidth, screenHeight float64) *Map {
|
|||||||
Color: color.NRGBA{R: 139, G: 69, B: 19, A: 255},
|
Color: color.NRGBA{R: 139, G: 69, B: 19, A: 255},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Platforms - sandy brown color
|
||||||
|
platformColor := color.NRGBA{R: 194, G: 134, B: 64, A: 255}
|
||||||
|
|
||||||
|
// Platform 1: Low left
|
||||||
|
m.World.AddSurface(&world.Surface{
|
||||||
|
X: 120,
|
||||||
|
Y: screenHeight - 110,
|
||||||
|
Width: 140,
|
||||||
|
Height: 20,
|
||||||
|
Tag: world.TagPlatform,
|
||||||
|
Color: platformColor,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Platform 2: Mid center-left
|
||||||
|
m.World.AddSurface(&world.Surface{
|
||||||
|
X: 320,
|
||||||
|
Y: screenHeight - 180,
|
||||||
|
Width: 180,
|
||||||
|
Height: 20,
|
||||||
|
Tag: world.TagPlatform,
|
||||||
|
Color: platformColor,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Platform 3: High center-right
|
||||||
|
m.World.AddSurface(&world.Surface{
|
||||||
|
X: 580,
|
||||||
|
Y: screenHeight - 230,
|
||||||
|
Width: 160,
|
||||||
|
Height: 20,
|
||||||
|
Tag: world.TagPlatform,
|
||||||
|
Color: platformColor,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Platform 4: Mid right
|
||||||
|
m.World.AddSurface(&world.Surface{
|
||||||
|
X: 720,
|
||||||
|
Y: screenHeight - 140,
|
||||||
|
Width: 140,
|
||||||
|
Height: 20,
|
||||||
|
Tag: world.TagPlatform,
|
||||||
|
Color: platformColor,
|
||||||
|
})
|
||||||
|
|
||||||
// left portal to plains
|
// left portal to plains
|
||||||
leftPortal := portal.CreateSidePortal("desert_left", portal.SideLeft, screenWidth, screenHeight)
|
leftPortal := portal.CreateSidePortal("desert_left", portal.SideLeft, screenWidth, screenHeight)
|
||||||
leftPortal.DestinationMap = "plains"
|
leftPortal.DestinationMap = "plains"
|
||||||
|
|||||||
@@ -3,9 +3,16 @@ package maps
|
|||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
|
||||||
"github.com/atridad/LilGuy/internal/portal"
|
"github.com/atridad/LilGuy/internal/portal"
|
||||||
"github.com/atridad/LilGuy/internal/world"
|
"github.com/atridad/LilGuy/internal/world"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeOfDay string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Daytime TimeOfDay = "day"
|
||||||
|
Nighttime TimeOfDay = "night"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Map struct {
|
type Map struct {
|
||||||
@@ -17,6 +24,7 @@ type Map struct {
|
|||||||
World *world.World
|
World *world.World
|
||||||
Portals []*portal.Portal
|
Portals []*portal.Portal
|
||||||
BackgroundColor color.NRGBA
|
BackgroundColor color.NRGBA
|
||||||
|
TimeOfDay TimeOfDay // Day or Night tag
|
||||||
|
|
||||||
bakedImage *ebiten.Image
|
bakedImage *ebiten.Image
|
||||||
}
|
}
|
||||||
@@ -31,6 +39,7 @@ func NewMap(id string, number int, displayName string, width, height float64) *M
|
|||||||
World: world.NewWorld(),
|
World: world.NewWorld(),
|
||||||
Portals: make([]*portal.Portal, 0),
|
Portals: make([]*portal.Portal, 0),
|
||||||
BackgroundColor: color.NRGBA{R: 135, G: 206, B: 235, A: 255},
|
BackgroundColor: color.NRGBA{R: 135, G: 206, B: 235, A: 255},
|
||||||
|
TimeOfDay: Daytime, // Default to daytime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
func CreatePlains(screenWidth, screenHeight float64) *Map {
|
func CreatePlains(screenWidth, screenHeight float64) *Map {
|
||||||
m := NewMap("plains", 1, "Plains", screenWidth, screenHeight)
|
m := NewMap("plains", 1, "Plains", screenWidth, screenHeight)
|
||||||
m.BackgroundColor = color.NRGBA{R: 135, G: 206, B: 235, A: 255}
|
m.BackgroundColor = color.NRGBA{R: 135, G: 206, B: 235, A: 255}
|
||||||
|
m.TimeOfDay = Daytime
|
||||||
|
|
||||||
// ground surface
|
// ground surface
|
||||||
groundColor := color.NRGBA{R: 34, G: 139, B: 34, A: 255}
|
groundColor := color.NRGBA{R: 34, G: 139, B: 34, A: 255}
|
||||||
|
|||||||
353
internal/raycast/raycast.go
Normal file
353
internal/raycast/raycast.go
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
package raycast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/atridad/LilGuy/internal/world"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Line represents a line segment
|
||||||
|
type Line struct {
|
||||||
|
X1, Y1, X2, Y2 float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// angle returns the angle of the line
|
||||||
|
func (l *Line) angle() float64 {
|
||||||
|
return math.Atan2(l.Y2-l.Y1, l.X2-l.X1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LightSource represents a light that casts rays
|
||||||
|
type LightSource struct {
|
||||||
|
X, Y float64
|
||||||
|
Radius float64
|
||||||
|
Color color.RGBA
|
||||||
|
Intensity float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type System struct {
|
||||||
|
lights []*LightSource
|
||||||
|
shadowImage *ebiten.Image
|
||||||
|
triangleImage *ebiten.Image
|
||||||
|
screenWidth int
|
||||||
|
screenHeight int
|
||||||
|
enabled bool
|
||||||
|
shadowIntensity float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSystem(screenWidth, screenHeight int) *System {
|
||||||
|
shadowImage := ebiten.NewImage(screenWidth, screenHeight)
|
||||||
|
triangleImage := ebiten.NewImage(screenWidth, screenHeight)
|
||||||
|
triangleImage.Fill(color.White)
|
||||||
|
|
||||||
|
return &System{
|
||||||
|
lights: make([]*LightSource, 0),
|
||||||
|
shadowImage: shadowImage,
|
||||||
|
triangleImage: triangleImage,
|
||||||
|
screenWidth: screenWidth,
|
||||||
|
screenHeight: screenHeight,
|
||||||
|
enabled: true,
|
||||||
|
shadowIntensity: 0.7,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) AddLight(light *LightSource) {
|
||||||
|
s.lights = append(s.lights, light)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) ClearLights() {
|
||||||
|
s.lights = s.lights[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) SetEnabled(enabled bool) {
|
||||||
|
s.enabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) IsEnabled() bool {
|
||||||
|
return s.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) SetShadowIntensity(intensity float64) {
|
||||||
|
s.shadowIntensity = intensity
|
||||||
|
}
|
||||||
|
|
||||||
|
func worldToLines(w *world.World) []Line {
|
||||||
|
var lines []Line
|
||||||
|
|
||||||
|
for _, surface := range w.Surfaces {
|
||||||
|
if !surface.IsSolid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y, width, height := surface.Bounds()
|
||||||
|
|
||||||
|
lines = append(lines,
|
||||||
|
Line{x, y, x + width, y},
|
||||||
|
Line{x + width, y, x + width, y + height},
|
||||||
|
Line{x + width, y + height, x, y + height},
|
||||||
|
Line{x, y + height, x, y},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
// intersection calculates the intersection of two lines
|
||||||
|
func intersection(l1, l2 Line) (float64, float64, bool) {
|
||||||
|
denom := (l1.X1-l1.X2)*(l2.Y1-l2.Y2) - (l1.Y1-l1.Y2)*(l2.X1-l2.X2)
|
||||||
|
tNum := (l1.X1-l2.X1)*(l2.Y1-l2.Y2) - (l1.Y1-l2.Y1)*(l2.X1-l2.X2)
|
||||||
|
uNum := -((l1.X1-l1.X2)*(l1.Y1-l2.Y1) - (l1.Y1-l1.Y2)*(l1.X1-l2.X1))
|
||||||
|
|
||||||
|
if denom == 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tNum / denom
|
||||||
|
if t > 1 || t < 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
u := uNum / denom
|
||||||
|
if u > 1 || u < 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
x := l1.X1 + t*(l1.X2-l1.X1)
|
||||||
|
y := l1.Y1 + t*(l1.Y2-l1.Y1)
|
||||||
|
return x, y, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRay creates a ray from a point at a given angle
|
||||||
|
func newRay(x, y, length, angle float64) Line {
|
||||||
|
return Line{
|
||||||
|
X1: x,
|
||||||
|
Y1: y,
|
||||||
|
X2: x + length*math.Cos(angle),
|
||||||
|
Y2: y + length*math.Sin(angle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLinePoints extracts unique points from lines
|
||||||
|
func getLinePoints(lines []Line) [][2]float64 {
|
||||||
|
pointMap := make(map[[2]float64]bool)
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
pointMap[[2]float64{line.X1, line.Y1}] = true
|
||||||
|
pointMap[[2]float64{line.X2, line.Y2}] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
points := make([][2]float64, 0, len(pointMap))
|
||||||
|
for p := range pointMap {
|
||||||
|
points = append(points, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
func castRays(cx, cy float64, lines []Line) []Line {
|
||||||
|
const rayLength = 2000.0
|
||||||
|
|
||||||
|
var rays []Line
|
||||||
|
points := getLinePoints(lines)
|
||||||
|
|
||||||
|
for _, p := range points {
|
||||||
|
l := Line{cx, cy, p[0], p[1]}
|
||||||
|
angle := l.angle()
|
||||||
|
|
||||||
|
for _, offset := range []float64{-0.001, 0.001} {
|
||||||
|
var intersections [][2]float64
|
||||||
|
ray := newRay(cx, cy, rayLength, angle+offset)
|
||||||
|
|
||||||
|
for _, wall := range lines {
|
||||||
|
if px, py, ok := intersection(ray, wall); ok {
|
||||||
|
intersections = append(intersections, [2]float64{px, py})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(intersections) > 0 {
|
||||||
|
minDist := math.Inf(1)
|
||||||
|
minIdx := -1
|
||||||
|
|
||||||
|
for i, point := range intersections {
|
||||||
|
dist := (cx-point[0])*(cx-point[0]) + (cy-point[1])*(cy-point[1])
|
||||||
|
if dist < minDist {
|
||||||
|
minDist = dist
|
||||||
|
minIdx = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if minIdx >= 0 {
|
||||||
|
rays = append(rays, Line{cx, cy, intersections[minIdx][0], intersections[minIdx][1]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(rays, func(i, j int) bool {
|
||||||
|
return rays[i].angle() < rays[j].angle()
|
||||||
|
})
|
||||||
|
|
||||||
|
return rays
|
||||||
|
}
|
||||||
|
|
||||||
|
// rayVertices creates vertices for a triangle between rays
|
||||||
|
func rayVertices(x1, y1, x2, y2, x3, y3 float64) []ebiten.Vertex {
|
||||||
|
return []ebiten.Vertex{
|
||||||
|
{DstX: float32(x1), DstY: float32(y1), SrcX: 0, SrcY: 0, ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1},
|
||||||
|
{DstX: float32(x2), DstY: float32(y2), SrcX: 0, SrcY: 0, ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1},
|
||||||
|
{DstX: float32(x3), DstY: float32(y3), SrcX: 0, SrcY: 0, ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) Draw(screen *ebiten.Image, w *world.World) {
|
||||||
|
if !s.enabled || len(s.lights) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := worldToLines(w)
|
||||||
|
|
||||||
|
sw := float64(s.screenWidth)
|
||||||
|
sh := float64(s.screenHeight)
|
||||||
|
lines = append(lines,
|
||||||
|
Line{0, 0, sw, 0},
|
||||||
|
Line{sw, 0, sw, sh},
|
||||||
|
Line{sw, sh, 0, sh},
|
||||||
|
Line{0, sh, 0, 0},
|
||||||
|
)
|
||||||
|
|
||||||
|
s.shadowImage.Fill(color.RGBA{0, 0, 0, 180})
|
||||||
|
|
||||||
|
for _, light := range s.lights {
|
||||||
|
rays := castRays(light.X, light.Y, lines)
|
||||||
|
|
||||||
|
opt := &ebiten.DrawTrianglesOptions{}
|
||||||
|
opt.Blend = ebiten.BlendSourceOut
|
||||||
|
|
||||||
|
for i, ray := range rays {
|
||||||
|
nextRay := rays[(i+1)%len(rays)]
|
||||||
|
|
||||||
|
v := rayVertices(light.X, light.Y, nextRay.X2, nextRay.Y2, ray.X2, ray.Y2)
|
||||||
|
s.shadowImage.DrawTriangles(v, []uint16{0, 1, 2}, s.triangleImage, opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowOpt := &ebiten.DrawImageOptions{}
|
||||||
|
shadowOpt.ColorScale.ScaleAlpha(float32(s.shadowIntensity))
|
||||||
|
screen.DrawImage(s.shadowImage, shadowOpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) DrawDebug(screen *ebiten.Image, w *world.World) {
|
||||||
|
if !s.enabled || len(s.lights) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := worldToLines(w)
|
||||||
|
|
||||||
|
sw := float64(s.screenWidth)
|
||||||
|
sh := float64(s.screenHeight)
|
||||||
|
lines = append(lines,
|
||||||
|
Line{0, 0, sw, 0},
|
||||||
|
Line{sw, 0, sw, sh},
|
||||||
|
Line{sw, sh, 0, sh},
|
||||||
|
Line{0, sh, 0, 0},
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, light := range s.lights {
|
||||||
|
rays := castRays(light.X, light.Y, lines)
|
||||||
|
|
||||||
|
for _, ray := range rays {
|
||||||
|
drawLine(screen, ray, color.RGBA{255, 255, 0, 150})
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCircle(screen, light.X, light.Y, 5, color.RGBA{255, 200, 100, 255})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to draw a line
|
||||||
|
func drawLine(screen *ebiten.Image, l Line, c color.RGBA) {
|
||||||
|
x1, y1 := int(l.X1), int(l.Y1)
|
||||||
|
x2, y2 := int(l.X2), int(l.Y2)
|
||||||
|
|
||||||
|
dx := abs(x2 - x1)
|
||||||
|
dy := abs(y2 - y1)
|
||||||
|
sx := -1
|
||||||
|
if x1 < x2 {
|
||||||
|
sx = 1
|
||||||
|
}
|
||||||
|
sy := -1
|
||||||
|
if y1 < y2 {
|
||||||
|
sy = 1
|
||||||
|
}
|
||||||
|
err := dx - dy
|
||||||
|
|
||||||
|
for {
|
||||||
|
if x1 >= 0 && x1 < screen.Bounds().Dx() && y1 >= 0 && y1 < screen.Bounds().Dy() {
|
||||||
|
screen.Set(x1, y1, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if x1 == x2 && y1 == y2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
e2 := 2 * err
|
||||||
|
if e2 > -dy {
|
||||||
|
err -= dy
|
||||||
|
x1 += sx
|
||||||
|
}
|
||||||
|
if e2 < dx {
|
||||||
|
err += dx
|
||||||
|
y1 += sy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to draw a circle
|
||||||
|
func drawCircle(screen *ebiten.Image, cx, cy, radius float64, c color.RGBA) {
|
||||||
|
for angle := 0.0; angle < 2*math.Pi; angle += 0.1 {
|
||||||
|
x := int(cx + radius*math.Cos(angle))
|
||||||
|
y := int(cy + radius*math.Sin(angle))
|
||||||
|
if x >= 0 && x < screen.Bounds().Dx() && y >= 0 && y < screen.Bounds().Dy() {
|
||||||
|
screen.Set(x, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func abs(x int) int {
|
||||||
|
if x < 0 {
|
||||||
|
return -x
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *System) IsPointInShadow(x, y float64, w *world.World) bool {
|
||||||
|
if !s.enabled || len(s.lights) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := worldToLines(w)
|
||||||
|
|
||||||
|
for _, light := range s.lights {
|
||||||
|
rayToPoint := Line{light.X, light.Y, x, y}
|
||||||
|
distanceToPoint := math.Sqrt((x-light.X)*(x-light.X) + (y-light.Y)*(y-light.Y))
|
||||||
|
|
||||||
|
blocked := false
|
||||||
|
for _, wall := range lines {
|
||||||
|
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 {
|
||||||
|
blocked = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !blocked {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/atridad/LilGuy/internal/maps"
|
"github.com/atridad/LilGuy/internal/maps"
|
||||||
"github.com/atridad/LilGuy/internal/portal"
|
"github.com/atridad/LilGuy/internal/portal"
|
||||||
"github.com/atridad/LilGuy/internal/projectile"
|
"github.com/atridad/LilGuy/internal/projectile"
|
||||||
|
"github.com/atridad/LilGuy/internal/raycast"
|
||||||
"github.com/atridad/LilGuy/internal/save"
|
"github.com/atridad/LilGuy/internal/save"
|
||||||
"github.com/atridad/LilGuy/internal/status"
|
"github.com/atridad/LilGuy/internal/status"
|
||||||
"github.com/atridad/LilGuy/internal/ui/hud"
|
"github.com/atridad/LilGuy/internal/ui/hud"
|
||||||
@@ -48,9 +49,14 @@ type GameplayScreen struct {
|
|||||||
showSaveNotification bool
|
showSaveNotification bool
|
||||||
|
|
||||||
portalVisibility *bool
|
portalVisibility *bool
|
||||||
|
|
||||||
|
// Raycasting system
|
||||||
|
raycastSystem *raycast.System
|
||||||
|
raycastEnabled *bool
|
||||||
|
raycastDebugMode *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGameplayScreen(screenWidth, screenHeight int, mapManager *maps.Manager, fpsEnabled *bool, portalVisibility *bool) *GameplayScreen {
|
func NewGameplayScreen(screenWidth, screenHeight int, mapManager *maps.Manager, fpsEnabled *bool, portalVisibility *bool, raycastEnabled *bool, raycastDebugMode *bool) *GameplayScreen {
|
||||||
cfg := config.Default()
|
cfg := config.Default()
|
||||||
|
|
||||||
// Ensure we have a current map
|
// Ensure we have a current map
|
||||||
@@ -100,6 +106,9 @@ func NewGameplayScreen(screenWidth, screenHeight int, mapManager *maps.Manager,
|
|||||||
gameStartTime: time.Now(),
|
gameStartTime: time.Now(),
|
||||||
fpsEnabled: fpsEnabled,
|
fpsEnabled: fpsEnabled,
|
||||||
portalVisibility: portalVisibility,
|
portalVisibility: portalVisibility,
|
||||||
|
raycastSystem: raycast.NewSystem(screenWidth, screenHeight),
|
||||||
|
raycastEnabled: raycastEnabled,
|
||||||
|
raycastDebugMode: raycastDebugMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
gs.portals.OnTransition = gs.handlePortalTransition
|
gs.portals.OnTransition = gs.handlePortalTransition
|
||||||
@@ -132,6 +141,9 @@ func (g *GameplayScreen) Update(input GameplayInput, delta time.Duration) {
|
|||||||
g.projectiles.Update(dt, g.bounds.Width, g.bounds.Height)
|
g.projectiles.Update(dt, g.bounds.Width, g.bounds.Height)
|
||||||
g.portals.Update(dt)
|
g.portals.Update(dt)
|
||||||
|
|
||||||
|
// Update raycasting lights based on current map
|
||||||
|
g.updateRaycastLights()
|
||||||
|
|
||||||
// check for portal collisions
|
// check for portal collisions
|
||||||
g.checkPortalCollision()
|
g.checkPortalCollision()
|
||||||
|
|
||||||
@@ -148,6 +160,40 @@ func (g *GameplayScreen) Update(input GameplayInput, delta time.Duration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GameplayScreen) updateRaycastLights() {
|
||||||
|
g.raycastSystem.ClearLights()
|
||||||
|
|
||||||
|
currentMap := g.mapManager.CurrentMap()
|
||||||
|
if currentMap == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
screenWidth := g.bounds.Width
|
||||||
|
|
||||||
|
lightX := float64(screenWidth - 80)
|
||||||
|
lightY := 80.0
|
||||||
|
|
||||||
|
if currentMap.TimeOfDay == maps.Daytime {
|
||||||
|
g.raycastSystem.SetShadowIntensity(0.25)
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// portal collision detection
|
// portal collision detection
|
||||||
func (g *GameplayScreen) checkPortalCollision() {
|
func (g *GameplayScreen) checkPortalCollision() {
|
||||||
heroRadius := g.hero.Radius
|
heroRadius := g.hero.Radius
|
||||||
@@ -229,6 +275,26 @@ func (g *GameplayScreen) Draw(screen *ebiten.Image) {
|
|||||||
if currentMap != nil {
|
if currentMap != nil {
|
||||||
currentMap.Draw(screen)
|
currentMap.Draw(screen)
|
||||||
g.hud.ScreenName = currentMap.DisplayName
|
g.hud.ScreenName = currentMap.DisplayName
|
||||||
|
|
||||||
|
// Apply nighttime darkening overlay BEFORE raycasting
|
||||||
|
if currentMap.TimeOfDay == maps.Nighttime {
|
||||||
|
g.drawNightOverlay(screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw raycasting shadows/lighting
|
||||||
|
if g.raycastEnabled != nil && *g.raycastEnabled {
|
||||||
|
g.raycastSystem.Draw(screen, g.world)
|
||||||
|
|
||||||
|
if g.raycastDebugMode != nil && *g.raycastDebugMode {
|
||||||
|
g.raycastSystem.DrawDebug(screen, g.world)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw sun or moon circle in top-right
|
||||||
|
g.drawCelestialBody(screen, currentMap)
|
||||||
|
|
||||||
|
// Update hero lighting state
|
||||||
|
g.updateHeroLighting(currentMap)
|
||||||
} else {
|
} else {
|
||||||
screen.Fill(cfg.Visual.BackgroundColor)
|
screen.Fill(cfg.Visual.BackgroundColor)
|
||||||
}
|
}
|
||||||
@@ -307,6 +373,55 @@ func (g *GameplayScreen) drawSaveNotification(screen *ebiten.Image) {
|
|||||||
ebitenutil.DebugPrintAt(screen, msg, int(textX), int(textY))
|
ebitenutil.DebugPrintAt(screen, msg, int(textX), int(textY))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GameplayScreen) updateHeroLighting(currentMap *maps.Map) {
|
||||||
|
if g.raycastEnabled == nil || !*g.raycastEnabled {
|
||||||
|
g.hero.InShadow = false
|
||||||
|
g.hero.ShadowIntensity = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if hero is in shadow
|
||||||
|
inShadow := g.raycastSystem.IsPointInShadow(g.hero.X, g.hero.Y, g.world)
|
||||||
|
g.hero.InShadow = inShadow
|
||||||
|
|
||||||
|
if inShadow {
|
||||||
|
if currentMap.TimeOfDay == maps.Daytime {
|
||||||
|
g.hero.ShadowIntensity = 0.3
|
||||||
|
} else {
|
||||||
|
g.hero.ShadowIntensity = 0.6
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.hero.ShadowIntensity = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GameplayScreen) drawNightOverlay(screen *ebiten.Image) {
|
||||||
|
opts := &ebiten.DrawImageOptions{}
|
||||||
|
opts.ColorScale.Scale(0.4, 0.4, 0.6, 1.0)
|
||||||
|
|
||||||
|
overlay := ebiten.NewImage(int(g.bounds.Width), int(g.bounds.Height))
|
||||||
|
overlay.Fill(color.RGBA{R: 0, G: 0, B: 30, A: 120})
|
||||||
|
screen.DrawImage(overlay, &ebiten.DrawImageOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GameplayScreen) drawCelestialBody(screen *ebiten.Image, currentMap *maps.Map) {
|
||||||
|
if currentMap == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cx := float32(g.bounds.Width - 80)
|
||||||
|
cy := float32(80)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GameplayScreen) ShowSaveNotification() {
|
func (g *GameplayScreen) ShowSaveNotification() {
|
||||||
g.showSaveNotification = true
|
g.showSaveNotification = true
|
||||||
g.saveNotificationTimer = config.SaveNotificationDuration
|
g.saveNotificationTimer = config.SaveNotificationDuration
|
||||||
@@ -431,3 +546,33 @@ func (g *GameplayScreen) LoadState(state *save.GameState) {
|
|||||||
g.fpsAccumulator = 0
|
g.fpsAccumulator = 0
|
||||||
g.fpsValue = 0
|
g.fpsValue = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToggleRaycast toggles the raycasting lighting system on/off
|
||||||
|
func (g *GameplayScreen) ToggleRaycast() {
|
||||||
|
if g.raycastEnabled != nil {
|
||||||
|
*g.raycastEnabled = !*g.raycastEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleRaycastDebug toggles the raycasting debug visualization
|
||||||
|
func (g *GameplayScreen) ToggleRaycastDebug() {
|
||||||
|
if g.raycastDebugMode != nil {
|
||||||
|
*g.raycastDebugMode = !*g.raycastDebugMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRaycastEnabled returns whether raycasting is enabled
|
||||||
|
func (g *GameplayScreen) IsRaycastEnabled() bool {
|
||||||
|
if g.raycastEnabled == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *g.raycastEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRaycastDebugMode returns whether raycasting debug mode is enabled
|
||||||
|
func (g *GameplayScreen) IsRaycastDebugMode() bool {
|
||||||
|
if g.raycastDebugMode == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *g.raycastDebugMode
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ type SettingsScreen struct {
|
|||||||
fpsMonitorValue *bool
|
fpsMonitorValue *bool
|
||||||
fpsCapValue FPSCapSetting
|
fpsCapValue FPSCapSetting
|
||||||
portalVisibilityValue *bool
|
portalVisibilityValue *bool
|
||||||
|
raycastEnabledValue *bool
|
||||||
|
raycastDebugValue *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSettingsScreen() *SettingsScreen {
|
func NewSettingsScreen() *SettingsScreen {
|
||||||
@@ -48,6 +50,11 @@ func (s *SettingsScreen) SetPortalVisibility(enabled *bool) {
|
|||||||
s.portalVisibilityValue = enabled
|
s.portalVisibilityValue = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingsScreen) SetRaycastSettings(enabled *bool, debugMode *bool) {
|
||||||
|
s.raycastEnabledValue = enabled
|
||||||
|
s.raycastDebugValue = debugMode
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingsScreen) Update() bool {
|
func (s *SettingsScreen) Update() bool {
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
||||||
if s.currentScreen == settingsDebugOptions {
|
if s.currentScreen == settingsDebugOptions {
|
||||||
@@ -92,7 +99,7 @@ func (s *SettingsScreen) updateMain() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingsScreen) updateDebugOptions() bool {
|
func (s *SettingsScreen) updateDebugOptions() bool {
|
||||||
debugOptionsCount := 3
|
debugOptionsCount := 5 // FPS Monitor, Portal Visibility, Raycast Enabled, Raycast Debug, Back
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
|
||||||
s.selectedIndex--
|
s.selectedIndex--
|
||||||
if s.selectedIndex < 0 {
|
if s.selectedIndex < 0 {
|
||||||
@@ -111,7 +118,11 @@ func (s *SettingsScreen) updateDebugOptions() bool {
|
|||||||
*s.fpsMonitorValue = !*s.fpsMonitorValue
|
*s.fpsMonitorValue = !*s.fpsMonitorValue
|
||||||
} else if s.selectedIndex == 1 && s.portalVisibilityValue != nil {
|
} else if s.selectedIndex == 1 && s.portalVisibilityValue != nil {
|
||||||
*s.portalVisibilityValue = !*s.portalVisibilityValue
|
*s.portalVisibilityValue = !*s.portalVisibilityValue
|
||||||
} else if s.selectedIndex == 2 {
|
} else if s.selectedIndex == 2 && s.raycastEnabledValue != nil {
|
||||||
|
*s.raycastEnabledValue = !*s.raycastEnabledValue
|
||||||
|
} else if s.selectedIndex == 3 && s.raycastDebugValue != nil {
|
||||||
|
*s.raycastDebugValue = !*s.raycastDebugValue
|
||||||
|
} else if s.selectedIndex == 4 {
|
||||||
s.currentScreen = settingsMain
|
s.currentScreen = settingsMain
|
||||||
s.selectedIndex = 0
|
s.selectedIndex = 0
|
||||||
}
|
}
|
||||||
@@ -211,10 +222,44 @@ func (s *SettingsScreen) drawDebugOptions(screen *ebiten.Image, screenWidth, scr
|
|||||||
s.drawText(screen, portalVisText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, portalY)
|
s.drawText(screen, portalVisText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, portalY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Raycast enabled toggle
|
||||||
|
raycastText := "Lighting: "
|
||||||
|
if s.raycastEnabledValue != nil && *s.raycastEnabledValue {
|
||||||
|
raycastText += "ON"
|
||||||
|
} else {
|
||||||
|
raycastText += "OFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
raycastY := startY + 80
|
||||||
|
if s.selectedIndex == 2 {
|
||||||
|
indicatorX := leftMargin - 20
|
||||||
|
s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, raycastY)
|
||||||
|
s.drawText(screen, raycastText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, raycastY)
|
||||||
|
} else {
|
||||||
|
s.drawText(screen, raycastText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, raycastY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raycast debug toggle
|
||||||
|
raycastDebugText := "Debug Rays: "
|
||||||
|
if s.raycastDebugValue != nil && *s.raycastDebugValue {
|
||||||
|
raycastDebugText += "ON"
|
||||||
|
} else {
|
||||||
|
raycastDebugText += "OFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
raycastDebugY := startY + 120
|
||||||
|
if s.selectedIndex == 3 {
|
||||||
|
indicatorX := leftMargin - 20
|
||||||
|
s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, raycastDebugY)
|
||||||
|
s.drawText(screen, raycastDebugText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, raycastDebugY)
|
||||||
|
} else {
|
||||||
|
s.drawText(screen, raycastDebugText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, raycastDebugY)
|
||||||
|
}
|
||||||
|
|
||||||
// back option
|
// back option
|
||||||
backText := "< Back"
|
backText := "< Back"
|
||||||
backY := startY + 80
|
backY := startY + 160
|
||||||
if s.selectedIndex == 2 {
|
if s.selectedIndex == 4 {
|
||||||
indicatorX := leftMargin - 20
|
indicatorX := leftMargin - 20
|
||||||
s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, backY)
|
s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, backY)
|
||||||
s.drawText(screen, backText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, backY)
|
s.drawText(screen, backText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, backY)
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ func (t *TitleScreen) SetPortalVisibility(enabled *bool) {
|
|||||||
t.settingsScreen.SetPortalVisibility(enabled)
|
t.settingsScreen.SetPortalVisibility(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TitleScreen) SetRaycastSettings(enabled *bool, debugMode *bool) {
|
||||||
|
t.settingsScreen.SetRaycastSettings(enabled, debugMode)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TitleScreen) SetHasSaveGame(hasSave bool) {
|
func (t *TitleScreen) SetHasSaveGame(hasSave bool) {
|
||||||
t.hasSaveGame = hasSave
|
t.hasSaveGame = hasSave
|
||||||
if !hasSave && t.selectedIndex == 0 {
|
if !hasSave && t.selectedIndex == 0 {
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ func (m *PauseMenu) SetPortalVisibility(enabled *bool) {
|
|||||||
m.settingsScreen.SetPortalVisibility(enabled)
|
m.settingsScreen.SetPortalVisibility(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *PauseMenu) SetRaycastSettings(enabled *bool, debugMode *bool) {
|
||||||
|
m.settingsScreen.SetRaycastSettings(enabled, debugMode)
|
||||||
|
}
|
||||||
|
|
||||||
// Update logic
|
// Update logic
|
||||||
|
|
||||||
func (m *PauseMenu) Update() *MenuOption {
|
func (m *PauseMenu) Update() *MenuOption {
|
||||||
|
|||||||
Reference in New Issue
Block a user