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