所用基本知识
简单的C++知识
OpenGL的二维纹理贴图和透明化贴图
推荐OpenGL入门教程:
http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html
飞机类
#ifndef PLANE_H
#define PLANE_H
#include
#include
#include
#include
#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
#include
#include
#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;
}