[OpenGL]游戏中的动态追踪算法

        我们应该很熟悉游戏里这样的画面:

        1.npc在地图上漫无目的的游走

        2.玩家操控主角靠近npc

        3.npc感受到玩家的存在,开始追着玩家跑

        4.玩家操控主角离开npc一定范围,或者遇到障碍物后,npc放弃追踪玩家


        以上行为就是追踪问题。它是游戏编程人工智能的重要一部分,这和传统的人工智能领域可能存在一定的差异,因为我们的npc只要"表现得"足够智能就可以了,在追踪问题中,这种智能表现为:npc会追着玩家的方向前进。

       为了保证游戏的可玩性,我们必须保证npc可以追上玩家(可能性),同样的,我们也必须保证npc不一定追的上玩家(必然性)。

       在这里,我们主要实现了2,3两个部分。

       在以下的样例中,包括了一个头文件test.h,四个源文件:


        collision.cpp(判断碰撞检测)

        sprite.cpp(精灵类)

        texture.cpp(加载纹理)

        main.cpp(入口,追踪函数)


朴素的算法

  
         如果没有相关的算法背景,我们会如何思考这个问题呢?
         一个非常直接的想法就是,如果主角在npc右边,那么npc就往右边走,如果主角在npc左边,那么就往左边走……以此类推。
        当然,我们一次只能选择一个方向,所以我们可以做一个简单的推广:

        如果y /  x > C(C是常数),那么我们优先选择y方向。
        如果x  / y > C(C是常数),那么我们优先选择x方向。
        否则,我们随机选择一个方向。(连续调用下表现为折线走)
     
        注意到这是一个在线的算法,玩家的位置是不确定的,所以,我们总是选择当前最有可能接近玩家的一个方向。这个算法非常快,而且表现也非常好:

        (图片跳动比较快是因为截图软件帧率比较低,实际会好很多)


chase.cpp

#include<math.h>
#include<stdlib.h>
#include"test.h"
void chase(sprite* s,sprite* npc)
{
	//计算x,y距离差
	float _x = fabs(npc->pos_x - s->pos_x);
	float _y = fabs(npc->pos_y - s->pos_y);
	if (!(_x<0.1f && _y<0.1f)) {
		
		int r;
		//y比x更大,优先走y方向
		if (_y / _x > 3)r = 1;
		//x比y更大,优先走x方向
		else if (_x / _y > 3)r = 0;
		//比较接近时走随机方向(效果是折线前进)
		else r = rand() % 2;

		//根据目标方向选择前进方向
		if (r == 0) {
			if (s->pos_x > npc->pos_x) {
				npc->moveRight();
			}
			else if (s->pos_x <= npc->pos_x) {
				npc->moveLeft();
			}
		}
		else {
			if (s->pos_y > npc->pos_y) {
				npc->moveBack();
			}
			else if (s->pos_y <= npc->pos_y) {
				npc->moveFront();
			}
		}
	}

	return;
}


         当然,这个算法有着比较严重的问题。

         我们一开始考虑的场景是一片空地,但是,实际中的场景往往是有障碍物的,当障碍物出现的时候,这个算法的表现就变成了这个样子:



        当遇到了障碍物后,npc仍然会朝着那个方向前进,而没有意识到自己可以转变方向,在这一点上,我们的人工智能表现得比较“蠢”。当然,我们完全可以把它设计的这么“蠢”,但是,如果我们希望它变得“聪明”一点儿呢?

宽度优先搜索

          我们可能会想,我们可不可以在遇到障碍物或者即将遇到障碍物时,让npc意识到这一点,然后改变方向呢?如果你深究于这个细节,你会发现你很难控制npc总是找到比较好的路,因为当前方有障碍物的时候,我们究竟是该选择后方,左方还是右方呢?我们并不知道哪个才会是最好的。

         之所以出现这样的困扰,是因为我们解决问题时,考虑的方面太少了。我们总是去思考下一步应该怎样走,而没有去思考,完整的路线可以是怎样的。当我们知道了完整的路线后,下一步是什么也就迎刃而解了。

         提到路线,我们很容易想到经典的寻路算法:bfs和dfs,它们可以保证在连通的情况下找到一条从起点到终点的路,当然,我们只需要用到它的第一步。那么,在这个问题中 ,它是否适用呢?我们来回顾搜索算法的步骤,它实际上是漫无目的地向四方搜索的,而我们只使用了第一步,相当于我们是直接让npc随机走了一个方向,这实在是太糟糕了,甚至连最初的朴素算法都不如。


A*算法


       既然问题出在行走方向是随机的,那么我们是否可以引导npc更趋向于选择某个方向呢。当然这是可行的,如果我们把第一种算法的思想和第二种结合起来的话,我们可以得到比较好的结果。

       在具体的编程中,我们取出一个点时,检查它邻接点,计算哪个方向是最好的(可以使用哈曼顿距离衡量)。

       在这里仅作简单介绍,未实现编程。  


最短路径算法


        如果我们非常追求精确性,那么,就可以考虑最短路径算法了。事实上最短路径算法也是在bfs的基础上的,和A*比起来,会尽可能搜索更多的方向。
        如下图,我们可以看到,使用最短路径算法的追踪效果已经非常好了:
        
        

         在这里,精灵的行走并不是严格意义上的按照网格方向的,比如从坐标点(3,4)移动到(3,5),再移动到(5,6)。所以在这里我们在地图中抽象出了网格,然后我们计算精灵的中心的x,y坐标位置(它们实际上是浮点数)处在哪个网格中。

         对于网格里的每一格,我们把它抽象为图中的一个点,设这一格的坐标为i,j,每一行有x个网格,我们用x*i+j来标识每一个点。

         那么,这个点的四个邻接点就是x * i + j - 1   ,  x * i + j + 1  ,  ( x + 1 ) * i + j  ,  ( x - 1 ) * i + j。

        由于npc的行走的步长小于网格的单位长度,所以在最终执行的时候,会产生这样一个问题:npc的中心落在网格的 i , j 坐标,但精灵事实上分布在i , j 和i, j + 1两个网格中,算法已经给出了i , j情况下正确的道路,但是因为npc身体有一部分在i, j + 1网格中,它无法通过这部分一网格的碰撞检测。所以,依然会出现撞墙走的现象。

        当我们的算法检测到npc与墙壁发生了碰撞后,比如是和前方的墙发生了碰撞,我们可以让精灵左右走动一下,避开墙壁,然后再继续按照算法给出的方向前进。


         如果我们给地图加上更多障碍物,npc依然能够做出非常“聪明”的选择。在检测到另一个方向可以更快的接近主角后,它甚至会选择掉头走。

        


代码


        出于电脑性能的考虑,有以下两个参数可以修改,一个是sprite.cpp中的void sprite::drawSprite(),修改step可以改变精灵摆动身体的速度。另一个是main中drawScene函数中的count,可以改变npc移动的速度(一般来说,需要调小很多)。此外init中精灵的构造函数最后一个参数可以修改精灵的速度,可以尝试改变你和npc的速度,来看看追踪的效果有何差异。

test.h
#pragma once

#define GLUT_DISABLE_ATEXIT_HACK      
#include "GL/GLUT.H"    
class sprite;
void loadTex(int i, char *filename, GLuint* texture);//一般纹理    
void loadTex(int i, char *filename, GLuint* texture, unsigned char* backgroundColor);//透明纹理    
void collisionTest(sprite* s, float size, int(*map)[20], float(*place_x)[20], float(*place_y)[20], const int x, const int y);
inline bool isBarrier(int map);
class sprite
{
private:
	//bool isColl;
	//帧动画参数    
	int num;//一共多少帧    
	int col;//一行有多少帧    

				 //精灵索引下标  
				 //前、左、右、后  
	int index[4][3][2];

	//步长  
	float step;

	//用于计数  
	int count;
	int count3;

	//精灵贴图  
	GLuint texture;

	//是否停止  
	bool isStop = true;

	//快速索引绘制精灵  
	void drawRect(GLuint texture, int i, int j);

public:
	//绘制精灵  
	void drawSprite();

	//用于计数 
	int count2;

	//是否碰撞
	bool isColl = false;

	//行走方向(枚举量)  
	typedef enum { left, right, front, back }direction;

	//精灵位置(中心位置)  
	float pos_x;
	float pos_y;

	sprite(int _col, int _num, float x, float y, GLuint _texture, int* index, float _step);
	void moveLeft();
	void moveRight();
	void moveFront();
	void moveBack();
	//行走方向  
	direction dir;
};

sprite.cpp
#include"test.h"
#include<stdio.h>
sprite::sprite(int _col, int _num, float x, float y, GLuint _texture, int* _index, float _step)
{
	count = count2 = count3 = 0;
	col = _col;
	num = _num;
	pos_x = x;
	pos_y = y;
	texture = _texture;
	dir = front;
	int cnt = 0;
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 3; j++) {
			for (int k = 0; k < 2; k++) {
				index[i][j][k] = _index[cnt++];
			}
		}
	}
	step = _step;
}
void sprite::drawRect(GLuint texture, int i, int j)
{
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, texture);  //选择纹理texture[status]         

	const GLfloat x1 = -0.5, x2 = 0.5;
	const GLfloat y1 = -0.5, y2 = 0.5;
	const GLfloat x = 1.0 / col, y = 1.0 / (num / col);
	const GLfloat point[4][2] = { { x1,y1 },{ x2,y1 },{ x2,y2 },{ x1,y2 } };
	const GLfloat dir[4][2] = { { j*x,1 - (i + 1)*y },{ (j + 1)*x,1 - (i + 1)*y },{ (j + 1)*x ,1 - i*y },{ j*x,1 - i*y } };
	glBegin(GL_QUADS);

	for (int k = 0; k < 4; k++) {
		glTexCoord2fv(dir[k]);
		glVertex2fv(point[k]);
	}
	glEnd();

	glDisable(GL_TEXTURE_2D);
}

void sprite::drawSprite()
{
	const int step = 800;
	count++;

	if (isStop) {
		if (dir == front) {
			drawRect(texture, index[0][1][0], index[0][1][1]);
		}
		else if (dir == back) {
			drawRect(texture, index[3][1][0], index[3][1][1]);
		}
		else if (dir == left) {
			drawRect(texture, index[1][1][0], index[1][1][1]);
		}
		else if (dir == right) {
			drawRect(texture, index[2][1][0], index[2][1][1]);
		}
	}
	else if (dir == front) {
		if (count <= step) {
			drawRect(texture, index[0][0][0], index[0][0][1]);
		}
		else if (count > step&&count <= step * 2) {
			drawRect(texture, index[0][1][0], index[0][1][1]);
		}
		else if (count > step * 2 && count <= step * 3) {
			drawRect(texture, index[0][2][0], index[0][2][1]);
		}
	}
	else if (dir == back) {
		if (count <= step) {
			drawRect(texture, index[3][0][0], index[3][0][1]);
		}
		else if (count > step && count <= step * 2) {
			drawRect(texture, index[3][1][0], index[3][1][1]);
		}
		else if (count > step * 2 && count <= step * 3) {
			drawRect(texture, index[3][2][0], index[3][2][1]);
		}
	}
	else if (dir == left) {
		if (count <= step) {
			drawRect(texture, index[1][0][0], index[1][0][1]);
		}
		else if (count > step && count <= step * 2) {
			drawRect(texture, index[1][1][0], index[1][1][1]);
		}
		else if (count > step * 2 && count <= step * 3) {
			drawRect(texture, index[1][2][0], index[1][2][1]);
		}
	}
	else if (dir == right) {
		if (count <= step) {
			drawRect(texture, index[2][0][0], index[2][0][1]);
		}
		else if (count > step && count <= step * 2) {
			drawRect(texture, index[2][1][0], index[2][1][1]);
		}
		else if (count > step * 2 && count <= step * 3) {
			drawRect(texture, index[2][2][0], index[2][2][1]);
		}
	}
	if (count%step == 0) {
		if (count2 == count3) {
			if (dir == front) {
				drawRect(texture, index[0][1][0], index[0][1][1]);
			}
			else if (dir == back) {
				drawRect(texture, index[3][1][0], index[3][1][1]);
			}
			else if (dir == left) {
				drawRect(texture, index[1][1][0], index[1][1][1]);
			}
			else if (dir == right) {
				drawRect(texture, index[2][1][0], index[2][1][1]);
			}
			isStop = true;

		}
		count3 = count2;
	}
	if (count == step * 3) {
		count = 0;
	}
}
void sprite::moveFront()
{
	dir = front;
	isStop = false;
	pos_y -= step;
	if (pos_y < -3.8f)pos_y = -3.9f;
}

void sprite::moveBack()
{
	dir = back;
	isStop = false;
	pos_y += step;
	if (pos_y > 3.8f)pos_y = 3.8f;
}

void sprite::moveLeft()
{
	dir = left;
	isStop = false;
	pos_x -= step;
	if (pos_x < -3.8f)pos_x = -3.8f;
}

void sprite::moveRight()
{
	dir = right;
	isStop = false;
	pos_x +=step;
	if (pos_x > 3.8f)pos_x = 3.8f;
}


collision.cpp
#include"test.h"

//判断是否是障碍物  
inline bool isBarrier(int map)
{
	//定义纹理索引编号为0的不是障碍物,该数字可以自己指定  
	if (map != 0) {
		return true;
	}
	else return false;
}

//碰撞检测  
bool test(int i, int j, sprite* s, int (*map)[20],float size, float (*place_x)[20], float (*place_y)[20],const int x,const int y)
{
	if (s->dir == sprite::left&&j >= 1) {
		
		if (isBarrier(map[i][j - 1])) { //遇到障碍物  
			if (s->pos_x - place_x[i][j - 1] < size) { //产生碰撞  
				s->pos_x = place_x[i][j - 1] + size;
				return true;
			}
		}
	}
	
	else if (s->dir == sprite::right&&j <= y - 2) {
		if (isBarrier(map[i][j + 1])) {
			if (place_x[i][j + 1]- s->pos_x < size) {
				s->pos_x = place_x[i][j + 1] - size;
				return true;
			}
		}
	}
	else if (s->dir == sprite::front&&i >= 1) {
		if (isBarrier(map[i - 1][j])) {
			if (s->pos_y - place_y[i - 1][j] < size) {
				s->pos_y = place_y[i - 1][j] + size;
				return true;
			}
		}
	}
	else if (s->dir == sprite::back&&i <= x - 2) {
		if (isBarrier(map[i + 1][j])) {
			if (s->pos_y - place_y[i + 1][j] >-size) {
				s->pos_y = place_y[i + 1][j] - size;
				return true;
			}
		}

	}
	return false;
}

//碰撞检测入口
void collisionTest(sprite* s, float size, int(*map)[20], float(*place_x)[20], float(*place_y)[20], const int x, const int y)
{
	int i1, j1, i2, j2;
	s->isColl = false;
	if (s->dir == s->right || s->dir == s->left) {
		//计算得到当前人物位置索引  
		j1 = (s->pos_x + 4.0f) / size;
		i1 = (s->pos_y + 4.0f) / size;
		//执行2次碰撞检测  
		if (test(i1, j1, s, map, size, place_x, place_y, x, y)) {
			s->isColl = true;
		}
		i2 = (s->pos_y + 4.0f + size / 4) / size;
		if (i2 != i1) {
			if (test(i2, j1, s, map, size, place_x, place_y, x, y)) {
				s->isColl = true;
			}
		}
		i2 = (s->pos_y + 4.0f - size / 4) / size;
		if (i2 != i1) {
			if (test(i2, j1, s, map, size, place_x, place_y, x, y)) {
				s->isColl = true;
			}
		}
	}
	if (s->dir == s->front || s->dir == s->back) {
		//计算得到当前人物位置索引  
		j1 = (s->pos_x + 4.0f) / size;
		i1 = (s->pos_y + 4.0f) / size;
		if (test(i1, j1, s, map, size, place_x, place_y, x, y)) {
			s->isColl = true;
		}
		//执行2次碰撞检测  
		j2 = (s->pos_x + 4.0f + size / 4) / size;
		if (j2 != j1) {
			if (test(i1, j2, s, map, size, place_x, place_y, x, y)) {
				s->isColl = true;
			}
		}
		j2 = (s->pos_x + 4.0f - size / 4) / size;
		if (j2 != j1) {
			if (test(i1, j2, s, map, size, place_x, place_y, x, y)) {
				s->isColl = true;
			}
		}
	}
}



texture.cpp
#define _CRT_SECURE_NO_WARNINGS      
#include<stdio.h>      
#include<windows.h>      
#include"test.h"      
#define BITMAP_ID 0x4D42       


//读纹理图片        
static unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
{

	FILE *filePtr;    // 文件指针        
	BITMAPFILEHEADER bitmapFileHeader;    // bitmap文件头        
	unsigned char    *bitmapImage;        // bitmap图像数据        
	int    imageIdx = 0;        // 图像位置索引        
	unsigned char    tempRGB;    // 交换变量        

								 // 以“二进制+读”模式打开文件filename         
	filePtr = fopen(filename, "rb");
	if (filePtr == NULL) {
		printf("file not open\n");
		return NULL;
	}
	// 读入bitmap文件图        
	fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
	// 验证是否为bitmap文件        
	if (bitmapFileHeader.bfType != BITMAP_ID) {
		fprintf(stderr, "Error in LoadBitmapFile: the file is not a bitmap file\n");
		return NULL;
	}
	// 读入bitmap信息头        
	fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
	// 将文件指针移至bitmap数据        
	fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);
	// 为装载图像数据创建足够的内存        
	bitmapImage = new unsigned char[bitmapInfoHeader->biSizeImage];
	// 验证内存是否创建成功        
	if (!bitmapImage) {
		fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
		return NULL;
	}

	// 读入bitmap图像数据        
	fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);
	// 确认读入成功        
	if (bitmapImage == NULL) {
		fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
		return NULL;
	}
	//由于bitmap中保存的格式是BGR,下面交换R和B的值,得到RGB格式    

	for (imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3) {
		tempRGB = bitmapImage[imageIdx];
		bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
		bitmapImage[imageIdx + 2] = tempRGB;
	}
	// 关闭bitmap图像文件       
	fclose(filePtr);
	return bitmapImage;
}

//读纹理图片        
static unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader, unsigned char* backgroundColor)
{

	FILE *filePtr;    // 文件指针        
	BITMAPFILEHEADER bitmapFileHeader;    // bitmap文件头        
	unsigned char    *bitmapImage;        // bitmap图像数据        
	int    imageIdx = 0;        // 图像位置索引        

								// 以“二进制+读”模式打开文件filename         
	filePtr = fopen(filename, "rb");
	if (filePtr == NULL) {
		printf("file not open\n");
		return NULL;
	}
	// 读入bitmap文件图        
	fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
	// 验证是否为bitmap文件        
	if (bitmapFileHeader.bfType != BITMAP_ID) {
		fprintf(stderr, "Error in LoadBitmapFile: the file is not a bitmap file\n");
		return NULL;
	}
	// 读入bitmap信息头        
	fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
	// 将文件指针移至bitmap数据        
	fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);
	// 为装载图像数据创建足够的内存        
	bitmapImage = new unsigned char[bitmapInfoHeader->biSizeImage];
	// 验证内存是否创建成功        
	if (!bitmapImage) {
		fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
		return NULL;
	}

	// 读入bitmap图像数据        
	fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);
	// 确认读入成功        
	if (bitmapImage == NULL) {
		fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
		return NULL;
	}
	unsigned char*   bitmapData;   // 纹理数据     

	bitmapData = new unsigned char[bitmapInfoHeader->biSizeImage / 3 * 4];

	int count = 0;
	//添加alpha通道    
	for (imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3) {
		bitmapData[count] = bitmapImage[imageIdx + 2];
		bitmapData[count + 1] = bitmapImage[imageIdx + 1];
		bitmapData[count + 2] = bitmapImage[imageIdx];
		if (bitmapData[count] >= backgroundColor[0]
			&& bitmapData[count + 1] >= backgroundColor[1]
			&& bitmapData[count + 2] >= backgroundColor[2]) {
			bitmapData[count + 3] = 0;
		}
		else bitmapData[count + 3] = 255;
		count += 4;
	}

	// 关闭bitmap图像文件       
	fclose(filePtr);
	return bitmapData;
}

//加载纹理的函数        
void loadTex(int i, char *filename, GLuint* texture)
{

	BITMAPINFOHEADER bitmapInfoHeader;                                 // bitmap信息头        
	unsigned char*   bitmapData;                                       // 纹理数据        

	bitmapData = LoadBitmapFile(filename, &bitmapInfoHeader);

	glBindTexture(GL_TEXTURE_2D, texture[i]);
	// 指定当前纹理的放大/缩小过滤方式        
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D,
		0,         //mipmap层次(通常为,表示最上层)         
		GL_RGB,    //我们希望该纹理有红、绿、蓝数据        
		bitmapInfoHeader.biWidth, //纹理宽带,必须是n,若有边框+2         
		bitmapInfoHeader.biHeight, //纹理高度,必须是n,若有边框+2         
		0, //边框(0=无边框, 1=有边框)         
		GL_RGB,    //bitmap数据的格式        
		GL_UNSIGNED_BYTE, //每个颜色数据的类型        
		bitmapData);    //bitmap数据指针        

}

//加载纹理的函数        
void loadTex(int i, char *filename, GLuint* texture, unsigned char* backgroundColor)
{

	BITMAPINFOHEADER bitmapInfoHeader;                                 // bitmap信息头        
	unsigned char*   bitmapData;                                       // 纹理数据        

	bitmapData = LoadBitmapFile(filename, &bitmapInfoHeader, backgroundColor);

	glBindTexture(GL_TEXTURE_2D, texture[i]);
	// 指定当前纹理的放大/缩小过滤方式        
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D,
		0,         //mipmap层次(通常为,表示最上层)         
		GL_RGBA,    //我们希望该纹理有红、绿、蓝、alpha数据        
		bitmapInfoHeader.biWidth, //纹理宽带,必须是n,若有边框+2         
		bitmapInfoHeader.biHeight, //纹理高度,必须是n,若有边框+2         
		0, //边框(0=无边框, 1=有边框)         
		GL_RGBA,    //bitmap数据的格式        
		GL_UNSIGNED_BYTE, //每个颜色数据的类型        
		bitmapData);    //bitmap数据指针        

}

main.cpp
#define _CRT_SECURE_NO_WARNINGS      

#include <stdio.h>      
#include <string.h>      
#include<time.h>    
#include <stdlib.h>    
#include"test.h"      
#include <math.h>     
#include<queue>
using namespace std;
#define size 0.4f
const float radius = 1.5f;
const float radius2 = 2.5f;
GLuint texture[3];
//视区      
float whRatio;
int wHeight = 0;
int wWidth = 0;
bool flag = false;
//视点      
float center[] = { 0, 0, 0 };
float eye[] = { 0, 0, 5 };
const int x = 20, y = 20;
//每个网格的坐标  
float place_x[x][y];
float place_y[x][y];

int map[x][y] = {
	0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,
	0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,1,1,0,0,0,
	0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
	0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,
	0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,1,1,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,
	0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,
	0,0,1,1,1,1,1,1,1,0,0,0,1,0,0,0,0,1,0,0,
	0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,
	0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,
	0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,
	0,0,1,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,
	0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,
	0,0,1,1,1,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,
	0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,
};
/*
int map[x][y] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};
*/
sprite *s;
sprite *npc;

inline float distance(float x1,float y1,float x2,float y2)
{
	return sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));
}

int path[x*y];


void ShortestDist();
void move()
{
	int j = (s->pos_x + 4.0f) / size;
	int i = (s->pos_y + 4.0f) / size;
	int k = x*i + j;
	int t = 0;
	while (path[k] != -1) {
		t = k;
		k = path[k];
	}
	if (t == 0) {
		npc->moveFront();
	}
	else if (t / x < k/x) {
		npc->moveFront();
	}
	else if (t / x > k/x) {
		npc->moveBack();
	}
	else if (t%x < k%x) {
		npc->moveLeft();
	}
	else if (t%x > k%x) {
		npc->moveRight();
	}
	collisionTest(npc, size, map, place_x, place_y, x, y);
	if (npc->isColl) {
		if (npc->dir == sprite::left||npc->dir==sprite::right) {
			int r = rand() % 2;
			if (r == 0)npc->moveFront();
			else npc->moveBack();
		}
		else if (npc->dir == sprite::front || npc->dir == sprite::back) {
			int r = rand() % 2;
			if (r == 0)npc->moveLeft();
			else npc->moveRight();
		}
	}
}

void ShortestDist()
{
	queue<int>q;
	int i;
	int dist[x*y];
	memset(dist,-1,sizeof(dist));
	memset(path, -1, sizeof(path));
	//计算得到当前人物位置索引  
	int j1 = (npc->pos_x + 4.0f) / size;
	int i1 = (npc->pos_y + 4.0f) / size;
	int j2 = (s->pos_x + 4.0f) / size;
	int i2 = (s->pos_y + 4.0f) / size;

	dist[x*i1+j1] = 0;
	q.push(x*i1 + j1);
	int count = 0;
	while (!q.empty()) {	
		int v = q.front();
		q.pop();
		if ((v + 1) % x&&dist[v + 1] == -1&& !isBarrier(map[(v + 1) / x][(v + 1) % x])) {
			dist[v + 1] = dist[v] + 1;
			path[v + 1] = v;
			q.push(v+1);
			if (v + 1 == x*i2 + j2)break;
		}
		if (v % x&&dist[v - 1] == -1 && !isBarrier(map[(v - 1) / x][(v - 1) % x])) {
			dist[v - 1] = dist[v] + 1;
			path[v - 1] = v;
			q.push(v - 1);
			if (v - 1 == x*i2 + j2)break;
		}
		if ((v + x)<x*y&&dist[v + x] == -1&&!isBarrier(map[(v + x) / x][(v + x) % x])) {
			dist[v + x] = dist[v] + 1;
			path[v + x] = v;
			q.push(v + x);
			if (v + x == x*i2 + j2)break;
		}
		if ((v - x) >0&&dist[v - x] == -1 && !isBarrier(map[(v - x) / x][(v - x) % x])) {
			dist[v - x] = dist[v] + 1;
			path[v - x] = v;
			q.push(v - x);
			if (v-x == x*i2 + j2)break;
		}
		
	}
	move();
}


void chase()
{
	float _x = fabs(npc->pos_x - s->pos_x);
	float _y = fabs(npc->pos_y - s->pos_y);
	if (!(_x<0.1f && _y<0.1f)) {
		//计算得到当前人物位置索引  
		int r;
		if (_y / _x > 3)r = 1;
		else if (_x / _y > 3)r = 0;
		else r = rand() % 2;

		if (r == 0) {
			if (s->pos_x > npc->pos_x) {
				npc->moveRight();
			}
			else if (s->pos_x <= npc->pos_x) {
				npc->moveLeft();
			}
		}
		else {
			if (s->pos_y > npc->pos_y) {
				npc->moveBack();
			}
			else if (s->pos_y <= npc->pos_y) {
				npc->moveFront();
			}
		}
		collisionTest(npc, size, map, place_x, place_y, x, y);
	}

	return;
}
void drawRect(GLuint texture)
{
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, texture);  //选择纹理texture[status]       

	const GLfloat x1 = -0.5, x2 = 0.5;
	const GLfloat y1 = -0.5, y2 = 0.5;
	const GLfloat point[4][2] = { { x1,y1 },{ x2,y1 },{ x2,y2 },{ x1,y2 } };
	int dir[4][2] = { { 0,0 },{ 1,0 },{ 1,1 },{ 0,1 } };
	glBegin(GL_QUADS);

	for (int i = 0; i < 4; i++) {
		glTexCoord2iv(dir[i]);
		glVertex2fv(point[i]);
	}
	glEnd();

	glDisable(GL_TEXTURE_2D);
}
void drawScene()
{
	//绘制地图块  
	glPushMatrix();
	glTranslatef(-3.8f, -3.8f, 0.0f);
	for (int i = 0; i < x; i++) {
		for (int j = 0; j < y; j++) {
			glPushMatrix();
			glScalef(size, size, 1);
			if (map[i][j] != -1)drawRect(texture[map[i][j]]);
			glPopMatrix();
			glTranslatef(size, 0, 0);
		}
		glTranslatef(-y*size, size, 0);
	}
	glPopMatrix();


	static int count = 0;
	count++;

	glPushMatrix();
	glTranslatef(s->pos_x, s->pos_y, 2);
	glScalef(size, size, 1);
	s->drawSprite();
	glPopMatrix();

	glPushMatrix();
	glTranslatef(npc->pos_x, npc->pos_y, 2);
	glScalef(size, size, 1);
	npc->drawSprite();
	glPopMatrix();

	if (distance(s->pos_x,  s->pos_y, npc->pos_x, npc->pos_y)<radius) {
		flag = true;
	}
	if (flag&&count >= 400) {
		//chase();
		ShortestDist();
		count = 0;
	}
}

void updateView(int height, int width)
{
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);//设置矩阵模式为投影           
	glLoadIdentity();   //初始化矩阵为单位矩阵              
	whRatio = (GLfloat)width / (GLfloat)height;  //设置显示比例         
	glOrtho(-4, 4, -4, 4, -100, 100); //正投影    
	glMatrixMode(GL_MODELVIEW);  //设置矩阵模式为模型        
}

void key(unsigned char k, int _x, int _y)
{
	s->count2++;
	switch (k)
	{
	case 'a': {//向左移动  
		s->moveLeft();
		collisionTest(s, size, map, place_x, place_y, x, y);
		break;
	}
	case 'd': {//向右移动  
		s->moveRight();
		collisionTest(s, size, map, place_x, place_y, x, y);
		break;
	}
	case 'w': {//向上移动  
		s->moveBack();
	    collisionTest(s, size, map, place_x, place_y, x, y);
		break;
	}
	case 's': {//向下移动  
		s->moveFront();
		collisionTest(s, size, map, place_x, place_y, x, y);
		break;
	}
	}
	updateView(wHeight, wWidth); //更新视角      
}

void reshape(int width, int height)
{
	if (height == 0)      //如果高度为0          
	{
		height = 1;   //让高度为1(避免出现分母为0的现象)          
	}

	wHeight = height;
	wWidth = width;

	updateView(wHeight, wWidth); //更新视角          
}


void idle()
{
	glutPostRedisplay();
}

void init()
{
	srand(unsigned(time(NULL)));
	glEnable(GL_DEPTH_TEST);//开启深度测试       
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0.5);
	unsigned char color[3] = { 255,255,255 };
	glGenTextures(3, texture);
	loadTex(2, "1.bmp", texture, color);
	int index1[] = { 0,3,0,4,0,5,1,3,1,4,1,5,2,3,2,4,2,5,3,3,3,4,3,5 };
	s = new sprite(12, 96, -2.0f, -2.5f, texture[2], index1, 0.15f);
	int index2[] = { 0,0,0,1,0,2,1,0,1,1,1,2,2,0,2,1,2,2,3,0,3,1,3,2 };
	npc = new sprite(12, 96, 0.0f, 0.0f, texture[2], index2, 0.10f);
	loadTex(0, "3.bmp", texture, color);
	loadTex(1, "4.bmp", texture, color);
	for (int i = 0; i < x; i++) {
		for (int j = 0; j < y; j++) {
			place_x[i][j] = -3.8f + size*j;
			place_y[i][j] = -3.8f + size*i;
		}
	}
}

void redraw()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除颜色和深度缓存        
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();   //初始化矩阵为单位矩阵          
	gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], 0, 1, 0);                // 场景(0,0,0)的视点中心 (0,5,50),Y轴向上      
	glPolygonMode(GL_FRONT, GL_FILL);
	drawScene();//绘制场景       
	glutSwapBuffers();//交换缓冲区      
}

int main(int argc, char *argv[])
{

	glutInit(&argc, argv);//对glut的初始化             
	glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
	//初始化显示模式:RGB颜色模型,深度测试,双缓冲               
	glutInitWindowSize(600, 600);//设置窗口大小             
	int windowHandle = glutCreateWindow("Simple GLUT App");//设置窗口标题               
	glutDisplayFunc(redraw); //注册绘制回调函数             
	glutReshapeFunc(reshape);   //注册重绘回调函数             
	glutKeyboardFunc(key); //注册按键回调函数  
	glutIdleFunc(idle);//注册全局回调函数:空闲时调用           

	init();
	glutMainLoop();  // glut事件处理循环           
	return 0;
}

图片资源(存为bmp):
1.bmp

3.bmp

4.bmp


  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值