C++ 和 OpenGL 实现简易飞机大战

C++ & OpenGL 实现简易飞机大战

  • 所用基本知识
    简单的C++知识
    OpenGL的二维纹理贴图和透明化贴图

  • 推荐OpenGL入门教程:

  • http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html

  • 飞机类

#ifndef PLANE_H
#define PLANE_H

#include <GL/glut.h>
#include <cstdlib>
#include <ctime>
#include <vector>
#define safe_delete(p) if(p){delete p;p=nullptr;}
using std::vector;
//父类
class Spirit {
protected:
	double left, right, up, down;    //上下左右坐标
	double length, width;            //图片长度和宽度
	double midx, midy;               //图片的中心x,y坐标
	int life;                        //生命
	GLint Tex;                       //对应的图片
public:
	Spirit(double u, double d, double l, double r, int li, GLint tex) :
		up(u), down(d), left(l), right(r), life(li), Tex(tex) {
		width = up - down; length = right - left;
		midx = (left + right) / 2; midy = (up + down) / 2;
	}
	Spirit() {}
	virtual ~Spirit() {}
	virtual bool exist()const = 0;    //判断是否存在
	virtual void move() = 0;          //控制移动
	void draw()const;                 //显示到屏幕上
};
//子弹类
class Bullet :public Spirit {
private:
	int dir;    //子弹方向
	double speed;    //子弹每帧移动的距离
	bool ex;    //判断是否已经击中目标,击中为false
public:
	Bullet(double u, double d, double l, double r, int di, double s, GLint tex)
		:dir(di), speed(s), ex(true) {
		up = u; down = d; left = l; right = r;Tex = tex; 
	}
	~Bullet() {}
	void move();
	void setexist(const bool b) { ex = b; }
	bool exist()const { 
		return ex&&left >= -1.0&&right <= 1.0&&up <= 1.0&&down >= -1.0;
	}
	//返回子弹坐标
	inline double l() { return left; }
	inline double r() { return right; }
	inline double u() { return up; }
	inline double d() { return down; }
};
//敌机类
class Plane;
class Enermy:public Spirit {
	friend class Plane;
public:
	Enermy(double u, double d, double l, double r, GLint texself, GLint tex, int li = 1) {
		up = u; down = d; left = l; right = r; Tex = texself; life = li;
		double mid = (l + r) / 2; TexBullet = tex;
		bullet = new Bullet(d, d - 0.1, mid - 0.05, mid + 0.05, 0, 0.006, tex);
		srand(time(NULL));
	}
	~Enermy() { safe_delete(bullet); }
	void draw()const;
	void attack();
	void move();
	//敌机出屏幕后判定为不存在
	bool exist()const { return life != 0 && up > -1.0;}
private:
	Bullet* bullet;    //子弹
	GLint TexBullet;    //子弹图片
};
//飞机类(玩家控制的角色)
class Plane:public Spirit {
public:
	Plane(double u, double d, double l, double r, int li, GLint texself, GLint tex) :
		score(0), TexBullet(tex) {
		up = u; down = d; left = l; right = r; width = r - l;
		Tex = texself; life = li; TexBullet = tex;
		double mid = (l + r) / 2;
		bullet = new Bullet(u + 0.004, u, l + mid - 0.002, r - mid + 0.002, 5, 0.04, tex);
		srand(time(NULL));
	}
	~Plane() { safe_delete(bullet); }
	void move() {}
	double move(unsigned char c);   
	bool exist()const { return life != 0; }
	void attack();
	void moveto(double, double);
	const int GetLife()const { return life; }
	const int GetScore()const { return score; }
	void checkattack(const vector<Enermy*>&);
	void checkcollide(const vector<Enermy*>&);
private:
	Bullet* bullet;      //子弹
	GLint TexBullet;    //子弹图片
	int score;
};

#endif // !PLANE_H


  • 飞机类的具体实现:
#include "plane.h"
//绘制函数
void Spirit::draw()const {
	if (exist()) {
		glEnable(GL_ALPHA_TEST);    //打开深度测试
		glAlphaFunc(GL_GREATER, 0.5f);  //alpha值小于0.5的不绘制
		glBindTexture(GL_TEXTURE_2D, Tex);  //绑定纹理
		glBegin(GL_QUADS);                 //绘制
		glTexCoord2f(0, 0), glVertex2f(left, down);
		glTexCoord2f(0, 1), glVertex2f(left, up);
		glTexCoord2f(1, 1), glVertex2f(right, up);
		glTexCoord2f(1, 0), glVertex2f(right, down);
		glEnd();
	}
}

void Bullet::move() {
    //0,1,2,3分别代表上下左右移动
	switch (dir) {
	case 0:
		up -= speed;
		down -= speed;
		break;
	case 1:
		up += speed;
		down += speed;
		break;
	case 2:
		left -= speed;
		right -= speed;
		break;
	case 3:
		left += speed;
		right += speed;
		break;
	default:
		break;
	}
}
//绘制敌机
void Enermy::draw()const {
	Spirit::draw();
	bullet->draw();
}
//敌机的移动
void Enermy::move() {
	up -= 0.002; down -= 0.002;
}
//敌机发射子弹
void Enermy::attack() {
	bullet->move();
	bullet->draw();
	//子弹不存在且敌机存在可以重新发射子弹
	if (!bullet->exist()&&exist()) {
		int t = rand() % 100;
		if (t < 99)return;  //每帧有1%的概率重新发射子弹
		double mid = (left + right) / 2;
		bullet = new Bullet(down, down - 0.1, mid - 0.05, mid + 0.05, 0, 0.006, TexBullet);
	}
}
//键盘控制飞机的移动,stime<0则初始化
//wasd控制上左下右移动,空格射击,esc键退出,r键重来
double Plane::move(unsigned char c) {
	double stime = 0;
	switch (c) {
	case 'a':    //left
	case 'A':
		left -= 0.05;
		if (left >= -1.0)
			right -= 0.05;
		else {
			left = -1.0;
			right = -0.75;
		}
		break;
	case 'w':
	case 'W':
		up += 0.05;
		if (up <= 1.0)
			down += 0.05;
		else {
			up = 1.0;
			down = 0.75;
		}
		break;
	case 'd':
	case 'D':
		right += 0.05;
		if (right <= 1.0)
			left += 0.05;
		else {
			right = 1.0;
			left = 0.75;
		}
		break;
	case 's':
	case 'S':
		down -= 0.05;
		if (down >= -1.0)
			up -= 0.05;
		else {
			down = -1.0;
			up = -0.75;
		}
		break;
	case ' ': {
		double mid = (right + left) / 2;
		bullet = new Bullet(up + 0.06, up, mid - 0.03,
			mid + 0.03, 1, 0.02, TexBullet);
		attack();
		break;
	}
	case 'r':
	case 'R':
		return -1;
	case '\033':
		exit(0);
	default:
		break;
	}
	return stime;
}
//发射子弹
void Plane::attack() {
	bullet->move();
	bullet->draw();
}
//判断有无被敌机子弹击中,击中则扣除1点体力
//若与敌机撞击则当场死亡
void Plane::checkcollide(const vector<Enermy*>& vec) {
	for (auto &i : vec) {
		if (i->bullet->l() > right || i->bullet->r() < left
			|| i->bullet->u() < down || i->bullet->d() > up || !i->bullet->exist());
		else {
			i->bullet->setexist(false);
			--life;
			if (life < 0)life = 0;
		}
		if (!i->exist()) continue;
		if (i->down >= up || i->left >= right ||
			i->right <= left || i->up <= down)
			continue;
		life = 0;
	}
}

void Plane::moveto(double x, double y) {
	double MidWidth = width / 2;
	left = x - MidWidth;
	right = x + MidWidth;
	up = y + MidWidth;
	down = y - MidWidth;
}
//判断有没有击中敌机,击中加10分
void Plane::checkattack(const vector<Enermy*>& vec) {
	double x1 = bullet->l(), x2 = bullet->r();
	double y1 = bullet->d(), y2 = bullet->u();
	if (!bullet->exist())return;
	for (auto& i : vec) {
		if (!i->exist())continue;
		if (y2 <= i->down || y1 >= i->up ||
			x1 >= i->right || x2 <= i->left)
			continue;
		i->life = 0;
		score += 10;
		bullet->setexist(false);
	}
}
  • 游戏部分代码
#include "stdafx.h"
#include <cmath>
#include <Windows.h>
#include <random>
#include "plane.h"

#define BMP_Header_Length 54
#define Width 400
#define Height 400

using namespace std;

GLint tex_enermy, tex_back, tex_plane, tex_bullet, tex_Bullet;
vector<Enermy*>vec;
Plane* plane;
double stime, etime;
default_random_engine e;

void init();
//判断是否是2的幂,使用了位运算的技巧
inline bool power_of_two(int n) {
	if (n <= 0) return false;
	return (n & (n - 1)) == 0;
}
//加载纹理,返回纹理对应的值
GLuint load_texture(const char* file_name) {
	GLint width, height, total_bytes;
	GLubyte* pixels = 0;
	GLuint last_texture_ID = 0, texture_ID = 0;

	// 打开文件,如果失败,返回
	FILE* pFile = fopen(file_name, "rb");
	if (pFile == 0)
		return 0;

	// 读取文件中图象的宽度和高度
	fseek(pFile, 0x0012, SEEK_SET);
	fread(&width, 4, 1, pFile);
	fread(&height, 4, 1, pFile);
	fseek(pFile, BMP_Header_Length, SEEK_SET);

	// 计算每行像素所占字节数,并根据此数据计算总像素字节数
	{
		GLint line_bytes = width * 3;
		while (line_bytes % 4 != 0)
			++line_bytes;
		total_bytes = line_bytes * height;
	}

	// 根据总像素字节数分配内存
	pixels = (GLubyte*)malloc(total_bytes);
	if (pixels == 0)
	{
		fclose(pFile);
		return 0;
	}

	// 读取像素数据
	if (fread(pixels, total_bytes, 1, pFile) <= 0) {
		free(pixels);
		fclose(pFile);
		return 0;
	}

	// 在旧版本的OpenGL中
	// 如果图象的宽度和高度不是的整数次方,则需要进行缩放
	// 这里并没有检查OpenGL版本,出于对版本兼容性的考虑,按旧版本处理
	// 另外,无论是旧版本还是新版本,
	// 当图象的宽度和高度超过当前OpenGL实现所支持的最大值时,也要进行缩放
	{
		GLint max;
		glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
		if (!power_of_two(width)
			|| !power_of_two(height)
			|| width > max
			|| height > max) {
			const GLint new_width = 256;
			const GLint new_height = 256; // 规定缩放后新的大小为边长的正方形
			GLint new_line_bytes, new_total_bytes;
			GLubyte* new_pixels = 0;

			// 计算每行需要的字节数和总字节数
			new_line_bytes = new_width * 3;
			while (new_line_bytes % 4 != 0)
				++new_line_bytes;
			new_total_bytes = new_line_bytes * new_height;

			// 分配内存
			new_pixels = (GLubyte*)malloc(new_total_bytes);
			if (new_pixels == 0) {
				free(pixels);
				fclose(pFile);
				return 0;
			}

			// 进行像素缩放
			gluScaleImage(GL_RGB,
				width, height, GL_UNSIGNED_BYTE, pixels,
				new_width, new_height, GL_UNSIGNED_BYTE, new_pixels);

			// 释放原来的像素数据,把pixels指向新的像素数据,并重新设置width和height
			free(pixels);
			pixels = new_pixels;
			width = new_width;
			height = new_height;
		}
	}

	// 分配一个新的纹理编号
	glGenTextures(1, &texture_ID);
	if (texture_ID == 0) {
		free(pixels);
		fclose(pFile);
		return 0;
	}
	GLint temp = last_texture_ID;
	// 绑定新的纹理,载入纹理并设置纹理参数
	// 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &temp);
	//使用第texture_ID幅纹理
	glBindTexture(GL_TEXTURE_2D, texture_ID);
	//设置纹理参数
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	//载入一个二维纹理
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
		GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
	glBindTexture(GL_TEXTURE_2D, last_texture_ID);

	// 之前为pixels分配的内存可在使用glTexImage2D以后释放
	// 因为此时像素数据已经被OpenGL另行保存了一份(可能被保存到专门的图形硬件中)
	free(pixels);
	return texture_ID;
}
//将特定颜色的背景的alpha值设置为0,绘制时就可以不绘制背景
void texture_colorkey(GLubyte r, GLubyte g, GLubyte b,
	GLubyte absolute) {
	GLint width, height;
	GLubyte *pixels = nullptr;
	//获取纹理大小信息
	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
	//分配空间并获得纹理像素
	pixels = (GLubyte*)malloc(width*height * 4);
	//pixels = new GLubyte[width*height * 4];
	if (pixels == nullptr)
		return;
	glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
	//修改像素中的Alpha值
	{
		GLint i, count = width*height;
		for (i = 0; i < count; ++i) {
			if (abs(pixels[i * 4] - b) <= absolute
				&&abs(pixels[i * 4 + 1] - g) <= absolute
				&&abs(pixels[i * 4 + 2] - r) <= absolute)
				pixels[i * 4 + 3] = 0;
			else
				pixels[i * 4 + 3] = 255;
		}
	}
	//将修改后的像素重新设置到纹理中
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
	free(pixels);
}

//判断敌机是否已经全部消失
bool checkall() {
	for (auto& i : vec)
		if (i->exist())return false;
	return true;
}
//产生新的敌机
void newenermy() {
	vec.clear();    //记得先清空之前的敌机
	//产生敌机的随机坐标
	uniform_real_distribution<double> u1(0.75, 1.00);
	uniform_real_distribution<double> u2(-1.00, 0.75);
	for (int i = 0; i < 10; ++i) {
		double u = u1(e), l = u2(e);
		Enermy *t = new Enermy(u, u - 0.25, l, l + 0.25, tex_enermy, tex_bullet);
		vec.push_back(t);
	}
}
//改变字体格式
void selectFont(int size, int charset, const char* face) {
	HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0,
		charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
		DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);
	HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);
	DeleteObject(hOldFont);
}
//画字符
void drawCNString(const char* str) {
	int len, i;
	wchar_t* wstring;
	HDC hDC = wglGetCurrentDC();
	GLuint list = glGenLists(1);

	// 计算字符的个数
	// 如果是双字节字符的(比如中文字符),两个字节才算一个字符
	// 否则一个字节算一个字符
	len = 0;
	for (i = 0; str[i] != '\0'; ++i) {
		if (IsDBCSLeadByte(str[i]))
			++i;
		++len;
	}

	// 将混合字符转化为宽字符
	wstring = (wchar_t*)malloc((len + 1) * sizeof(wchar_t));
	MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
	wstring[len] = L'\0';

	// 逐个输出字符
	for (i = 0; i < len; ++i) {
		wglUseFontBitmapsW(hDC, wstring[i], 1, list);
		glCallList(list);
	}

	// 回收所有临时资源
	free(wstring);
	glDeleteLists(list, 1);
}
//结束页面
void endgame() {
	glDisable(GL_TEXTURE_2D);
	glColor3f(0.0f, 1.0f, 1.0f);
	glRasterPos2f(-0.80, 0.00);
	selectFont(50, DEFAULT_CHARSET, "黑体");
	drawCNString("You have died");
	glEnable(GL_TEXTURE_2D);
}
//将数字转成字符
char* to_char(int t) {
	char *str = new char[12];
	int  i = 0;
	while (t) {
		str[i] = t % 10 + '0';
		++i;
		t /= 10;
	}
	str[i] = '\0';
	int len = i;
	for (i -= 1; i >= len >> 1; --i)
		swap(str[i], str[len - i - 1]);
	str = str;
	return str;
}
//显示分数
void showscore() {
	glDisable(GL_TEXTURE_2D);
	glColor3f(1.0f, 0.0f, 0.0f);
	glRasterPos2f(-1.00, 0.90);
	selectFont(20, DEFAULT_CHARSET, "华文仿宋");
	drawCNString("Your Score:");

	glRasterPos2f(-0.40, 0.90);
	selectFont(20, DEFAULT_CHARSET, "华文仿宋");
	char *temp = to_char(plane->GetScore());
	drawCNString(temp);
	delete temp;

	glRasterPos2f(-0.10, 0.90);
	selectFont(20, DEFAULT_CHARSET, "华文仿宋");
	drawCNString("Your Life:");

	glRasterPos2f(0.40, 0.90);
	selectFont(20, DEFAULT_CHARSET, "华文仿宋");
	temp = to_char(plane->GetLife());
	drawCNString(temp);
	delete temp;

	glEnable(GL_TEXTURE_2D);
}

//判断是否击中敌机
void checkcollide() {
	plane->checkattack(vec);
	if (checkall()) newenermy();
}
//判断是否被击中
void checkcollide1() {
	plane->checkcollide(vec);
}
//发射子弹
void drawbullet() {
	plane->attack();
	checkcollide();
}
//绘制敌机
void drawenermy() {
	for (auto &i : vec)i->draw();
}
//所有图片的显示
void display() {	
	glClear(GL_COLOR_BUFFER_BIT);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, tex_back);
	glBegin(GL_QUADS);
	glTexCoord2f(0, 0), glVertex2f(-1, -1);
	glTexCoord2f(0, 1), glVertex2f(-1, 1);
	glTexCoord2f(1, 1), glVertex2f(1, 1);
	glTexCoord2f(1, 0), glVertex2f(1, -1);
	glEnd();

	checkcollide1();

	plane->draw();
	if (plane->exist()) drawbullet();
	else endgame();

	showscore();
	drawenermy();
	glutSwapBuffers();
}
//键盘控制函数
void keycontrol(unsigned char c, int x, int y) {
	double temp = plane->move(c);
	if (temp > 0) stime = temp;
	else if (temp < 0) init();
}
//鼠标控制函数,飞机随着鼠标坐标移动
void move(int x, int y) {
	plane->moveto(double(x) / (glutGet(GLUT_WINDOW_WIDTH) >> 1) - 1.0,
		1.0 - double(y) / (glutGet(GLUT_WINDOW_HEIGHT) >> 1));
}
//鼠标控制函数,左击发射
void attack(int button, int state, int x, int y) {
	if (state == GLUT_LEFT)plane->move(' ');
}
//回调函数
void idle() {
	display();
	for (auto& i : vec) {
		i->move();
		i->attack();
	}
}
//初始化
void init() {
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(Width, Height);
	glutCreateWindow("Plane War");
	tex_back = load_texture("Back.bmp");
	tex_enermy = load_texture("Yellow.bmp");
	tex_plane = load_texture("Black.bmp");
	tex_bullet = load_texture("bullet.bmp");
	tex_Bullet = load_texture("Bomb.bmp");
    glBindTexture(GL_TEXTURE_2D, tex_plane);
	texture_colorkey(255, 255, 255, 10);
	glBindTexture(GL_TEXTURE_2D, tex_enermy);
	texture_colorkey(255, 255, 255, 10);
	glBindTexture(GL_TEXTURE_2D, tex_bullet);
	texture_colorkey(255, 255, 255, 10);
	glBindTexture(GL_TEXTURE_2D, tex_Bullet);
	texture_colorkey(255, 255, 255, 10);
    glEnable(GL_TEXTURE_2D);
	newenermy();
	plane = new Plane(-0.50, -0.75, -0.125, 0.125, 10, tex_plane, tex_Bullet);
	glutDisplayFunc(&display);
	glutKeyboardFunc(&keycontrol);
	glutPassiveMotionFunc(&move);
	glutMouseFunc(&attack);
	glutIdleFunc(&idle);
	glutMainLoop();
}

int main(int argc, char* argv[]) {
	// GLUT初始化
	glutInit(&argc, argv);
	ShowCursor(false);
	init();
	return 0;
}
  1. 运行效果
    在这里插入图片描述
    图片资源来自:http://www.aigei.com/
  2. 更新:游戏代码中的move函数 if(temp==0)改为if(temp>0)
  3. 代码更新 : Github地址
  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值