FPS counter in settings
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight)
|
ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight)
|
||||||
ebiten.SetWindowTitle(game.WindowTitle)
|
ebiten.SetWindowTitle(game.WindowTitle)
|
||||||
ebiten.SetTPS(game.TargetTPS)
|
ebiten.SetVsyncEnabled(false)
|
||||||
|
|
||||||
if err := ebiten.RunGame(game.New()); err != nil {
|
if err := ebiten.RunGame(game.New()); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,10 +14,7 @@ import (
|
|||||||
"github.com/atridad/BigFeelings/internal/ui/menu"
|
"github.com/atridad/BigFeelings/internal/ui/menu"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ============================================================
|
// Game settings.
|
||||||
// CONFIGURATION
|
|
||||||
// Tweak these values to change game settings
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ScreenWidth = 960
|
ScreenWidth = 960
|
||||||
@@ -25,11 +23,51 @@ const (
|
|||||||
WindowTitle = "Big Feelings"
|
WindowTitle = "Big Feelings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FPS cap options.
|
||||||
|
type FPSCap int
|
||||||
|
|
||||||
|
const (
|
||||||
|
FPSCap60 FPSCap = iota
|
||||||
|
FPSCap120
|
||||||
|
FPSCapUncapped
|
||||||
|
fpsCapCount
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f FPSCap) TPS() int {
|
||||||
|
switch f {
|
||||||
|
case FPSCap60:
|
||||||
|
return 60
|
||||||
|
case FPSCap120:
|
||||||
|
return 120
|
||||||
|
case FPSCapUncapped:
|
||||||
|
return -1
|
||||||
|
default:
|
||||||
|
return 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FPSCap) String() string {
|
||||||
|
switch f {
|
||||||
|
case FPSCap60:
|
||||||
|
return "60 FPS"
|
||||||
|
case FPSCap120:
|
||||||
|
return "120 FPS"
|
||||||
|
case FPSCapUncapped:
|
||||||
|
return "Uncapped"
|
||||||
|
default:
|
||||||
|
return "60 FPS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FPSCap) Cycle() {
|
||||||
|
*f = (*f + 1) % fpsCapCount
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
backgroundColor = color.NRGBA{R: 0, G: 0, B: 0, A: 255}
|
backgroundColor = color.NRGBA{R: 0, G: 0, B: 0, A: 255}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hero configuration
|
// Hero settings.
|
||||||
const (
|
const (
|
||||||
heroStartX = ScreenWidth / 2
|
heroStartX = ScreenWidth / 2
|
||||||
heroStartY = ScreenHeight / 2
|
heroStartY = ScreenHeight / 2
|
||||||
@@ -44,26 +82,28 @@ var (
|
|||||||
heroColor = color.NRGBA{R: 210, G: 220, B: 255, A: 255}
|
heroColor = color.NRGBA{R: 210, G: 220, B: 255, A: 255}
|
||||||
)
|
)
|
||||||
|
|
||||||
// HUD configuration
|
// HUD settings.
|
||||||
const (
|
const (
|
||||||
hudX = ScreenWidth - 220
|
hudX = ScreenWidth - 220
|
||||||
hudY = 20
|
hudY = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stamina bar colors
|
// HUD colors.
|
||||||
var (
|
var (
|
||||||
staminaNormalColor = color.NRGBA{R: 0, G: 255, B: 180, A: 255}
|
staminaNormalColor = color.NRGBA{R: 0, G: 255, B: 180, A: 255}
|
||||||
staminaLowColor = color.NRGBA{R: 255, G: 60, B: 60, 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}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
staminaLowThreshold = 0.2
|
staminaLowThreshold = 0.2
|
||||||
|
fpsWarnThreshold = 0.85
|
||||||
|
fpsPoorThreshold = 0.6
|
||||||
|
fpsSampleWindow = time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// TYPES
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
type gameState int
|
type gameState int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -90,19 +130,20 @@ type state struct {
|
|||||||
lastTick time.Time
|
lastTick time.Time
|
||||||
pauseMenu *menu.PauseMenu
|
pauseMenu *menu.PauseMenu
|
||||||
gameState gameState
|
gameState gameState
|
||||||
|
fpsEnabled bool
|
||||||
|
fpsFrames int
|
||||||
|
fpsAccumulator time.Duration
|
||||||
|
fpsValue float64
|
||||||
|
fpsCap FPSCap
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// INITIALIZATION
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func New() *Game {
|
func New() *Game {
|
||||||
return &Game{state: newState()}
|
return &Game{state: newState()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newState() *state {
|
func newState() *state {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return &state{
|
s := &state{
|
||||||
hero: hero.New(hero.Config{
|
hero: hero.New(hero.Config{
|
||||||
StartX: heroStartX,
|
StartX: heroStartX,
|
||||||
StartY: heroStartY,
|
StartY: heroStartY,
|
||||||
@@ -125,13 +166,15 @@ func newState() *state {
|
|||||||
lastTick: now,
|
lastTick: now,
|
||||||
pauseMenu: menu.NewPauseMenu(),
|
pauseMenu: menu.NewPauseMenu(),
|
||||||
gameState: statePlaying,
|
gameState: statePlaying,
|
||||||
|
fpsEnabled: false,
|
||||||
|
fpsCap: FPSCap60,
|
||||||
}
|
}
|
||||||
|
s.pauseMenu.SetFPSMonitor(&s.fpsEnabled)
|
||||||
|
s.pauseMenu.SetFPSCap(&s.fpsCap)
|
||||||
|
ebiten.SetTPS(s.fpsCap.TPS())
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// INPUT
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func readControls() controls {
|
func readControls() controls {
|
||||||
return controls{
|
return controls{
|
||||||
Left: ebiten.IsKeyPressed(ebiten.KeyArrowLeft) || ebiten.IsKeyPressed(ebiten.KeyA),
|
Left: ebiten.IsKeyPressed(ebiten.KeyArrowLeft) || ebiten.IsKeyPressed(ebiten.KeyA),
|
||||||
@@ -142,11 +185,15 @@ func readControls() controls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// UPDATE
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func (g *Game) Update() error {
|
func (g *Game) Update() error {
|
||||||
|
// Update TPS if FPS cap changed.
|
||||||
|
currentTPS := g.state.fpsCap.TPS()
|
||||||
|
if currentTPS < 0 {
|
||||||
|
ebiten.SetTPS(ebiten.SyncWithFPS)
|
||||||
|
} else {
|
||||||
|
ebiten.SetTPS(currentTPS)
|
||||||
|
}
|
||||||
|
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
||||||
if g.state.gameState == statePlaying {
|
if g.state.gameState == statePlaying {
|
||||||
g.state.gameState = statePaused
|
g.state.gameState = statePaused
|
||||||
@@ -157,6 +204,12 @@ func (g *Game) Update() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track FPS.
|
||||||
|
now := time.Now()
|
||||||
|
if !g.state.lastTick.IsZero() {
|
||||||
|
g.state.trackFPS(now.Sub(g.state.lastTick))
|
||||||
|
}
|
||||||
|
|
||||||
if g.state.gameState == statePlaying {
|
if g.state.gameState == statePlaying {
|
||||||
g.state.update(readControls())
|
g.state.update(readControls())
|
||||||
} else if g.state.gameState == statePaused {
|
} else if g.state.gameState == statePaused {
|
||||||
@@ -188,9 +241,20 @@ func (s *state) update(input controls) {
|
|||||||
}, dt, s.bounds)
|
}, dt, s.bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
func (s *state) trackFPS(delta time.Duration) {
|
||||||
// RENDERING
|
if !s.fpsEnabled {
|
||||||
// ============================================================
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.fpsAccumulator += delta
|
||||||
|
s.fpsFrames++
|
||||||
|
|
||||||
|
if s.fpsAccumulator >= fpsSampleWindow {
|
||||||
|
s.fpsValue = float64(s.fpsFrames) / s.fpsAccumulator.Seconds()
|
||||||
|
s.fpsAccumulator = 0
|
||||||
|
s.fpsFrames = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Game) Draw(screen *ebiten.Image) {
|
func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
g.state.draw(screen)
|
g.state.draw(screen)
|
||||||
@@ -211,13 +275,46 @@ func (s *state) draw(screen *ebiten.Image) {
|
|||||||
Level: s.hero.Stamina,
|
Level: s.hero.Stamina,
|
||||||
Color: staminaColor,
|
Color: staminaColor,
|
||||||
}
|
}
|
||||||
s.hud.Draw(screen, []status.Meter{staminaMeter})
|
|
||||||
|
meters := []status.Meter{staminaMeter}
|
||||||
|
|
||||||
|
if s.fpsEnabled {
|
||||||
|
// Color based on target FPS (60).
|
||||||
|
ratio := s.fpsValue / float64(TargetTPS)
|
||||||
|
fpsColor := fpsGoodColor
|
||||||
|
switch {
|
||||||
|
case ratio < fpsPoorThreshold:
|
||||||
|
fpsColor = fpsPoorColor
|
||||||
|
case ratio < fpsWarnThreshold:
|
||||||
|
fpsColor = fpsWarnColor
|
||||||
|
}
|
||||||
|
|
||||||
|
fpsMeter := status.Meter{
|
||||||
|
Label: fmt.Sprintf("Framerate: %3.0f FPS", s.fpsValue),
|
||||||
|
Base: -1, // Negative base means text-only display.
|
||||||
|
Level: 0,
|
||||||
|
Color: fpsColor,
|
||||||
|
}
|
||||||
|
meters = append(meters, fpsMeter)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.hud.Draw(screen, meters)
|
||||||
|
|
||||||
if s.gameState == statePaused {
|
if s.gameState == statePaused {
|
||||||
s.pauseMenu.Draw(screen, ScreenWidth, ScreenHeight)
|
s.pauseMenu.Draw(screen, ScreenWidth, ScreenHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clampFloat(value, min, max float64) float64 {
|
||||||
|
if value < min {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
if value > max {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||||
return ScreenWidth, ScreenHeight
|
return ScreenWidth, ScreenHeight
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ============================================================
|
// Default values and gameplay constants.
|
||||||
// CONFIGURATION
|
|
||||||
// Tweak these values to change gameplay behavior
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Default values if not specified in config
|
// Default values if not specified in config
|
||||||
@@ -32,10 +29,6 @@ const (
|
|||||||
exhaustedThreshold = 0.2 // Show exhausted state below 20% stamina
|
exhaustedThreshold = 0.2 // Show exhausted state below 20% stamina
|
||||||
)
|
)
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// TYPES
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
type Input struct {
|
type Input struct {
|
||||||
Left bool
|
Left bool
|
||||||
Right bool
|
Right bool
|
||||||
@@ -104,10 +97,6 @@ type Config struct {
|
|||||||
StaminaRegen float64
|
StaminaRegen float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// INITIALIZATION
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func New(cfg Config) *Hero {
|
func New(cfg Config) *Hero {
|
||||||
if cfg.Radius <= 0 {
|
if cfg.Radius <= 0 {
|
||||||
cfg.Radius = defaultRadius
|
cfg.Radius = defaultRadius
|
||||||
@@ -146,10 +135,6 @@ func New(cfg Config) *Hero {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// UPDATE
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func (h *Hero) Update(input Input, dt float64, bounds Bounds) {
|
func (h *Hero) Update(input Input, dt float64, bounds Bounds) {
|
||||||
h.updateMovement(input, dt, bounds)
|
h.updateMovement(input, dt, bounds)
|
||||||
h.updateStamina(input, dt)
|
h.updateStamina(input, dt)
|
||||||
@@ -242,10 +227,6 @@ func (h *Hero) updateAnimation(input Input, dt float64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// STATE
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func (h *Hero) getVisualState() VisualState {
|
func (h *Hero) getVisualState() VisualState {
|
||||||
if h.Stamina < h.MaxStamina*exhaustedThreshold {
|
if h.Stamina < h.MaxStamina*exhaustedThreshold {
|
||||||
return StateExhausted
|
return StateExhausted
|
||||||
@@ -258,10 +239,6 @@ func (h *Hero) getVisualState() VisualState {
|
|||||||
return StateIdle
|
return StateIdle
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// RENDERING
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func (h *Hero) Draw(screen *ebiten.Image) {
|
func (h *Hero) Draw(screen *ebiten.Image) {
|
||||||
sprite := h.getCurrentSprite()
|
sprite := h.getCurrentSprite()
|
||||||
|
|
||||||
@@ -319,10 +296,6 @@ func (h *Hero) getCurrentSprite() *ebiten.Image {
|
|||||||
return sprite
|
return sprite
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// UTILITIES
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func clamp(value, min, max float64) float64 {
|
func clamp(value, min, max float64) float64 {
|
||||||
if value < min {
|
if value < min {
|
||||||
return min
|
return min
|
||||||
|
|||||||
@@ -78,8 +78,15 @@ func (m MeterLabel) Draw(screen *ebiten.Image, x, y int) (int, int) {
|
|||||||
if m.Color == nil {
|
if m.Color == nil {
|
||||||
m.Color = color.White
|
m.Color = color.White
|
||||||
}
|
}
|
||||||
txt := fmt.Sprintf("%s: %3.0f%%", m.Meter.Label, m.Meter.Level)
|
var txt string
|
||||||
drawHUDText(screen, txt, m.Color, x, y)
|
if m.Meter.Base < 0 {
|
||||||
|
// Text-only display without percentage.
|
||||||
|
txt = m.Meter.Label
|
||||||
|
} else {
|
||||||
|
// Standard meter with percentage.
|
||||||
|
txt = fmt.Sprintf("%s: %3.0f%%", m.Meter.Label, m.Meter.Level)
|
||||||
|
}
|
||||||
|
drawHUDText(screen, txt, m.Meter.Color, x, y)
|
||||||
return len(txt) * 7, 13
|
return len(txt) * 7, 13
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ func (o Overlay) Draw(screen *ebiten.Image, meters []status.Meter) {
|
|||||||
// Meter column
|
// Meter column
|
||||||
meterElements := make([]Element, 0, len(meters))
|
meterElements := make([]Element, 0, len(meters))
|
||||||
for _, meter := range meters {
|
for _, meter := range meters {
|
||||||
|
if meter.Base < 0 {
|
||||||
|
// Text-only display (no bar).
|
||||||
|
meterElements = append(meterElements,
|
||||||
|
MeterLabel{Meter: meter, Color: o.Color},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Full meter with bar.
|
||||||
meterElements = append(meterElements, Column{
|
meterElements = append(meterElements, Column{
|
||||||
Elements: []Element{
|
Elements: []Element{
|
||||||
MeterLabel{Meter: meter, Color: o.Color},
|
MeterLabel{Meter: meter, Color: o.Color},
|
||||||
@@ -42,6 +49,7 @@ func (o Overlay) Draw(screen *ebiten.Image, meters []status.Meter) {
|
|||||||
Spacing: 2,
|
Spacing: 2,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
meterPanel := Column{
|
meterPanel := Column{
|
||||||
Elements: meterElements,
|
Elements: meterElements,
|
||||||
|
|||||||
@@ -42,41 +42,67 @@ const (
|
|||||||
optionCount
|
optionCount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type menuScreen int
|
||||||
|
|
||||||
|
const (
|
||||||
|
screenMain menuScreen = iota
|
||||||
|
screenSettings
|
||||||
|
)
|
||||||
|
|
||||||
type PauseMenu struct {
|
type PauseMenu struct {
|
||||||
selectedIndex int
|
selectedIndex int
|
||||||
showWIP bool
|
currentScreen menuScreen
|
||||||
|
fpsMonitorValue *bool
|
||||||
|
fpsCapValue FPSCapSetting
|
||||||
|
}
|
||||||
|
|
||||||
|
type FPSCapSetting interface {
|
||||||
|
String() string
|
||||||
|
Cycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPauseMenu() *PauseMenu {
|
func NewPauseMenu() *PauseMenu {
|
||||||
return &PauseMenu{
|
return &PauseMenu{
|
||||||
selectedIndex: 0,
|
selectedIndex: 0,
|
||||||
showWIP: false,
|
currentScreen: screenMain,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *PauseMenu) SetFPSMonitor(enabled *bool) {
|
||||||
|
m.fpsMonitorValue = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PauseMenu) SetFPSCap(cap FPSCapSetting) {
|
||||||
|
m.fpsCapValue = cap
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the selected option if one was chosen, nil otherwise
|
// Returns the selected option if one was chosen, nil otherwise
|
||||||
func (m *PauseMenu) Update() *MenuOption {
|
func (m *PauseMenu) Update() *MenuOption {
|
||||||
// Handle up/down navigation
|
if m.currentScreen == screenSettings {
|
||||||
|
return m.updateSettings()
|
||||||
|
}
|
||||||
|
return m.updateMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PauseMenu) updateMain() *MenuOption {
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
|
||||||
m.selectedIndex--
|
m.selectedIndex--
|
||||||
if m.selectedIndex < 0 {
|
if m.selectedIndex < 0 {
|
||||||
m.selectedIndex = int(optionCount) - 1
|
m.selectedIndex = int(optionCount) - 1
|
||||||
}
|
}
|
||||||
m.showWIP = false
|
|
||||||
}
|
}
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
|
||||||
m.selectedIndex++
|
m.selectedIndex++
|
||||||
if m.selectedIndex >= int(optionCount) {
|
if m.selectedIndex >= int(optionCount) {
|
||||||
m.selectedIndex = 0
|
m.selectedIndex = 0
|
||||||
}
|
}
|
||||||
m.showWIP = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle selection
|
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeySpace) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeySpace) {
|
||||||
selected := MenuOption(m.selectedIndex)
|
selected := MenuOption(m.selectedIndex)
|
||||||
if selected == OptionSettings {
|
if selected == OptionSettings {
|
||||||
m.showWIP = true
|
m.currentScreen = screenSettings
|
||||||
|
m.selectedIndex = 0
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &selected
|
return &selected
|
||||||
@@ -85,19 +111,48 @@ func (m *PauseMenu) Update() *MenuOption {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *PauseMenu) updateSettings() *MenuOption {
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
||||||
|
m.currentScreen = screenMain
|
||||||
|
m.selectedIndex = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsCount := 2
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
|
||||||
|
m.selectedIndex--
|
||||||
|
if m.selectedIndex < 0 {
|
||||||
|
m.selectedIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
|
||||||
|
m.selectedIndex++
|
||||||
|
if m.selectedIndex >= settingsCount {
|
||||||
|
m.selectedIndex = settingsCount - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeySpace) {
|
||||||
|
if m.selectedIndex == 0 && m.fpsMonitorValue != nil {
|
||||||
|
*m.fpsMonitorValue = !*m.fpsMonitorValue
|
||||||
|
} else if m.selectedIndex == 1 && m.fpsCapValue != nil {
|
||||||
|
m.fpsCapValue.Cycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *PauseMenu) Draw(screen *ebiten.Image, screenWidth, screenHeight int) {
|
func (m *PauseMenu) Draw(screen *ebiten.Image, screenWidth, screenHeight int) {
|
||||||
// Draw semi-transparent overlay
|
|
||||||
if overlay := getOverlayImage(screenWidth, screenHeight); overlay != nil {
|
if overlay := getOverlayImage(screenWidth, screenHeight); overlay != nil {
|
||||||
screen.DrawImage(overlay, nil)
|
screen.DrawImage(overlay, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Menu dimensions
|
|
||||||
menuWidth := 400
|
menuWidth := 400
|
||||||
menuHeight := 300
|
menuHeight := 300
|
||||||
menuX := (screenWidth - menuWidth) / 2
|
menuX := (screenWidth - menuWidth) / 2
|
||||||
menuY := (screenHeight - menuHeight) / 2
|
menuY := (screenHeight - menuHeight) / 2
|
||||||
|
|
||||||
// Draw menu background
|
|
||||||
vector.DrawFilledRect(screen,
|
vector.DrawFilledRect(screen,
|
||||||
float32(menuX), float32(menuY),
|
float32(menuX), float32(menuY),
|
||||||
float32(menuWidth), float32(menuHeight),
|
float32(menuWidth), float32(menuHeight),
|
||||||
@@ -105,7 +160,6 @@ func (m *PauseMenu) Draw(screen *ebiten.Image, screenWidth, screenHeight int) {
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Draw menu border
|
|
||||||
vector.StrokeRect(screen,
|
vector.StrokeRect(screen,
|
||||||
float32(menuX), float32(menuY),
|
float32(menuX), float32(menuY),
|
||||||
float32(menuWidth), float32(menuHeight),
|
float32(menuWidth), float32(menuHeight),
|
||||||
@@ -114,13 +168,19 @@ func (m *PauseMenu) Draw(screen *ebiten.Image, screenWidth, screenHeight int) {
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Draw title
|
if m.currentScreen == screenSettings {
|
||||||
|
m.drawSettings(screen, menuX, menuY, menuWidth, menuHeight)
|
||||||
|
} else {
|
||||||
|
m.drawMain(screen, menuX, menuY, menuWidth, menuHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PauseMenu) drawMain(screen *ebiten.Image, menuX, menuY, menuWidth, menuHeight int) {
|
||||||
titleText := "PAUSED"
|
titleText := "PAUSED"
|
||||||
titleX := menuX + (menuWidth / 2) - (len(titleText) * 7 / 2)
|
titleX := menuX + (menuWidth / 2) - (len(titleText) * 7 / 2)
|
||||||
titleY := menuY + 50
|
titleY := menuY + 50
|
||||||
m.drawText(screen, titleText, color.White, titleX, titleY)
|
m.drawText(screen, titleText, color.White, titleX, titleY)
|
||||||
|
|
||||||
// Draw menu options
|
|
||||||
options := []string{"Resume", "Settings", "Quit"}
|
options := []string{"Resume", "Settings", "Quit"}
|
||||||
startY := menuY + 110
|
startY := menuY + 110
|
||||||
|
|
||||||
@@ -128,7 +188,6 @@ func (m *PauseMenu) Draw(screen *ebiten.Image, screenWidth, screenHeight int) {
|
|||||||
optionY := startY + (i * 40)
|
optionY := startY + (i * 40)
|
||||||
optionX := menuX + (menuWidth / 2) - (len(option) * 7 / 2)
|
optionX := menuX + (menuWidth / 2) - (len(option) * 7 / 2)
|
||||||
|
|
||||||
// Draw selection indicator
|
|
||||||
if i == m.selectedIndex {
|
if i == m.selectedIndex {
|
||||||
indicatorX := optionX - 20
|
indicatorX := optionX - 20
|
||||||
m.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, optionY)
|
m.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, optionY)
|
||||||
@@ -138,16 +197,53 @@ func (m *PauseMenu) Draw(screen *ebiten.Image, screenWidth, screenHeight int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw WIP message if settings was selected
|
hintText := "Use Arrow Keys/WASD to navigate, Enter/Space to select"
|
||||||
if m.showWIP {
|
hintX := menuX + (menuWidth / 2) - (len(hintText) * 7 / 2)
|
||||||
wipY := startY + 140
|
hintY := menuY + menuHeight - 30
|
||||||
wipText := "Work In Progress"
|
m.drawText(screen, hintText, color.RGBA{R: 150, G: 150, B: 150, A: 255}, hintX, hintY)
|
||||||
wipX := menuX + (menuWidth / 2) - (len(wipText) * 7 / 2)
|
|
||||||
m.drawText(screen, wipText, color.RGBA{R: 255, G: 150, B: 0, A: 255}, wipX, wipY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw controls hint at bottom
|
func (m *PauseMenu) drawSettings(screen *ebiten.Image, menuX, menuY, menuWidth, menuHeight int) {
|
||||||
hintText := "Use Arrow Keys/WASD to navigate, Enter/Space to select"
|
titleText := "SETTINGS"
|
||||||
|
titleX := menuX + (menuWidth / 2) - (len(titleText) * 7 / 2)
|
||||||
|
titleY := menuY + 50
|
||||||
|
m.drawText(screen, titleText, color.White, titleX, titleY)
|
||||||
|
|
||||||
|
startY := menuY + 110
|
||||||
|
leftMargin := menuX + 40
|
||||||
|
|
||||||
|
fpsMonitorText := "FPS Monitor: "
|
||||||
|
if m.fpsMonitorValue != nil && *m.fpsMonitorValue {
|
||||||
|
fpsMonitorText += "ON"
|
||||||
|
} else {
|
||||||
|
fpsMonitorText += "OFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.selectedIndex == 0 {
|
||||||
|
indicatorX := leftMargin - 20
|
||||||
|
m.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, startY)
|
||||||
|
m.drawText(screen, fpsMonitorText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, startY)
|
||||||
|
} else {
|
||||||
|
m.drawText(screen, fpsMonitorText, color.RGBA{R: 180, G: 180, B: 180, A: 255}, leftMargin, startY)
|
||||||
|
}
|
||||||
|
|
||||||
|
fpsCapText := "FPS Cap: "
|
||||||
|
if m.fpsCapValue != nil {
|
||||||
|
fpsCapText += m.fpsCapValue.String()
|
||||||
|
} else {
|
||||||
|
fpsCapText += "60 FPS"
|
||||||
|
}
|
||||||
|
|
||||||
|
capY := startY + 30
|
||||||
|
if m.selectedIndex == 1 {
|
||||||
|
indicatorX := leftMargin - 20
|
||||||
|
m.drawText(screen, ">", color.RGBA{R: 255, G: 200, B: 0, A: 255}, indicatorX, capY)
|
||||||
|
m.drawText(screen, fpsCapText, color.RGBA{R: 255, G: 255, B: 100, A: 255}, leftMargin, capY)
|
||||||
|
} else {
|
||||||
|
m.drawText(screen, fpsCapText, color.RGBA{R: 180, G: 180, B: 180, A: 255}, leftMargin, capY)
|
||||||
|
}
|
||||||
|
|
||||||
|
hintText := "Enter/Space to toggle, ESC to go back"
|
||||||
hintX := menuX + (menuWidth / 2) - (len(hintText) * 7 / 2)
|
hintX := menuX + (menuWidth / 2) - (len(hintText) * 7 / 2)
|
||||||
hintY := menuY + menuHeight - 30
|
hintY := menuY + menuHeight - 30
|
||||||
m.drawText(screen, hintText, color.RGBA{R: 150, G: 150, B: 150, A: 255}, hintX, hintY)
|
m.drawText(screen, hintText, color.RGBA{R: 150, G: 150, B: 150, A: 255}, hintX, hintY)
|
||||||
@@ -162,5 +258,5 @@ func (m *PauseMenu) drawText(screen *ebiten.Image, txt string, clr color.Color,
|
|||||||
|
|
||||||
func (m *PauseMenu) Reset() {
|
func (m *PauseMenu) Reset() {
|
||||||
m.selectedIndex = 0
|
m.selectedIndex = 0
|
||||||
m.showWIP = false
|
m.currentScreen = screenMain
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user