Almost any game code has functions that mustn’t fire every tick. A shoot routine that lets at most one shot through every three frames. A key handler that doesn’t trigger ten times a second just because the key’s been held. A logger that writes once per second instead of 60 times.
My first instinct was to handle this with globals. It works, but every sub drags its little bit of state through the global namespace, and clashes are a matter of time. STATIC is the cleaner answer.
How STATIC behaves
STATIC declares a variable inside a sub or function that keeps its value across calls. On the first call it’s initialised to 0 (or whatever default you set), and from then on it persists between calls, without ever leaking outside the sub.
SUB FireShot()
STATIC INTEGER cooldown
IF cooldown > 0 THEN
INC cooldown, -1
EXIT SUB
END IF
' ... fire the shot ...
cooldown = 3
END SUB
Three frames of cooldown, all in one sub, no global echo, no init at program start. The first time the sub runs, cooldown is already 0, and from then on it lives where it belongs.
Key debouncing
Classic case: a key that flips a toggle shouldn’t flip again every frame while you hold it. Same pattern, but the state we track is “was it held last frame”:
SUB CheckMuteKey()
STATIC INTEGER prev_held
IF IsKeyPressed(KEY_M) THEN
IF NOT prev_held THEN
ToggleMute()
END IF
prev_held = 1
ELSE
prev_held = 0
END IF
END SUB
prev_held remembers whether the key was down last frame. The toggle only fires on the edge from “not pressed” to “pressed”. Clean edge detection, no global helper.
Rate-limiting logging or audio
When something should happen once per second instead of 60 times per frame, a frame counter does the job:
SUB LogPerformance()
STATIC INTEGER tick
INC tick
IF tick < 60 THEN EXIT SUB
tick = 0
PRINT "FPS: " + STR$(actual_fps)
END SUB
The same shape works for rhythmic audio cues or periodic HUD updates. The narrow scope keeps the whole thing easy to reason about — if something’s off, I know exactly where the state lives.
Three or four subs with their own little cooldowns and the difference from the global version becomes obvious. My code is noticeably tidier since I switched, and each sub carries its own state instead of fishing it out of a shared pool.