人工智能在很长时间因为某些原因在游戏开发里拖后腿,但游戏的特性将会越来越依赖更多地AI。如果一个游戏的AI没有达到玩家与其的水平,那么这个游戏将会感到很过时并因人们的感受而尴尬。
Game AI is not just neural networks and learning systems and complex mathematical structures, although it can be, but primarily game AI is about creating an environment and the appearance of thought from units. Game AI is behavioral, not scientific.
游戏AI不只是自然网络和学习系统,以及复杂的数据结构,尽管它包含这些,但主要的AI是创造一个环境并从单位来展现。游戏AI是基于行为的,不是科学性的。
The key to understanding how to create game AI is understanding what you want your final results to be and then building the system to provide those results. It all comes down to what the player can see; if they can't tell it's happening, then it might as well not be.
理解如果创建游戏AI的关键在于你想要什么样的最终结果,并要如果构建系统来提供这些结果。AI的全部结果是玩家看得到的,如果没看到,那就是没管用。
The examples and discussion will be given based on the format of Real-Time Strategy (RTS) games, however some of these concepts can be translated into being appropriate for other genres as well. All data examples are done in standard C format.
这里给出的例子和讨论是基于实时策略游戏,然而这里的一些内容可以转化为其他适合的游戏。所以数据结构都是以标准C写成。
状态机
有限状态机
A finite state machine (FSM) is a system that has a limited number of states of operation. A real world example could be a light switch which is either on or off, or an alarm clock that is either idling by telling time, ringing an alarm or having its time or alarm set. Any system that has a limited number of possibilities where something can be defined by one state (even combinations) can be represented as a finite state machine.
一个有限状态机(FSM)是一个有有限状态操作的系统。一个实际的例子是一盏灯可以被打开或者关闭,或者一个闹钟可以闲着报时间,也可以响铃如果设置了响铃时间。任何有限数目的可能性都可以被一个状态或者一个组合来表示。
Finite state machines are natural for any type of computer program and understanding how to use them effectively to create game AI is only as hard as understanding the system you are trying to represent, which is as detailed or simple as you make it.
FSM对于任何类型的计算机程序都是自然的并且写代码的难度与你理解你所代表的东西难度一致。
Using Finite State Machines
用FSM
There are many purposes for using FSMs in games but one of the more intricate ones you have to deal with is trying to model unit behavior since trying to simulate human beings is the toughest simulation there is. As guaranteed hard as it is to simulate human behavior there have been many stories of detailed game AI's that were mistaken for human players and vice versa by other players and spectators of the games, especially some detailed FSMs systems.
用FSM有很多目的,但一个其中一个复杂的原因是你必须处理尝试对以模拟人类行为为最难模拟部分的单元行为进行建模。有很多在这方面采坑的FSMS。
While some other systems are designed to more accurately model the way humans think and learn, sometimes you can just never beat the simplicity of having a choice, weighing the factors and deciding, as a human, which one you would make given that choice. When learning more about AI decision and learning systems always keep this in mind as often the best system for the job is the simplest and not the most scientifically accurate.
当一个其他系统被设计为精确模仿人类思想和学习的系统时,你将没有容易的选择,因为要像人类一样权衡因子的大小。当学习更多AI决策和学习系统后把这个记载心中:越简单越有效。
Don’t misunderstand this as opposition to Neural Network, Genetic Algorithms or any other artificial intelligence systems, just don’t mistake clever routines and interesting algorithms as being a better solution if they wont give better results. Weigh your choices based off what you need to get your end result, not the latest trends.
不要将AI与神经网络,遗传算法和其他人工智能系统混为一谈。以你需要得到的最终结果来权衡你的选择而不是最终的趋势。
游戏状态机
Creating a believable environment for your game means that you need to consider as many detailed elements that the player might possibly focus their attention on as you can. The more of these you anticipate by planning and testing, the more immersive the environment will be for the player when they are discovering your creation.
为你的游戏创建可信的环境意味着你需要与玩家可能在游戏里注意到的一样考虑哪些细节元素。你参与指定和测试的越多,为玩家提供给的环境就越引人入胜。
In your total game state there will be at least two division of state machines that you will need to keep your game going. The first state machine will deal with the game interface, which includes whether the game is paused, if there are different modes the player can be looking at the world in, then which one, what things the player can and can't see and any other flags you might use for your particular interface.
在你的所有状态里,至少有你需要保证游戏进行下去的两个状态。第一类状态机是处理游戏接口,包含游戏暂停。如果玩家可以在世界里看到不同的模式,这时玩家可以和不可以看到的和其他表示必须在你的接口里。
The second state machine will deal with what is actually going on in the game, the current state of the environment, objects in the level, objectives completed or failed in the mission and all other variables that you use to guide and challenge the player.
第二类状态机要处理游戏要继续下去,也就是环境的当前环境,每层的对象,目标完成或者失败和你用来指导和挑战玩家的其他变量。
You may have an alert system where the enemies will be actively patrolling if the player has been spotted or has fired shots, or flags for whether certain critical pieces have been destroyed or not. All of these items can be contained inside of a structure such as the example one below.
你可能有一个警报系统:如果玩家射击后会有敌人巡逻,或者某个重要地点被摧毁的标志。所以的这些可以被包含在下面的数据结构里。
struct GameLevelState { int alert; // Alert status of enemies // struct Positionshot; // Position of last shot fired // int shotTime; // Game cycle last shot was fired // int hostage; // Hostage rescued // int explosives; // Explosives set or not // int tank; // Tank destroyed // int dialogue; // Dialogue variable // int complete; // Mission completed // };
灵活性
保持AI灵活是极端重要的。做的越是模块化,将来越容易扩展。理解AI是一个非常迭代的过程是重要的,你需要尝试并且构建之。
构建优秀AI的目标是单元在一个看起来真实的环境里很好的交互。单元行动
在游戏AI里玩家控制单元,这对他们是很有意义的。没有这些,单元适应玩家将是困难的。如果一个玩家没有感到他在控制单元并从他们身上得到恰当信息,他就不会继续玩。
剖析101
你可以提供玩家一个简单的数据结构
struct Character { struct Positionpos; // Map position // int screenX, screenY; // Screen position // int animDir, animAction, animNum; // Animation information, action and animation frame number // int rank; // Rank // int health; // Health // int num; // Number of unit // int group; // Group number // int style; // Style of unit (elf, human) // struct AnimationObject animObj; // Animation object for complex animations // };
Now some definitions of the variables:
The pos variable determines the unit's position in the game world and the screenX, screenY variables are useful for easily adding information around the unit on the screen such as health or selection information.
The animDir, animAction and animNum all refer to the units current animated state that will be drawn to the screen.
The rank and health variables are both fairly obvious and are extremely simplified for what information they could hold.
The num variable is the number that the unit is in the main unit array. Later, when calling the unit information from its group it is sometimes useful to pass off which unit it is, without giving the actual structure address.
The group variable determines which group the unit belongs to as a unit should belong to a group at almost all times. The only time a unit should not be in a group is if the unit is dead.
The style and animObj variables are both more information about how the unit's graphics will be drawn.
Building Past Basics
Once you have your initial routines working based on a simple version of your units, such as the above, its time to start building more information into them to really bring them to life.
You will need to think about what kind of actions and reactions you want the units to have. Do you want them to be controlled by emotions? Do you want them to be able to freeze up? Run away? Charge like a madman?
If you do then adding variables to determining emotional states could be a next step. The best way to try to understand what components your units can have is to try and understand yourself and what you would be dealing with in a situation that they are. In order to create human like reactions, you need to base it off of how a human would react.
There is another side to this, almost an opposite of human reaction, which is providing a synthetic experience that is not based on reality, but instead based on challenging the player. Instead of making your units based on your instincts, you will need to think in whatever manner you want your units to react in. The point is that you have to make the decisions about every facet you can or you will end up with flat, boring reactions that are too easy to predict, or even seemingly random. Either of these traits could ruin an otherwise good game, so put a lot of thought into it, and most importantly, play it to death to make sure it works!
Grouping
To group or not to group?
If you are creating a First Person Shooter game then it comes as no big surprise that grouping isn't for you. However, if you are creating a RTS or a game that has the player controlling more than one unit at a time then you have a question to ask yourself.
Do you need your units to act in a coordinated way?
If the answer is yes, then there is a good chance that grouping is for you. If the answer is no there still may be advantages to grouping but you will have to sort those out on your own as they will no doubt be totally dependent on exactly the kind of actions you want your units to perform.
Benefits of Grouping
- Units can move in a formation only accessing one master list of movement information. The advantage here is that you do not have to propagate information to every unit in the group when a destination or target changes as they all get their movement information off of the group source.
- Multi-unit coordinated actions, such as surrounding a building, can be controlled at a central location instead of each unit trying to work out where it is in relation to other units and bumping back and forth until they are in the correct position.
- Groups can maintain their structure so that issuing new orders only takes the same amount of time and data as issuing an order to a single unit. Most important you will create something that can be easily understood and read. Changing around 25 units or so and having them try to pass information to each other can be quite a chore if they don’t have any common ground.
- Depending on the formation of the group, obstacle avoidance and detection can be simplified and time to find paths can be reduced, which can be a serious concern when dealing with a large amount of units.
Organizing your group, just like everything else in creating a game AI, is about understand the final affect you want to gain with control of the units. The idea is to create a place where there is a central repository of information which can be found quickly and shared between units. The idea is also not to duplicate any data, you want data to be found at one source and one source only, and that source needs to be the most logical position for the information so that when you are later working with it and building off of it, other logical extensions will equally seem to be in the correct places.
From my experience I decided that this separation in my work should be split where anything that has to do with the unit as an enclosed entity will be placed in the unit's data structure, while anything that had to do with movement, or actions, since those are what we are trying to organize and share, will be placed in the group data structures.
This means that the units alone will not have any information on where they are going, or what they are doing beyond the physical position they are in, like their animation frame and position in the world. To do this it means that a unit must ALWAYS be in a group as long as they are capable of moving or their actions changing. If they are alone, then they are just a group of one.
One of many
While we are ultimately looking for a group to act as a coordinated system, the system is definitely made up of individual pieces and it's important to keep track of what each unit is doing individually so that when we need the group to break formation and move about as separate entities with common or individual purposes we can. For this goal I created a structure similar to the one below.
struct GroupUnit { int unitNum; // Character Number // struct Unit *unit; // Unit character data // struct Positionwaypoint[50]; // Path in waypoints for units // int action[50]; // Actions by waypoints // int stepX, stepY; // Step for individual units, when in cover mode // int run, walk, sneak, fire, hurt, sprint, crawl; // Actions // int target; // Targets for units // struct Position targetPos; // Target Position // };
Explanations of the variables:
The unitNum is the number of the unit in the group. If there is a maximum of 10 units in a group, then there will be 10 possible slots that could have units. The first unit would be unitNum 0, following to unitNum 9.
The unit is a pointer to the unit's character data, which holds information like the characters current position, health and every other piece of information on the individual. Its important that a unit's vital signs and other information can be monitored from the group so that you can easily check to see if a group member has been wounded and communicate this to the other members, along with a myriad of other possibilities.
The waypoint array contains all the places that the unit has to move in a queue. All actions and waypoints are only specified in the GroupUnit structure if the group is not in a formation and units need to move about on their own.
The action array contains actions that are associated with the movements to waypoints. This allows you to create more detailed command chains, as telling units to sneak across one area and then sprint across another adds a lot of possibilities to making more strategic and thought out movements by the player.
The stepX, stepY information can be used for simple velocity; every frame move this unit this many world-position-units in any direction on the map. Used properly this can be just as applicable for all situations as doing real physics modeling, only with a simpler system and usually reduced processing time (not to mention ease of implementing the first time or quickly).
The run, walk, sneak… variables all deal with different states the unit are in. These are not animations, but action states that can be toggled easily and even have multiple states that effect each other differently when more than one are turned on.
The target and targetPos variables are used to hold the unit number of the enemy being targeted and his current position. The enemies position, as well as health and other attributes, could just be referenced each time by looking up the enemies unit number, but for readability I decided it would be easier to keep a local copy of the enemies position.
Mob mentality
The ultimate goal of course is to have a centralized location for as much of the data as possible to limit the amount of look ups and processing to a minimum and keep things simple. Lets take a look at a sample data structure for doing this.
struct Group { int numUnits; // Units in group // struct GroupUnit unit[4]; // Unit info // int formation; // Formation information for units and group // struct Position destPos; // Destination (for dest. circle) // int destPX, destPY; // Destination Screen Coords // struct Position wayX[50]; // Path in waypoints for group // float formStepX, formStepY; // Formation step values for group movements // int formed; // If true, then find cover and act as individuals, otherwise move in formation // int action, plan; // Group action and plans // int run, walk, sneak, sprint, crawl, sniper; // Actions // struct Position spotPos; // Sniper Coords // int strategyMode; // Group strategy mode // int orders[5]; // Orders for group // int goals[5]; // Goals for group // int leader; // Leader of the group // struct SentryInfo sentry; // Sentry List // struct AIStateaiState; // AI State // };
The numUnits variable refers to the number of units in the group and the unit array holds the GroupUnit information. For this particular group the maximum units has been hard coded to 4.
The formation flag determines what type of formation the group is in. They could be formed in a column, wedge or diamond shape easily by just changing this variable and letting the units reposition themselves appropriately.
The destPos and destPX,destPY are all information for keeping track of the final destination of the group and relaying that information quickly to the player. The waypoints and steps work the same manner as individuals except that when units are in a formation they will all have the same speed so they stay in formation. There is no need to update each unit by its own speed value, as the group's can be used.
The formed variable is one of the most important as it determines whether the units act in formation or as individuals. The concept is that if the group is formed then all the units will have the same operations performed on them each cycle. If there is a reason that they can't all move the same way, such as enemies attacking or a necessary break in formation to get past an obstacle, then the units need to move on their own.
The actions are the same as individual, and you'll notice that there is a variable for a sniper that is not in GroupUnit structure as there is no reason to have one unit in a group be a sniper while the rest are off running around. It is logical to split that unit into its own group and then control the sniper activities at the group level. This is the kind of planning you need to do to figure out what information is best served in what section of your structures.
The strategyMode is a quick variable that determines how the units respond to enemies. Is it aggressive, aggressive with cause, defensive, or run-on-sight? Having an easy to access overview variable that controls basic responses is a good way to cut out a lot of individual unit and group situation calculations. Beyond that it gives the control to the player, who can set different groups in different modes so they know how each group will react if they encounter any enemies.
The orders and goals arrays point to orders and goals described in an order and goal database so that when orders are given they can be assigned to multiple groups easily and each group feeds off the same information.
The sentry and aiState are fairly self explanatory as they contain sentry information and more detailed aiState information for doing detailed pattern matching.
Putting it together
Now that we have some structures for our groups, what's next? The next step is to figure out how you are going to use this information by routines in your game.
It's crucial that you carefully plan for your AI routines to be modular and flexible so that you can add on to them later and easily call different pieces. The concept here, as in data structure organization, is to only do something in one function, and to make that function limited so that it does a specific thing. Then if you need to do that thing again you can call the routine that is already tested and is a known single point of operation on that data. Later if you run into problems with your AI and need to debug it, you don’t have to go hunting all over the place for the offending routine, because there is only one routine that would operate on that data, or at least in that manner.
Tips
Walk before you run. Learn the basics, create the basics, add more advanced routines for your particular game as you need them. Never fall into the trap of using a routine because it is popular and everyone else seems to be using it. Often other people are using a routine because its' benefits fit their needs, but it may not fit yours. Furthermore, people often use routines just because they are the standard, even if they are actually not the best routines for their situation.
Your concern should always be on getting the best results, not having the current fashionable routines; if it works, use it. The game developer mantra used to be, and always should be, "If it looks right, it is right." Don’t let the people who are interested in designing real world total physics simulations make you feel bad for creating a simple positional system where you add X to the position each frame. If that is what works in your particular situation, then do it. Correct physics has its place in some games, but not all of them and there are other ways of achieving nearly the same results.
You can never build a perfect replica of reality. That is just a fact. So you need to draw your own line on where good enough is and then make your good enough reality.