Timers
Games are full of "wait N frames, then do something" patterns: countdown before a round starts, cooldown between attacks, flashing a hit sprite for 10 frames, blinking a cursor every half second. The Timer and RepeatingTimer types handle these without manual frame counters.
Timer
A Timer counts a fixed number of frames, then stops.
// Wait 60 frames (1 second at 60 FPS) before dropping the next pill
dropDelay := gosprite64.NewTimer(60)
// In Update():
if dropDelay.Tick() {
// This runs on exactly the frame the timer finishes
dropPill()
dropDelay.Reset() // start the next delay
}
Checking state
timer.Done() // true after all frames elapsed
timer.Elapsed() // frames so far
timer.Remaining() // frames left
timer.Duration() // total frame count
timer.Progress() // 0.0 to 1.0 ratio
Progress for animation
Progress returns a 0..1 value that pairs naturally with easing functions:
// Fade out over 30 frames
fadeTimer := gosprite64.NewTimer(30)
// In Update():
fadeTimer.Tick()
// In Draw():
alpha := 1.0 - fadeTimer.Progress()
// or with easing:
alpha := 1.0 - math2d.EaseOutQuad(fadeTimer.Progress())
Reset
Reset restarts with the same duration. ResetWith changes the duration:
timer.Reset() // restart same countdown
timer.ResetWith(120) // restart with 2 seconds
Zero and negative durations
A zero-duration timer is immediately Done. Negative durations are clamped to 0:
gosprite64.NewTimer(0).Done() // true
gosprite64.NewTimer(-5).Done() // true (clamped to 0)
RepeatingTimer
A RepeatingTimer fires at a fixed interval and counts how many times it has triggered.
// Blink cursor every 30 frames (twice per second)
blink := gosprite64.NewRepeatingTimer(30)
// In Update():
blink.Tick()
// In Draw():
if blink.Count()%2 == 0 {
drawCursor()
}
Spawn waves
// Spawn an enemy every 90 frames
spawner := gosprite64.NewRepeatingTimer(90)
// In Update():
if spawner.Tick() {
spawnEnemy()
}
Count and Reset
spawner.Count() // how many times it has triggered
spawner.Reset() // clear count and elapsed
Typical patterns
Countdown before round start
type PlayState struct {
countdown *gosprite64.Timer
started bool
}
func (s *PlayState) Enter() {
s.countdown = gosprite64.NewTimer(180) // 3 seconds
}
func (s *PlayState) Update() {
if !s.countdown.Done() {
s.countdown.Tick()
return // skip gameplay logic during countdown
}
s.started = true
// normal gameplay...
}
func (s *PlayState) Draw() {
if !s.countdown.Done() {
remaining := 3 - s.countdown.Elapsed()/60
gosprite64.DrawText(fmt.Sprintf("%d", remaining), 140, 100, gosprite64.Yellow)
}
}
Flash on hit
hitFlash := gosprite64.NewTimer(10)
// When hit:
hitFlash.Reset()
// In Draw():
if !hitFlash.Done() {
hitFlash.Tick()
if hitFlash.Elapsed()%2 == 0 {
drawPlayer() // skip every other frame for flash effect
}
} else {
drawPlayer()
}
Complete example
See examples/splitscreen_demo for timers used alongside draw regions and menus. Build with:
GOENV=n64.env go1.24.5-embedded build -o splitscreen.elf ./examples/splitscreen_demo
n64go rom splitscreen.elf