Patterns and Basic Control ScriptingAlgorithmic and deterministic algorithms are great, but sometimes you need to make a game object follow a sequence of steps, or a script of sorts. For example, when you start your car, there is a specific sequence of steps that you perform:
The point is that there's a sequence of steps that you don't think much about. You just replay them every time. Of course, if something goes wrong, you might change your sequence, like pressing the gas pedal or jump-starting the car because you left the lights on last night. Patterns are an important part of intelligent behavior, and even humans, the epitome of intelligent life on this planet (yeah, right), use them. Basic PatternsCreating patterns for game objects can be simple, depending on the game object itself. For example, motion control patterns are very simple to implement. Let's say you're writing a shoot-'em-up game similar to Phoenix or Galaxian. The alien attackers must follow a left-right pattern and then at some point attack you with a specific attack pattern. This kind of pattern or scripted AI can be achieved using a number of different techniques, but I think the easiest technique is based on interpreted motion instructions, as shown in Figure 12.5. Figure 12.5. The pattern engine.Each motion pattern is stored as a sequence of directions or instructions, as shown in Table 12.1.
Along with each directional instruction might be another operand or piece of data that further qualifies the instruction, such as how long to do it. As a result, the pattern language instruction format might look like the following: INSTRUCTION OPERAND INSTRUCTION is from the previous list (usually encoded as a single number), and OPERAND is another number that helps further define the behavior of the instruction. With this simple instruction format, you create a program (sequence of instructions) that defines the pattern. Then you write an interpreter that feeds from a source pattern and controls the game creature appropriately. For example, let's say your pattern language is formatted so that the first number is the instruction itself and the second number indicates how long to perform the motion, in cycles. Creating a square pattern with a spin and stop, as shown in Figure 12.6, would be trivial. Figure 12.6. A detailed square pattern.Here's an example of that in coded [INSTRUCTION, OPERAND] format: int num_instructions = 6; // number of instructions in script pattern // this holds the actual pattern script int square_stop_spin[ 1,30, 3,1, // go forward then turn right 1,30, 3,1, // go forward then turn right 1,30, 3,1, // go forward then turn right 1,30, // go forward and finish square 6,60, // stop for 60 cycles 4,8, ]; // spin for 8 cycles To process the pattern instructions, all you need is a big switch() statement that interprets each instruction and tells the game creature what it's supposed to do, like this: // points to first instruction (2 words per instruction) int instruction_ptr = 0; // first extract the number of cycles int cycles = square_stop_spin[instruction_ptr+1]; // now process instruction switch(square_stop_spin[instruction_ptr]) { case GO_FORWARD: // move creature forward... break; case GO_BACKWARD: // move creature backward... break; case TURN_RIGHT_90: // turn creature 90 degrees right... break; case TURN_LEFT_90: // turn creature 90 degrees left... break; case SELECT_RANDOM_DIECTION: // select random dir... break; case STOP: // stop the creature break; } // end switch // advance instruction pointer (2 words per instruction) instruction_ptr+=2; // test if end of sequence has been detected... if (instruction_ptr > num_instructions*2) {/* sequence over */ } And, of course, you would add the logic to track the cycle counter and make the motion happen. There's one catch to all this pattern stuff: reasonable motion. Because the game object is feeding off a pattern, it might decide to select a pattern that forces the object to smash into something. If the pattern AI doesn't take this into consideration, patterns will be followed blindly. As a result, you must have a feedback loop with your pattern AI (as with any AI) that instructs the AI that it has done something illegal, impossible, or unreasonable, and it must reset to another pattern or strategy. This is shown in Figure 12.7. Figure 12.7. Pattern engine with feedback control.NOTE Of course, you might want to use a better data structure than an array. For example, try using a class or structure containing a list of records in [INSTRUCTION, OPERAND] format, along with the number of instructions. That way you could very easily create an array of these structures, each containing a different pattern, and then select a pattern and pass it to the pattern processor. Stop for a minute and think about the power of patterns. With them, you could record hundreds of moves and flight patterns. Patterns that would be nearly impossible to create in any reasonable amount of time using other AI techniques can be created in minutes with a tool (that you would write), recorded in a file, and then played back in your game. Using this technique, you can make a game creature look as if it's extremely intelligent. This technique is used by nearly all games, including most fighting games such as Dead or Alive, Tekken, Soul Blade, Mortal Kombat, and so on. Furthermore, there's no need to stop with motion patterns. You could use patterns to control weapon selection, animation control, and so on. There's no limit to how they can be applied. For an example of patterns in action, take a look at DEMO12_5.CPP|EXE (16-bit version, DEMO12_5_16B.CPP|EXE), which demonstrates a monster that moves around using a number of patterns and selects a new pattern every so often. Patterns with Conditional Logic ProcessingPatterns are cool, but they're extremely deterministic. That is, once the player has memorized a pattern, it's useless. Players can always beat your AI because they know what's going to happen next. The solution to this problem, and to other problems that pop up with patterns, is to add a bit of conditional logic that selects patterns based on more than random selection, taking into account the conditions of the game world and the actual player. Take a look at Figure 12.8 to see this abstractly. Figure 12.8. Patterns with conditional logic.Patterns with conditional logic give you yet one more level of control over your AI models—you can select patterns that contain conditional branches as well as the patterns being selected based on conditional logic. For example, you might add a new instruction to the pattern language that is a conditional logic test: TEST_DISTANCE 7 The TEST_DISTANCE conditional might work by testing the distance of the player from the object performing the pattern. If the distance is too close, too far, or whatever, the pattern AI engine might change what it's doing, making for a seemingly more intelligent opponent. For example, you might put a TEST_DISTANCE instruction every so many instructions in a standard pattern, like this: TURN_RIGHT_90, GO_FORWARD, STOP, ...TEST_DISTANCE, ...TURN_LEFT_90, ...TEST_DISTANCE, ... GO_BACKWARD The pattern does its thing, but every time a TEST_DISTANCE instruction is encountered, the pattern AI uses the operand following the TEST_DISTANCE instruction as a measure to test the player's position. If the player is getting too far away, the pattern AI stops the current pattern and branches to another pattern. Or possibly better yet, it switches to a deterministic tracking algorithm to get closer to the player. Take a look at the following code: if (instruction_stream[instruction_ptr] == TEST_DISTANCE) { // obtain distance, note that on the test // instructions the operand is no // longer a time or cycle count // but becomes context dependent int min_distance = instruction_stream[instruction_ptr]; // if test if player is too far if (Distance(player, object) > min_distance) { // set system state to switch to track ai_state = TRACK_PLAYER; // .. or you might just switch to // another pattern and hope // that the object gets closer } // end if }// end if There's no limit to the complexity of the conditional tests that you can perform in the pattern script. In addition, you may want to create patterns on-the-fly and then use them. One such example is to mimic the player's motion. You could sample what the player does each time she kills one of your game characters, and then use the same tactic against her! In conclusion, technology like this (although much more sophisticated) is used in many sports games, such as football, baseball, and hockey, as well as action and strategy games. It allows the game objects to make predictable moves, while still allowing them to "change their minds." As an example, DEMO12_6.CPP|EXE (16-bit version, DEMO12_6_16B.CPP|EXE) illustrates the conditional technique. You control a bat creature with the arrow keys, and there is an AI skeleton on the screen. The skeleton follows randomly selected patterns until you get too far away, and then it gets lonely and chases you because it wants your attention. (Reflect on what I just said…I placed an emotional motive on 100 lines of computer code. But isn't that what it seems like, from a spectator's point of view? Mr. Turing, are you there?) |