Bresenham整数追逐算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LightInDarkness/article/details/82025588

       近来买了一本讲游戏AI的入门书,在这里记录一下的自己的学习过程。不得不吐槽的是,对于游戏中的算法,相关解释给的很是简略而且不准确(或者说是不对,可能是翻译的锅!),而且游戏中给出的是算法的升级版!更坑爹的是,竟然是升级版的简单改造版!这就让读者的理解成本变高,很不友好啊。

       今天讲讲追逐算法,最基础的方法莫过于下面的代码了:

if(predatorCol > preyCol)
	predatorCol--;
else if(predatorCol < preyCol)
	predatorCol++;
if(predatorRow > preyRow)
	predatorRow--;
else if(predatorRow < preyRow)
	predatorRow++;

       这个方法有个问题,就是追逐着会首先按照对角线行走,然后按照直线的方向行走。这在玩家看来,无疑是很狗血的,这样会显得敌人十分愚蠢,因为我们都知道两点之间线段最短,敌人为什么不直接朝我走来呢?

       基于此,我们有了接下来要讲的算法,Bresenham算法,这里是在砖块环境下的追逐,即追逐者和猎物的坐标都保存在一个整型的二维数组中。值得一提的是,Bresenham算法是计算机图形学领域使用最广泛的直线扫描转换方法。

       这里假设直线的斜率k在0到1之间(如果k > 1, 则采取对应的算法),起点坐标x1,y1,终点坐标x2,y2。横轴间距Δx = x2 - x1 大于 纵轴间距Δy = y2 - y1。因此我们要在这两点这件画一条近似直线的路径,只需要考虑在追逐者“横行”时,需不需要“纵行”即可。假设当前坐标为(xi, yi),那么下一步的坐标可能为(xi + 1, yi)或者(xi + 1, yi + 1)。

       上面橙线为起点到终点的直线,我们目前的问题是当追逐者走到横坐标为xi + 1, 纵坐标为yi 还是 yi + 1呢?Bresenham算法是要比较一下dy1与dy2的大小。如果dy1小,则上面的点距直线更近;反之则下面的点距直线更近。

图片来源于:https://blog.csdn.net/natsu1211/article/details/17004375

       如果按照上面的方法选好了点,也就能够确定了追逐者的路径。总的来说,整个过程是这样的。误差d,由dy1和dy2转换而来,如图所示。只要d >= 0.5, 即选择上方的点;否则选下方点。初值为0,每横向走一步d = d + k(在坐标轴中,直线每次走的横向距离为单位距离1,纵坐标增加k,k = Δy / Δx),当d >= 0.5,则减去1。

这里呢,我们做个小改进,令e = d - 0.5。相应地,e初值为-0.5,每横向走一步e = e + k,当e >= 0,则减去1。这样的好处在于我们只需要判断e的正负即可,而不必与0.5做比较。

但我们如此就满足了吗!不!再审视一下这个式子,斜率用到了除法,相加用到了浮点运算,为了便于硬件运算和提高速度,我们令e' = e * 2 * Δx。那么e'初值为-Δx,每横向走一步e' = e' + 2 * Δy。当e' >= 0,则减去2 * Δx。

下面是我用C++的简单实现,与书中代码略有不同:

#include<iostream>
#include<cmath>

using namespace std;

int main(){

	int map[30][30], pathRow[30], pathCol[30];
	int i, j;
	int col = 20, row = 2;
	int endCol = 4, endRow = 24;
	int nextCol = col, nextRow = row;
	int deltaRow = endRow - row, deltaCol = endCol - col;
	int stepCol, stepRow, currentStep;
	int recordCol, recordRow;
	float d = 0, k, e;
	//初始路径设定
	for(i = 0; i < 30; i++){
		for(j = 0; j < 30; j++){
			map[i][j] = -1;
		}
	}
	for(currentStep = 0; currentStep < 900; currentStep++){
		pathRow[currentStep] = -1;
		pathCol[currentStep] = -1;
	}
	currentStep = 0;
	//路径方向计算
	if(deltaRow < 0) stepRow = -1; else stepRow = 1;
	if(deltaCol < 0) stepCol = -1; else stepCol = 1;
	recordCol = abs(deltaCol);
	recordRow = abs(deltaRow);
	deltaRow = abs(deltaRow * 2);
	deltaCol = abs(deltaCol * 2);
	pathRow[currentStep] = nextRow;
	pathCol[currentStep] = nextCol;
	currentStep++;
	//Bresenham算法
	if(deltaCol > deltaRow){
		e = -0.5;
		k = abs(deltaRow) * 1.0 / abs(deltaCol);
		while(nextCol != endCol){	
			//d = d + k;
			e = e + k;
			if(e > 0){
				nextRow = nextRow + stepRow;
				e -= 1;
			}
			nextCol = nextCol + stepCol;
			pathRow[currentStep] = nextRow;
			pathCol[currentStep] = nextCol;
			currentStep++;
		}
	}else{
		e = -0.5;
		k = abs(deltaCol) * 1.0 / abs(deltaRow);
		while(nextRow != endRow){
			e = e + k;
			if(e > 0){
				nextCol = nextCol + stepCol;
				e -= 1;
			}
			nextRow = nextRow + stepRow;
			pathRow[currentStep] = nextRow;
			pathCol[currentStep] = nextCol;
			currentStep++;
		}
	}
	//更改地图
	//起点为2, 终点为3 
	for(i = 0; i < currentStep; i++){
		if(pathRow[i] == row && pathCol[i] == col)
			map[pathRow[i]][pathCol[i]] = 2;
		else if(pathRow[i] == endRow && pathCol[i] == endCol)
			map[pathRow[i]][pathCol[i]] = 3;
		else map[pathRow[i]][pathCol[i]] = 1;
	} 
	//打印
	for(i = 0; i < 30; i++){
		for(j = 0; j < 30; j++){
			if(map[i][j] == -1) cout << "■";
			else if(map[i][j] == 2) cout << "☆";
			else if(map[i][j] == 3) cout << "★";
			else cout << "□";
		}
		cout << '\n';
	}
}

运行结果如下图:

猜你喜欢

转载自blog.csdn.net/LightInDarkness/article/details/82025588
今日推荐