diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..22bfa14 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,80 @@ +package config + +import "image/color" + +const ( + ScreenWidth = 960 + ScreenHeight = 540 +) + +type GameConfig struct { + Screen ScreenConfig + Hero HeroConfig + HUD HUDConfig + Visual VisualConfig +} + +type ScreenConfig struct { + Width int + Height int +} + +type HeroConfig struct { + StartX float64 + StartY float64 + Radius float64 + Speed float64 + MaxStamina float64 + StaminaDrain float64 + StaminaRegen float64 + Color color.NRGBA +} + +type HUDConfig struct { + X int + Y int + Margin int +} + +type VisualConfig struct { + BackgroundColor color.NRGBA + SaveNotificationColor color.NRGBA + StaminaNormalColor color.NRGBA + StaminaLowColor color.NRGBA + FPSGoodColor color.NRGBA + FPSWarnColor color.NRGBA + FPSPoorColor color.NRGBA +} + +func Default() GameConfig { + return GameConfig{ + Screen: ScreenConfig{ + Width: ScreenWidth, + Height: ScreenHeight, + }, + Hero: HeroConfig{ + StartX: ScreenWidth / 2, + StartY: ScreenHeight / 2, + Radius: 28.0, + Speed: 180.0, + MaxStamina: 100.0, + StaminaDrain: 50.0, + StaminaRegen: 30.0, + Color: color.NRGBA{R: 210, G: 220, B: 255, A: 255}, + }, + HUD: HUDConfig{ + X: ScreenWidth - 220, + Y: 20, + Margin: 16, + }, + Visual: VisualConfig{ + BackgroundColor: color.NRGBA{R: 135, G: 206, B: 235, A: 255}, + SaveNotificationColor: color.NRGBA{R: 50, G: 200, B: 50, A: 255}, + StaminaNormalColor: color.NRGBA{R: 0, G: 255, B: 180, A: 255}, + StaminaLowColor: color.NRGBA{R: 255, G: 60, B: 60, A: 255}, + FPSGoodColor: color.NRGBA{R: 120, G: 255, B: 120, A: 255}, + FPSWarnColor: color.NRGBA{R: 255, G: 210, B: 100, A: 255}, + FPSPoorColor: color.NRGBA{R: 255, G: 120, B: 120, A: 255}, + }, + } +} diff --git a/internal/config/constants.go b/internal/config/constants.go new file mode 100644 index 0000000..2ee0916 --- /dev/null +++ b/internal/config/constants.go @@ -0,0 +1,60 @@ +package config + +import "time" + +// Animation speeds +const ( + NormalAnimSpeed = 0.03 + IdleAnimSpeed = 0.1 + SprintAnimSpeed = 0.01 + JumpingAnimSpeed = 0.005 +) + +// Physics +const ( + Gravity = 1200.0 + JumpStrength = -450.0 + MaxFallSpeed = 800.0 + GroundFriction = 0.85 + AirFriction = 0.95 +) + +// Gameplay +const ( + SprintSpeedMultiplier = 1.8 + SprintRecoveryThreshold = 0.2 + ExhaustedThreshold = 0.2 + StaminaLowThreshold = 0.2 +) + +// FPS Monitoring +const ( + FPSWarnThreshold = 0.85 + FPSPoorThreshold = 0.6 + FPSSampleWindow = time.Second + TargetTPS = 60 +) + +// Sprite +const ( + AnimFrameWrap = 4096 + HeroSpriteScale = 0.175 + FixedSpriteHeight = 329.0 + FixedSpriteWidth = 315.0 +) + +// Notifications +const ( + SaveNotificationDuration = 2 * time.Second +) + +// Portal +const ( + PortalThickness = 20.0 + PortalTransitionCooldown = 0.5 +) + +// World +const ( + GroundHeight = 16.0 +) diff --git a/internal/config/factory.go b/internal/config/factory.go new file mode 100644 index 0000000..d9eb03d --- /dev/null +++ b/internal/config/factory.go @@ -0,0 +1,78 @@ +package config + +import ( + "image/color" +) + +type MapConfig struct { + ID string + Number int + GroundColor color.NRGBA + BackgroundColor color.NRGBA +} + +func DefaultMap1() MapConfig { + return MapConfig{ + ID: "map1", + Number: 1, + GroundColor: color.NRGBA{R: 34, G: 139, B: 34, A: 255}, + BackgroundColor: color.NRGBA{R: 135, G: 206, B: 235, A: 255}, + } +} + +func DefaultMap2() MapConfig { + return MapConfig{ + ID: "map2", + Number: 2, + GroundColor: color.NRGBA{R: 139, G: 69, B: 19, A: 255}, + BackgroundColor: color.NRGBA{R: 155, G: 196, B: 215, A: 255}, + } +} + +type PortalConfig struct { + ID string + DestinationMap string + DestinationPortal string + Color color.NRGBA + GlowColor color.NRGBA +} + +func LeftPortalMap1() PortalConfig { + return PortalConfig{ + ID: "map1_left", + DestinationMap: "map2", + DestinationPortal: "map2_right", + Color: color.NRGBA{R: 255, G: 100, B: 100, A: 180}, + GlowColor: color.NRGBA{R: 255, G: 150, B: 150, A: 100}, + } +} + +func RightPortalMap1() PortalConfig { + return PortalConfig{ + ID: "map1_right", + DestinationMap: "map2", + DestinationPortal: "map2_left", + Color: color.NRGBA{R: 100, G: 255, B: 100, A: 180}, + GlowColor: color.NRGBA{R: 150, G: 255, B: 150, A: 100}, + } +} + +func LeftPortalMap2() PortalConfig { + return PortalConfig{ + ID: "map2_left", + DestinationMap: "map1", + DestinationPortal: "map1_right", + Color: color.NRGBA{R: 100, G: 255, B: 100, A: 180}, + GlowColor: color.NRGBA{R: 150, G: 255, B: 150, A: 100}, + } +} + +func RightPortalMap2() PortalConfig { + return PortalConfig{ + ID: "map2_right", + DestinationMap: "map1", + DestinationPortal: "map1_left", + Color: color.NRGBA{R: 255, G: 100, B: 100, A: 180}, + GlowColor: color.NRGBA{R: 255, G: 150, B: 150, A: 100}, + } +} diff --git a/internal/game/game.go b/internal/game/game.go index 3b41267..7304a8a 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -101,9 +101,10 @@ type state struct { gameplayScreen *screens.GameplayScreen pauseMenu *menu.PauseMenu - fpsEnabled bool - fpsCap FPSCap - saveManager *save.Manager + fpsEnabled bool + fpsCap FPSCap + portalVisibility bool + saveManager *save.Manager lastAutoSave time.Time autoSaveInterval time.Duration @@ -120,6 +121,7 @@ func newState() *state { lastTick: now, fpsEnabled: false, fpsCap: FPSCap60, + portalVisibility: false, lastAutoSave: now, autoSaveInterval: 30 * time.Second, } @@ -133,6 +135,7 @@ func newState() *state { if saveManager != nil { if settings, err := saveManager.LoadSettings(); err == nil { s.fpsEnabled = settings.FPSMonitor + s.portalVisibility = settings.PortalVisibility switch settings.FPSCap { case "60": s.fpsCap = FPSCap60 @@ -149,14 +152,16 @@ func newState() *state { // Initialize screens s.splashScreen = screens.NewSplashScreen() s.titleScreen = screens.NewTitleScreen() - s.gameplayScreen = screens.NewGameplayScreen(ScreenWidth, ScreenHeight, &s.fpsEnabled) + s.gameplayScreen = screens.NewGameplayScreen(ScreenWidth, ScreenHeight, &s.fpsEnabled, &s.portalVisibility) s.pauseMenu = menu.NewPauseMenu() // Wire up settings references s.titleScreen.SetFPSMonitor(&s.fpsEnabled) s.titleScreen.SetFPSCap(&s.fpsCap) + s.titleScreen.SetPortalVisibility(&s.portalVisibility) s.pauseMenu.SetFPSMonitor(&s.fpsEnabled) s.pauseMenu.SetFPSCap(&s.fpsCap) + s.pauseMenu.SetPortalVisibility(&s.portalVisibility) if saveManager != nil { s.titleScreen.SetHasSaveGame(saveManager.HasSavedGame()) @@ -167,8 +172,9 @@ func newState() *state { // Create initial save file if saveManager != nil { settings := &save.Settings{ - FPSMonitor: s.fpsEnabled, - FPSCap: s.fpCapToStringHelper(s.fpsCap), + FPSMonitor: s.fpsEnabled, + FPSCap: s.fpCapToStringHelper(s.fpsCap), + PortalVisibility: s.portalVisibility, } saveManager.SaveSettings(settings) } @@ -192,6 +198,7 @@ func (s *state) fpCapToStringHelper(cap FPSCap) string { func (g *Game) Update() error { prevFPSEnabled := g.state.fpsEnabled prevFPSCap := g.state.fpsCap + prevPortalVisibility := g.state.portalVisibility currentTPS := g.state.fpsCap.TPS() if currentTPS < 0 { @@ -213,7 +220,7 @@ func (g *Game) Update() error { err = g.updatePaused() } - if prevFPSEnabled != g.state.fpsEnabled || prevFPSCap != g.state.fpsCap { + if prevFPSEnabled != g.state.fpsEnabled || prevFPSCap != g.state.fpsCap || prevPortalVisibility != g.state.portalVisibility { g.saveSettings() } @@ -338,8 +345,9 @@ func (g *Game) saveSettings() { } settings := &save.Settings{ - FPSMonitor: g.state.fpsEnabled, - FPSCap: g.fpCapToString(g.state.fpsCap), + FPSMonitor: g.state.fpsEnabled, + FPSCap: g.fpCapToString(g.state.fpsCap), + PortalVisibility: g.state.portalVisibility, } g.state.saveManager.SaveSettings(settings) } diff --git a/internal/hero/hero.go b/internal/hero/hero.go index c46e268..baca4c5 100644 --- a/internal/hero/hero.go +++ b/internal/hero/hero.go @@ -5,10 +5,10 @@ import ( "github.com/hajimehoshi/ebiten/v2" + "github.com/atridad/LilGuy/internal/config" "github.com/atridad/LilGuy/internal/projectile" ) -// Hero defaults const ( defaultRadius = 24.0 defaultSpeed = 200.0 @@ -16,25 +16,7 @@ const ( defaultStaminaDrain = 50.0 defaultStaminaRegen = 30.0 - sprintSpeedMultiplier = 1.8 - sprintRecoveryThreshold = 0.2 - - normalAnimSpeed = 0.15 - idleAnimSpeed = 0.3 - sprintAnimSpeed = 0.08 - - exhaustedThreshold = 0.2 - - animFrameWrap = 4096 - heroSpriteScale = 0.175 - fixedSpriteHeight = 329.0 - fixedSpriteWidth = 315.0 - - gravity = 1200.0 - jumpStrength = -450.0 - maxFallSpeed = 800.0 - groundFriction = 0.85 - airFriction = 0.95 + heroSpriteScale = 0.175 ) // Input and bounds @@ -163,9 +145,9 @@ func (h *Hero) Update(input Input, dt float64, bounds Bounds) { // Movement and physics func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) { - h.VelocityY += gravity * dt - if h.VelocityY > maxFallSpeed { - h.VelocityY = maxFallSpeed + h.VelocityY += config.Gravity * dt + if h.VelocityY > config.MaxFallSpeed { + h.VelocityY = config.MaxFallSpeed } h.Y += h.VelocityY * dt @@ -180,7 +162,7 @@ func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) { } if input.Jump && h.isGrounded { - h.VelocityY = jumpStrength + h.VelocityY = config.JumpStrength h.isGrounded = false } @@ -198,12 +180,12 @@ func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) { h.isSprinting = input.Sprint && h.canSprint && h.Stamina > 0 && h.isMoving if h.isSprinting { - targetVelocityX *= sprintSpeedMultiplier + targetVelocityX *= config.SprintSpeedMultiplier } - friction := groundFriction + friction := config.GroundFriction if !h.isGrounded { - friction = airFriction + friction = config.AirFriction } h.VelocityX = h.VelocityX*friction + targetVelocityX*(1-friction) @@ -223,7 +205,7 @@ func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) { func (h *Hero) updateStamina(input Input, dt float64) { if !input.Sprint { h.wasSprintHeld = false - if h.Stamina >= h.MaxStamina*sprintRecoveryThreshold { + if h.Stamina >= h.MaxStamina*config.SprintRecoveryThreshold { h.canSprint = true } } @@ -259,21 +241,24 @@ func (h *Hero) updateAnimation(dt float64) { } if isMoving { - animSpeed := normalAnimSpeed * 0.5 + animSpeed := config.NormalAnimSpeed * 0.5 if h.isSprinting { - animSpeed = sprintAnimSpeed * 0.5 + animSpeed = config.SprintAnimSpeed * 0.5 + } + if !h.isGrounded { + animSpeed = config.JumpingAnimSpeed * 0.5 } h.animTimer += dt frameAdvance := int(h.animTimer / animSpeed) if frameAdvance > 0 { h.animTimer -= animSpeed * float64(frameAdvance) - h.animFrame = (h.animFrame + frameAdvance) % animFrameWrap + h.animFrame = (h.animFrame + frameAdvance) % config.AnimFrameWrap } } } func (h *Hero) getVisualState() VisualState { - if h.Stamina < h.MaxStamina*exhaustedThreshold { + if h.Stamina < h.MaxStamina*config.ExhaustedThreshold { return StateExhausted } @@ -296,7 +281,7 @@ func (h *Hero) Draw(screen *ebiten.Image) { op := &ebiten.DrawImageOptions{} op.GeoM.Translate(-actualWidth/2, -actualHeight) - op.GeoM.Scale(heroSpriteScale, heroSpriteScale) + op.GeoM.Scale(config.HeroSpriteScale, config.HeroSpriteScale) op.GeoM.Translate(h.X, h.Y) state := h.getVisualState() diff --git a/internal/maps/map.go b/internal/maps/map.go new file mode 100644 index 0000000..90148d0 --- /dev/null +++ b/internal/maps/map.go @@ -0,0 +1,79 @@ +package maps + +import ( + "image/color" + + "github.com/atridad/LilGuy/internal/config" + "github.com/atridad/LilGuy/internal/portal" + "github.com/atridad/LilGuy/internal/world" +) + +type Map struct { + ID string + Number int + Width float64 + Height float64 + World *world.World + Portals []*portal.Portal + BackgroundColor color.NRGBA +} + +func NewMap(id string, number int, width, height float64) *Map { + return &Map{ + ID: id, + Number: number, + Width: width, + Height: height, + World: world.NewWorld(), + Portals: make([]*portal.Portal, 0), + BackgroundColor: color.NRGBA{R: 135, G: 206, B: 235, A: 255}, + } +} + +func (m *Map) AddPortal(p *portal.Portal) { + m.Portals = append(m.Portals, p) +} + +func (m *Map) GetPortalByID(id string) *portal.Portal { + for _, p := range m.Portals { + if p.ID == id { + return p + } + } + return nil +} + +func CreateDefaultMaps(screenWidth, screenHeight float64) (*Map, *Map) { + map1 := createMapFromConfig(config.DefaultMap1(), screenWidth, screenHeight) + map2 := createMapFromConfig(config.DefaultMap2(), screenWidth, screenHeight) + + addPortalFromConfig(map1, config.LeftPortalMap1(), portal.SideLeft, screenWidth, screenHeight) + addPortalFromConfig(map1, config.RightPortalMap1(), portal.SideRight, screenWidth, screenHeight) + addPortalFromConfig(map2, config.LeftPortalMap2(), portal.SideLeft, screenWidth, screenHeight) + addPortalFromConfig(map2, config.RightPortalMap2(), portal.SideRight, screenWidth, screenHeight) + + return map1, map2 +} + +func createMapFromConfig(cfg config.MapConfig, width, height float64) *Map { + m := NewMap(cfg.ID, cfg.Number, width, height) + m.BackgroundColor = cfg.BackgroundColor + m.World.AddSurface(&world.Surface{ + X: 0, + Y: height - config.GroundHeight, + Width: width, + Height: config.GroundHeight, + Tag: world.TagGround, + Color: cfg.GroundColor, + }) + return m +} + +func addPortalFromConfig(m *Map, cfg config.PortalConfig, side portal.PortalSide, screenWidth, screenHeight float64) { + p := portal.CreateSidePortal(cfg.ID, side, screenWidth, screenHeight) + p.DestinationMap = cfg.DestinationMap + p.DestinationPortal = cfg.DestinationPortal + p.Color = cfg.Color + p.GlowColor = cfg.GlowColor + m.AddPortal(p) +} diff --git a/internal/portal/portal.go b/internal/portal/portal.go new file mode 100644 index 0000000..bdc2b5c --- /dev/null +++ b/internal/portal/portal.go @@ -0,0 +1,270 @@ +package portal + +import ( + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/vector" + + "github.com/atridad/LilGuy/internal/config" +) + +type PortalSide int + +const ( + SideLeft PortalSide = iota + SideRight + SideTop + SideBottom +) + +func (s PortalSide) String() string { + switch s { + case SideLeft: + return "Left" + case SideRight: + return "Right" + case SideTop: + return "Top" + case SideBottom: + return "Bottom" + default: + return "Unknown" + } +} + +type Portal struct { + ID string + X float64 + Y float64 + Width float64 + Height float64 + Side PortalSide + + DestinationMap string + DestinationPortal string + + Color color.NRGBA + GlowColor color.NRGBA + Enabled bool + Visible bool + GlowIntensity float64 + + SpawnOffsetX float64 + SpawnOffsetY float64 +} + +type TransitionEvent struct { + Portal *Portal + HeroX float64 + HeroY float64 + DestinationMap string + DestinationPortal string +} + +func NewPortal(id string, side PortalSide, x, y, width, height float64) *Portal { + return &Portal{ + ID: id, + X: x, + Y: y, + Width: width, + Height: height, + Side: side, + Color: color.NRGBA{R: 100, G: 100, B: 255, A: 180}, + GlowColor: color.NRGBA{R: 150, G: 150, B: 255, A: 100}, + Enabled: true, + Visible: true, + GlowIntensity: 0, + } +} + +// Contains checks if a point is within the portal bounds +func (p *Portal) Contains(x, y float64) bool { + return x >= p.X && x <= p.X+p.Width && y >= p.Y && y <= p.Y+p.Height +} + +// Overlaps checks if a rectangular area overlaps with the portal +func (p *Portal) Overlaps(x, y, width, height float64) bool { + return x+width > p.X && x < p.X+p.Width && + y+height > p.Y && y < p.Y+p.Height +} + +func (p *Portal) CanTransition() bool { + return p.Enabled && p.DestinationMap != "" +} + +func (p *Portal) Update(dt float64) { + if !p.Enabled { + return + } + + p.GlowIntensity += dt * 2.0 + if p.GlowIntensity > 6.28 { // 2*PI + p.GlowIntensity -= 6.28 + } +} + +func (p *Portal) Draw(screen *ebiten.Image) { + if !p.Visible { + return + } + + vector.FillRect( + screen, + float32(p.X), + float32(p.Y), + float32(p.Width), + float32(p.Height), + p.Color, + false, + ) + + if p.Enabled { + glowAlpha := uint8(float64(p.GlowColor.A) * (0.5 + 0.5*float64(p.GlowIntensity))) + glowColor := color.NRGBA{ + R: p.GlowColor.R, + G: p.GlowColor.G, + B: p.GlowColor.B, + A: glowAlpha, + } + + borderWidth := float32(4) + vector.StrokeRect( + screen, + float32(p.X)-borderWidth/2, + float32(p.Y)-borderWidth/2, + float32(p.Width)+borderWidth, + float32(p.Height)+borderWidth, + borderWidth, + glowColor, + false, + ) + } +} + +type Manager struct { + Portals []*Portal + OnTransition func(event TransitionEvent) + transitionCooldown float64 +} + +func NewManager() *Manager { + return &Manager{ + Portals: make([]*Portal, 0), + transitionCooldown: 0, + } +} + +func (m *Manager) AddPortal(portal *Portal) { + m.Portals = append(m.Portals, portal) +} + +func (m *Manager) GetPortalByID(id string) *Portal { + for _, p := range m.Portals { + if p.ID == id { + return p + } + } + return nil +} + +func (m *Manager) RemovePortal(id string) { + for i, p := range m.Portals { + if p.ID == id { + m.Portals = append(m.Portals[:i], m.Portals[i+1:]...) + return + } + } +} + +func (m *Manager) Clear() { + m.Portals = make([]*Portal, 0) +} + +func (m *Manager) Update(dt float64) { + for _, p := range m.Portals { + p.Update(dt) + } + + if m.transitionCooldown > 0 { + m.transitionCooldown -= dt + } +} + +func (m *Manager) CheckCollision(x, y, width, height float64) *Portal { + if m.transitionCooldown > 0 { + return nil + } + + for _, p := range m.Portals { + if !p.CanTransition() { + continue + } + + if p.Overlaps(x, y, width, height) { + return p + } + } + return nil +} + +func (m *Manager) TriggerTransition(portal *Portal, heroX, heroY float64) { + if portal == nil || !portal.CanTransition() { + return + } + + if m.OnTransition != nil { + event := TransitionEvent{ + Portal: portal, + HeroX: heroX, + HeroY: heroY, + DestinationMap: portal.DestinationMap, + DestinationPortal: portal.DestinationPortal, + } + m.OnTransition(event) + } + + m.transitionCooldown = config.PortalTransitionCooldown +} + +func (m *Manager) Draw(screen *ebiten.Image) { + for _, p := range m.Portals { + p.Draw(screen) + } +} + +func CreateSidePortal(id string, side PortalSide, screenWidth, screenHeight float64) *Portal { + var x, y, width, height float64 + + switch side { + case SideLeft: + x = 0 + y = 0 + width = config.PortalThickness + height = screenHeight + case SideRight: + x = screenWidth - config.PortalThickness + y = 0 + width = config.PortalThickness + height = screenHeight + case SideTop: + x = 0 + y = 0 + width = screenWidth + height = config.PortalThickness + case SideBottom: + x = 0 + y = screenHeight - config.PortalThickness + width = screenWidth + height = config.PortalThickness + } + + return NewPortal(id, side, x, y, width, height) +} + +func CreateDoorPortal(id string, side PortalSide, x, y float64) *Portal { + const doorWidth = 80.0 + const doorHeight = 120.0 + + return NewPortal(id, side, x, y, doorWidth, doorHeight) +} diff --git a/internal/save/save.go b/internal/save/save.go index 7e455e9..f548d10 100644 --- a/internal/save/save.go +++ b/internal/save/save.go @@ -21,8 +21,9 @@ type Data struct { } type Settings struct { - FPSMonitor bool `toml:"fps_monitor"` - FPSCap string `toml:"fps_cap"` + FPSMonitor bool `toml:"fps_monitor"` + FPSCap string `toml:"fps_cap"` + PortalVisibility bool `toml:"portal_visibility"` } type GameState struct { @@ -59,8 +60,9 @@ func (m *Manager) LoadData() (*Data, error) { if _, err := os.Stat(m.dataPath); os.IsNotExist(err) { return &Data{ Settings: Settings{ - FPSMonitor: false, - FPSCap: "60", + FPSMonitor: false, + FPSCap: "60", + PortalVisibility: false, }, GameState: GameState{ HasSave: false, @@ -137,8 +139,9 @@ func (m *Manager) SaveGameState(state *GameState) error { if err != nil { data = &Data{ Settings: Settings{ - FPSMonitor: false, - FPSCap: "60", + FPSMonitor: false, + FPSCap: "60", + PortalVisibility: false, }, GameState: *state, } diff --git a/internal/screens/gameplay.go b/internal/screens/gameplay.go index 5dd4423..b106e7e 100644 --- a/internal/screens/gameplay.go +++ b/internal/screens/gameplay.go @@ -9,7 +9,10 @@ import ( "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/vector" + "github.com/atridad/LilGuy/internal/config" "github.com/atridad/LilGuy/internal/hero" + "github.com/atridad/LilGuy/internal/maps" + "github.com/atridad/LilGuy/internal/portal" "github.com/atridad/LilGuy/internal/projectile" "github.com/atridad/LilGuy/internal/save" "github.com/atridad/LilGuy/internal/status" @@ -17,51 +20,6 @@ import ( "github.com/atridad/LilGuy/internal/world" ) -// Screen and hero defaults -var ( - backgroundColor = color.NRGBA{R: 135, G: 206, B: 235, A: 255} - saveNotificationColor = color.NRGBA{R: 50, G: 200, B: 50, A: 255} -) - -const ( - heroStartX = 960 / 2 - heroStartY = 540 / 2 - heroRadius = 28.0 - heroSpeed = 180.0 - heroMaxStamina = 100.0 - heroStaminaDrain = 50.0 - heroStaminaRegen = 30.0 - - saveNotificationDuration = 2 * time.Second -) - -var ( - heroColor = color.NRGBA{R: 210, G: 220, B: 255, A: 255} -) - -// HUD positioning and colors -const ( - hudX = 960 - 220 - hudY = 20 -) - -var ( - staminaNormalColor = color.NRGBA{R: 0, G: 255, B: 180, A: 255} - staminaLowColor = color.NRGBA{R: 255, G: 60, B: 60, A: 255} - fpsGoodColor = color.NRGBA{R: 120, G: 255, B: 120, A: 255} - fpsWarnColor = color.NRGBA{R: 255, G: 210, B: 100, A: 255} - fpsPoorColor = color.NRGBA{R: 255, G: 120, B: 120, A: 255} -) - -// FPS monitoring thresholds -const ( - staminaLowThreshold = 0.2 - fpsWarnThreshold = 0.85 - fpsPoorThreshold = 0.6 - fpsSampleWindow = time.Second - targetTPS = 60 -) - type GameplayInput struct { Left bool Right bool @@ -75,6 +33,7 @@ type GameplayScreen struct { hud hud.Overlay world *world.World projectiles *projectile.Manager + portals *portal.Manager bounds hero.Bounds lastTick time.Time gameStartTime time.Time @@ -86,48 +45,69 @@ type GameplayScreen struct { saveNotificationTimer time.Duration showSaveNotification bool + + portalVisibility *bool + + currentMap *maps.Map + allMaps map[string]*maps.Map } -func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool) *GameplayScreen { - w := world.NewWorld() +func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVisibility *bool) *GameplayScreen { + cfg := config.Default() + map1, map2 := maps.CreateDefaultMaps(float64(screenWidth), float64(screenHeight)) - groundHeight := 16.0 - w.AddSurface(&world.Surface{ - X: 0, - Y: float64(screenHeight) - groundHeight, - Width: float64(screenWidth), - Height: groundHeight, - Tag: world.TagGround, - Color: color.NRGBA{R: 34, G: 139, B: 34, A: 255}, - }) + allMaps := make(map[string]*maps.Map) + allMaps["map1"] = map1 + allMaps["map2"] = map2 - return &GameplayScreen{ + portalMgr := portal.NewManager() + + for _, p := range map1.Portals { + portalMgr.AddPortal(p) + } + + if portalVisibility != nil && *portalVisibility { + for _, p := range portalMgr.Portals { + p.Visible = true + } + } + + gs := &GameplayScreen{ hero: hero.New(hero.Config{ - StartX: heroStartX, - StartY: heroStartY, - Radius: heroRadius, - Speed: heroSpeed, - Color: heroColor, - MaxStamina: heroMaxStamina, - StaminaDrain: heroStaminaDrain, - StaminaRegen: heroStaminaRegen, + StartX: cfg.Hero.StartX, + StartY: cfg.Hero.StartY, + Radius: cfg.Hero.Radius, + Speed: cfg.Hero.Speed, + Color: cfg.Hero.Color, + MaxStamina: cfg.Hero.MaxStamina, + StaminaDrain: cfg.Hero.StaminaDrain, + StaminaRegen: cfg.Hero.StaminaRegen, }), hud: hud.Overlay{ - X: hudX, - Y: hudY, - Color: color.White, + X: cfg.HUD.X, + Y: cfg.HUD.Y, + Color: color.White, + ScreenName: "Map 1", }, - world: w, + world: map1.World, projectiles: projectile.NewManager(), + portals: portalMgr, bounds: hero.Bounds{ Width: float64(screenWidth), Height: float64(screenHeight), - Ground: float64(screenHeight) - groundHeight, + Ground: float64(screenHeight) - config.GroundHeight, }, - lastTick: time.Now(), - gameStartTime: time.Now(), - fpsEnabled: fpsEnabled, + lastTick: time.Now(), + gameStartTime: time.Now(), + fpsEnabled: fpsEnabled, + portalVisibility: portalVisibility, + currentMap: map1, + allMaps: allMaps, } + + gs.portals.OnTransition = gs.handlePortalTransition + + return gs } func (g *GameplayScreen) Update(input GameplayInput, delta time.Duration) { @@ -149,6 +129,10 @@ func (g *GameplayScreen) Update(input GameplayInput, delta time.Duration) { } g.projectiles.Update(dt, g.bounds.Width, g.bounds.Height) + g.portals.Update(dt) + + // Check for portal collisions + g.checkPortalCollision() g.totalPlayTime += delta @@ -162,6 +146,71 @@ func (g *GameplayScreen) Update(input GameplayInput, delta time.Duration) { } } +// Portal collision detection +func (g *GameplayScreen) checkPortalCollision() { + heroRadius := g.hero.Radius + heroX := g.hero.X - heroRadius + heroY := g.hero.Y - heroRadius + heroWidth := heroRadius * 2 + heroHeight := heroRadius * 2 + + if p := g.portals.CheckCollision(heroX, heroY, heroWidth, heroHeight); p != nil { + g.portals.TriggerTransition(p, g.hero.X, g.hero.Y) + } + + g.updatePortalVisibility() +} + +func (g *GameplayScreen) updatePortalVisibility() { + if g.portalVisibility == nil { + return + } + + visible := *g.portalVisibility + for _, p := range g.portals.Portals { + p.Visible = visible + } +} + +func (g *GameplayScreen) handlePortalTransition(event portal.TransitionEvent) { + destMap, exists := g.allMaps[event.DestinationMap] + if !exists { + return + } + + // Switch to destination map + g.currentMap = destMap + g.world = destMap.World + + // Clear and reload portals for new map + g.portals.Clear() + for _, p := range destMap.Portals { + g.portals.AddPortal(p) + } + + g.updatePortalVisibility() + + // Find destination portal and position hero + destPortal := destMap.GetPortalByID(event.DestinationPortal) + if destPortal != nil { + // Position hero based on which side they're entering from + switch destPortal.Side { + case portal.SideLeft: + g.hero.X = destPortal.X + destPortal.Width + g.hero.Radius + 10 + g.hero.Y = event.HeroY + case portal.SideRight: + g.hero.X = destPortal.X - g.hero.Radius - 10 + g.hero.Y = event.HeroY + case portal.SideTop: + g.hero.X = event.HeroX + g.hero.Y = destPortal.Y + destPortal.Height + g.hero.Radius + 10 + case portal.SideBottom: + g.hero.X = event.HeroX + g.hero.Y = destPortal.Y - g.hero.Radius - 10 + } + } +} + // FPS tracking func (g *GameplayScreen) trackFPS(delta time.Duration) { if g.fpsEnabled == nil || !*g.fpsEnabled { @@ -171,7 +220,7 @@ func (g *GameplayScreen) trackFPS(delta time.Duration) { g.fpsAccumulator += delta g.fpsFrames++ - if g.fpsAccumulator >= fpsSampleWindow { + if g.fpsAccumulator >= config.FPSSampleWindow { g.fpsValue = float64(g.fpsFrames) / g.fpsAccumulator.Seconds() g.fpsAccumulator = 0 g.fpsFrames = 0 @@ -180,15 +229,25 @@ func (g *GameplayScreen) trackFPS(delta time.Duration) { // Rendering func (g *GameplayScreen) Draw(screen *ebiten.Image) { - screen.Fill(backgroundColor) + cfg := config.Default() + bgColor := cfg.Visual.BackgroundColor + if g.currentMap != nil { + bgColor = g.currentMap.BackgroundColor + } + screen.Fill(bgColor) g.world.Draw(screen) + g.portals.Draw(screen) g.projectiles.Draw(screen) g.hero.Draw(screen) - staminaColor := staminaNormalColor - if g.hero.Stamina < g.hero.MaxStamina*staminaLowThreshold { - staminaColor = staminaLowColor + if g.currentMap != nil { + g.hud.ScreenName = fmt.Sprintf("Map %d", g.currentMap.Number) + } + + staminaColor := cfg.Visual.StaminaNormalColor + if g.hero.Stamina < g.hero.MaxStamina*config.StaminaLowThreshold { + staminaColor = cfg.Visual.StaminaLowColor } staminaMeter := status.Meter{ @@ -201,13 +260,13 @@ func (g *GameplayScreen) Draw(screen *ebiten.Image) { meters := []status.Meter{staminaMeter} if g.fpsEnabled != nil && *g.fpsEnabled { - ratio := g.fpsValue / float64(targetTPS) - fpsColor := fpsGoodColor + ratio := g.fpsValue / float64(config.TargetTPS) + fpsColor := cfg.Visual.FPSGoodColor switch { - case ratio < fpsPoorThreshold: - fpsColor = fpsPoorColor - case ratio < fpsWarnThreshold: - fpsColor = fpsWarnColor + case ratio < config.FPSPoorThreshold: + fpsColor = cfg.Visual.FPSPoorColor + case ratio < config.FPSWarnThreshold: + fpsColor = cfg.Visual.FPSWarnColor } fpsMeter := status.Meter{ @@ -233,7 +292,8 @@ func (g *GameplayScreen) drawSaveNotification(screen *ebiten.Image) { boxWidth := float32(140) boxHeight := float32(40) - vector.DrawFilledRect(screen, + cfg := config.Default() + vector.FillRect(screen, centerX-boxWidth/2, centerY-boxHeight/2, boxWidth, @@ -247,7 +307,7 @@ func (g *GameplayScreen) drawSaveNotification(screen *ebiten.Image) { boxWidth, boxHeight, 2, - saveNotificationColor, + cfg.Visual.SaveNotificationColor, false) msg := "Game Saved!" @@ -258,41 +318,43 @@ func (g *GameplayScreen) drawSaveNotification(screen *ebiten.Image) { func (g *GameplayScreen) ShowSaveNotification() { g.showSaveNotification = true - g.saveNotificationTimer = saveNotificationDuration + g.saveNotificationTimer = config.SaveNotificationDuration } // State management func (g *GameplayScreen) Reset() { + cfg := config.Default() screenWidth := int(g.bounds.Width) screenHeight := int(g.bounds.Height) - groundHeight := 16.0 - w := world.NewWorld() - w.AddSurface(&world.Surface{ - X: 0, - Y: float64(screenHeight) - groundHeight, - Width: float64(screenWidth), - Height: groundHeight, - Tag: world.TagGround, - Color: color.NRGBA{R: 34, G: 139, B: 34, A: 255}, - }) + map1, map2 := maps.CreateDefaultMaps(float64(screenWidth), float64(screenHeight)) + g.allMaps = make(map[string]*maps.Map) + g.allMaps["map1"] = map1 + g.allMaps["map2"] = map2 + g.currentMap = map1 g.hero = hero.New(hero.Config{ - StartX: heroStartX, - StartY: heroStartY, - Radius: heroRadius, - Speed: heroSpeed, - Color: heroColor, - MaxStamina: heroMaxStamina, - StaminaDrain: heroStaminaDrain, - StaminaRegen: heroStaminaRegen, + StartX: cfg.Hero.StartX, + StartY: cfg.Hero.StartY, + Radius: cfg.Hero.Radius, + Speed: cfg.Hero.Speed, + Color: cfg.Hero.Color, + MaxStamina: cfg.Hero.MaxStamina, + StaminaDrain: cfg.Hero.StaminaDrain, + StaminaRegen: cfg.Hero.StaminaRegen, }) - g.world = w + g.world = map1.World g.projectiles = projectile.NewManager() + g.portals = portal.NewManager() + for _, p := range map1.Portals { + g.portals.AddPortal(p) + } + g.portals.OnTransition = g.handlePortalTransition + g.updatePortalVisibility() g.bounds = hero.Bounds{ Width: float64(screenWidth), Height: float64(screenHeight), - Ground: float64(screenHeight) - groundHeight, + Ground: float64(screenHeight) - config.GroundHeight, } g.lastTick = time.Now() g.gameStartTime = time.Now() @@ -312,37 +374,39 @@ func (g *GameplayScreen) SaveState() *save.GameState { } func (g *GameplayScreen) LoadState(state *save.GameState) { + cfg := config.Default() screenWidth := int(g.bounds.Width) screenHeight := int(g.bounds.Height) - groundHeight := 16.0 - w := world.NewWorld() - w.AddSurface(&world.Surface{ - X: 0, - Y: float64(screenHeight) - groundHeight, - Width: float64(screenWidth), - Height: groundHeight, - Tag: world.TagGround, - Color: color.NRGBA{R: 34, G: 139, B: 34, A: 255}, - }) + map1, map2 := maps.CreateDefaultMaps(float64(screenWidth), float64(screenHeight)) + g.allMaps = make(map[string]*maps.Map) + g.allMaps["map1"] = map1 + g.allMaps["map2"] = map2 + g.currentMap = map1 g.hero = hero.New(hero.Config{ StartX: state.HeroX, StartY: state.HeroY, - Radius: heroRadius, - Speed: heroSpeed, - Color: heroColor, - MaxStamina: heroMaxStamina, - StaminaDrain: heroStaminaDrain, - StaminaRegen: heroStaminaRegen, + Radius: cfg.Hero.Radius, + Speed: cfg.Hero.Speed, + Color: cfg.Hero.Color, + MaxStamina: cfg.Hero.MaxStamina, + StaminaDrain: cfg.Hero.StaminaDrain, + StaminaRegen: cfg.Hero.StaminaRegen, }) g.hero.Stamina = state.HeroStamina - g.world = w + g.world = map1.World g.projectiles = projectile.NewManager() + g.portals = portal.NewManager() + for _, p := range map1.Portals { + g.portals.AddPortal(p) + } + g.portals.OnTransition = g.handlePortalTransition + g.updatePortalVisibility() g.bounds = hero.Bounds{ Width: float64(screenWidth), Height: float64(screenHeight), - Ground: float64(screenHeight) - groundHeight, + Ground: float64(screenHeight) - config.GroundHeight, } g.totalPlayTime = time.Duration(state.PlayTimeMS) * time.Millisecond g.lastTick = time.Now() diff --git a/internal/screens/settings.go b/internal/screens/settings.go index 56e8437..a938c66 100644 --- a/internal/screens/settings.go +++ b/internal/screens/settings.go @@ -15,9 +15,10 @@ type FPSCapSetting interface { } type SettingsScreen struct { - selectedIndex int - fpsMonitorValue *bool - fpsCapValue FPSCapSetting + selectedIndex int + fpsMonitorValue *bool + fpsCapValue FPSCapSetting + portalVisibilityValue *bool } func NewSettingsScreen() *SettingsScreen { @@ -34,12 +35,16 @@ func (s *SettingsScreen) SetFPSCap(cap FPSCapSetting) { s.fpsCapValue = cap } +func (s *SettingsScreen) SetPortalVisibility(enabled *bool) { + s.portalVisibilityValue = enabled +} + func (s *SettingsScreen) Update() bool { if inpututil.IsKeyJustPressed(ebiten.KeyEscape) { return true } - settingsCount := 2 + settingsCount := 3 if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) { s.selectedIndex-- if s.selectedIndex < 0 { @@ -58,6 +63,8 @@ func (s *SettingsScreen) Update() bool { *s.fpsMonitorValue = !*s.fpsMonitorValue } else if s.selectedIndex == 1 && s.fpsCapValue != nil { s.fpsCapValue.Cycle() + } else if s.selectedIndex == 2 && s.portalVisibilityValue != nil { + *s.portalVisibilityValue = !*s.portalVisibilityValue } } @@ -109,6 +116,23 @@ func (s *SettingsScreen) Draw(screen *ebiten.Image, screenWidth, screenHeight in s.drawText(screen, fpsCapText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, capY) } + // Portal visibility toggle + portalVisText := "Portal Visibility: " + if s.portalVisibilityValue != nil && *s.portalVisibilityValue { + portalVisText += "ON" + } else { + portalVisText += "OFF" + } + + portalY := startY + 80 + if s.selectedIndex == 2 { + indicatorX := leftMargin - 20 + s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, portalY) + s.drawText(screen, portalVisText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, portalY) + } else { + s.drawText(screen, portalVisText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, portalY) + } + // Instructions hintText := "Enter/Space to toggle, ESC to go back" hintX := (screenWidth / 2) - (len(hintText) * 7 / 2) diff --git a/internal/screens/title.go b/internal/screens/title.go index 81d07a6..e5331be 100644 --- a/internal/screens/title.go +++ b/internal/screens/title.go @@ -6,7 +6,6 @@ import ( "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/text/v2" - "github.com/hajimehoshi/ebiten/v2/vector" ) // Menu options @@ -52,6 +51,10 @@ func (t *TitleScreen) SetFPSCap(cap FPSCapSetting) { t.settingsScreen.SetFPSCap(cap) } +func (t *TitleScreen) SetPortalVisibility(enabled *bool) { + t.settingsScreen.SetPortalVisibility(enabled) +} + func (t *TitleScreen) SetHasSaveGame(hasSave bool) { t.hasSaveGame = hasSave if !hasSave && t.selectedIndex == 0 { @@ -151,15 +154,6 @@ func (t *TitleScreen) Draw(screen *ebiten.Image, screenWidth, screenHeight int) indicatorX := optionX - 20 t.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, optionY) t.drawText(screen, option, color.RGBA{R: 255, G: 255, B: 100, A: 255}, optionX, optionY) - - boxPadding := float32(10.0) - boxWidth := float32(len(option)*7) + boxPadding*2 - boxHeight := float32(20) - boxX := float32(optionX) - boxPadding - boxY := float32(optionY) - float32(basicFaceAscent) - boxPadding/2 - - vector.StrokeRect(screen, boxX, boxY, boxWidth, boxHeight, 2, - color.RGBA{R: 255, G: 200, B: 0, A: 255}, false) } else { t.drawText(screen, option, optionColor, optionX, optionY) } diff --git a/internal/ui/hud/elements.go b/internal/ui/hud/elements.go index 5c74b79..7dea53c 100644 --- a/internal/ui/hud/elements.go +++ b/internal/ui/hud/elements.go @@ -114,21 +114,24 @@ func (b Bar) Draw(screen *ebiten.Image, x, y int) (int, int) { fillWidth = maxWidth } - if b.ShowBorder { - borderColor := b.BorderColor - if borderColor == nil { - borderColor = color.RGBA{R: 80, G: 80, B: 80, A: 255} - } - drawRect(screen, x, y, maxWidth, 1, borderColor) - drawRect(screen, x, y+height-1, maxWidth, 1, borderColor) - drawRect(screen, x, y, 1, height, borderColor) - drawRect(screen, x+maxWidth-1, y, 1, height, borderColor) - } + // Draw dark background + drawRect(screen, x, y, maxWidth, height, color.RGBA{R: 30, G: 30, B: 30, A: 255}) + // Draw filled portion if fillWidth > 0 { drawRect(screen, x, y, fillWidth, height, b.Meter.Color) } + // Draw border + borderColor := b.BorderColor + if borderColor == nil { + borderColor = color.RGBA{R: 180, G: 180, B: 180, A: 255} + } + drawRect(screen, x, y, maxWidth, 1, borderColor) + drawRect(screen, x, y+height-1, maxWidth, 1, borderColor) + drawRect(screen, x, y, 1, height, borderColor) + drawRect(screen, x+maxWidth-1, y, 1, height, borderColor) + return maxWidth, height } diff --git a/internal/ui/hud/hud.go b/internal/ui/hud/hud.go index a25a069..3d811de 100644 --- a/internal/ui/hud/hud.go +++ b/internal/ui/hud/hud.go @@ -4,14 +4,16 @@ import ( "image/color" "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/vector" "github.com/atridad/LilGuy/internal/status" ) type Overlay struct { - X int - Y int - Color color.Color + X int + Y int + Color color.Color + ScreenName string } func (o Overlay) Draw(screen *ebiten.Image, meters []status.Meter) { @@ -19,35 +21,64 @@ func (o Overlay) Draw(screen *ebiten.Image, meters []status.Meter) { o.Color = color.White } + // Draw background box for instructions + instrBoxWidth := float32(280) + instrBoxHeight := float32(78) + instrBoxPaddingX := float32(12) + instrBoxPaddingY := float32(15) + vector.FillRect(screen, 10, 10, instrBoxWidth, instrBoxHeight, + color.NRGBA{R: 0, G: 0, B: 0, A: 180}, false) + vector.StrokeRect(screen, 10, 10, instrBoxWidth, instrBoxHeight, 2, + color.NRGBA{R: 200, G: 200, B: 200, A: 255}, false) + + screenName := o.ScreenName + if screenName == "" { + screenName = "Lil Guy" + } + instructions := Column{ Elements: []Element{ - Label{Text: "Lil Guy", Color: o.Color}, - Label{Text: "Move with Arrow Keys / WASD", Color: o.Color}, - Label{Text: "Hold Shift to Sprint", Color: o.Color}}, + Label{Text: screenName, Color: color.NRGBA{R: 255, G: 255, B: 100, A: 255}}, + Label{Text: "Move with Arrow Keys / WASD", Color: color.NRGBA{R: 230, G: 230, B: 230, A: 255}}, + Label{Text: "Hold Shift to Sprint", Color: color.NRGBA{R: 230, G: 230, B: 230, A: 255}}, + }, Spacing: 7, } - instructions.Draw(screen, 16, 16) + instructions.Draw(screen, int(10+instrBoxPaddingX), int(10+instrBoxPaddingY)) + + // Draw background box for meters + meterBoxPaddingX := float32(12) + meterBoxPaddingTop := float32(15) + meterBoxPaddingBottom := float32(8) + meterBoxWidth := float32(220) + meterBoxHeight := float32(meterBoxPaddingTop + meterBoxPaddingBottom + float32(len(meters))*35 - 5) + meterBoxX := float32(o.X) - meterBoxPaddingX + meterBoxY := float32(o.Y) - meterBoxPaddingTop + vector.FillRect(screen, meterBoxX, meterBoxY, meterBoxWidth, meterBoxHeight, + color.NRGBA{R: 0, G: 0, B: 0, A: 180}, false) + vector.StrokeRect(screen, meterBoxX, meterBoxY, meterBoxWidth, meterBoxHeight, 2, + color.NRGBA{R: 200, G: 200, B: 200, A: 255}, false) meterElements := make([]Element, 0, len(meters)) for _, meter := range meters { if meter.Base < 0 { meterElements = append(meterElements, - MeterLabel{Meter: meter, Color: o.Color}, + MeterLabel{Meter: meter, Color: color.NRGBA{R: 255, G: 255, B: 255, A: 255}}, ) } else { meterElements = append(meterElements, Column{ Elements: []Element{ - MeterLabel{Meter: meter, Color: o.Color}, - Bar{Meter: meter, MaxWidth: 180, Height: 8, ShowBorder: false}, + MeterLabel{Meter: meter, Color: color.NRGBA{R: 255, G: 255, B: 255, A: 255}}, + Bar{Meter: meter, MaxWidth: 180, Height: 10, ShowBorder: true}, }, - Spacing: 2, + Spacing: 4, }) } } meterPanel := Column{ Elements: meterElements, - Spacing: 16, + Spacing: 18, } meterPanel.Draw(screen, o.X, o.Y) } diff --git a/internal/ui/menu/menu.go b/internal/ui/menu/menu.go index 31e591a..2d9ace5 100644 --- a/internal/ui/menu/menu.go +++ b/internal/ui/menu/menu.go @@ -84,6 +84,10 @@ func (m *PauseMenu) SetFPSCap(cap FPSCapSetting) { m.settingsScreen.SetFPSCap(cap) } +func (m *PauseMenu) SetPortalVisibility(enabled *bool) { + m.settingsScreen.SetPortalVisibility(enabled) +} + // Update logic func (m *PauseMenu) Update() *MenuOption { @@ -140,7 +144,7 @@ func (m *PauseMenu) Draw(screen *ebiten.Image, screenWidth, screenHeight int) { menuX := (screenWidth - menuWidth) / 2 menuY := (screenHeight - menuHeight) / 2 - vector.DrawFilledRect(screen, + vector.FillRect(screen, float32(menuX), float32(menuY), float32(menuWidth), float32(menuHeight), color.RGBA{R: 40, G: 40, B: 50, A: 255}, @@ -192,7 +196,7 @@ func (m *PauseMenu) drawMain(screen *ebiten.Image, menuX, menuY, menuWidth, menu } func (m *PauseMenu) drawSettings(screen *ebiten.Image, menuX, menuY, menuWidth, menuHeight int) { - vector.DrawFilledRect(screen, + vector.FillRect(screen, float32(menuX), float32(menuY), float32(menuWidth), float32(menuHeight), color.RGBA{R: 40, G: 40, B: 50, A: 255}, diff --git a/internal/world/surface.go b/internal/world/surface.go index 15f71ec..0910ead 100644 --- a/internal/world/surface.go +++ b/internal/world/surface.go @@ -58,7 +58,7 @@ func (s *Surface) IsWalkable() bool { } func (s *Surface) Draw(screen *ebiten.Image) { - vector.DrawFilledRect( + vector.FillRect( screen, float32(s.X), float32(s.Y),