[OpenGL] 二维游戏:网格布局与碰撞检测

        二维游戏中的经典布局方式莫过于网格布局。在网格布局的基础上,还可以实现基于网格的碰撞检测,算法实现简单,且效果较好。碰撞检测的精度主要取决于网格的大小。

        (图片素材来源:rpg maker)

       网格与图层

        所谓网格布局,就是用一个二维数组来存储地图每个位置存放的图片索引,用此数组来编辑地图,然后绘制。比如在这里,我们用-1表示不绘制,0表示浅色草地,1表示深色草地,2表示土块,3表示小路,4表示水面,5表示森林……而在绘制函数中,直接使用二维循环,指定偏移量,就能完成地图的快速绘制。

        除了这些基本的地图,我们还有一些比较大的场景物件,比如房子、箱子、树木、桶等等,它们无法绘制在一个小地图块中,而是分布在多个地图块中。在这里,我们并不需要把图片分割成块,只需要直接绘制就可以了,但是我们需要清楚,这张图片占据了哪些网格,我们要在地图索引中把这些块单独标记出来。

        比如说,我们可以用-1(不绘制)进行标记,如果图片是含透明通道的,那么我们也可以采用原来的地图图片作为背景,但是一定要用一个新的索引编号,比如在这个代码中,使用12作为浅色草地的索引编号,代表这个网格已经绘制了物体。因为我们需要通过这个编号来告诉碰撞检测的函数,这个网格有障碍物,是不能走过去的。


              


        我们同时还需要实现物体的遮挡关系效果,比如如上图,我们希望人走在房子前面时,人遮挡住房子;而人走到房子后面时,房子遮挡住人。在这里我们可以引入图层的概念,当然,OpenGL中是没有图层这一说的,所以我们实际上是通过z轴坐标来管理。

        我们把地图背景放置在z = 1的位置,把人物放置在z = 2的位置。在实现遮挡关系的时候,如果希望人挡住物体,物体的z值就设为1,反之设为3。

        同时,我们也注意到,就在上图,这种遮挡关系不是一定的,也就是说,我们有时希望A遮挡B,有时又希望B遮挡住A,这时候只要用一个小小的技巧就能解决这个问题,我们根据人物的位置来判断这个时候谁应该遮挡谁,加一个条件判断语句,来绘制不同位置的物体即可。

       

        总而言之,我们在OpenGL的三维框架下实现二维场景是一件非常轻松愉快的事情,因为我们可以利用多出来的一维,发挥自己的想象力,实现很多障眼法,然而,二维的纸片人永远不知道你动了什么手脚,它看到的仅仅是你搭建出来世界的一个投影而已。


        碰撞检测

        现在,我们可以来考虑碰撞检测了。我们记录了当前精灵的位置以及每个网格的坐标。在网格布局下,碰撞检测可以非常高效,因为我们根本不需要遍历所有的网格来检查,我们也不用像四叉树一样还需要通过四分搜索来找到可能发生碰撞的物体,根据当前方向和精灵位置,我们可以在常数时间内计算出精灵所在网格。当然,这个常数的大小和精灵有关,在这里,我们的精灵大小只占一个网格,所以问题变得更简单,我们的精灵最多只可能与两个网格发生碰撞,我们首先判断可能发生碰撞的网格是不是我们定义的障碍物,如果是,我们再判断它们在当前方向上的距离是否小于网格长度,如果小于,那么就认为发生碰撞,直接弹回即可。

        具体代码是这样的,非常清晰:

//...

i = (s->pos_x + 4.0f) / size;
j = (s->pos_y + 4.0f) / size;

//...

bool test(int i,int j)
{
	if (s->dir == s->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 == s->right&&j <= y-2) {
		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 == s->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 == s->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;
}


        当然,这个是实际的运算。但是我们也发现,上图的精灵实际上与房子碰撞了——人的头部和房子已经有重叠部分了,但是对于人的视觉而言,这是正常的,因为精灵在房子之前,它们有前后关系,但是我们的算法并没有这么智能,它不知道所谓的前后关系,那么,我们的算法是否需要改进呢?

        其实也不用,我们只要把房子的位置稍微下移一点就可以了!房子在视觉上下移了,但是网格却还在原来的位置,我们检测的是和网格的碰撞,却不是和房子的碰撞。在图中也就是检测与白线的碰撞。

      



test.h

#pragma once  
#define GLUT_DISABLE_ATEXIT_HACK    
#include "GL/GLUT.H"    
void loadTex(int i, char *filename, GLuint* texture);//一般纹理  
void loadTex(int i, char *filename, GLuint* texture, unsigned char* backgroundColor);//透明纹理  

sprite.h

#pragma once     

class sprite
{
public:
	//精灵位置(中心位置)  
	float pos_x;
	float pos_y;

	//帧动画参数    
	int num = 96;//一共多少帧    
	int col = 12;//一行有多少帧    

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

	//步长  
	float step;

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

	//精灵贴图  
	unsigned int texture;

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

	//是否停止  
	bool isStop = true;

	//行走方向  
	direction dir = front;

	sprite(int _col, int _num, float x, float y, unsigned int _texture, int* index, float _step);

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

	//绘制精灵  
	void drawSprite();
};

sprite.cpp

#include"sprite.h"  
#define GLUT_DISABLE_ATEXIT_HACK    
#include "GL/GLUT.H"   
sprite::sprite(int _col, int _num, float x, float y, unsigned int _texture, int* _index, float _step)
{
	count = count2 = count3 = 0;
	col = _col;
	num = _num;
	pos_x = x;
	pos_y = y;
	texture = _texture;
	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(unsigned int 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 = 50;
	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;
	}
}

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"sprite.h"
#define size 0.2f

//视区    
float whRatio;
int wHeight = 0;
int wWidth = 0;

//网格个数
const int x = 40;
const int y = 40;

GLuint texture[23];

//视窗大小
float size_x = 4.0f;
float size_y = 4.0f;

//视点    
float center[] = { 0, 0, 0 };
float eye[] = { 0, 0, 5 };

//每个网格的坐标
float place_x[x][y];
float place_y[x][y];

//精灵
sprite *s;

int map[x][y] = { 0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,
                  0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,-1,-1,0,0,0,0,0,0,0,0,0,0,0,8,
                  0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,1,5,5,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,1,1,1,1,1,0,0,5,5,5,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,
                  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,0,3,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,
	              0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,5,5,5,5,0,3,0,0,12,12,12,0,0,0,0,5,-1,-1,-1,-1,-1,-1,5,0,
	              0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,5,5,5,5,0,3,0,0,0,12,0,0,12,12,0,0,-1,-1,-1,-1,-1,-1,5,5,
	              3,3,3,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,5,5,0,3,0,0,0,0,0,12,12,12,12,0,-1,-1,-1,-1,-1,-1,5,5,
	              0,0,3,0,0,0,0,1,4,4,4,4,4,1,1,0,0,0,0,0,0,3,0,0,0,0,0,12,12,12,12,0,-1,-1,-1,-1,-1,-1,5,5,
	              0,0,3,0,0,0,0,1,4,4,4,4,4,1,1,0,0,0,0,0,0,3,0,0,0,0,0,12,12,12,12,0,-1,-1,-1,-1,-1,-1,0,0,
	              0,0,3,0,0,0,0,1,4,4,4,4,4,4,1,0,0,0,0,0,0,3,0,0,0,0,0,0,12,12,12,12,12,12,12,12,12,12,12,0,
	              0,0,3,3,3,0,0,1,4,4,4,4,4,4,1,0,0,0,0,0,0,3,0,0,0,0,0,0,0,12,12,12,12,12,0,12,12,12,12,0,
	              0,0,0,0,3,0,0,1,4,4,4,4,4,4,1,0,0,0,3,3,3,3,3,3,0,0,0,0,0,12,12,12,12,12,0,12,12,12,0,0,
                  0,0,0,0,3,0,0,1,7,4,4,4,4,4,1,0,0,0,3,0,0,0,0,3,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,
                  0,0,0,0,3,0,0,1,7,4,4,4,4,1,0,0,0,0,3,0,0,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,
                  2,2,2,0,3,0,0,0,0,1,1,1,1,0,0,0,0,0,3,0,0,-1,-1,-1,-1,-1,-1,0,0,0,12,0,0,0,3,3,3,3,3,3,
                  2,2,2,0,3,0,0,0,0,0,1,0,0,0,0,0,0,0,3,0,0,-1,-1,-1,-1,-1,-1,0,12,12,12,0,0,0,3,0,0,0,0,0,
                  2,2,2,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,3,0,0,0,0,0,
                  0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1,1,
                  0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,4,4,
	              0,0,0,0,0,0,0,0,0,0,1,1,3,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,4,4,
	              0,0,0,0,0,0,1,1,1,1,1,1,3,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,3,0,0,0,0,0,3,0,0,4,4,4,
	              0,0,0,0,0,1,1,1,1,1,1,1,3,1,0,0,0,1,5,5,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,0,0,3,0,0,4,4,4,
	              0,0,0,0,1,1,1,1,1,1,1,1,3,1,1,5,5,5,5,5,0,0,12,12,0,-1,-1,-1,-1,-1,-1,-1,0,0,3,0,0,4,4,4,
	              0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,5,5,5,5,5,0,12,12,12,12,-1,-1,-1,-1,-1,-1,-1,0,0,3,0,4,4,4,4,
	              0,0,0,0,0,0,0,0,0,0,1,1,3,0,0,5,5,5,5,5,0,12,12,12,12,-1,-1,-1,-1,-1,-1,-1,0,0,3,0,4,4,4,4,
	              0,0,0,0,0,0,0,3,3,3,3,3,3,0,0,5,5,5,5,5,5,12,12,12,12,-1,-1,-1,-1,-1,-1,-1,0,0,3,0,4,4,4,4,
	              0,0,0,0,0,0,0,3,1,1,1,1,3,1,1,0,5,5,5,5,5,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,0,0,3,0,4,4,4,4,
	              0,0,0,0,0,-1,-1,-1,-1,-1,-1,0,3,1,1,0,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,4,4,4,4,
	              0,0,0,12,12,-1,-1,-1,-1,-1,-1,0,3,1,1,0,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,4,4,4,
	              0,0,12,12,12,-1,-1,-1,-1,-1,-1,0,3,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,4,4,4,
	              0,0,12,12,12,-1,-1,-1,-1,-1,-1,0,3,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,4,4,4,
	              0,0,0,0,0,0,0,0,0,0,0,0,3,0,1,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,4,4,
	              0,0,0,0,0,0,0,0,0,0,0,0,3,0,1,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,
	              0,0,0,0,0,0,0,0,0,0,0,0,3,1,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,12,
	              7,7,7,7,7,7,7,7,7,7,7,7,3,0,0,0,0,0,0,0,0,0,3,0,0,-1,-1,-1,0,0,0,0,0,0,0,12,12,12,12,12,
	              7,7,7,7,7,7,7,7,7,7,7,7,3,0,0,0,0,0,0,0,0,0,3,0,0,-1,-1,-1,0,0,0,0,0,0,0,0,12,12,12,12,
	              7,7,7,7,7,7,7,7,7,7,7,7,3,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,
	              7,7,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,12,0,0,
	              7,7,7,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,

};

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.9f, -3.9f, 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();

	//绘制房子
	glPushMatrix();
	glTranslatef(3.0f, -2.5f, 1.0f);
	glScalef(size*6, size*5, 1);
	drawRect(texture[10]);
	glPopMatrix();

	//绘制树
	glPushMatrix();
	glTranslatef(1.8f, -2.3f, 1.0f);
	glScalef(size * 4, size * 5, 1);
	drawRect(texture[11]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(2.3f, -1.7f, 1.0f);
	glScalef(size * 4, size * 5, 1);
	drawRect(texture[14]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(3.3f, -1.9f, 1.0f);
	glScalef(size * 4, size * 5, 1);
	drawRect(texture[14]);
	glPopMatrix();

	//绘制井
	glPushMatrix();

	if (s->pos_y > -3.8f&&s->pos_x<1.7f) {
		glTranslatef(1.4f, -3.5f, 3.0f);
	}
	else{
		glTranslatef(1.4f, -3.5f, 1.0f);
	}
	glScalef(size * 2.6, size * 3.5, 1);
	drawRect(texture[15]);
	glPopMatrix();

	//绘制房子
	glPushMatrix();
    if(s->pos_y>-0.8f&&s->pos_x<1.4f)glTranslatef(0.85f, -0.8f,2.0f);
	else glTranslatef(0.85f, -0.8f, 1.0f);
	glScalef(size * 6.6, size * 5, 1);
	drawRect(texture[16]);
	glPopMatrix();


	//桶
	glPushMatrix();
	glTranslatef(1.9f, -0.8f, 1.0f);
	glScalef(size * 3, size*2 , 1);
	drawRect(texture[17]);
	glPopMatrix();

	//灌木
	glPushMatrix();
	glTranslatef(3.5f, 2.9f, 1.0f);
	glScalef(size * 2, size * 2, 1);
	drawRect(texture[18]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(3.2f, 3.0f, 1.0f);
	glScalef(size * 2, size * 2, 1);
	drawRect(texture[18]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(3.8f, 3.1f, 1.0f);
	glScalef(size * 2, size * 2, 1);
	drawRect(texture[18]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(3.3f, 3.3f, 1.0f);
	glScalef(size * 2, size * 2, 1);
	drawRect(texture[18]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(3.7f, 3.4f, 1.0f);
	glScalef(size * 2, size * 2, 1);
	drawRect(texture[18]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(3.5f, 3.5f, 1.0f);
	glScalef(size * 2, size * 2, 1);
	drawRect(texture[18]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(1.1f, -2.9f, 1.0f);
	glScalef(size * 3, size * 2, 1);
	drawRect(texture[13]);
	glPopMatrix();

	//绘制箱子
	glPushMatrix();
	if (s->pos_y > 3.2f&&s->pos_x<1.6f)glTranslatef(1.4f, 3.2f, 3.0f);
	else glTranslatef(1.4f, 3.2f, 1.0f);
	glScalef(size * 4, size * 3, 1);
	drawRect(texture[19]);
	glPopMatrix();

	//绘制房子
	glPushMatrix();
	glTranslatef(1.8f, 1.0f, 1.0f);
	glScalef(size * 8, size * 6, 1);
	drawRect(texture[20]);
	glPopMatrix();

	//绘制树
	glPushMatrix();
	glTranslatef(0.6f, 1.0f, 1.0f);
	glScalef(size * 3.5, size * 4.6, 1);
	drawRect(texture[14]);
	glPopMatrix();

	//绘制房子
	glPushMatrix();
	if (s->pos_y > 2.0f&&s->pos_x < -1.85f) {
		glTranslatef(-2.4f, 2.0f, 3.0f);
	}
	else glTranslatef(-2.4f, 2.0f, 1.0f);
	glScalef(size * 6, size * 5, 1);
	drawRect(texture[10]);
	glPopMatrix();

	//灌木
	glPushMatrix();
	glTranslatef(-3.2f, 1.9f, 1.0f);
	glScalef(size * 2, size * 2, 1);
	drawRect(texture[18]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(-3.5f, 2.1f, 1.0f);
	glScalef(size * 2, size * 2, 1);
	drawRect(texture[18]);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(-3.3f, 2.2f, 1.0f);
	glScalef(size * 2, size * 2, 1);
	drawRect(texture[18]);
	glPopMatrix();

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

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

//判断是否是障碍物
inline bool isBarrier(int map)
{
	//定义纹理索引编号为0,1,3的不是障碍物,该数字可以自己指定
	if (map != 1 && map != 0 && map != 3) {
		return true;
	}
	else return false;
}
//碰撞检测
bool test(int i,int j)
{
	if (s->dir == s->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 == s->right&&j <= y-2) {
		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 == s->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 == s->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()
{
	int i1, j1, i2, j2;
	bool flag = 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)) {
			flag = true;
		}
		i2 = (s->pos_y + 4.0f + size / 4) / size;
		if (i2 != i1) {
			if (test(i2, j1)) {
				flag = true;
			}
		}
		i2 = (s->pos_y + 4.0f - size / 4) / size;
		if (i2 != i1) {
			if (test(i2, j1)) {
				flag = true;
			}
		}

		//地图卷动
		if (s->dir == s->right&&j1 > x / 2 - 5 && j1<x&&!flag) {
			eye[0] += s->step;
			center[0] += s->step;
			if (eye[0]>4.0f * 2 / 3) {
				eye[0] = 4.0f * 2 / 3;
				center[0] = 4.0f * 2 / 3;
			}
		}
		else if (s->dir == s->left&&j1 > 0 && j1<x / 2 + 5 && !flag) {
			eye[0] -= s->step;
			center[0] -= s->step;
			if (eye[0]<0) {
				eye[0] = 0;
				center[0] = 0;
			}
		}
	}
	flag = false;
	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)) {
			flag = true;
		}
		//执行2次碰撞检测
		j2 = (s->pos_x + 4.0f + size / 4) / size;
		if (j2 != j1) {
			if (test(i1, j2)) {
				flag = true;
			}
		}
		j2 = (s->pos_x + 4.0f - size / 4) / size;
		if (j2 != j1) {
			if (test(i1, j2)) {
				flag = true;
			}
		}

		//地图卷动
		if (s->dir == s->back&&i1 > y / 2 - 5 && i1<y && !flag) {
			eye[1] += s->step;
			center[1] += s->step;
			if (eye[1]>4.0f) {
				eye[1] = 4.0f;
				center[1] = 4.0f;
			}
		}
		else if (s->dir == s->front&&i1 > 0 && i1<y / 2 + 5 && !flag) {
			eye[1] -= s->step;
			center[1] -= s->step;
			if (eye[1]<0) {
				eye[1] = 0;
				center[1] = 0;
			}
		}
	}
}

void key(unsigned char k, int _x, int _y)
{
	s->count2++;
	switch (k)
	{
	case 'a': {//向左移动
		s->dir = sprite::left;
		s->isStop = false;
		s->pos_x -= s->step;
		if (s->pos_x < -size_x)s->pos_x = -size_x;
		collisionTest();
		break;
	}
	case 'd': {//向右移动
		s->dir = sprite::right;
		s->isStop = false;
		s->pos_x += s->step;
		if (s->pos_x > size_x)s->pos_x = size_x;
		collisionTest();
		break;
	}
	case 'w': {//向上移动
		s->dir = sprite::back;
		s->isStop = false;
		s->pos_y += s->step;
		if (s->pos_y > size_y)s->pos_y = size_y;
		collisionTest();
		break;
	}
	case 's': {//向下移动
		s->dir = sprite::front;
		s->isStop = false;
		s->pos_y -= s->step;
		if (s->pos_y < -size_y)s->pos_y = -size_y;
		collisionTest();
		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); 

	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

	int index[] = { 0,0,0,1,0,2,1,0,1,1,1,2,2,0,2,1,2,2,3,0,3,1,3,2 };

	unsigned char color[] = {170,170,170 };
	unsigned char color2[] = {230,230,230 };
	glGenTextures(23, texture);
	loadTex(0, "9.bmp", texture);
	loadTex(1, "10.bmp", texture);
	loadTex(2, "11.bmp", texture);
	loadTex(3, "12.bmp", texture);
	loadTex(4, "13.bmp", texture);
	loadTex(5, "14.bmp", texture);
	loadTex(6, "15.bmp", texture);
	loadTex(7, "16.bmp", texture);
	loadTex(8, "17.bmp", texture);
	loadTex(9, "1.bmp", texture,color2);
	loadTex(10, "30.bmp", texture);
	loadTex(11, "23.bmp", texture,color);
	loadTex(13, "24.bmp", texture, color);
	loadTex(14, "25.bmp", texture, color);
	loadTex(15, "26.bmp", texture, color2);
	loadTex(16, "27.bmp", texture);
	loadTex(17, "28.bmp", texture, color);
	loadTex(18, "31.bmp", texture, color);
	loadTex(19, "32.bmp", texture, color);
	loadTex(20, "33.bmp", texture);
	texture[12] = texture[0];
	for (int i = 0; i < x; i++) {
		for (int j = 0; j < y; j++) {
			place_x[i][j] = -3.9f + size*j;
			place_y[i][j] = -3.9f + size*i;
		}
	}
	s = new sprite(12, 96, -3.5f, -3.5f, texture[9], index, 0.06f);
}



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(800,600);//设置窗口大小           
	int windowHandle = glutCreateWindow("Simple GLUT App");//设置窗口标题             
	glutDisplayFunc(redraw); //注册绘制回调函数           
	glutReshapeFunc(reshape);   //注册重绘回调函数           
	glutKeyboardFunc(key); //注册按键回调函数
	glutIdleFunc(idle);//注册全局回调函数:空闲时调用         

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

图片素材:


1.bmp

9.bmp

10.bmp

11.bmp

12.bmp

13.bmp


14.bmp


15.bmp


16.bmp


17.bmp


23.bmp


24.bmp


25.bmp


26.bmp


28.bmp


30.bmp


31.bmp


32.bmp


33.bmp



作者对游戏的说明: 首先,您应当以一种批判的眼光来看待本程序。这个游戏是我制作 的第一部RPG游戏,无任何经验可谈,完全按照自己对游戏的理解进 行设计的。当我参照了《圣剑英雄2》的源码之后,才体会到专业游 戏引擎的博大精深。 该程序的内核大约有2000余行,能够处理人物的行走、对话、战斗, 等等。由于该程序的结构并不适于这种规模的程序,故不推荐您详 细研究该程序。所附地图编辑器的源程序我已经添加了详细的注释, 其程序结构也比较合理,可以作为初学VC的例子。 该程序在VC的程序向导所生成的SDI框架的基础上修改而成。它没有 使用任何关于VC底层的东西。程序的绝大部分都是在CgameView类中 制作的,只有修改窗口特征的一段代码在CMainFrm类中。其他的类 统统没有用到。另外添加的一个类是CEnemy类。 整个游戏的故事情节分成8段,分别由Para1.h ~ Para8.h八个文件 实现。由于程序仅仅能够被动的处理各种各样的消息,所以情节的 实现也只能根据系统的一些参数来判断当前应当做什么。在程序中 使用了冗长的if……else if……结构来实现这种判断。 当然,在我的记录本上,详细的记录了每个事件的判断条件。这种 笨拙的设计当然是不可取的。成都金点所作《圣剑英雄II》采用了 剧本解读的方式,这才是正统的做法。但这也需要更多的编程经验 和熟练的code功夫。 下面列举的是程序编制过程中总结出来的经验和教训。 第一,对话方式应该采用《圣剑英雄II》的剧本方式。 现在的方式把一个段落中所有的对话都混在一个文件中,然后给每 句话一个号码相对应。这样做虽然降低了引擎的难度,却导致剧情的 编写极其繁琐。 第二,运动和显示应当完全分开。 现在的程序中,运动和显示是完全同步的。即:在定时器中调用所有 敌人的运动函数,然后将主角的动画向前推一帧,接着绘制地图,调 用所有敌人的显示函数、重绘主角。这样的好处是不会掉帧,但带来 的问题是,如果要提高敌人的运动速度,那么帧数也跟着上去了。所 以当DEMO版反馈说速度太慢的时候,我修改起来非常困难。而这个问 题到最后也仅仅是将4步一格该成了2步一格。 第三,VC中数组存在上限。如果用“int aaa[1000000000]”定义一个 数组,编译器肯定不会给分配那么大的内存空间。而在这个程序中, 地图矩阵、NPC矩阵都超过了VC中数组的上限。但这一点知道的太晚了。 在1.0版本中已经发现地图最右端缺少了几行,但不知道是什么原因 造成的。(地图编辑器中未出现此问题,因为地图编辑器是用“序列 化”的方式存盘读盘的。)解决这个问题的方法是用“new”来分配 内存空间。 第四,由于不知道应该如何使用“new”和“delete”,几乎所有的DC 都使用了全局变量。这是完全没有必要的。程序运行期大约会耗用20 多M的内存空间,相当于一个大型游戏所使用的内存空间了。 另外,在游戏的剧情、美工方面也有许多问题,总之一个词“业余”。 我就不总结了。下一部作品,我将争取在程序上有一个质的飞跃。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值