Optimizations and debug options
This commit is contained in:
@@ -12,18 +12,18 @@ const (
|
|||||||
|
|
||||||
// Physics
|
// Physics
|
||||||
const (
|
const (
|
||||||
Gravity = 1200.0
|
Gravity = 1400.0
|
||||||
JumpStrength = -450.0
|
JumpStrength = -480.0
|
||||||
MaxFallSpeed = 800.0
|
MaxFallSpeed = 900.0
|
||||||
GroundFriction = 0.85
|
GroundFriction = 0.82
|
||||||
AirFriction = 0.95
|
AirFriction = 0.96
|
||||||
)
|
)
|
||||||
|
|
||||||
// Gameplay
|
// Gameplay
|
||||||
const (
|
const (
|
||||||
SprintSpeedMultiplier = 1.8
|
SprintSpeedMultiplier = 2.0
|
||||||
SprintRecoveryThreshold = 0.2
|
SprintRecoveryThreshold = 0.25
|
||||||
ExhaustedThreshold = 0.2
|
ExhaustedThreshold = 0.15
|
||||||
StaminaLowThreshold = 0.2
|
StaminaLowThreshold = 0.2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -273,6 +273,16 @@ func (g *Game) updatePlaying() error {
|
|||||||
delta := now.Sub(g.state.lastTick)
|
delta := now.Sub(g.state.lastTick)
|
||||||
g.state.lastTick = now
|
g.state.lastTick = now
|
||||||
|
|
||||||
|
maxDelta := 100 * time.Millisecond
|
||||||
|
if delta > maxDelta {
|
||||||
|
delta = maxDelta
|
||||||
|
}
|
||||||
|
|
||||||
|
minDelta := time.Microsecond
|
||||||
|
if delta < minDelta {
|
||||||
|
delta = minDelta
|
||||||
|
}
|
||||||
|
|
||||||
input := readControls()
|
input := readControls()
|
||||||
g.state.gameplayScreen.Update(screens.GameplayInput{
|
g.state.gameplayScreen.Update(screens.GameplayInput{
|
||||||
Left: input.Left,
|
Left: input.Left,
|
||||||
@@ -284,7 +294,6 @@ func (g *Game) updatePlaying() error {
|
|||||||
|
|
||||||
if now.Sub(g.state.lastAutoSave) >= g.state.autoSaveInterval {
|
if now.Sub(g.state.lastAutoSave) >= g.state.autoSaveInterval {
|
||||||
g.saveGame()
|
g.saveGame()
|
||||||
g.state.gameplayScreen.ShowSaveNotification()
|
|
||||||
g.state.lastAutoSave = now
|
g.state.lastAutoSave = now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ func New(cfg Config) *Hero {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hero) Update(input Input, dt float64, bounds Bounds) {
|
func (h *Hero) Update(input Input, dt float64, bounds Bounds) {
|
||||||
|
if dt > 0.1 {
|
||||||
|
dt = 0.1
|
||||||
|
}
|
||||||
h.updateMovement(input, dt, bounds)
|
h.updateMovement(input, dt, bounds)
|
||||||
h.updateStamina(input, dt)
|
h.updateStamina(input, dt)
|
||||||
h.updateAnimation(dt)
|
h.updateAnimation(dt)
|
||||||
@@ -145,27 +148,30 @@ func (h *Hero) Update(input Input, dt float64, bounds Bounds) {
|
|||||||
// Movement and physics
|
// Movement and physics
|
||||||
|
|
||||||
func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) {
|
func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) {
|
||||||
|
// apply gravity
|
||||||
h.VelocityY += config.Gravity * dt
|
h.VelocityY += config.Gravity * dt
|
||||||
if h.VelocityY > config.MaxFallSpeed {
|
if h.VelocityY > config.MaxFallSpeed {
|
||||||
h.VelocityY = config.MaxFallSpeed
|
h.VelocityY = config.MaxFallSpeed
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Y += h.VelocityY * dt
|
newY := h.Y + h.VelocityY*dt
|
||||||
|
|
||||||
footPosition := h.Y
|
wasGrounded := h.isGrounded
|
||||||
if footPosition >= bounds.Ground {
|
if newY >= bounds.Ground {
|
||||||
h.Y = bounds.Ground
|
h.Y = bounds.Ground
|
||||||
h.VelocityY = 0
|
h.VelocityY = 0
|
||||||
h.isGrounded = true
|
h.isGrounded = true
|
||||||
} else {
|
} else {
|
||||||
|
h.Y = newY
|
||||||
h.isGrounded = false
|
h.isGrounded = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.Jump && h.isGrounded {
|
if input.Jump && (h.isGrounded || (!wasGrounded && h.isGrounded)) {
|
||||||
h.VelocityY = config.JumpStrength
|
h.VelocityY = config.JumpStrength
|
||||||
h.isGrounded = false
|
h.isGrounded = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// horizontal input
|
||||||
targetVelocityX := 0.0
|
targetVelocityX := 0.0
|
||||||
if input.Left {
|
if input.Left {
|
||||||
targetVelocityX -= h.Speed
|
targetVelocityX -= h.Speed
|
||||||
@@ -178,6 +184,7 @@ func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) {
|
|||||||
|
|
||||||
h.isMoving = targetVelocityX != 0
|
h.isMoving = targetVelocityX != 0
|
||||||
|
|
||||||
|
// sprinting
|
||||||
h.isSprinting = input.Sprint && h.canSprint && h.Stamina > 0 && h.isMoving
|
h.isSprinting = input.Sprint && h.canSprint && h.Stamina > 0 && h.isMoving
|
||||||
if h.isSprinting {
|
if h.isSprinting {
|
||||||
targetVelocityX *= config.SprintSpeedMultiplier
|
targetVelocityX *= config.SprintSpeedMultiplier
|
||||||
@@ -188,17 +195,22 @@ func (h *Hero) updateMovement(input Input, dt float64, bounds Bounds) {
|
|||||||
friction = config.AirFriction
|
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.X = h.Radius
|
||||||
h.VelocityX = 0
|
h.VelocityX = 0
|
||||||
}
|
} else if newX > bounds.Width-h.Radius {
|
||||||
if h.X > bounds.Width-h.Radius {
|
|
||||||
h.X = bounds.Width - h.Radius
|
h.X = bounds.Width - h.Radius
|
||||||
h.VelocityX = 0
|
h.VelocityX = 0
|
||||||
|
} else {
|
||||||
|
h.X = newX
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,12 +246,14 @@ func (h *Hero) updateAnimation(dt float64) {
|
|||||||
key.state = animMove
|
key.state = animMove
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset animation on state change
|
||||||
if key != h.lastAnimKey {
|
if key != h.lastAnimKey {
|
||||||
h.animFrame = 0
|
h.animFrame = 0
|
||||||
h.animTimer = 0
|
h.animTimer = 0
|
||||||
h.lastAnimKey = key
|
h.lastAnimKey = key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// advance animation only when moving
|
||||||
if isMoving {
|
if isMoving {
|
||||||
animSpeed := config.NormalAnimSpeed * 0.5
|
animSpeed := config.NormalAnimSpeed * 0.5
|
||||||
if h.isSprinting {
|
if h.isSprinting {
|
||||||
@@ -248,12 +262,17 @@ func (h *Hero) updateAnimation(dt float64) {
|
|||||||
if !h.isGrounded {
|
if !h.isGrounded {
|
||||||
animSpeed = config.JumpingAnimSpeed * 0.5
|
animSpeed = config.JumpingAnimSpeed * 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
h.animTimer += dt
|
h.animTimer += dt
|
||||||
|
|
||||||
|
// precise frame advancement
|
||||||
|
if h.animTimer >= animSpeed {
|
||||||
frameAdvance := int(h.animTimer / animSpeed)
|
frameAdvance := int(h.animTimer / animSpeed)
|
||||||
if frameAdvance > 0 {
|
|
||||||
h.animTimer -= animSpeed * float64(frameAdvance)
|
h.animTimer -= animSpeed * float64(frameAdvance)
|
||||||
h.animFrame = (h.animFrame + frameAdvance) % config.AnimFrameWrap
|
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())
|
actualWidth := float64(bounds.Dx())
|
||||||
|
|
||||||
op := &ebiten.DrawImageOptions{}
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
|
||||||
|
// center sprite horizontally, align bottom to feet
|
||||||
op.GeoM.Translate(-actualWidth/2, -actualHeight)
|
op.GeoM.Translate(-actualWidth/2, -actualHeight)
|
||||||
op.GeoM.Scale(config.HeroSpriteScale, config.HeroSpriteScale)
|
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()
|
state := h.getVisualState()
|
||||||
switch state {
|
switch state {
|
||||||
case StateExhausted:
|
case StateExhausted:
|
||||||
op.ColorScale.ScaleWithColor(color.RGBA{R: 255, G: 100, B: 100, A: 255})
|
op.ColorScale.ScaleWithColor(color.RGBA{R: 255, G: 100, B: 100, A: 255})
|
||||||
case StateSprinting:
|
case StateSprinting:
|
||||||
|
// no color modification for sprinting
|
||||||
case StateIdle:
|
case StateIdle:
|
||||||
|
// no color modification for idle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
op.Filter = ebiten.FilterNearest
|
||||||
screen.DrawImage(sprite, op)
|
screen.DrawImage(sprite, op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
internal/maps/desert.go
Normal file
42
internal/maps/desert.go
Normal 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
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package maps
|
|||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"github.com/atridad/LilGuy/internal/config"
|
|
||||||
"github.com/atridad/LilGuy/internal/portal"
|
"github.com/atridad/LilGuy/internal/portal"
|
||||||
"github.com/atridad/LilGuy/internal/world"
|
"github.com/atridad/LilGuy/internal/world"
|
||||||
)
|
)
|
||||||
@@ -11,6 +10,7 @@ import (
|
|||||||
type Map struct {
|
type Map struct {
|
||||||
ID string
|
ID string
|
||||||
Number int
|
Number int
|
||||||
|
DisplayName string
|
||||||
Width float64
|
Width float64
|
||||||
Height float64
|
Height float64
|
||||||
World *world.World
|
World *world.World
|
||||||
@@ -18,10 +18,11 @@ type Map struct {
|
|||||||
BackgroundColor color.NRGBA
|
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{
|
return &Map{
|
||||||
ID: id,
|
ID: id,
|
||||||
Number: number,
|
Number: number,
|
||||||
|
DisplayName: displayName,
|
||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
Height: height,
|
||||||
World: world.NewWorld(),
|
World: world.NewWorld(),
|
||||||
@@ -44,36 +45,7 @@ func (m *Map) GetPortalByID(id string) *portal.Portal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateDefaultMaps(screenWidth, screenHeight float64) (*Map, *Map) {
|
func CreateDefaultMaps(screenWidth, screenHeight float64) (*Map, *Map) {
|
||||||
map1 := createMapFromConfig(config.DefaultMap1(), screenWidth, screenHeight)
|
plains := CreatePlains(screenWidth, screenHeight)
|
||||||
map2 := createMapFromConfig(config.DefaultMap2(), screenWidth, screenHeight)
|
desert := CreateDesert(screenWidth, screenHeight)
|
||||||
|
return plains, desert
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
42
internal/maps/plains.go
Normal file
42
internal/maps/plains.go
Normal 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
|
||||||
|
}
|
||||||
@@ -98,6 +98,10 @@ func (p *Portal) Update(dt float64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dt > 0.1 {
|
||||||
|
dt = 0.1
|
||||||
|
}
|
||||||
|
|
||||||
p.GlowIntensity += dt * 2.0
|
p.GlowIntensity += dt * 2.0
|
||||||
if p.GlowIntensity > 6.28 { // 2*PI
|
if p.GlowIntensity > 6.28 { // 2*PI
|
||||||
p.GlowIntensity -= 6.28
|
p.GlowIntensity -= 6.28
|
||||||
@@ -109,18 +113,24 @@ func (p *Portal) Draw(screen *ebiten.Image) {
|
|||||||
return
|
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(
|
vector.FillRect(
|
||||||
screen,
|
screen,
|
||||||
float32(p.X),
|
drawX,
|
||||||
float32(p.Y),
|
drawY,
|
||||||
float32(p.Width),
|
drawWidth,
|
||||||
float32(p.Height),
|
drawHeight,
|
||||||
p.Color,
|
p.Color,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.Enabled {
|
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{
|
glowColor := color.NRGBA{
|
||||||
R: p.GlowColor.R,
|
R: p.GlowColor.R,
|
||||||
G: p.GlowColor.G,
|
G: p.GlowColor.G,
|
||||||
@@ -131,10 +141,10 @@ func (p *Portal) Draw(screen *ebiten.Image) {
|
|||||||
borderWidth := float32(4)
|
borderWidth := float32(4)
|
||||||
vector.StrokeRect(
|
vector.StrokeRect(
|
||||||
screen,
|
screen,
|
||||||
float32(p.X)-borderWidth/2,
|
drawX-borderWidth/2,
|
||||||
float32(p.Y)-borderWidth/2,
|
drawY-borderWidth/2,
|
||||||
float32(p.Width)+borderWidth,
|
drawWidth+borderWidth,
|
||||||
float32(p.Height)+borderWidth,
|
drawHeight+borderWidth,
|
||||||
borderWidth,
|
borderWidth,
|
||||||
glowColor,
|
glowColor,
|
||||||
false,
|
false,
|
||||||
@@ -182,12 +192,20 @@ func (m *Manager) Clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Update(dt float64) {
|
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 {
|
for _, p := range m.Portals {
|
||||||
p.Update(dt)
|
p.Update(dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.transitionCooldown > 0 {
|
if m.transitionCooldown > 0 {
|
||||||
m.transitionCooldown -= dt
|
m.transitionCooldown -= dt
|
||||||
|
if m.transitionCooldown < 0 {
|
||||||
|
m.transitionCooldown = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,10 +38,15 @@ func New(x, y, directionX, directionY float64, config ProjectileConfig) *Project
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Projectile) Update(dt float64, screenWidth, screenHeight float64) {
|
func (p *Projectile) Update(dt float64, screenWidth, screenHeight float64) {
|
||||||
|
if dt > 0.1 {
|
||||||
|
dt = 0.1
|
||||||
|
}
|
||||||
|
|
||||||
p.X += p.VelocityX * dt
|
p.X += p.VelocityX * dt
|
||||||
p.Y += p.VelocityY * 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
|
p.Active = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,13 +55,17 @@ func (p *Projectile) Draw(screen *ebiten.Image) {
|
|||||||
if !p.Active {
|
if !p.Active {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawX := float32(int(p.X + 0.5))
|
||||||
|
drawY := float32(int(p.Y + 0.5))
|
||||||
|
|
||||||
vector.DrawFilledCircle(
|
vector.DrawFilledCircle(
|
||||||
screen,
|
screen,
|
||||||
float32(p.X),
|
drawX,
|
||||||
float32(p.Y),
|
drawY,
|
||||||
float32(p.Radius),
|
float32(p.Radius),
|
||||||
p.Color,
|
p.Color,
|
||||||
false,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type Settings struct {
|
|||||||
type GameState struct {
|
type GameState struct {
|
||||||
HasSave bool `toml:"has_save"`
|
HasSave bool `toml:"has_save"`
|
||||||
SavedAt time.Time `toml:"saved_at"`
|
SavedAt time.Time `toml:"saved_at"`
|
||||||
|
CurrentMap string `toml:"current_map"`
|
||||||
HeroX float64 `toml:"hero_x"`
|
HeroX float64 `toml:"hero_x"`
|
||||||
HeroY float64 `toml:"hero_y"`
|
HeroY float64 `toml:"hero_y"`
|
||||||
HeroStamina float64 `toml:"hero_stamina"`
|
HeroStamina float64 `toml:"hero_stamina"`
|
||||||
|
|||||||
@@ -54,15 +54,15 @@ type GameplayScreen struct {
|
|||||||
|
|
||||||
func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVisibility *bool) *GameplayScreen {
|
func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVisibility *bool) *GameplayScreen {
|
||||||
cfg := config.Default()
|
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 := make(map[string]*maps.Map)
|
||||||
allMaps["map1"] = map1
|
allMaps["plains"] = plains
|
||||||
allMaps["map2"] = map2
|
allMaps["desert"] = desert
|
||||||
|
|
||||||
portalMgr := portal.NewManager()
|
portalMgr := portal.NewManager()
|
||||||
|
|
||||||
for _, p := range map1.Portals {
|
for _, p := range plains.Portals {
|
||||||
portalMgr.AddPortal(p)
|
portalMgr.AddPortal(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +87,9 @@ func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVi
|
|||||||
X: cfg.HUD.X,
|
X: cfg.HUD.X,
|
||||||
Y: cfg.HUD.Y,
|
Y: cfg.HUD.Y,
|
||||||
Color: color.White,
|
Color: color.White,
|
||||||
ScreenName: "Map 1",
|
ScreenName: "Plains",
|
||||||
},
|
},
|
||||||
world: map1.World,
|
world: plains.World,
|
||||||
projectiles: projectile.NewManager(),
|
projectiles: projectile.NewManager(),
|
||||||
portals: portalMgr,
|
portals: portalMgr,
|
||||||
bounds: hero.Bounds{
|
bounds: hero.Bounds{
|
||||||
@@ -101,7 +101,7 @@ func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVi
|
|||||||
gameStartTime: time.Now(),
|
gameStartTime: time.Now(),
|
||||||
fpsEnabled: fpsEnabled,
|
fpsEnabled: fpsEnabled,
|
||||||
portalVisibility: portalVisibility,
|
portalVisibility: portalVisibility,
|
||||||
currentMap: map1,
|
currentMap: plains,
|
||||||
allMaps: allMaps,
|
allMaps: allMaps,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +111,11 @@ func NewGameplayScreen(screenWidth, screenHeight int, fpsEnabled *bool, portalVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GameplayScreen) Update(input GameplayInput, delta time.Duration) {
|
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()
|
dt := delta.Seconds()
|
||||||
|
|
||||||
g.hero.Update(hero.Input{
|
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.projectiles.Update(dt, g.bounds.Width, g.bounds.Height)
|
||||||
g.portals.Update(dt)
|
g.portals.Update(dt)
|
||||||
|
|
||||||
// Check for portal collisions
|
// check for portal collisions
|
||||||
g.checkPortalCollision()
|
g.checkPortalCollision()
|
||||||
|
|
||||||
g.totalPlayTime += delta
|
g.totalPlayTime += delta
|
||||||
|
|
||||||
g.trackFPS(delta)
|
g.trackFPS(delta)
|
||||||
|
|
||||||
|
// update save notification timer
|
||||||
if g.showSaveNotification {
|
if g.showSaveNotification {
|
||||||
g.saveNotificationTimer -= delta
|
g.saveNotificationTimer -= delta
|
||||||
if g.saveNotificationTimer <= 0 {
|
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() {
|
func (g *GameplayScreen) checkPortalCollision() {
|
||||||
heroRadius := g.hero.Radius
|
heroRadius := g.hero.Radius
|
||||||
heroX := g.hero.X - heroRadius
|
heroX := g.hero.X - heroRadius
|
||||||
@@ -157,19 +163,6 @@ func (g *GameplayScreen) checkPortalCollision() {
|
|||||||
if p := g.portals.CheckCollision(heroX, heroY, heroWidth, heroHeight); p != nil {
|
if p := g.portals.CheckCollision(heroX, heroY, heroWidth, heroHeight); p != nil {
|
||||||
g.portals.TriggerTransition(p, g.hero.X, g.hero.Y)
|
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) {
|
func (g *GameplayScreen) handlePortalTransition(event portal.TransitionEvent) {
|
||||||
@@ -188,7 +181,13 @@ func (g *GameplayScreen) handlePortalTransition(event portal.TransitionEvent) {
|
|||||||
g.portals.AddPortal(p)
|
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
|
// Find destination portal and position hero
|
||||||
destPortal := destMap.GetPortalByID(event.DestinationPortal)
|
destPortal := destMap.GetPortalByID(event.DestinationPortal)
|
||||||
@@ -242,7 +241,7 @@ func (g *GameplayScreen) Draw(screen *ebiten.Image) {
|
|||||||
g.hero.Draw(screen)
|
g.hero.Draw(screen)
|
||||||
|
|
||||||
if g.currentMap != nil {
|
if g.currentMap != nil {
|
||||||
g.hud.ScreenName = fmt.Sprintf("Map %d", g.currentMap.Number)
|
g.hud.ScreenName = g.currentMap.DisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
staminaColor := cfg.Visual.StaminaNormalColor
|
staminaColor := cfg.Visual.StaminaNormalColor
|
||||||
@@ -327,11 +326,11 @@ func (g *GameplayScreen) Reset() {
|
|||||||
screenWidth := int(g.bounds.Width)
|
screenWidth := int(g.bounds.Width)
|
||||||
screenHeight := int(g.bounds.Height)
|
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 = make(map[string]*maps.Map)
|
||||||
g.allMaps["map1"] = map1
|
g.allMaps["plains"] = plains
|
||||||
g.allMaps["map2"] = map2
|
g.allMaps["desert"] = desert
|
||||||
g.currentMap = map1
|
g.currentMap = plains
|
||||||
|
|
||||||
g.hero = hero.New(hero.Config{
|
g.hero = hero.New(hero.Config{
|
||||||
StartX: cfg.Hero.StartX,
|
StartX: cfg.Hero.StartX,
|
||||||
@@ -343,14 +342,22 @@ func (g *GameplayScreen) Reset() {
|
|||||||
StaminaDrain: cfg.Hero.StaminaDrain,
|
StaminaDrain: cfg.Hero.StaminaDrain,
|
||||||
StaminaRegen: cfg.Hero.StaminaRegen,
|
StaminaRegen: cfg.Hero.StaminaRegen,
|
||||||
})
|
})
|
||||||
g.world = map1.World
|
g.world = plains.World
|
||||||
g.projectiles = projectile.NewManager()
|
g.projectiles = projectile.NewManager()
|
||||||
g.portals = portal.NewManager()
|
g.portals = portal.NewManager()
|
||||||
for _, p := range map1.Portals {
|
for _, p := range plains.Portals {
|
||||||
g.portals.AddPortal(p)
|
g.portals.AddPortal(p)
|
||||||
}
|
}
|
||||||
g.portals.OnTransition = g.handlePortalTransition
|
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{
|
g.bounds = hero.Bounds{
|
||||||
Width: float64(screenWidth),
|
Width: float64(screenWidth),
|
||||||
Height: float64(screenHeight),
|
Height: float64(screenHeight),
|
||||||
@@ -365,7 +372,12 @@ func (g *GameplayScreen) Reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GameplayScreen) SaveState() *save.GameState {
|
func (g *GameplayScreen) SaveState() *save.GameState {
|
||||||
|
currentMapID := "map1"
|
||||||
|
if g.currentMap != nil {
|
||||||
|
currentMapID = g.currentMap.ID
|
||||||
|
}
|
||||||
return &save.GameState{
|
return &save.GameState{
|
||||||
|
CurrentMap: currentMapID,
|
||||||
HeroX: g.hero.X,
|
HeroX: g.hero.X,
|
||||||
HeroY: g.hero.Y,
|
HeroY: g.hero.Y,
|
||||||
HeroStamina: g.hero.Stamina,
|
HeroStamina: g.hero.Stamina,
|
||||||
@@ -378,11 +390,17 @@ func (g *GameplayScreen) LoadState(state *save.GameState) {
|
|||||||
screenWidth := int(g.bounds.Width)
|
screenWidth := int(g.bounds.Width)
|
||||||
screenHeight := int(g.bounds.Height)
|
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 = make(map[string]*maps.Map)
|
||||||
g.allMaps["map1"] = map1
|
g.allMaps["plains"] = plains
|
||||||
g.allMaps["map2"] = map2
|
g.allMaps["desert"] = desert
|
||||||
g.currentMap = map1
|
|
||||||
|
// 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{
|
g.hero = hero.New(hero.Config{
|
||||||
StartX: state.HeroX,
|
StartX: state.HeroX,
|
||||||
@@ -395,14 +413,22 @@ func (g *GameplayScreen) LoadState(state *save.GameState) {
|
|||||||
StaminaRegen: cfg.Hero.StaminaRegen,
|
StaminaRegen: cfg.Hero.StaminaRegen,
|
||||||
})
|
})
|
||||||
g.hero.Stamina = state.HeroStamina
|
g.hero.Stamina = state.HeroStamina
|
||||||
g.world = map1.World
|
g.world = g.currentMap.World
|
||||||
g.projectiles = projectile.NewManager()
|
g.projectiles = projectile.NewManager()
|
||||||
g.portals = portal.NewManager()
|
g.portals = portal.NewManager()
|
||||||
for _, p := range map1.Portals {
|
for _, p := range g.currentMap.Portals {
|
||||||
g.portals.AddPortal(p)
|
g.portals.AddPortal(p)
|
||||||
}
|
}
|
||||||
g.portals.OnTransition = g.handlePortalTransition
|
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{
|
g.bounds = hero.Bounds{
|
||||||
Width: float64(screenWidth),
|
Width: float64(screenWidth),
|
||||||
Height: float64(screenHeight),
|
Height: float64(screenHeight),
|
||||||
|
|||||||
@@ -14,8 +14,16 @@ type FPSCapSetting interface {
|
|||||||
Cycle()
|
Cycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type settingsScreen int
|
||||||
|
|
||||||
|
const (
|
||||||
|
settingsMain settingsScreen = iota
|
||||||
|
settingsDebugOptions
|
||||||
|
)
|
||||||
|
|
||||||
type SettingsScreen struct {
|
type SettingsScreen struct {
|
||||||
selectedIndex int
|
selectedIndex int
|
||||||
|
currentScreen settingsScreen
|
||||||
fpsMonitorValue *bool
|
fpsMonitorValue *bool
|
||||||
fpsCapValue FPSCapSetting
|
fpsCapValue FPSCapSetting
|
||||||
portalVisibilityValue *bool
|
portalVisibilityValue *bool
|
||||||
@@ -24,6 +32,7 @@ type SettingsScreen struct {
|
|||||||
func NewSettingsScreen() *SettingsScreen {
|
func NewSettingsScreen() *SettingsScreen {
|
||||||
return &SettingsScreen{
|
return &SettingsScreen{
|
||||||
selectedIndex: 0,
|
selectedIndex: 0,
|
||||||
|
currentScreen: settingsMain,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,10 +50,22 @@ func (s *SettingsScreen) SetPortalVisibility(enabled *bool) {
|
|||||||
|
|
||||||
func (s *SettingsScreen) Update() bool {
|
func (s *SettingsScreen) Update() bool {
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
||||||
|
if s.currentScreen == settingsDebugOptions {
|
||||||
|
s.currentScreen = settingsMain
|
||||||
|
s.selectedIndex = 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
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) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
|
||||||
s.selectedIndex--
|
s.selectedIndex--
|
||||||
if s.selectedIndex < 0 {
|
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 inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeySpace) {
|
||||||
if s.selectedIndex == 0 && s.fpsMonitorValue != nil {
|
if s.selectedIndex == 0 && s.fpsMonitorValue != nil {
|
||||||
*s.fpsMonitorValue = !*s.fpsMonitorValue
|
*s.fpsMonitorValue = !*s.fpsMonitorValue
|
||||||
} else if s.selectedIndex == 1 && s.fpsCapValue != nil {
|
} else if s.selectedIndex == 1 && s.portalVisibilityValue != nil {
|
||||||
s.fpsCapValue.Cycle()
|
|
||||||
} else if s.selectedIndex == 2 && s.portalVisibilityValue != nil {
|
|
||||||
*s.portalVisibilityValue = !*s.portalVisibilityValue
|
*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
|
titleY := screenHeight/3 - 50
|
||||||
s.drawText(screen, title, color.White, titleX, titleY)
|
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
|
startY := screenHeight/2 - 20
|
||||||
leftMargin := screenWidth/2 - 120
|
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)
|
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
|
// Portal visibility toggle
|
||||||
portalVisText := "Portal Visibility: "
|
portalVisText := "Portal Visibility: "
|
||||||
if s.portalVisibilityValue != nil && *s.portalVisibilityValue {
|
if s.portalVisibilityValue != nil && *s.portalVisibilityValue {
|
||||||
@@ -124,8 +202,8 @@ func (s *SettingsScreen) Draw(screen *ebiten.Image, screenWidth, screenHeight in
|
|||||||
portalVisText += "OFF"
|
portalVisText += "OFF"
|
||||||
}
|
}
|
||||||
|
|
||||||
portalY := startY + 80
|
portalY := startY + 40
|
||||||
if s.selectedIndex == 2 {
|
if s.selectedIndex == 1 {
|
||||||
indicatorX := leftMargin - 20
|
indicatorX := leftMargin - 20
|
||||||
s.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, portalY)
|
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)
|
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)
|
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
|
// 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)
|
hintX := (screenWidth / 2) - (len(hintText) * 7 / 2)
|
||||||
hintY := screenHeight - 50
|
hintY := screenHeight - 50
|
||||||
s.drawText(screen, hintText, color.RGBA{R: 120, G: 120, B: 150, A: 255}, hintX, hintY)
|
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() {
|
func (s *SettingsScreen) Reset() {
|
||||||
s.selectedIndex = 0
|
s.selectedIndex = 0
|
||||||
|
s.currentScreen = settingsMain
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,12 +58,17 @@ func (s *Surface) IsWalkable() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Surface) Draw(screen *ebiten.Image) {
|
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(
|
vector.FillRect(
|
||||||
screen,
|
screen,
|
||||||
float32(s.X),
|
drawX,
|
||||||
float32(s.Y),
|
drawY,
|
||||||
float32(s.Width),
|
drawWidth,
|
||||||
float32(s.Height),
|
drawHeight,
|
||||||
s.Color,
|
s.Color,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user