花玻璃图案大全:脚本AI与脚本引擎(上)

来源:百度文库 编辑:九乡新闻网 时间:2024/05/03 19:49:56

脚本AI与脚本引擎(上)

(2007-08-10 12:03:00)转载 标签:

杂谈

CHAPTER 8 Scripted AI and Scripting Engines
脚本AI与脚本引擎(译者:leexuany)

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章了解模式移动的相关技术