FPS counter in settings
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
@@ -13,10 +14,7 @@ import (
|
||||
"github.com/atridad/BigFeelings/internal/ui/menu"
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
// CONFIGURATION
|
||||
// Tweak these values to change game settings
|
||||
// ============================================================
|
||||
// Game settings.
|
||||
|
||||
const (
|
||||
ScreenWidth = 960
|
||||
@@ -25,11 +23,51 @@ const (
|
||||
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 (
|
||||
backgroundColor = color.NRGBA{R: 0, G: 0, B: 0, A: 255}
|
||||
)
|
||||
|
||||
// Hero configuration
|
||||
// Hero settings.
|
||||
const (
|
||||
heroStartX = ScreenWidth / 2
|
||||
heroStartY = ScreenHeight / 2
|
||||
@@ -44,26 +82,28 @@ var (
|
||||
heroColor = color.NRGBA{R: 210, G: 220, B: 255, A: 255}
|
||||
)
|
||||
|
||||
// HUD configuration
|
||||
// HUD settings.
|
||||
const (
|
||||
hudX = ScreenWidth - 220
|
||||
hudY = 20
|
||||
)
|
||||
|
||||
// Stamina bar colors
|
||||
// HUD colors.
|
||||
var (
|
||||
staminaNormalColor = color.NRGBA{R: 0, G: 255, B: 180, A: 255}
|
||||
staminaLowColor = color.NRGBA{R: 255, G: 60, B: 60, A: 255}
|
||||
fpsGoodColor = color.NRGBA{R: 120, G: 255, B: 120, A: 255}
|
||||
fpsWarnColor = color.NRGBA{R: 255, G: 210, B: 100, A: 255}
|
||||
fpsPoorColor = color.NRGBA{R: 255, G: 120, B: 120, A: 255}
|
||||
)
|
||||
|
||||
const (
|
||||
staminaLowThreshold = 0.2
|
||||
fpsWarnThreshold = 0.85
|
||||
fpsPoorThreshold = 0.6
|
||||
fpsSampleWindow = time.Second
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
// TYPES
|
||||
// ============================================================
|
||||
|
||||
type gameState int
|
||||
|
||||
const (
|
||||
@@ -84,25 +124,26 @@ type Game struct {
|
||||
}
|
||||
|
||||
type state struct {
|
||||
hero *hero.Hero
|
||||
hud hud.Overlay
|
||||
bounds hero.Bounds
|
||||
lastTick time.Time
|
||||
pauseMenu *menu.PauseMenu
|
||||
gameState gameState
|
||||
hero *hero.Hero
|
||||
hud hud.Overlay
|
||||
bounds hero.Bounds
|
||||
lastTick time.Time
|
||||
pauseMenu *menu.PauseMenu
|
||||
gameState gameState
|
||||
fpsEnabled bool
|
||||
fpsFrames int
|
||||
fpsAccumulator time.Duration
|
||||
fpsValue float64
|
||||
fpsCap FPSCap
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// INITIALIZATION
|
||||
// ============================================================
|
||||
|
||||
func New() *Game {
|
||||
return &Game{state: newState()}
|
||||
}
|
||||
|
||||
func newState() *state {
|
||||
now := time.Now()
|
||||
return &state{
|
||||
s := &state{
|
||||
hero: hero.New(hero.Config{
|
||||
StartX: heroStartX,
|
||||
StartY: heroStartY,
|
||||
@@ -122,16 +163,18 @@ func newState() *state {
|
||||
Width: ScreenWidth,
|
||||
Height: ScreenHeight,
|
||||
},
|
||||
lastTick: now,
|
||||
pauseMenu: menu.NewPauseMenu(),
|
||||
gameState: statePlaying,
|
||||
lastTick: now,
|
||||
pauseMenu: menu.NewPauseMenu(),
|
||||
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 {
|
||||
return controls{
|
||||
Left: ebiten.IsKeyPressed(ebiten.KeyArrowLeft) || ebiten.IsKeyPressed(ebiten.KeyA),
|
||||
@@ -142,11 +185,15 @@ func readControls() controls {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// UPDATE
|
||||
// ============================================================
|
||||
|
||||
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 g.state.gameState == statePlaying {
|
||||
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 {
|
||||
g.state.update(readControls())
|
||||
} else if g.state.gameState == statePaused {
|
||||
@@ -188,9 +241,20 @@ func (s *state) update(input controls) {
|
||||
}, dt, s.bounds)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// RENDERING
|
||||
// ============================================================
|
||||
func (s *state) trackFPS(delta time.Duration) {
|
||||
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) {
|
||||
g.state.draw(screen)
|
||||
@@ -211,13 +275,46 @@ func (s *state) draw(screen *ebiten.Image) {
|
||||
Level: s.hero.Stamina,
|
||||
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 {
|
||||
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) {
|
||||
return ScreenWidth, ScreenHeight
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user