Scripted AI and Scripting Engines
脚本AI与脚本引擎
This chapter discusses some of the techniques you can use to apply a scripting system to the problem of game AI, and the benefits you can reap from doing this. At its most basic level, you can think of scripting as a very simple programming language tailored to a specific task related to the game in enables the game designers rather than the game programmers to write and refine much of the game mechanics. Players also can use scripting to create or modify their own game worlds or levels. Taken a step further, you can use a scripting system in a massively multiplayer online role-playing game (MMORPG) to alter the game behavior while the game is actually being played.
这一章讨论一些如何应用脚本系统到游戏AI问题中去的技术,以及这样做你 能获得哪些好处。在最基础的层次上,你可以认为脚本是一种非常简单的为游戏中特殊的任务描述而定制的编程语言,它可以让游戏设计者而不是游戏程序员编写和 精制大部分的游戏结构。玩家同样可以使用脚本来创建或修改他们自己的游戏世界和等级。进一步说,当游戏实际上正在运行着的时候,你可以在一个MMORPG 中使用脚本来改变游戏的行为。
You can take several approaches when implementing a scripting system. A sophisticated scripting system might interface an already existing scripting language, such as Lua or Python, for example, with the actual game engine. Some games create a proprietary scripting language designed for the needs of the individual game. Although it's sometimes beneficial to use those methods, it's easier to have the game parse standard text files containing the scripting commands. Employing this approach, you can create scripts using any standard text editor. In a real game, the scripts can be read in and parsed when the game first starts, or at some other specified time. For example, scripts that control creatures or events in a dungeon can be read in and parsed when the player actually enters the dungeon area.
在 实现一个脚本系统时,你可以使用几种方法。实际的游戏引擎中一个复杂完善的脚本系统可以使用一种已经存在的脚本语言,例如Lua或者Python。一些游 戏为了特殊需要创建自己的脚本语言。有时候它不仅有利于使用那些方法(?),还可以方便的让游戏解析含有脚本命令的标准文本文件。采用这种方法,你可以使 用任何标准的文本编辑器来创建脚本。在真正的游戏中,这些脚本可以在游戏开始时或者其他特定的时间读取进来并被解析。例如,当玩家进入地牢的区域时控制地 牢中人和事件的脚本被读取并解析。
In the scope of game AI, you can use scripting to alter opponent attributes, behavior, responses, and game events. This chapter looks at all these uses.
在游戏AI中,你可以使用脚本来修改对手的属性、行为、反应以及游戏事件。这一章将着眼于所有这些应用。
Scripting Techniques
脚本技术
The actual scripting language used in a game is ultimately up to the game designers and programmers. It can resemble preexisting languages such as C or C++, or it can take a totally approach; perhaps even a graphical rather than a text-based approach. Deciding how the scripting system looks and works depends primarily on who will be using the scripting system. If your target is the end player, a more natural language or graphical approach might be beneficial. If the system is primarily for the designers and programmers, it might not be beneficial to spend your development time on a complex and time-consuming natural language parsing system. A quick and dirty approach might be better.
一个真正应用于游戏的脚本语言是从根本上服务于游戏设计者和程序员的。它可以类似于先前存在的语言像C和C++,或者完全与之相同;或许甚至是图形上的而不 是文字上的相似。脚本系统看上去像什么和怎样工作从根本上取决于谁将要使用它。如果你的目标是一个最终的玩家,那么一个更类似于自然语言或者图形化的方法 也许不错。如果这个系统是针对于设计者和程序员的,那么浪费你宝贵的开发时间在一个复杂的耗时的(译者认为是指运行效率)自然语言解析系统上是不明智的。 一个快的脏的(?!dirty怎么翻译)方法会好一点。
You also should consider other factors when developing a scripting system. Perhaps you want the script to be easy to read and write for the game designers, but not necessarily for the game player. In this case, you might want to use a form of encryption. You also could develop a script compiler so that the end result is less readable to humans.
在开发一个脚本系统时你还应当考虑其他一些因素。或许你希望对于设计者来说脚本是易于读和写的,而对于玩家却不是必需的。因此,你可能想要使用一种形式的加密。你同样可以开发一个脚本的编译器,那样最终的结果对(旁)人来说就不是那么容易读懂了。
In this chapter we create simple scripting commands and save them in standard text files. We want to avoid the need for a complex language parser, but at the same time we have been careful to choose a vocabulary that makes it relatively easy for humans to read and write the scripts. In other words, we use words that accurately reflect the aspect of the game that the script is altering.
在这一章我们将要创建简单的脚本命令并把它们存储在标准的文本文件里。我们希望避免对一个复杂语言分析器的需求,与此同时我们还要小心地选取一个词汇集来使人们读写脚本相对的简单些。换句话说,我们在脚本中使用正确的词汇来精确地反映游戏的样子。
Scripting Opponent Attributes
脚本控制的对手的属性
It's common and beneficial to specify all the basic attributes of each AI opponent by using some type of scripting. This makes it easy to tweak the AI opponents throughout the development and testing process. If all the vital data were hardcoded into the program, you would have to recompile for even the most basic change.
通过使用一些类型的脚本可以方便的指定每一个AI对手(由AI控制的对手)所有的基本属性。这使得在整个开发和测试过程中调整AI对手(的属性)变得容易。如果所有重要的数据是被硬编码在程序中的,你将不得不重新编译哪怕是最基本的变动。
In general, you can script opponent attributes such as intelligence, speed, strength, courage, and magical ability. In really comes down to the type of game you're developing. Of course, the game engine ultimately will use these attributes whenever a computer-controlled friend or foe interacts with the player. For example, an opponent that has a higher intelligence attribute would be expected to behave differently from one of lower intelligence. Perhaps a more intelligent opponent would use a more sophisticated pathfinding algorithm to track down a player, while a less intelligent opponent might become easily confused when trying to reach the player.
一般而言,你可以把对手的属性诸如智慧、速度、力 量、精神以及不可思议的能力编写进脚本里。但这到底包括哪些就要依你所开发的游戏类型而定了。当然,无论何时,只要玩家与电脑控制的朋友或敌人相遇,游戏 引擎最终都会使用到这些属性。例如,一个有着较高智慧属性的对手与那些较低智慧的对手的预期行为不同。也许,一个有着更高智慧的对手会使用一个更加高级、 完善、狡猾的寻路算法追捕到玩家,而一个低智慧的对手试图接近玩家的方法是那样的简单并且缺乏智慧。
Example 8-1 shows a basic script you can use to set game attributes.
例8-1展示了一个你能用来设置游戏属性的基础的脚本
Example 8-1. Basic script to set attributes
CREATURE=1;
INTELLIGENCE=20;
STRENGTH=75;
SPEED=50;
END
In this example, our script parser has to interpret five commands. The first, CREATURE, indicates which AI opponent is being set. The next three, INTELLIGENCE, STRENGTH, and SPEED, are the actual attributes being set. The final command, END, tells the script parser that we are finished with that creature. Anything that follows comprises a new and separate block of commands.
在这个例子中,我们的脚本解释器需要解释5条命令。第一条,CREATURE指 出哪一个AI对手将要被设置。接下来的三条INTELLIGENCE、STRENGTH、SPEED是实际被设置的属性。最后的一个命令END告诉脚本解 释器对这个AI对手的设置结束了。之后的任何东西都是由新的独立的命令块组成的。
It would be just as easy to include the numbers 1,20,75,50 in a file and thus avoid any need for parsing the script text. That approach works and developers use it frequently, but it does have some disadvantages. First, you lose quite a bit of readability. Second, and most important, your scripting system can increase in complexity to the point where specifying attributes by just including their numerical values in a file becomes impractical. Example 8-2 shows how a script can become more complicated by using a conditional statement.
你可以简单的在文件中包含1、20、75、50这样的数字,从而避免对解析脚本的需求。这 种方法很奏效并且开发者经常使用它,但是它存在一些缺点。首先,你损失了相当的可读性。其次,也是最重要的,你的脚本系统可能增长,复杂到仅仅通过在文件 中包含它们的数值来指定属性变得不切实际的地步。例8-2展示了如何通过使用一个条件语句来使脚本变得复杂。
Example 8-2. Conditional script to set attributes
CREATURE=1;
If (LEVEL<5)
BEGIN
INTELLIGENCE=20;
STRENGTH=75;
SPEED=50;
END
ELSE
BEGIN
INTELLIGENCE=40;
STRENGTH=150;
SPEED=100;
END
As shown in Example 8-2, we now have conditional statements that initialize the creature attributes to different values depending on the current game level.
正如例8-2所示,我们使用条件语句来根据当前的游戏等级来初始化人的属性。
Basic Script Parsing
基本的脚本解析
Now that we've shown what a basic attribute script looks like, we're going to explore how a game reads and parses a script. As an example, we will use a basic script to set some of the attributes for a troll. We will create a text file called Troll Settings.txt. Example 8-3 shows the contents of the troll settings file.
troll--<北欧神话>居住在洞穴或山中的巨人
既 然我们已经知道了基本的属性脚本是什么样子的,下面我们就来研究一个游戏是如何读取和解析脚本的。作为一个例子,我们将使用一个基本的脚本来设置一个巨人 的部分属性。我们将创建一个叫做Troll Settings.txt的文本文件。例8-3展示了巨人设置文件的内容。
Example 8-3. Basic script to set attributes
INTELLIGENCE=20;
STRENGTH=75;
SPEED=50;
Example 8-3 is a simple example that sets only three creature attributes. However, we will set up our code so that we can easily add more attributes with very little change to our script parser. Basically, we are going to set up our parser so that it will search a given file for a specified keyword and then return the value associated with the keyword. Example 8-4 shows how this might look in an actual game.
例 8-3是一个简单的实例,它只设置了人的三个属性。无论如何,我们要建立起我们的代码这样不用对我们的脚本解释器做很大的改动就可以添加更多的属性。最主 要的,我们应建立我们的解释器,它将会在给定的文件中搜索指定的关键词并返回与之关联的值。例8-4展示了这个在真正游戏中的样子。
Example 8-4. Basic script to set attributes
intelligence[kTroll]=fi_GetData("Troll Settings.txt,"INTELLIGENCE");
strength[kTroll]=fi_GetData("Troll Settings.txt,"STRENGTH");
speed[kTroll]=fi_GetData("Troll Settings.txt,"SPEED");
Example 8-4 shows three hypothetical arrays that can store creature attributes. Rather than hardcoding these values into the game, they are loaded from an external script file called Troll Setting.txt. The function fi_GetData traverses the external file until it finds the specified keyword. It then returns the value associated with that keyword. The game designers are free to tweak the creature setting without the need to recompile the program code after each change.
例8-4假设有3个数组来存放人的 属性。相对于把这些数值硬编码到游戏中,它们是从一个叫做Troll Setting.txt的外部脚本文件中加载的。函数fi_GetData遍历整个文件直到找到指定的关键词。然后,返回与之关联的值。这样游戏设计者就 可以轻轻松松地调整人的设定,而不需要在每次改动之后重新编译程序代码。
Now that we have seen how you can use the fi_GetData function to set the attributes for a troll, let's go a step further. Example 8-5 shows how the function accomplishes its task.
既然我们已经知道如何使用fi_GetData函数来设定巨人的属性,那么不妨更进一步。例8-5展示了函数是如何完成它的任务的。
Example 8-5. Reading data from a script
// 代码段,译者修改了2处小错误,你可以直接使用
#define kStringLength 99
int fi_GetData(char filename[kStringLength], char searchFor[kStringLength])
{
FILE *dataStream;
char inStr[kStringLength];
char rinStr[kStringLength];
char value[kStringLength];
long ivalue;
int i;
int j;
dataStream=fopen(filename,"r");
if(dataStream!=NULL)
{
while(!feof(dataStream))
{
if(!fgets(rinStr, kStringLength, dataStream))
{
fclose(dataStream);
return (0);
}
j=0;
strcpy(inStr, "");
for(i=0; i
if(rinStr[i]!=' ')
{
inStr[j]=rinStr[i];
inStr[j+1]='/0';
j++;
}
if(strncmp(searchFor, inStr, strlen(searchFor))==0)
{
j=0;
for(i=strlen(searchFor)+1; i
{
if(inStr[i]==';')
break;
value[j]=inStr[i];
value[j+1]='/0';
j++;
}
// StringToNumber(value, &ivalue);
ivalue=atol(value);
fclose(dataStream);
return((int)ivalue);
}
}
fclose(dataStream);
return (0);
}
return (0);
}
// 代码结束
The function in Example 8-5 begins by accepting two string parameters. The first specifies the name of the script file to be searched and the second is the search term. The function then opens the text file using the specified file name. Once the file is opened, the function begins traversing the script file once text line at a time. Each line is read in as a string.
例8-5中的函数从接受两个字符串变量开始。第一个指出将要搜索的脚本的名字,第二个是要搜索的项。接着函数会以文本方式打开指定的文件。一旦成功打开文件,函数便开始逐行遍历脚本,并把每一行都当作一个字符串读取进来。
Notice that each line is read into the variable rinStr, and then it's copied immediately to inStr, but without the spaces. The spaces are eliminated to make the parsing a bit more foolproof. This prevents our script parser from getting tripped up if the script writer adds one or more spaces before or after the search term or attributes. Once we have a script line stored in a string, sans spaces, we can search for the search term.
注意,读取进来的一行数据是存放在rinStr变量中的,紧接着它会被立即拷贝到inStr中,但是不包括其中 的空格。消除这些空格是为了让解析的过程变得简单一些。这可以预防当脚本的作者在准备搜索的项或属性前后添加了空格时我们的解释程序出错。一旦我们把脚本 行存储在字符串中,并且其中没有空格,我们就可以开始搜索了。
As you recall, we passed our search term to the fi_GetData function by using the string variable searchFor. At this point in the function, we use the C function strncmp to search inStr for the search term.
正如你记得那样,我们通过字符串变量searchFor来把我们要查找的项传递给fi_GetData函数。这里我们使用C语言的strncmp函数在inStr中搜索查找项。
If the search term is not found, the function simply proceeds to read the next text line in the script file . However, if it is found, we enter a new loop that copies into a new string named value the part of inStr that contains the attribute value. The string value is converted to an integer value by calling the outside function StringToNumber. The fi_GetData function then returns the value in ivalue.
如果没有找到查找项的 话,函数就会读取脚本中的下一个文本行。但是,如果找到了,那就会进入一个新的循环来把inStr中包含属性值的那部分拷贝到名字为value的字符串 中。这个字符串将通过外部函数StringToNumber转化成整形数值(存放在ivalue中)。最后fi_GetData函数会返回这个存放在 ivalue中的值。
This function is written in a very generic way. No search terms are hardcoded into the function. It simply searches the given file for a search term and then returns an integer value associated with it. This makes it easy to add new attributes to our program code.
此函数以一种很常见的方式书写。没有查找项是硬编码在函数中的。它仅仅是在给定的文件中搜索查找项并返回与之关联的整数值。这样做将为我们的程序代码增加新的属性变得很简单。
Also, note that this is one area of game development where it is important to check for errors. This is true particularly if you want players as well as game designers to use the scripting system. You should never assume any of the scripts being parsed are valid. For example, you shouldn't rely on the script writers to keep all the numeric values within legal bounds.
同样应注意,这正是在游戏开发中着重检查错误的区域。如果你希望玩家能像游戏设计者一样使用脚本系统的话,这是十分重要的。你绝不能假设所有的脚本都正确的解释。例如,你不能依赖(保证)脚本作者把所有的数值都限定在合法的范围内。
Scripting Opponent Behavior
脚本控制的对手的行为
Directly affecting an opponent's behavior is one of the most common uses of scripting in game AI. Some of the previous examples showed how scripting attributes can have an indirect effect on behavior. This included such examples as modifying a creature's intelligence attribute, which presumably would alter its behavior in the game.
直接影响一个脚本对手的行为是游戏AI中脚本的一个最常见的用途。先前的部分实例说明使用脚本控制属性可以间接地影响到人的行为。最典型的例子就是修改人的智慧属性将会影响到他在游戏中对自己行为的推测。
Scripting behavior enables us to directly manipulate the actions of an AI opponent. For this to be useful, however, we need some way for our script to see into the game world and check for conditions that might alter our AI behavior. To accomplish this we can add predefined global variables to our scripting system. The actual game engine, not our scripting language, will assign the values in these variables. They are used simply as a way for the script to evaluate a particular condition in the game world. We will use these global variables in conditional scripting statements. For example, in our scripting system we might have a global boolean variable called PlayerArmed which will direct a cowardly troll to ambush only unarmed opponents. Example 8-6 shows how such a script might look.
使用脚本控制行为可以让我们能够直接的控制AI对手的动作。这很有用,但是,我们还 需要一些方法让我们的脚本进入游戏世界并检查那些可能引起AI行为改变的条件。为了完成这些我们需要向脚本系统中添加预定义的全局变量。对这些变量的赋值 将会由真正的游戏引擎而不是我们的脚本语言完成。作为脚本估测(计算)游戏世界中一个特殊条件的通道,它们使用简单。我们将会在脚本的条件语句中使用这些 全局变量。例如,在我们的脚本系统中有一个控制着胆怯的巨人只是埋伏徒手的敌人(译者注:也可能指的是玩家)的全局布尔型变量。例8-6展示了这个脚本的样子。
Example 8-6. Basic behavior script
If (PlayerArmed==TRUE)
BEGIN
DoFlee();
END
ELSE
BEGIN
DoAttack();
END
In Example 8-6, the script does not assign the value PlayerArmed. It represents a value within the game engine. The game engine will evaluate the script and link this behavior to the cowardly troll.
在例8-6中,脚本并没有对PlayerArmed赋值。它表示的是一个游戏引擎内部的值。游戏引擎将会计算这个脚本并把这个行为和胆怯的巨人相关联。
In this example, the value PlayerArmed is a simple boolean value that represents nothing more than another boolean value within the game engine. There certainly is nothing wrong with this, but scripting is more useful when you use simple global variables which represent a more complex series of evaluations. For example, in this sample script we checked whether the player was armed. Although that might be useful for an opponent to know, it doesn't necessarily represent how challenging the opponent will be in a fight.
在这个例子中,PlayerArmed是一个简单的布尔值,它和游戏 引擎中其它的布尔值没有什么分别。当然这一点问题都没有,但是当你使用好几个全局变量表示一个复杂的数值的序列时,脚本会更有用。例如,在这个例子中我们 检测玩家是否持有武器。虽然让一个对手知道这些会很有用,但是对表示怎样才能引起打斗这是不必要的(?)。
Many factors could contribute to how challenging a potential opponent will be. We can make our scripting system even more powerful if we evaluate these conditions in the game engine and then make the result available to the script as a single global variable. For example, we could use a Bayesian network to evaluate how tough an opponent the player is and then make the result available in a variable such as PlayerChallenge. The script shown in Example 8-7 is just as simple as the one in Example 8-6, but it can have a much more sophisticated effect on the gameplay.
challenge--困难程度,复杂程度,对手有多么的强大(?)
许 多因素影响一个潜在对手的挑战是多少(?)。如果我们在游戏引擎中计算这些条件并让脚本能像使用一个全局变量那样使用这个结果,我们的脚本系统会更加强 大。例如,我们可以使用贝叶斯网络估算玩家是一个如何强大的对手,然后把结果存放在像PlayerChallenge那样的变量中(这样在脚本中也可以使 用了,嘿嘿)。例8-7中的脚本和例8-6中的一样简单,但它对游戏的运行有更大的影响。
Example 8-7. Behavior script
If (PlayerChallenge==DIFFICULT)
BEGIN
DoFlee();
END
ELSE
BEGIN
DoAttack();
END
In the case of Example 8-7, PlayerChallenge could represent a series of complex evaluations that rank the player. Some of the factors could include whether the player is armed, the type of armor being worn, the current player's health, whether any other players in the area might come to his defense, and so on.
在例8-7中,PlayerChallenge可以表示一系列复杂的用来评定玩家实力的值。这些因素包含玩家是否持有武器,所穿的护甲是否是过时的,玩家当前的健康状况,是否有其他玩家进入他的防御区等等。
Another aspect of behavior that you can script is AI character movement. We can take a concept, such as pattern movement from chapter 3, and implement it in a scripting system. For example, it might be useful for the game designer to establish patrol patterns for AI characters. Chapter 3 showed some examples of hardcoded pattern movement. Of course, hardcoding behavior has many disadvantages. It's much more difficult to tweak a game's design if a recompile is needed after every minor change. Figure 8-1 shows an example of a movement pattern that a game designer can implement using a scripting system.
你能用脚本控制的行为的另一种形式 是AI角色的移动。我们可以提出一种像第3章的模式移动那样的概念并在脚本系统中实现它。例如,它可能对游戏设计者建立游戏角色的巡逻模式很有帮助。在第 3章中展示了一些硬编码的模式移动。当然,硬编码的行为有许多的不利之处。如果对于每一次小的改变都要重新编译的话,那么调整游戏设定就太麻烦了。图 8-1展示了游戏设计者可以用脚本系统实现的模式移动的实例。
Example 8-8 shows how we can construct a script to achieve the desired behavior.
例8-8展示了如何构造一个脚本来完成我们想要的行为。
Figure 8-1. Scripted pattern movement
Example 8-8. Pattern movement script
If (creature.state==kPatrol)
Begin
move(0,1);
move(0,1);
move(0,1);
move(0,1);
move(0,1);
move(-1,0);
move(-1,0);
move(0,-1);
move(0,-1);
move(0,-1);
move(0,-1);
move(0,-1);
move(1,0); // 书上写的是move(0,1),但是只要和图8-1对照一下就会发现,
move(1,0); // 此脚本是控制人物做逆时针的循环移动,所以我修改成了这样
end
Example 8-8 gives a glimpse of finite state machines from Chapter 9. In the scripting example, if the AI creature is in the patrolling state, it uses the specified movement pattern. Each move is shown as a single unit change from the previous position. See Chapter 3 for a detailed explanation of pattern movement techiniques.
例8-8小窥了一下第9章的有限状态机。在此脚本实例中,如果AI人物(角色)正处于巡逻状态,那么它就会根据指定的模式移动。每一次移动都被看作是相对于前一个位置的改变。请参看第3章了解模式移动的相关技术。
Scripting Verbal Interaction
脚本控制的语言交互(不知道怎么翻译好了,大概是指用脚本控制人物的对白吧)
The benefits of scripting go beyond just making an AI opponent more sophisticated and challenging. Many types of games incorporate intelligent behavior in ways that aren't meant to be a direct challenge to the player. A role-playing game, for example, might provide the player with a series of subtle hints meant to move the story along. Scripting is an excellent way to enable the game designer to create a compelling story without the need to alter the actual game program.
使 用脚本的好处远远超过了让一个AI对手更加狡猾更具挑战性。许多类型的游戏在不同的方式上组合智能的行为并没有打算给玩家一个直接的挑战。例如一个RPG 会向玩家提供一系列的线索来推动故事的发展。脚本就是一个不错的方式来让游戏设计者创建一个引人入胜的故事而不需要修改已经完成的游戏程序。
Intelligent behavior can make a game more challenging, but verbal responses that are intelligent and appropriate to the situation can go even farther when creating an immersive environment for the player. Verbal interaction can range from helpful hints from a friendly nonplayer character to taunts from an adversary. Verbal interaction seems most intelligent and immersive when it relates to the current game situation. This means the game AI needs to check a given set of game parameters and then respond to them accordingly.
智能的行为可以让游戏更具挑战性, 但是在为一个玩家创建immersive的环境时让语言响应做的智能且合乎形势需要更多的工作(?)。从一个友善的NPC的有用提示到对手的戏弄嘲笑都属 于语言交互的范畴。当语言交互涉及到当前游戏的状态时它就显得那么的智能和immersive。这就意味着游戏AI需要检查一个给定的游戏参数从而对它们 做出反应。
For example, how a player is armed might be one parameter that can be checked. We can then have an adversarial AI character comment on how ineffective that weapon will be once combat starts. This seems more intelligent and immersive because it's not just a random taunt. It applies to the current game situation. It makes it seem as though the computer-controlled characters are aware of what's happening in the game. A quick example of how this script might look is shown in Example 8-9.
例如,玩家是否持有武器就可以作为一个检测的参数。我们可以有一个AI的对手来评论,如果打起架来那个武器是多 么的没有用。这看起来更智能和immersive,因为它不仅仅是一个随机的嘲讽。它取决于当前的游戏状态。它让这一切看起来就好像这个电脑控制的角色知 道游戏里到底发生了什么一样。例8-9是关于此的一个简单实例。
Example 8-9. Verbal taunt script
If (PlayerArmed==Dagger) // 匕首
Say("What a cute little knife.");
If (PlayerArmed==Bow) // 弓
Say("Drop the bow now and I'll let you live");
If (PlayerArmed==Sword) // 剑
Say("That sword will fit nicely in my collection.");
If (PlayerArmed==BattleAxe) // 战斧
Say("You're too weak to wield that battle axe.");
As Example 8-9 shows, knowing a bit about the current game situation can add an immersive effect to gameplay. This is much more effective than simply adding random general taunts.
如例8-9所示,知道一点游戏当前的状况就可以增加游戏的immersive。这比简单的增加普通的随机嘲讽要有效的多。
So, an important aspect of a scripting system is to enable the script writer to see what's happening inside the game engine. The more game elements the script can see, the better. Figure 8-2 shows a hypothetical game scenario in which an evil giant is chasing the player. In this case, the game AI is able to use unique elements of the game state to supply a taunt appropriate to the situation. In this case, we know that the adversary is a giant, the player is a human, and the player is armed with a staff.
因此,脚本系统的一个重要功能是让脚本作者能够看到游戏中发生了什 么。脚本中能看到的游戏元素越多越好。图8-2展示了一个假想的关卡,里面邪恶的大力士在追赶玩家。既然这样,游戏AI就可以根据游戏状态的唯一元素来提 供适合当前状况的嘲讽。我们知道了对手是一个大力士,玩家是普通的人类并且拿着木棍。
Figure 8-2. Giant taunt
Giant大力士 taunt嘲笑
Example 8-10 shows how the game AI might can be an appropriate taunt during a battle between a computer-controlled giant and a player-controlled human. In a real game you probably would want to add multiple responses for each given situation and then randomly select among them. This would help prevent the responses from becoming repetitive and predictable.
例8-10展示了在电脑控制的大力士和玩家控制的人的战斗中游戏AI如何做出适当的嘲讽。在真正的游戏中你可能会想为每一个给出的情况加入多重响应,然后随机的选择它们。这将有助于预防回应的重复性和可预知性。
Example 8-10. Giant taunt script
If (Creature==Giant)and(player==Human)
Begin
if (PlayerArmed==Staff)
Say("You will need more than a staff, puny human!");
if (PlayerArmed==Sword)
Say("Drop your sword and I might not crush you!");
if (PlayerArmed==Dagger)
Say("Your tiny dagger is no match for my club!");
End
Of course, this type of scripting isn't limited to adversarial characters that are out to kill the players. Benevolent computer-controlled characters can use the same techniques. This can help the script writer create an engaging and immersive plot. Example 8-11 shows how a script helps construct a plot and guide the player actions toward the game goals.
当然,这种形式的脚本并不仅限于想要杀死玩家的敌人。友善的NPC也可以使用这项技术。这将有助于脚本作者完成动人的immersive的情节。例8-11展示了脚本如何构建剧情并引导玩家接近游戏的目标。
Example 8-11. Benevolent AI script
If (Creature==FriendlyWizard)
Begin
if (PlayerHas==RedAmulet)
Say("I see you found the Red Amulet.
Bring it to the stone temple
and you will be rewizard.");
end
As Example 8-11 shows, a vital piece of information concerning where the amulet should be placed won't be revealed to the player until the amulet is found and the player confronts the friendly wizard.
正如例8-11展示的,如果玩家没有得到护身符或者没有见到友善的向导时一条重要的信息护身符应该放在哪里是不会被透漏给玩家的。
The previous script examples show how game AI can respond in a given situation, but it's also sometimes necessary for game characters to have some type of verbal interaction with the player. This could be benevolent characters meant to provide the player with helpful information, or perhaps a less-than-honest character meant to intentionally mislead the player.
先前的脚本实例展示过游戏AI如何给定的状态做出反应,但有时候对于游戏角色来说有必要与玩家进行某种类型的语言交互。这可能是友善的NPC向玩家提供有用的信息,或者是一个不诚实的角色有意地误导玩家。
In this type of scenario, the player needs some mechanism to input text into the game. The game engine then makes the text strings available to the script system, which analyzes the text and provides an appropriate response. Figure 8-3 shows how this might appear in an actual game.
在这种类型的关卡中,玩家需要某种机制来向游戏输入信息。然后游戏引擎让脚本系统可以使用这些文本,以便于分析文本并对此做出适当的响应。图8-3展示了这在真正游戏中的样子。
In the case of Figure 8-3, the player would type in the text "What is your name?" and the scripting system would return the text "I am Merlin." Example 8-12 shows a basic script that you could use to accomplish this.
在图8-3的情况中,玩家输入“你的名字是什么?”然后脚本系统会返回“我叫梅林。”例8-12展示了一个可以完成这个任务的基本的脚本。
Example 8-12. Basic "What is your name?" script
If Ask("What is your name?")
Begin
Say("I am Merlin.");
End
Figure 8-3. Merlin
Of course, Example 8-12 does have one serious flaw. It works only when the player types in the exact text of the question as it appears in the script. In reality, you can form a question in many way. For example, what happens if the player enters one of the lines of text shown in Example 8-13?
当然,例8-12存在严重的缺陷。只有当玩家键入了准确的问题,跟脚本中出现的一样的时候,他才能工作。实际上,你可以以许多方式提出这个问题。例如,如果玩家键入了例8-13中的一个会发生什么呢?
Example 8-13. Example player input
What's your name?
Whats your name? // 能这么缩写?
What is your name.
What is thy name?
What is your name, Wizard?
Hello, what is your name?
As you can see, the script in Example 8-12 would fail for all the questions shown in Example 8-13, even though it's quite obvious what's being asked. Not only can you ask a question in many ways, but we also have to consider the possibility that the player might not form the question in a correct manner. In fact, you can see that one of the example questions ends in a period rather than a question mark. We could have strict requirements for the player-entered text, but it would have the effect of removing the player from the immersive effect of the game whenever he made the inevitable minor error.
正如你所看到的, 例8-12中的脚本对例8-13中的问题一点作用也不起,即使很明显是在问什么。因为你可以用很多方式问同一个问题,所以我们不得不考虑玩家可能组织一个 意义上正确的问题。事实上,你可能注意到其中一个例句是以句号而不是问号结束的。我们可以严格的要求玩家输入的文本,但是一旦他犯了不可避免的小错误,这 就会减弱游戏给玩家带来的immersive的效果。
One alternative to checking each literal text string is to create a language parser to decipher each sentence to determine exactly what is being asded. For some games a sophisticated language parser might be appropriate; however, for most games there is a simpler approach. As you saw in Example 8-13, you can form the same question in many ways, but if you'll notice, they all have something in common. They all contain the words "what" and "name". So, instead of checking for each literal text string, we can simply search for and respond to particular keywords. In this case, the scripting engine simply checks for the presence of given keywords within a text string.
一种选择是检测每一个文本串也就是创建一个语言解释器分析每一个句子来确定到底在问什么。对 一些游戏来说可能一个复杂的语言解释器是合适的,但是对于大多数的游戏来说有一个简单的方法。正如你在例8-13中看到的,组织问题的方式可能不同,但是 你是否注意到,它们有一些相同之处。它们都包括单词"what"和"name"。因此,我们可以简单的搜索并响应特定的关键词来代替检测每一个文本串。既 然这样,脚本系统就可以简单的检测文本串中是否有关键词出现。
As Example 8-14 shows, the script is checking for the presence of two keywords in the player-entered text. Using this approach, the script responds correctly to every question in Example 8-13.
如例8-14所示,脚本检测玩家输入的文本中是否有这2个关键词出现。使用这个方法,脚本就可以正确的响应例8-13中的所有问题。
Example 8-14. Keyword scripting
If (Ask("What") and Ask("name"))
Begin
Say("I am Merlin.");
End
Now that we've shown you how to write a typical script to check player input for a give set of keywords, let's look at how the actual game engine checks player input for a given keyword. Example 8-15 shows how to do this.
既然我们已经向你展示了如何编写在一个典型的检测玩家的输入是否存在给定的关键词集,让我们看看真正的游戏引擎是如何检测玩家输入中的关键词的。例8-15展示了这怎么实现。
Example 8-15. Searching for keywords
// 代码段,这段代码写的可谓是相当的别扭
Boolean FoundKeyword(char inputText[kStringLength], char searchFor[kStringLength])
{
char inStr[kStringLength];
char searchStr[kStringLength];
int i;
for(i=0;i<=strlen(inputText);i++)
{
inStr[i]=inputText[i];
if(((int)inStr[i]>65) && ((int)inStr[i]<=90))
inStr[i]=(char)((int)inStr[i]+32);
}
for(i=0;i<=strlen(searchFor);i++)
{
searchStr[i]=searchFor[i];
if(((int)searchStr[i]>=65) && ((int)searchStr[i]<=90))
searchStr[i]=(char)((int)searchStr[i]+32);
}
if(strstr(inStr,searchStr)!=NULL)
return (true);
return (false);
}
// 代码结束
Example 8-15 shows the actual code in the game engine that is invoked whenever the "Ask" function is called from the game designer's script. This function takes two parameters: inputText, which is the line of text the player entered, and searchFor, which is the keyword we want to search for. The first thing we do in this function is to convert both strings to all lowercase. Like many programming languages, C and C++ are case-sensitive. A string containing the text "Name" is not equal to a string containing the text "name".(不知道为什么书上总是写成"name.")We can't rely on the player always using capitalization consistently or properly. The simplest solution is to convert all strings to lowercase. That way, it doesn't matter if the player enters all uppercase, all lowercase, or some combination.
例8-15展示了游戏引擎中的真正代码,无论何时游戏设计者的脚本调用了Ask(),也就是间接调用 了引擎中的代码(?)。这个函数take两个变量:inputText,这是玩家输入的文本行,和searchFor,这是我们想要搜索的关键词。在这个 函数中我们做的第一件事是将两个字符串转化成小写字母。就像许多编程语言一样,C和C++是大小些敏感的。字符串"Name"和"name"是不等同的。 我们不能指望玩家总是以大写字母开头。最简单的解决办法就是把所有的字符串都转化成小写字母。这样,无论玩家输入的全是大写、全是小写还是大小写兼有都没 有问题了。
Once we have two lowercase strings, we call the C function strstr to compare the text strings. The strstr function searches inStr for the first occurrence of searchStr. If searchStr is not found in inStr, a null pointer is returned.
一旦有了2个小写的字符串,我们就可以调用C语言的strstr函数来比较文本串了。strstr函数在inStr中搜索searchStr出现的第一个位置。如果没有找到,则返回一个空指针。
Scripting Events
脚本事件
Now let's examine some of the other ways scripting can make gameplay more immersive. The previous sections showed how scripts alter the behavior of AI characters. Scripting behavior goes a long way toward making games and AI characters seem more real. However, you can use scripting to make games more entertaining and realistic in other ways as well. In this section we examine how scripts trigger in-game events that might not be related directly to AI characters. For example, perhaps stepping on a particular location will trigger a trap. Example 8-16 shows how this might look in a text-based scripting language.
现在让我们来分析一下脚本的 其它形式来使游戏运行的更immersive。前一部分展示了脚本如何控制AI角色的行为。为了让游戏和AI角色看起来更真实脚本行为帮助很大。但是,在 其他方面脚本同样可以让游戏更有趣更真实。在这一部分,我们研究如何用脚本控制与AI角色直接相关的游戏事件的触发器。例如,走上一个特殊的位置也许将会 触发一个陷阱。例8-16展示了这在基于文本的脚本语言中是个什么样子。
Example 8-16. Trap event script
例8-16 陷阱脚本事件
If (PlayerLocation(120,76))
Trigger(kExposionTrap);
If (PlayerLocation(56,16)
Trigger(kPoisonTrap);
As Example 8-16 shows, the scripting system can compare the player position to some predetermined value and then trigger a trap if they are equal. Of course, you can make this much more sophisticated by making the triggering mechanism more complex. Perhaps the trap is triggered only if the player is holding a certain item or wearing a particular piece of armor.
如例8-16所示,脚本系统会拿玩家的位置和预先设定的值比较,当它们相等的时候陷阱就被触发了。当然,你也可以通过创建更复杂的触发机制来让这变得更复杂些。也许只有当玩家持有某一物品或者穿了一件特殊的护甲时陷阱才被触发。
Scripting also can be an effective way to add a sense of ambience to gameplay. For example, you can link certain situations or objects to particular sound effects. If the player walks on a dock, a seagull sound effect might be triggered. You could use an entire scripting file solely for linking sound effects to different situations.
脚本也可以是增加游戏气氛的有效途径。例如,你可以将某些情况或对象和特殊的音效相关联。当玩家走上码头时,海鸥的声音被触发。你可以一整个脚本文件来把音效和不同的情况连接。
Figure 8-4 shows the player standing in a doorway. This would be an excellent situation to link to a creaking-door sound effect.
图8-4展示了玩家站在门口的画面。这是放置门吱吱作响的音效的极好位置。
Figure 8-4. Door Sound script
Example 8-17 shows how the player location or game situation, such as the game time, can trigger relevant sound effects.
例8-17展示了如何通过玩家的位置或是游戏状态(像游戏的时间)来触发相关的音效。
Example 8-17. Triggered sound script
If (PlayerLocation(kDoorway))
PlaySound(kCreakingDoorSnd);
If (PlayerLocation(kDock))
PlaySound(kSeagullSnd);
If (PlayerLocation(kBoat))
PlaySound(kWavesSnd);
If (GameTime==kNight) // 晚上放蟋蟀的声音
PlaySound(kCricketsSnd);
If (GameTime==kDay) // 白天放鸟叫
PlaySound(kBirdsSnd);
Although this chapter differentiated between the types of AI scripting, in a real game it can be beneficial to use them together. For example, instead of a player action triggering an effect such as a sound effect, perhaps it triggers a specific creature AI patrolling pattern. We also showed examples of how AI creatures can respond to text entered by the player; however, this also can be a very useful way to trigger in-game events. For example, the player could recite a spell that triggers some event.
虽然本章区分了AI脚本的不同类型,但是在真正的游戏中结合使用它们会很有好处。例如,用玩家的动作触发一个特殊AI人物的 巡逻模式来代替触发一个音效。我们同样展示了AI人物如何来响应玩家键入的文字,这同样会是触发游戏事件的有效途径。例如,玩家可以背出一段符咒来触发什 么事件。
Further Information
更多信息
In this chapter we showed you how to implement basic scripting that enables you to alter game AI outside the main game program. Such scripting can be very effective. Indeed, we successfully implemented these techniques in an MMORPG to enable game masters to change the game AI and other game parameters in real time. Implementing a full-fledged scripting engine can be very challenging, and it involves additional concepts that we have not yet covered. These concepts include finite state machines and rule-based systems, which we'll get to in Chapters 9 and 11 of this book.
这一章向你介绍了如何实现基本的脚本系统来响应游戏主程序外的AI。这样的脚本非常奏效。Indeed,我们已经成功地将这些 技术应用于MMORPG中从而使游戏管理员能够实时地改变游戏AI及其他的游戏参数。实现一个功能完善的脚本系统是十分具有挑战性的,它还包含许多我们没 有设计的概念。这包括有限状态机和规则集系统,你可以在本书的第9和11章获得这些知识。
If you decide to pursue scripting even further than we do in this book, you might find the following resources to be particularly helpful:
如果你决定继续研究本书所没有涉及的脚本知识,下面的资料可能对你有很大的帮助:
AI Application Programming by M.Tim Jones(Charles River Media)
应用程序AI编程 M.Tim Jones著(Charles River Media)
AI Game Programming Wisdom by Steve Rabin, ed.(Charles River Media)
游戏AI编程知识 Steve Rabin等著(Charles River Media)
In the first reference, author Tim Jones shows how to implement a scripted rule-based system from scratch. His approach combines we covered in this chapter and those we will cover in Chapter 11. The second reference includes seven articles written by game programming veterans focusing specifically on issues related to implementing scripting engines for games.
在第一本参考书中,作者Tim Jones展示了如何实现一个规则集脚本系统from scratch。他的方法包括了这一章和11章中涉及的内容。第二本参考书包括7篇由游戏编程老手写的文章,这些文章特别的聚焦于已经发布的涉及实现游戏脚本引擎(?)
后记:这篇译文来自第 8章,翻译的感觉是大部分段落简单易懂,部分语句需要推敲。至于知识,译者认为,平平无奇,没能学到什么知识。开始还想从中得到什么脚本分析之类的知识, 现在看来我还是老老实实地去学习编译原理解释原理什么的吧。由于目前对UI的研究可能用到有限状态机的知识,或许会对第9章进行翻译,如果能找到别的资料 就不翻译了,对这本书没什么好感了。
这本书用处最大莫过于目录了,是个不错的参考,但是你自己找相关资料学习比较好。目录列表如下:
Preface
1. Introduction to Game AI
Deterministic Versus Nondeterministic AI
Established Game AI
The Future of Game AI
2. Chasing and Evading
Basic Chasing and Evading
Line-of-Sight Chasing
Line-of-Sight Chasing in Tiled Environments
Intercepting
3. Pattern Movement
Standard Algorithm
Pattern Movement in Tiled Environments
Pattern Movement in Physically Simulated Environments
4. Flocking
Classic Flocking
Flocking Example
Obstacle Avoidance
Follow the Leader
5. Potential Function-Based Movement
How Can You Use Potential Functions for Game AI?
Chasing/Evading
Obstacle Avoidance
Swarming
Optimization Suggestions
6. Basic Pathfinding and Waypoints
Basic Pathfinding
Breadcrumb Pathfinding
Path Following
Wall Tracing
Waypoint Navigation
7. A* Pathfinding
Defining the Search Area
Starting the Search
Scoring
Finding a Dead End
Terrain Cost
Influence Mapping
Further Information
8. Scripted AI and Scripting Engines
Scripting Techniques
Scripting Opponent Attribures
Basic Script Parsing
Scripting Opponent Behavior
Scripting Verbal Interaction
Scripting Events
Further Information
9. Finite State Machines
Basic State Machine Model
Finite State Machine Design
Ant Example
Further Information
10. Fuzzy Logic
How Can You Use Fuzzy Logic in Games?
Fuzzy Logic Basics
Control Example
Threat Assessment Example
11. Rule-Based AI
Rule-Based System Basics
Fighting Game Strike Prediction
Further Information
12. Basic Probability
How Do You Use Probability in Games?
What is Probability?
Probability Rules
Conditional Probability
13. Decisions Under Uncertainty--Bayesian Techniques
What is a Bayesian Network?
Trapped?
Treasure?
By Air or Land
Kung Fu Fighting
Further Information
14. Neural Networks
Dissecting Neural Networks
Training
Neural Network Source Code
Chasing and Evading with Brains
Further Information
15. Genetic Algorithms
Evolutionary Process
Evolving Plant Life
Genetics in Game Development
Further Information
Appendix: Vector Operations
Index