-
Notifications
You must be signed in to change notification settings - Fork 7
Game Programming Concepts
In the first chapters we explored the low-level libraries used to create multimedia and interact with hardware.
In the following chapters we are going to explore higher-level concepts of game programming. These will be patterns and solutions applied to common problems found in games.
Most of the ideas we will explore are common in game programming and you can find more about them in standard literature. The solutions, however, will sometimes be different. Also, some situations will be very specific to functional languages and to Haskell, and we will visit additional constrations due to purity and how to circumvent them.
Most games need to address many of the following concerns:
- Asset management (locating assets, loading them, using them, freeing them, recovering from errors loading assets, showing info about loading progress).
- Physics and collisions (simulating a realistic world)
- Error handling (reporting useful information while keeping the game running).
- Cameras (projections, interaction).
- Asynchronicity (communciation of different threads running different subcomponents).
- Audio (background music, sound effects, audio synchronisation).
- Menus (configuring preferences, jumping to specific parts of the game, resuming the game).
- Preferences.
- Animations.
- Backgrounds and scrolling effects.
- Z-axis (in 2.5, parallax or 3D).
- Reconfigurable input devices.
- Saving or interrupting the game.
- Time transformations and alterations: pausing
- Testing.
- On-screen HUDS.
- Network communication.
- AI.
During gameplay, your game transitions over several states. At the beginning, it will probably show a loading splash screen to let users know there should be a delay while assets are being loaded. Then it might show a menu and let users select and configure devices, quality and game settings select a level and start playing.
During gameplay, your game can be loading a level, playing, paused, showing won/game over screens, showing a menu (save, quit, etc.) or depending on the kind of game, waiting for you or other players, to make a move. It is easy to see then that your game is, in some respect, a state machine.
Simple state machines can be described by a diagram of states (circles) with directed links between them (arrows). Arrows can be labeled with inputs, or the events that would make the machine transition from one state to the next. An arrow from a state x to a state y labeled '0' could be interpreted as "if the user enters '0' while in state 'x', the game can/will transition to state 'y'". The initial state can be marked with an arrow pointing towards it, and final states are often circled twice.
⭐ Representation
There are other ways of representing state machines. The machine's output can also be included in the diagram. See REFERENCES for alternative representations and a description of other areas that use this abstraction.
Apart from the idea that your game switches from one state to the next, it is important to understand that both the input and the output may be completely different between one state and another.
Consider, for example, a menu. An abstract description of the possible input actions (without depending on any specific input device or keyboard layout) might be termed 'up', 'down', 'activate', 'back' and so forth. During another game stage, your game might need continuous information from the accelerometers of a wiimote.
While it is possible to use the part of the complete input that each part of the program is interested in, it pays to create a first layer of filtering that transforms the raw input into a more declarative description based on the context of the program.
The same is true about the output: it is best to structure each state's processing function so that the output is a declarative description of that particular state. For instance, while in the menu state, we could create a type to represent the possible input, one to represent the state while in the menu, and create one function that processes new input to produce an ever changing state.
⭐ Change
Such a function would need to have access to the current state to produce the new one. Different abstractions that address this are monads, arrows, continuations, stream processors, functional reactive programming and explicit state passing. We will explore some of them later (NEEDS REFERENCE).
PLACE FOR AN EXAMPLE WITHOUT FRP, ONE WITH FRP.
Games are interactive visualizations and, as such, they need to process user input and produce continuous video and/or audio.
One possible way of structuring them, akin to mathematical functions and command line invocations, is to repeatedly detect the input, process it (based on the program's state at the time), present the output after that. This results in a sense-process-render loop often called the game loop.
⭐ Past, present and future
Game loops have been used for many years, and they continue to be used to this day. See, for example, REFERENCE and REFERENCE for an old example and a new one.
Input and output, in the real world, occur in continuous time. Yet your game gathers input at discrete points, and renders a little bit afterwards. A sequence of actions as required by a game loop creates a small delay between some effects and others, and also enforces a certain priority of some over others. For instance, in your game you might advance the physics (movement of elements based on velocities and accelerations) before or after you process the user's input. If you do it afterwards, then the physics at a given time will be independent of all the input since the last time the game was rendered until now. You might also run the players actions before or after the artificial intelligence that controls the opponents, in which case you are deciding on an order that might change the game's outcome completely. Depending on you game pace, this may or may not make a noticeable difference.
