Optimizations and debug options

This commit is contained in:
2025-11-25 01:06:35 -07:00
parent 57d08f2f04
commit c84ba37353
13 changed files with 377 additions and 212 deletions

View File

@@ -12,18 +12,18 @@ const (
// Physics
const (
Gravity = 1200.0
JumpStrength = -450.0
MaxFallSpeed = 800.0
GroundFriction = 0.85
AirFriction = 0.95
Gravity = 1400.0
JumpStrength = -480.0
MaxFallSpeed = 900.0
GroundFriction = 0.82
AirFriction = 0.96
)
// Gameplay
const (
SprintSpeedMultiplier = 1.8
SprintRecoveryThreshold = 0.2
ExhaustedThreshold = 0.2
SprintSpeedMultiplier = 2.0
SprintRecoveryThreshold = 0.25
ExhaustedThreshold = 0.15
StaminaLowThreshold = 0.2
)

View File

@@ -1,78 +0,0 @@
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},
}
}

View File

@@ -273,6 +273,16 @@ func (g *Game) updatePlaying() error {
delta := now.Sub(g.state.lastTick)
g.state.lastTick = now
maxDelta := 100 * time.Millisecond
if delta > maxDelta {
delta = maxDelta
}
minDelta := time.Microsecond
if delta < minDelta {
delta = minDelta
}
input := readControls()
g.state.gameplayScreen.Update(screens.GameplayInput{
Left: input.Left,
@@ -284,7 +294,6 @@ func (g *Game) updatePlaying() error {
if now.Sub(g.state.lastAutoSave) >= g.state.autoSaveInterval {
g.saveGame()
g.state.gameplayScreen.ShowSaveNotification()
g.state.lastAutoSave = now
}

View File

@@ -137,6 +137,9 @@ func New(cfg Config) *Hero {
}
func (h *Hero) Update(input Input, dt float64, bounds Bounds) {
if dt > 0.1 {
dt = 0.1
}
h.updateMovement(input, dt, bounds)
h.updateStamina(input, dt)
h.updateAnimation(dt)
@@ -145,27 +148,30 @@ func (h *Hero) Update(input Input, dt float64, bounds Bounds) {
// Movement and physics
func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) {
// apply gravity
h.VelocityY += config.Gravity * dt
if h.VelocityY > config.MaxFallSpeed {
h.VelocityY = config.MaxFallSpeed
}
h.Y += h.VelocityY * dt
newY := h.Y + h.VelocityY*dt
footPosition := h.Y
if footPosition >= bounds.Ground {
wasGrounded := h.isGrounded
if newY >= bounds.Ground {
h.Y = bounds.Ground
h.VelocityY = 0
h.isGrounded = true
} else {
h.Y = newY
h.isGrounded = false
}
if input.Jump && h.isGrounded {
if input.Jump && (h.isGrounded || (!wasGrounded && h.isGrounded)) {
h.VelocityY = config.JumpStrength
h.isGrounded = false
}
// horizontal input
targetVelocityX := 0.0
if input.Left {
targetVelocityX -= h.Speed
@@ -178,6 +184,7 @@ func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) {
h.isMoving = targetVelocityX != 0
// sprinting
h.isSprinting = input.Sprint && h.canSprint && h.Stamina > 0 && h.isMoving
if h.isSprinting {
targetVelocityX *= config.SprintSpeedMultiplier
@@ -188,17 +195,22 @@ func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) {
friction = config.AirFriction
}
h.VelocityX = h.VelocityX*friction + targetVelocityX*(1-friction)
frictionFactor := 1.0 - (1.0-friction)*dt*60.0
if frictionFactor < 0 {
frictionFactor = 0
}
h.VelocityX = h.VelocityX*frictionFactor + targetVelocityX*(1.0-frictionFactor)
h.X += h.VelocityX * dt
newX := h.X + h.VelocityX*dt
if h.X < h.Radius {
if newX < h.Radius {
h.X = h.Radius
h.VelocityX = 0
}
if h.X > bounds.Width-h.Radius {
} else if newX > bounds.Width-h.Radius {
h.X = bounds.Width - h.Radius
h.VelocityX = 0
} else {
h.X = newX
}
}
@@ -234,12 +246,14 @@ func (h *Hero) updateAnimation(dt float64) {
key.state = animMove
}
// reset animation on state change
if key != h.lastAnimKey {
h.animFrame = 0
h.animTimer = 0
h.lastAnimKey = key
}
// advance animation only when moving
if isMoving {
animSpeed := config.NormalAnimSpeed * 0.5
if h.isSprinting {
@@ -248,12 +262,17 @@ func (h *Hero) updateAnimation(dt float64) {
if !h.isGrounded {
animSpeed = config.JumpingAnimSpeed * 0.5
}
h.animTimer += dt
// precise frame advancement
if h.animTimer >= animSpeed {
frameAdvance := int(h.animTimer / animSpeed)
if frameAdvance > 0 {
h.animTimer -= animSpeed * float64(frameAdvance)
h.animFrame = (h.animFrame + frameAdvance) % config.AnimFrameWrap
}
} else {
h.animTimer = 0
}
}
@@ -280,18 +299,28 @@ func (h *Hero) Draw(screen *ebiten.Image) {
actualWidth := float64(bounds.Dx())
op := &ebiten.DrawImageOptions{}
// center sprite horizontally, align bottom to feet
op.GeoM.Translate(-actualWidth/2, -actualHeight)
op.GeoM.Scale(config.HeroSpriteScale, config.HeroSpriteScale)
op.GeoM.Translate(h.X, h.Y)
// round position to nearest pixel for crisp rendering
drawX := float64(int(h.X + 0.5))
drawY := float64(int(h.Y + 0.5))
op.GeoM.Translate(drawX, drawY)
// apply visual state coloring
state := h.getVisualState()
switch state {
case StateExhausted:
op.ColorScale.ScaleWithColor(color.RGBA{R: 255, G: 100, B: 100, A: 255})
case StateSprinting:
// no color modification for sprinting
case StateIdle:
// no color modification for idle
}
op.Filter = ebiten.FilterNearest
screen.DrawImage(sprite, op)
}
}

42
internal/maps/desert.go Normal file
View File

@@ -0,0 +1,42 @@
package maps
import (
"image/color"
"github.com/atridad/LilGuy/internal/config"
"github.com/atridad/LilGuy/internal/portal"
"github.com/atridad/LilGuy/internal/world"
)
func CreateDesert(screenWidth, screenHeight float64) *Map {
m := NewMap("desert", 2, "Desert", screenWidth, screenHeight)
m.BackgroundColor = color.NRGBA{R: 155, G: 196, B: 215, A: 255}
// ground surface
m.World.AddSurface(&world.Surface{
X: 0,
Y: screenHeight - config.GroundHeight,
Width: screenWidth,
Height: config.GroundHeight,
Tag: world.TagGround,
Color: color.NRGBA{R: 139, G: 69, B: 19, A: 255},
})
// left portal to plains
leftPortal := portal.CreateSidePortal("desert_left", portal.SideLeft, screenWidth, screenHeight)
leftPortal.DestinationMap = "plains"
leftPortal.DestinationPortal = "plains_right"
leftPortal.Color = color.NRGBA{R: 100, G: 255, B: 100, A: 180}
leftPortal.GlowColor = color.NRGBA{R: 150, G: 255, B: 150, A: 100}
m.AddPortal(leftPortal)
// right portal to plains
rightPortal := portal.CreateSidePortal("desert_right", portal.SideRight, screenWidth, screenHeight)
rightPortal.DestinationMap = "plains"
rightPortal.DestinationPortal = "plains_left"
rightPortal.Color = color.NRGBA{R: 255, G: 100, B: 100, A: 180}
rightPortal.GlowColor = color.NRGBA{R: 255, G: 150, B: 150, A: 100}
m.AddPortal(rightPortal)
return m
}

View File

@@ -3,7 +3,6 @@ package maps
import (
"image/color"
"github.com/atridad/LilGuy/internal/config"
"github.com/atridad/LilGuy/internal/portal"
"github.com/atridad/LilGuy/internal/world"
)
@@ -11,6 +10,7 @@ import (
type Map struct {
ID string
Number int
DisplayName string
Width float64
Height float64
World *world.World
@@ -18,10 +18,11 @@ type Map struct {
BackgroundColor color.NRGBA
}
func NewMap(id string, number int, width, height float64) *Map {
func NewMap(id string, number int, displayName string, width, height float64) *Map {
return &Map{
ID: id,
Number: number,
DisplayName: displayName,
Width: width,
Height: height,
World: world.NewWorld(),
@@ -44,36 +45,7 @@ func (m *Map) GetPortalByID(id string) *portal.Portal {
}
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)
plains := CreatePlains(screenWidth, screenHeight)
desert := CreateDesert(screenWidth, screenHeight)
return plains, desert
}

42
internal/maps/plains.go Normal file
View File

@@ -0,0 +1,42 @@
package maps
import (
"image/color"
"github.com/atridad/LilGuy/internal/config"
"github.com/atridad/LilGuy/internal/portal"
"github.com/atridad/LilGuy/internal/world"
)
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}
// ground surface
m.World.AddSurface(&world.Surface{
X: 0,
Y: screenHeight - config.GroundHeight,
Width: screenWidth,
Height: config.GroundHeight,
Tag: world.TagGround,
Color: color.NRGBA{R: 34, G: 139, B: 34, A: 255},
})
// left portal to desert
leftPortal := portal.CreateSidePortal("plains_left", portal.SideLeft, screenWidth, screenHeight)
leftPortal.DestinationMap = "desert"
leftPortal.DestinationPortal = "desert_right"
leftPortal.Color = color.NRGBA{R: 255, G: 100, B: 100, A: 180}
leftPortal.GlowColor = color.NRGBA{R: 255, G: 150, B: 150, A: 100}
m.AddPortal(leftPortal)
// right portal to desert
rightPortal := portal.CreateSidePortal("plains_right", portal.SideRight, screenWidth, screenHeight)
rightPortal.DestinationMap = "desert"
rightPortal.DestinationPortal = "desert_left"
rightPortal.Color = color.NRGBA{R: 100, G: 255, B: 100, A: 180}
rightPortal.GlowColor = color.NRGBA{R: 150, G: 255, B: 150, A: 100}
m.AddPortal(rightPortal)
return m
}

View File

@@ -98,6 +98,10 @@ func (p *Portal) Update(dt float64) {
return
}
if dt > 0.1 {
dt = 0.1
}
p.GlowIntensity += dt * 2.0
if p.GlowIntensity > 6.28 { // 2*PI
p.GlowIntensity -= 6.28
@@ -109,18 +113,24 @@ func (p *Portal) Draw(screen *ebiten.Image) {
return
}
drawX := float32(int(p.X + 0.5))
drawY := float32(int(p.Y + 0.5))
drawWidth := float32(int(p.Width + 0.5))
drawHeight := float32(int(p.Height + 0.5))
vector.FillRect(
screen,
float32(p.X),
float32(p.Y),
float32(p.Width),
float32(p.Height),
drawX,
drawY,
drawWidth,
drawHeight,
p.Color,
false,
)
if p.Enabled {
glowAlpha := uint8(float64(p.GlowColor.A) * (0.5 + 0.5*float64(p.GlowIntensity)))
glowPulse := 0.5 + 0.5*float64(p.GlowIntensity)/6.28
glowAlpha := uint8(float64(p.GlowColor.A) * glowPulse)
glowColor := color.NRGBA{
R: p.GlowColor.R,
G: p.GlowColor.G,
@@ -131,10 +141,10 @@ func (p *Portal) Draw(screen *ebiten.Image) {
borderWidth := float32(4)
vector.StrokeRect(
screen,
float32(p.X)-borderWidth/2,
float32(p.Y)-borderWidth/2,
float32(p.Width)+borderWidth,
float32(p.Height)+borderWidth,
drawX-borderWidth/2,
drawY-borderWidth/2,
drawWidth+borderWidth,
drawHeight+borderWidth,
borderWidth,
glowColor,
false,
@@ -182,12 +192,20 @@ func (m *Manager) Clear() {
}
func (m *Manager) Update(dt float64) {
// clamp delta time to prevent physics issues
if dt > 0.1 {
dt = 0.1
}
for _, p := range m.Portals {
p.Update(dt)
}
if m.transitionCooldown > 0 {
m.transitionCooldown -= dt
if m.transitionCooldown < 0 {
m.transitionCooldown = 0
}
}
}

View File

@@ -38,10 +38,15 @@ func New(x, y, directionX, directionY float64, config ProjectileConfig) *Project
}
func (p *Projectile) Update(dt float64, screenWidth, screenHeight float64) {
if dt > 0.1 {
dt = 0.1
}
p.X += p.VelocityX * dt
p.Y += p.VelocityY * dt
if p.X < 0 || p.X > screenWidth || p.Y < 0 || p.Y > screenHeight {
margin := p.Radius * 2
if p.X < -margin || p.X > screenWidth+margin || p.Y < -margin || p.Y > screenHeight+margin {
p.Active = false
}
}
@@ -50,13 +55,17 @@ func (p *Projectile) Draw(screen *ebiten.Image) {
if !p.Active {
return
}
drawX := float32(int(p.X + 0.5))
drawY := float32(int(p.Y + 0.5))
vector.DrawFilledCircle(
screen,
float32(p.X),
float32(p.Y),
drawX,
drawY,
float32(p.Radius),
p.Color,
false,
true,
)
}

View File

@@ -29,6 +29,7 @@ type Settings struct {
type GameState struct {
HasSave bool `toml:"has_save"`
SavedAt time.Time `toml:"saved_at"`
CurrentMap string `toml:"current_map"`
HeroX float64 `toml:"hero_x"`
HeroY float64 `toml:"hero_y"`
HeroStamina float64 `toml:"hero_stamina"`

View File

@@ -54,15 +54,15 @@ type GameplayScreen struct {
func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVisibility *bool) *GameplayScreen {
cfg := config.Default()
map1, map2 := maps.CreateDefaultMaps(float64(screenWidth), float64(screenHeight))
plains, desert := maps.CreateDefaultMaps(float64(screenWidth), float64(screenHeight))
allMaps := make(map[string]*maps.Map)
allMaps["map1"] = map1
allMaps["map2"] = map2
allMaps["plains"] = plains
allMaps["desert"] = desert
portalMgr := portal.NewManager()
for _, p := range map1.Portals {
for _, p := range plains.Portals {
portalMgr.AddPortal(p)
}
@@ -87,9 +87,9 @@ func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVi
X: cfg.HUD.X,
Y: cfg.HUD.Y,
Color: color.White,
ScreenName: "Map 1",
ScreenName: "Plains",
},
world: map1.World,
world: plains.World,
projectiles: projectile.NewManager(),
portals: portalMgr,
bounds: hero.Bounds{
@@ -101,7 +101,7 @@ func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVi
gameStartTime: time.Now(),
fpsEnabled: fpsEnabled,
portalVisibility: portalVisibility,
currentMap: map1,
currentMap: plains,
allMaps: allMaps,
}
@@ -111,6 +111,11 @@ func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVi
}
func (g *GameplayScreen) Update(input GameplayInput, delta time.Duration) {
// clamp delta to prevent physics issues from lag spikes
if delta > 100*time.Millisecond {
delta = 100 * time.Millisecond
}
dt := delta.Seconds()
g.hero.Update(hero.Input{
@@ -131,13 +136,14 @@ 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
// check for portal collisions
g.checkPortalCollision()
g.totalPlayTime += delta
g.trackFPS(delta)
// update save notification timer
if g.showSaveNotification {
g.saveNotificationTimer -= delta
if g.saveNotificationTimer <= 0 {
@@ -146,7 +152,7 @@ func (g *GameplayScreen) Update(input GameplayInput, delta time.Duration) {
}
}
// Portal collision detection
// portal collision detection
func (g *GameplayScreen) checkPortalCollision() {
heroRadius := g.hero.Radius
heroX := g.hero.X - heroRadius
@@ -157,19 +163,6 @@ func (g *GameplayScreen) checkPortalCollision() {
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) {
@@ -188,7 +181,13 @@ func (g *GameplayScreen) handlePortalTransition(event portal.TransitionEvent) {
g.portals.AddPortal(p)
}
g.updatePortalVisibility()
// set portal visibility
if g.portalVisibility != nil {
visible := *g.portalVisibility
for _, p := range g.portals.Portals {
p.Visible = visible
}
}
// Find destination portal and position hero
destPortal := destMap.GetPortalByID(event.DestinationPortal)
@@ -242,7 +241,7 @@ func (g *GameplayScreen) Draw(screen *ebiten.Image) {
g.hero.Draw(screen)
if g.currentMap != nil {
g.hud.ScreenName = fmt.Sprintf("Map %d", g.currentMap.Number)
g.hud.ScreenName = g.currentMap.DisplayName
}
staminaColor := cfg.Visual.StaminaNormalColor
@@ -327,11 +326,11 @@ func (g *GameplayScreen) Reset() {
screenWidth := int(g.bounds.Width)
screenHeight := int(g.bounds.Height)
map1, map2 := maps.CreateDefaultMaps(float64(screenWidth), float64(screenHeight))
plains, desert := 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.allMaps["plains"] = plains
g.allMaps["desert"] = desert
g.currentMap = plains
g.hero = hero.New(hero.Config{
StartX: cfg.Hero.StartX,
@@ -343,14 +342,22 @@ func (g *GameplayScreen) Reset() {
StaminaDrain: cfg.Hero.StaminaDrain,
StaminaRegen: cfg.Hero.StaminaRegen,
})
g.world = map1.World
g.world = plains.World
g.projectiles = projectile.NewManager()
g.portals = portal.NewManager()
for _, p := range map1.Portals {
for _, p := range plains.Portals {
g.portals.AddPortal(p)
}
g.portals.OnTransition = g.handlePortalTransition
g.updatePortalVisibility()
// set portal visibility
if g.portalVisibility != nil {
visible := *g.portalVisibility
for _, p := range g.portals.Portals {
p.Visible = visible
}
}
g.bounds = hero.Bounds{
Width: float64(screenWidth),
Height: float64(screenHeight),
@@ -365,7 +372,12 @@ func (g *GameplayScreen) Reset() {
}
func (g *GameplayScreen) SaveState() *save.GameState {
currentMapID := "map1"
if g.currentMap != nil {
currentMapID = g.currentMap.ID
}
return &save.GameState{
CurrentMap: currentMapID,
HeroX: g.hero.X,
HeroY: g.hero.Y,
HeroStamina: g.hero.Stamina,
@@ -378,11 +390,17 @@ func (g *GameplayScreen) LoadState(state *save.GameState) {
screenWidth := int(g.bounds.Width)
screenHeight := int(g.bounds.Height)
map1, map2 := maps.CreateDefaultMaps(float64(screenWidth), float64(screenHeight))
plains, desert := 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.allMaps["plains"] = plains
g.allMaps["desert"] = desert
// load the saved map or default to plains
savedMap := g.allMaps[state.CurrentMap]
if savedMap == nil {
savedMap = plains
}
g.currentMap = savedMap
g.hero = hero.New(hero.Config{
StartX: state.HeroX,
@@ -395,14 +413,22 @@ func (g *GameplayScreen) LoadState(state *save.GameState) {
StaminaRegen: cfg.Hero.StaminaRegen,
})
g.hero.Stamina = state.HeroStamina
g.world = map1.World
g.world = g.currentMap.World
g.projectiles = projectile.NewManager()
g.portals = portal.NewManager()
for _, p := range map1.Portals {
for _, p := range g.currentMap.Portals {
g.portals.AddPortal(p)
}
g.portals.OnTransition = g.handlePortalTransition
g.updatePortalVisibility()
// set portal visibility
if g.portalVisibility != nil {
visible := *g.portalVisibility
for _, p := range g.portals.Portals {
p.Visible = visible
}
}
g.bounds = hero.Bounds{
Width: float64(screenWidth),
Height: float64(screenHeight),

View File

@@ -14,8 +14,16 @@ type FPSCapSetting interface {
Cycle()
}
type settingsScreen int
const (
settingsMain settingsScreen = iota
settingsDebugOptions
)
type SettingsScreen struct {
selectedIndex int
currentScreen settingsScreen
fpsMonitorValue *bool
fpsCapValue FPSCapSetting
portalVisibilityValue *bool
@@ -24,6 +32,7 @@ type SettingsScreen struct {
func NewSettingsScreen() *SettingsScreen {
return &SettingsScreen{
selectedIndex: 0,
currentScreen: settingsMain,
}
}
@@ -41,10 +50,22 @@ func (s *SettingsScreen) SetPortalVisibility(enabled *bool) {
func (s *SettingsScreen) Update() bool {
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
if s.currentScreen == settingsDebugOptions {
s.currentScreen = settingsMain
s.selectedIndex = 0
return false
}
return true
}
settingsCount := 3
if s.currentScreen == settingsDebugOptions {
return s.updateDebugOptions()
}
return s.updateMain()
}
func (s *SettingsScreen) updateMain() bool {
settingsCount := 2
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
s.selectedIndex--
if s.selectedIndex < 0 {
@@ -58,13 +79,41 @@ func (s *SettingsScreen) Update() bool {
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeySpace) {
if s.selectedIndex == 0 && s.fpsCapValue != nil {
s.fpsCapValue.Cycle()
} else if s.selectedIndex == 1 {
s.currentScreen = settingsDebugOptions
s.selectedIndex = 0
}
}
return false
}
func (s *SettingsScreen) updateDebugOptions() bool {
debugOptionsCount := 3
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
s.selectedIndex--
if s.selectedIndex < 0 {
s.selectedIndex = 0
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
s.selectedIndex++
if s.selectedIndex >= debugOptionsCount {
s.selectedIndex = debugOptionsCount - 1
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeySpace) {
if s.selectedIndex == 0 && s.fpsMonitorValue != nil {
*s.fpsMonitorValue = !*s.fpsMonitorValue
} else if s.selectedIndex == 1 && s.fpsCapValue != nil {
s.fpsCapValue.Cycle()
} else if s.selectedIndex == 2 && s.portalVisibilityValue != nil {
} else if s.selectedIndex == 1 && s.portalVisibilityValue != nil {
*s.portalVisibilityValue = !*s.portalVisibilityValue
} else if s.selectedIndex == 2 {
s.currentScreen = settingsMain
s.selectedIndex = 0
}
}
@@ -80,6 +129,52 @@ func (s *SettingsScreen) Draw(screen *ebiten.Image, screenWidth, screenHeight in
titleY := screenHeight/3 - 50
s.drawText(screen, title, color.White, titleX, titleY)
if s.currentScreen == settingsDebugOptions {
s.drawDebugOptions(screen, screenWidth, screenHeight)
} else {
s.drawMain(screen, screenWidth, screenHeight)
}
}
func (s *SettingsScreen) drawMain(screen *ebiten.Image, screenWidth, screenHeight int) {
startY := screenHeight/2 - 20
leftMargin := screenWidth/2 - 120
// FPS cap setting
fpsCapText := "FPS Cap: "
if s.fpsCapValue != nil {
fpsCapText += s.fpsCapValue.String()
} else {
fpsCapText += "60 FPS"
}
if s.selectedIndex == 0 {
indicatorX := leftMargin - 20
s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, startY)
s.drawText(screen, fpsCapText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, startY)
} else {
s.drawText(screen, fpsCapText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, startY)
}
// debug options submenu
debugOptionsText := "Debug Options >"
debugY := startY + 40
if s.selectedIndex == 1 {
indicatorX := leftMargin - 20
s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, debugY)
s.drawText(screen, debugOptionsText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, debugY)
} else {
s.drawText(screen, debugOptionsText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, debugY)
}
// Instructions
hintText := "Enter/Space to select, ESC to go back"
hintX := (screenWidth / 2) - (len(hintText) * 7 / 2)
hintY := screenHeight - 50
s.drawText(screen, hintText, color.RGBA{R: 120, G: 120, B: 150, A: 255}, hintX, hintY)
}
func (s *SettingsScreen) drawDebugOptions(screen *ebiten.Image, screenWidth, screenHeight int) {
startY := screenHeight/2 - 20
leftMargin := screenWidth/2 - 120
@@ -99,23 +194,6 @@ func (s *SettingsScreen) Draw(screen *ebiten.Image, screenWidth, screenHeight in
s.drawText(screen, fpsMonitorText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, startY)
}
// FPS cap setting
fpsCapText := "FPS Cap: "
if s.fpsCapValue != nil {
fpsCapText += s.fpsCapValue.String()
} else {
fpsCapText += "60 FPS"
}
capY := startY + 40
if s.selectedIndex == 1 {
indicatorX := leftMargin - 20
s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, capY)
s.drawText(screen, fpsCapText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, capY)
} else {
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 {
@@ -124,8 +202,8 @@ func (s *SettingsScreen) Draw(screen *ebiten.Image, screenWidth, screenHeight in
portalVisText += "OFF"
}
portalY := startY + 80
if s.selectedIndex == 2 {
portalY := startY + 40
if s.selectedIndex == 1 {
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)
@@ -133,8 +211,19 @@ func (s *SettingsScreen) Draw(screen *ebiten.Image, screenWidth, screenHeight in
s.drawText(screen, portalVisText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, portalY)
}
// back option
backText := "< Back"
backY := startY + 80
if s.selectedIndex == 2 {
indicatorX := leftMargin - 20
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)
} else {
s.drawText(screen, backText, color.RGBA{R: 180, G: 180, B: 200, A: 255}, leftMargin, backY)
}
// Instructions
hintText := "Enter/Space to toggle, ESC to go back"
hintText := "Enter/Space to select, ESC to go back"
hintX := (screenWidth / 2) - (len(hintText) * 7 / 2)
hintY := screenHeight - 50
s.drawText(screen, hintText, color.RGBA{R: 120, G: 120, B: 150, A: 255}, hintX, hintY)
@@ -149,4 +238,5 @@ func (s *SettingsScreen) drawText(screen *ebiten.Image, txt string, clr color.Co
func (s *SettingsScreen) Reset() {
s.selectedIndex = 0
s.currentScreen = settingsMain
}

View File

@@ -58,12 +58,17 @@ func (s *Surface) IsWalkable() bool {
}
func (s *Surface) Draw(screen *ebiten.Image) {
drawX := float32(int(s.X + 0.5))
drawY := float32(int(s.Y + 0.5))
drawWidth := float32(int(s.Width + 0.5))
drawHeight := float32(int(s.Height + 0.5))
vector.FillRect(
screen,
float32(s.X),
float32(s.Y),
float32(s.Width),
float32(s.Height),
drawX,
drawY,
drawWidth,
drawHeight,
s.Color,
false,
)