[OpenGL]游戏中的动态追踪算法
2016-08-12 03:59
701 查看
我们应该很熟悉游戏里这样的画面:
以上行为就是追踪问题。它是游戏编程人工智能的重要一部分,这和传统的人工智能领域可能存在一定的差异,因为我们的npc只要"表现得"足够智能就可以了,在追踪问题中,这种智能表现为:npc会追着玩家的方向前进。
为了保证游戏的可玩性,我们必须保证npc可以追上玩家(可能性),同样的,我们也必须保证npc不一定追的上玩家(必然性)。
在这里,我们主要实现了2,3两个部分。
在以下的样例中,包括了一个头文件test.h,四个源文件:
如果没有相关的算法背景,我们会如何思考这个问题呢?
一个非常直接的想法就是,如果主角在npc右边,那么npc就往右边走,如果主角在npc左边,那么就往左边走……以此类推。
当然,我们一次只能选择一个方向,所以我们可以做一个简单的推广:
如果y / x > C(C是常数),那么我们优先选择y方向。
如果x / y > C(C是常数),那么我们优先选择x方向。
否则,我们随机选择一个方向。(连续调用下表现为折线走)
注意到这是一个在线的算法,玩家的位置是不确定的,所以,我们总是选择当前最有可能接近玩家的一个方向。这个算法非常快,而且表现也非常好:
(图片跳动比较快是因为截图软件帧率比较低,实际会好很多)
chase.cpp
当然,这个算法有着比较严重的问题。
我们一开始考虑的场景是一片空地,但是,实际中的场景往往是有障碍物的,当障碍物出现的时候,这个算法的表现就变成了这个样子:
当遇到了障碍物后,npc仍然会朝着那个方向前进,而没有意识到自己可以转变方向,在这一点上,我们的人工智能表现得比较“蠢”。当然,我们完全可以把它设计的这么“蠢”,但是,如果我们希望它变得“聪明”一点儿呢?
之所以出现这样的困扰,是因为我们解决问题时,考虑的方面太少了。我们总是去思考下一步应该怎样走,而没有去思考,完整的路线可以是怎样的。当我们知道了完整的路线后,下一步是什么也就迎刃而解了。
提到路线,我们很容易想到经典的寻路算法:bfs和dfs,它们可以保证在连通的情况下找到一条从起点到终点的路,当然,我们只需要用到它的第一步。那么,在这个问题中 ,它是否适用呢?我们来回顾搜索算法的步骤,它实际上是漫无目的地向四方搜索的,而我们只使用了第一步,相当于我们是直接让npc随机走了一个方向,这实在是太糟糕了,甚至连最初的朴素算法都不如。
在具体的编程中,我们取出一个点时,检查它邻接点,计算哪个方向是最好的(可以使用哈曼顿距离衡量)。
在这里仅作简单介绍,未实现编程。
如下图,我们可以看到,使用最短路径算法的追踪效果已经非常好了:
在这里,精灵的行走并不是严格意义上的按照网格方向的,比如从坐标点(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依然能够做出非常“聪明”的选择。在检测到另一个方向可以更快的接近主角后,它甚至会选择掉头走。
test.h
sprite.cpp
collision.cpp
texture.cpp
main.cpp
图片资源(存为bmp):
1.bmp
3.bmp
4.bmp
1.npc在地图上漫无目的的游走 2.玩家操控主角靠近npc 3.npc感受到玩家的存在,开始追着玩家跑 4.玩家操控主角离开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
相关文章推荐
- 深入浅出游戏算法(1)-配置opengl、glut在codeblocks和vs2012(2)
- 游戏常用算法1-视线追踪算法
- 动态规划;多边形游戏;类似圈型石头合并;算法设计分析作业;
- 游戏的navmesh 与rvo动态避障算法(1)
- 深入浅出游戏算法(1)-配置opengl、glut在codeblocks和vs2012(3)
- 深入浅出游戏算法(1)-配置opengl、glut在codeblocks和vs2012(1)
- 精华游戏算法整理
- 数独游戏的生成算法
- 精华游戏算法整理
- 关于二十四点游戏的编程思路与基本算法
- 内存动态分配的首先适应、最优适应、最坏适应算法的实现(java 版)
- 学VC 编游戏》重点算法解疑·前言
- 百钱买百鸡算法的扩展,动态for循环构造
- 手机游戏动态论坛------高校讲座
- 正在考虑时下正流行的“数读”游戏的算法
- 游戏封包的加密与解密算法的破解
- 那位高手请指教小弟,网球游戏算法?????
- 双向路径追踪算法
- 学VC 编游戏 重点算法解疑系列文档 - 走四方
- 一个模仿三维的赛车游戏的算法,白话讲解--------