The game loop, events, and input
Download
Report
Transcript The game loop, events, and input
The game loop, events, input
Mark Nelson
[email protected]
Fall 2013
www.itu.dk
Today’s lecture
How to manage the top level of a game
Typically structured in the form of a game loop that checks
and generates events, and performs updates based on
those events
Engine often provides a particular style of game loop
Top-level loop
while(1) { run_game_tick(); }
Main considerations:
Factoring out logical components
How does the passage of time interact with everything?
Pong top-level loop
while (true)
{
readInput();
if (quitButtonPressed())
break;
movePaddles();
moveBall();
collideAndBounceBall();
if (ballImpactedSide(LEFT_PLAYER))
{
incrementScore(RIGHT_PLAYER);
resetBall();
}
// likewise for the other side…
renderPlayfield();
}
Pong loop features
Updates as fast as it can
Gameplay and update logic is hardcoded in the main loop
Update-as-fast-as-you-can
Classic style of game loop
Exposes some strange features, though
Faster CPU faster gameplay
Sometimes a problem if you try to play old games
More CPU-expensive stuff happening slower gameplay
Can exploit that on some old games
Frame-locked updates
More regular updates
Fix a framerate, e.g. 30 fps
Game loop runs once per frame
If there’s extra time, wait for next frame
Soft and hard realtime
Hard realtime system
Fixed time windows, hard deadlines
Every frame takes 1/30 sec or less to prepare and render
MUST meet the deadline!
Soft realtime system
Programming model generally assumes results within deadlines
But we should be able to sometimes miss it without disaster
Soft and hard realtime
A few systems impose hard realtime requirements
Atari 2600 updates
Modern systems don’t, and cost of getting it right is high
So, prefer soft realtime
What to do if we don’t meet the deadline?
Missing deadlines
It’s been 1/30 sec since the last frame, and we’re still
computing
What happens?
Option 1: Frame-locked, late frame
Finish our computation, frame will render when the code
finishes running
Both screen update and game-world time are delayed
E.g.: if an object is rotating 3 degrees per frame, it’s now
rotating fewer degrees per second (fewer frames/sec)
Option 1: Frame-locked, late frame
Can also see this in network games: Starcraft starts lagging
when updates are coming in too slowly
But, might not be desirable
Can we keep the game running the same apparent speed?
Option 2: Separate game-world time
Even if we can’t render 30 fps, might want game-world time
to stay constant
Object rotates N degrees/sec, not dependent on framerate
Game world has to update more per frame
Dropped frames make it look like stop-motion of a constant-speed world
Instead of a slowed-down world
Option 2: Separate game-world time
Keep track of deltaT: time since last frame
Game-world updates don’t assume 1/30s per frame
Parameterized by deltaT
Instead of X-per-frame, objects move/rotate/etc. x-per-sec
Calculate how much that corresponds to for this frame and update
Bonus: changing framerate (e.g. 30fps->60fps) is easy
But fallback if deltaT is unreasonably large
Delaying computation instead of frames
Not everything is equally important
Is it worth delaying the next frame due to path re-planning?
If something’s taking a long time
Either it delays the next frame
Or we can delay it to a later frame
Delaying computation instead of frames
Single-threaded, time budgeting
Example: A.I. code knows how to do only a limited amount of work per frame
Multi-threaded, preemptive
Scheduler interrupts code taking too long, tells it to finish up
Multi-threaded, multi-frame work threads
Stuff taking too long keeps running, but we don’t wait for its results
Structuring updates
Brings us to the other issue with the Pong loop
Specific code in the main loop
How do we structure game logic more generally?
Less-hardcoded loop
Common to use a generic top-level loop with phases
init_game();
while(1)
{
run_physics();
poll_input();
move_player();
run_ai();
update_screen();
}
Subsystem managers
Phases often associated with managers
Separate bit of code (e.g. a class) responsible for an area
Physics system
AI system
etc.
Each has internal data structures, and a way of getting
information to/from the other subsystems
Event-driven approach
Common way to factor a game based on activity/reactions
An event is just a ”thing that happens”
Define your own
Can be in an inheritance hierarchy
Can have parameters/data
Some code generates events
Other code registers to react to them
Event-driven approach
A function that will be called when an event happens is
called a callback
Callbacks register the events they want to listen for
Main loop:
Call event-generating functions (check input, check for collisions, etc.)
Foreach event: call registered callbacks
Render frame
Event hierarchy
Structuring events tends to be engine-specific
Level of granularity varies
InputHappened
KeyPressed
KeyAPressed
Events are classes that can have data: KeyPressed(’A’)
Platformer events
Take 5 minutes and brainstorm:
What entities and events are there in a platformer game?
What do they do? What parameters do they have?
Event-management styles
Events can be enum symbols
SDL_* examples
Or, can be classes
Inheriting from a base class (Event or something similar)
These can have parameters, and can define behavior
Event hierarchy: data versus separate class
Dispatch on separate classes
E.g.:
Some callbacks listen for any InputHappened
Others only want to be called for KeyPressed
Very far down the hierarchy, may be easier for callbacks to
just immediately return based on data
if (keyPressed.key != ’A’) return;
Event-management styles
Runtime modifiable?
A full event/callback system lets you register callbacks,
remove them, etc. all at runtime
Various strategies for maintaining and dispatching
Simplest: map from events to function pointers or functors
Event-management styles
Modular but compile-time
User code shouldn’t have to modify the main loop
But doesn’t need to be runtime-modifiable
Event.handle()
User code overrides Foo.handle() with its own functionality
Bit simpler to implement, and can be more efficient
Input
Sometimes, just another kind of event
Other times more continuous
Has various complexities
Two common types of input
Event-based
’A’ key was pressed
Events are queued, and serviced by an event framework
Polling-based
Is spacebar currently pressed?
Current status is queried when needed by input manager
Event-based input
Discrete events queue up, perhaps by the OS/framework
Ignore ones not of interest, use the rest
Some issues:
Repetition
Latency
Polling-based input
Gives instantaneous state
The only kind that makes sense on some devices
Mouse’s current position
Joystick axis values
Steering wheel angle
But, can miss ’events’
Or duplicate them
Some input questions
What should this input do over time?
What should happen if key is mashed lots of times in a row?
What should happen if key is held down?
Converting polling to events
Why?
Higher-level events on top of low-level input manager
”Moved mouse to hover over unit X”
Build custom event-based input
Ignore OS keyboard driver, so we can control rate of repeat/etc.
Converting events to polling
Why?
Manage a ”curent state”
Example: if we only get keydown/keyup events, can produce an isKeyDown()
In general, engine should determine when an input makes
sense as an event or polling, and convert either way if the
hardware/OS gives the other one
Other kinds of input management
Handling repeated keypresses: one kind of input smoothing
Directly coupling input to game actions isn’t always intuitive
Players want game to do the ”smart” thing
Other kinds of smoothing:
Nonlinear mappings
Time damping
Nonlinear mappings
How to map mouse movement to in-game movement
Depends!
Sometimes linear is what’s expected
Other times, hard to control or feels unnatural
Nonlinear mappings
Small vs. large movement sensitivity:
Want precise control w/o also having to move mouse across a huge desk
Small movements: low sensitivity to allow fine-tuned control
Large movements: high sensitivity to allow macro-level actions
Snapping to values
E.g. zero when close to zero
Bias mouse movements towards straight lines
Nonlinear mapping example
Rolling Explosive Device (ITU Spring 2011)
Input smoothing
Transform noisy input stream to a cleaner/smoother one
Dropping repeated key events is one kind of damping
Another simple kind: moving average
Input smoothing: moving average
For polling-based input
Instead of using the current value, use average of past N
Smooths out transient spikes, shaky movements
Improves robustness to errors
Pitch-tracking in a music game
More advanced input devices
Wii controller, Kinect, etc.
Need to map very noisy, not-gameplay-interpretable space
to higher-level space
Often done with machine learning
Via built-in SDK or third-party tools
Input management for platformer
Is any management needed? What kind?
Scene graph
Representation of objects in the scene
In platformers, often a ”flat” model: list of objects
Can be useful to have a hierarchical model
Many possible uses and criteria
Way to keep the scene organized
Efficient indexing
Data structure for rendering
Spatial indexing
Logical organization can be augmented with spatial indexes
Example uses
Find objects near the player avatar (e.g. for generating interaction events)
Exclude objects that can’t possibly be viewable
Data structures
Octree, kd-tree
In platformers, segmentation
Multithreading
Big topic
One style: asynchronous with callbacks
Main loop calls registered callbacks in new threads
(Or worker threads)
Doesn’t wait for them; checks on data later
Can also have longer-lived threads
Which may themselves call asynchronous callbacks
Multithreading strategies
Two main architectural styles
Threading per-subsystem
Physics thread
A.I. thread
Animation thread
Threading per-object
Multithreading strategies
A few pros and cons
Threading per-subsystem
Can match specialized hardware
Synchronization across each subsystem is easy (e.g. all physics updates in one place)
Threading per-object
Easier to get massive parallelism
Local synchronization is easy (e.g. animation that depends on A.I.)
One more main loop complication
Networking!
Tricky to get right, impacts engine deeply
Game-world timelines must be consistent between players
Local/global updates, message cycle
Assignment #1
Platformer engine, due September 27
Does not need to be networked or multithreaded
Pick a strategy for the game loop and use it consistently.
Recommended: deltaT-style framerate-independent updates.
Input:
Plan out (perhaps initially on paper) your input/event/entity design
What input will you receive? What will be affected? What subsystems?
Does it needs to be transformed (events<->polling) or adjusted?