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?