游戏开发中的人工智能(三):移动模式

2019-06-05 20:56:04

本文内容:许多游戏中经常出现固定模式的移动,比如守卫的巡逻行为,宇宙飞船的降落等。开发者可以将移动模式技术应用于特定行为的程序的编写中。



移动模式

本章主题是移动模式。移动模式是制造智能行为幻觉的简单方式。基本上,计算机控制的角色会根据一些预先定义好的模式移动,使其看起来好像是在执行复杂而绞尽脑汁的策略。

实现移动模式的标准做法是选取想要的模式,再将控制数据填入某个数组或多个数组。控制数据由特定的移动指令组成,比如向前移动再转弯,借此迫使计算机控制的物体或角色按所需模式移动。利用这些算法,你可以建立圆形、方形、蛇形、曲线以及任何类型的模式将之编制成一组精确的移动指令。


标准移动模式算法

标准移动模式算法使用控制指令(编码过的指令清单或数组),指示计算机控制的角色,在每一轮游戏循环中如何移动。每当循环运行一轮时,数组将编入索引值,以便处理下一组移动指令。

例3-1 给出了一组典型的控制指令。


//例3-1:控制指令数据结构

ControlData
{
    double turnRight; 
    double turnLeft;  
    double stepForward;
    double stepBackward;
};123456789

此例中,turnRight 和 turnLeft 中存放的是右转或左转的角度值。如果是在砖块环境中,角色能够前进的方向有限,则turnRight 和 turnLeft 的意义就是向右或者向左转一格。stepForward 和 stepBackward 是向前或向后的距离或者是砖块数。

这个控制结构也可以包含其他指令,比如开火、丢炸弹、放出干扰雷达的金属片、加速、减速、以及其他许多适合你的游戏的行为。

通常,你可以事先定义出控制结构体类型的全局数组或者一组数组,以便储存模式数据。设定这些模式数组初值的数据,可以从数据文件中加载或者直接编写在游戏程序中。这与你的编码风格以及游戏所需的条件有关。

直接在游戏程序中初始化模式数组,如例3-2 所示。


//例3-2:模式的初始化

Pattern[0].turnRight=0;
Pattern[0].turnLeft=0;
Pattern[0].stepForward=2;
Pattern[0].stepBackward=0;

Pattern[1].turnRight=0;
Pattern[1].turnLeft=0;
Pattern[1].stepForward=2;
Pattern[1].stepBackward=0;

Pattern[2].turnRight=10;
Pattern[2].turnLeft=0;
Pattern[2].stepForward=0;
Pattern[2].stepBackward=0;

Pattern[3].turnRight=10;
Pattern[3].turnLeft=0;
Pattern[3].stepForward=0;
Pattern[3].stepBackward=0;

Pattern[4].turnRight=0;
Pattern[4].turnLeft=0;
Pattern[4].stepForward=2;
Pattern[4].stepBackward=0;

Pattern[5].turnRight=0;
Pattern[5].turnLeft=0;
Pattern[5].stepForward=2;
Pattern[5].stepBackward=0;

Pattern[6].turnRight=0;
Pattern[6].turnLeft=10;
Pattern[6].stepForward=0;
Pattern[6].stepBackward=0;
…12345678910111213141516171819202122232425262728293031323334353637

此例中,这个模式会指示计算机控制的角色向前移2个单位的距离,再向前移2个单位的距离,向右转10度,再向右转10度,向前移2个单位的距离,再向前移2个单位的距离,然后再向左移10度。这个特定的模式会让计算机控制的角色,以蛇行模式前进。

为了处理这个模式,必须有一个控制该模式数组的索引值,每当游戏循环运行一次时,就递增一次。并且,在每次循环中,都必须读取并执行该模式数组当前索引值对应的控制指令。例3-3 是这些步骤在程序中的概略写法。


//例3-3:运行模式数组

void GameLoop(void)
{
    …
    Object.orientation += Pattern[CurrentIndex].turnRight;
    Object.orientation -= Pattern[CurrentIndex].turnLeft;
    Objetct.x += Pattern[CurrentIndex].stepForward;
    Object.x -= Pattern[CurrentIndex].stepBackward;

    CurrentIndex++;
    …
}12345678910111213

基本的算法非常简单,操作细节会因游戏结构而有所不同。

编写好几个不同模式,存放在不同数组中也是很常见的做法,然后让计算机随机选取一个模式来使用,或者按照游戏中某些其他决策逻辑来决定。这样的技巧可以强化智能的错觉,让计算机控制的角色行为有更多的变化。


砖块环境中的移动模式

对砖块环境中的移动模式来说,所要采用的方法,与第二章中讨论砖块环境中视线追逐时所用的方法类似。在视线追逐中,我们采用 Bresenham 的直线扫描转换算法,事先算出了起点和终点间的距离。

本章也是用 Bresenham 的线段算法,计算不同的移动模式。如同第二章所讲的,需要将行和列的坐标的位置存储在一组数组内。然后,以不同的模式移动计算机控制的角色(此例为巨人),再走遍这些数组。

本章的路径比只有起点和终点的情况复杂的多。路径将由好几条线段构成。每条新线段的起始处就是前一条线段的终止处。你必须确保最后一条线段的终点,是第一条线段的起点,才能让巨人在周而复始的模式中移动。

可以算出四条线段,完成矩形移动模式。在第二章的视线函数中,每次执行时都会清除坐标的路径数组内容。然而,就此例而言,每条线段只是整个模式的一套线段而已。因此,每次要计算线段时,我们不必对路径数组初始化,只需要把新的线段路径添加到前一条线段路径之后。

此例中,在计算模式之前,我们要先初始化坐标数组,例3-4 是初始化坐标路径数组的函数。


//例3-4:初始化路径数组

void InitializePathArrays(void)
{
    int i;
    for(i=0;i     {
        pathRow[i]=-1;
        pathCol[i]=-1;
    }
}1234567891011

如例3-4 所示,我们把两个数组的每个元素都初始化为-1。我们采用-1是因为-1在砖块环境中不是有效的坐标。在多数砖块环境中,左上角的坐标是(0,0),从该点开始,行和列会递增到整个砖块地图的大小。因此把路径数组中还未用到的元素赋值为-1。

例3-5 是修改后的 Bresenham 视线追踪算法,用于计算线段。


//例3-5:修改后的 Bresenham 视线追踪算法,用于计算线段

void ai_Entity::BuildPathSegment(void)
{
    int i;
    int nextCol=col;
    int nextRow=row;
    int deltaRow=endRow-row;
    int deltaCol=endCol-col;
    int stepCol;
    int stepRow;
    int currentStep;
    int fraction;
    int i;

    for(i=0;i     {
        if((pathRow[i]== -1) && (pathCol[i]== 1))
        {
            currentStep=i;
            break;
        }
    }
    if(deltaRow<0)
        stepRow=-1;
    else
        stepRow=1;
    if(deltaCol<0)
        stepCol=-1;
    else
        stepCol=1;
    deltaRow=abs(deltaRow*2);
    deltaCol=abs(deltaCol*2);

    pathRow[currentStep]=nextRow;
    pathCol[currentStep]=nextCol;
    currentStep++;
    if(currentStep >= kMaxPathLength)
        return;
    if(deltaCol>deltaRow)
    {
        fraction=deltaRow*2-deltaCol;
        while(nextCol != endCol)
        {
            if(fraction >= 0)
            {
                nextRow += stepRow;
                fraction =fraction-deltaCol;
            }
            nextCol=nextCol+stepCol;
            fraction=fraction+deltaRow;
            pathRow[currentStep]=nextRow;
            pathCol[currentStep]=nextCol;
            currentStep++;
            if(currentStep >= kMaxPathLength)
                return;
        }
    }
    else
    {
        fraction=deltaCol*2-deltaRow;
        while(nextRow!=endRow)
        {
            if(fraction>=0)
            {
                nextCol=nextCol+stepCol;
                fraction=fraction-deltaRow;
            }
            nextRow=nextRow+stepRow;
            fraction=fraction+deltaCol;
            pathRow[currentStep]=nextRow;
            pathCol[currentStep]=nextCol;
            currentStep++;
            if(currentStep >= kMaxPathLength)
                return;
       &nbs

  • Copyright© 2015-2021 长亭外链网版权所有
  • QQ客服

    需要添加好友

    扫码访问本站