This first macro block takes care of keeping the world simulator running. For the purpose of our global framework, we will divide it into three main blocks: updating the player, updating the world, and updating the nonplaying characters (NPCs).
A game must execute a routine that keeps an updated snapshot of the player state. As a first step in the routine, interaction requests by the player must be checked for. This is achieved differently for many control mechanisms such as joysticks, keyboards, and mice. But the end result is the same a series of numbers that can be understood by the game code that indicate the state of the player control. It is a good idea to use abstract device controllers so the game code does not actually interact with the physical controller. An abstract device controller handles joysticks and keyboards but uses a common interface so the calling application does not need to. We will talk about input processing and device abstraction in Chapter 5, "User Input."
We will not directly map control decisions to the player's actions because there are some items that can restrict the player's range of actions. He might indicate that he wants to move forward, but a wall may be blocking his path. Thus, a second routine must be designed that implements restrictions to player interaction. These restrictions can be geometric in nature, as in the previous example, or more complex, logic-based combinations. For example, the player can be pressing the "open door" control but because he is not holding the right key, the door won't open. Remember that we are talking about general principles here, which will be applicable to any game you need to code. Thus, we will dub this routine the "player restrictions" handler. A significant part of this routine will be devoted to collision detection, which is discussed in Chapter 21, "Procedural Techniques."
Once we have sensed the player's controller and checked for restrictions, a small update routine must be implemented so players see the result of their interaction; and game state is recomputed. Let's look at two examples to better understand the internals of these three components.
Imagine a game such as Nintendo's classic The Legend of Zelda. The three routines mentioned earlier would have the following responsibilities:
Some games, such as Tetris, do not have a clear onscreen avatar, but still these rules apply. As a rule of thumb, consider your onscreen character as anything you can interact with by pressing the controls. In Tetris, clearly, that refers to the bricks falling from the top of the screen. They are not a character in the strict sense of the word, but the principle is the same. We would check for player input in the first stage; in the second stage, we would test brick restrictions; and then we would execute player update to game state current. Restrictions are very easy to determine: Bricks cannot move outside the screen area, cannot fall below ground level, and cannot keep falling if the current brick is directly above any other previously fallen brick. In the player update segment, we simply move or rotate the brick according to the player's input and add a default behavior to the equation, which is that the bricks keep falling regardless of what the player does. This kind of idle, noninteractive behavior is very common in games in which you want to implement a sense of urgency and speed. Some arcades limit the time of your game (except if you find special "extended play" tokens), which is essentially the same formula in a different context.
The notion of a living game world that displays an active behavior has been present ever since the first games, such as Pong and Space Invaders. In addition to the player's action, the world keeps its own agenda, showing activity that is generally what the user responds to. For example, the user tries to avoid an incoming rock in Asteroids, attempts to return the ball in Arkanoid, and so on. Game world updates effectively implement gameplay and make games fun. It is not surprising then that this portion of the game code is especially important and, in modern-day games, complex.
To begin with, a distinction must be made into two broad game world entities. On the one hand, we have passive entities, such as walls and most scenario items. To provide a more formal definition, these are items that belong to the game world but do not have an attached behavior. These items play a key role in the player restriction section, but are not very important for the sake of world updating. In some games with large game worlds, the world update routines preselect a subsection of the game world, so the player restriction portion can focus on those elements, and thus become more efficient. Think of something like a graphics adventure. Somehow, we must store a pointer to the room the player is in, so we check the colliders in that room only.
But the majority of time in the world update section is spent checking the other type of entities, those that have an embedded behavior. From decorative elements such as flying birds to enemies or doors that open and close, these are the items that must be checked to keep a consistent, meaningful playing experience. Some games will divide active elements into simple logical items such as doors, elevators, or moving platforms and real enemies with a distinctive behavior. Here the differentiation comes from the complexity of the coding. The logical elements can be solved in a few lines of code, whereas real enemies require artificial intelligence with a higher complexity and computational cost.
In our generic game framework, we will assume there is a large number of these active elements, both logic and AI. So, the process of updating them will consist of four steps. First, a filter will select those elements that are relevant to the gameplay. An enemy 10 miles away from the player does not seem like a very important item from the player's standpoint, nor is a gate placed in a different game level altogether. This filter must not rule out anything. Some games (like real-time strategy titles, for example) will still need to compute the behavior of all entities. But many times level-of-detail (LOD) techniques will be used for distant items, so having them sorted by relevance is always desirable.
Second, the state of the active element must be updated. Here the distinction between logical and intelligent entities will be made obvious. The latter will require a more involved process to update their state.
Generally, within the overall game framework, AI systems will follow a four-step process too. First, goals and current state must be analyzed. For a flight simulator, this means obtaining the position and heading, state of the weapons systems, and sustained damage for both the AI-controlled and the player-controlled planes. The goal in this case is pretty straightforward: Shoot down the player. Second, restrictions must be sensed. This involves both the logical and geometrical restrictions we already sensed for the player. For our flight simulator example, the main restriction is avoiding a collision with the player and keeping an eye on the ground, so we do not crash into a nearby hill. After these two steps, we know everything about our state as AI entities, the player's state, the goal to achieve, and the overall restrictions that apply.
Returning to the overall framework, the third step requires that a decision/plan making engine must be implemented that effectively generates behavior rules. The plane will make a turn, then try to shoot, and so on. Some games implement instantaneous plans, which are recomputed each frame. Take the case of a very simple moving mine that chases the player around. For each frame, it must generate the optimal trajectory to finally blow the player up. But most plan-making routines generate tactics that persist for many clock cycles. A flight simulator might make decisions that span several seconds or even minutes of gameplay, and subsequent AI cycles focus only on refining the plan to adapt to changing conditions.
Fourth, we need to update the world state accordingly. We must store data, such as if the enemy moved, or eliminate it from the data structure if it was shot down by the player. As you will see when we study AI in detail, this four-step process blends extraordinarily well with most game AIs.
That completes the game logic framework. As a summary of the structure that was just exposed, here is the pseudocode for the approach:
Player update Sense Player input Compute restrictions Update player state World update Passive elements Pre-select active zone for engine use Logic-based elements Sort according to relevance Execute control mechanism Update state AI based elements Sort according to relevance Sense internal state and goals Sense restrictions Decision engine Update world End