第21课:线、反走样、正投影和简单的声音 (参照NeHe)
这次教程中,我们将介绍线、反走样、正投影和简单的声音,这是第一个大教程,希望这一课的东西大家能够喜欢(NeHe原文中有介绍计时器,但是Qt已经为我们封装好了计时器,所以这次教程中我省略了这部分,有兴趣了解VC中设置计时器的请点击这里)。
在这一课里,我们将学会绘制直接,使用反走样,正投影,基本的音效和一个简单的游戏逻辑,希望这里的东西可以让你高兴,毕竟我们会完成一个游戏!在这一课的结尾,你将获得一个叫“GRID CRAZY”的游戏,你的任务是走完每一段直线。这个程序有了一个基本游戏的一切要素:关卡,生命值,声明和一个游戏道具。
程序运行时效果如下:
下面进入教程:
我们这次将在第01课代码的基础上修改代码,这次是个大程序,我们一一解释新的内容和游戏逻辑,希望大家能理解和喜欢这第一个openGL游戏(虽然只是2D游戏)。首先打开项目文件(.pro文件)和myglwidget.h文件,将两个文件内容更改如下:
TARGET = QtOpenGL21
TEMPLATE = app
HEADERS += \
myglwidget.h
SOURCES += \
myglwidget.cpp \
main.cpp
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
QT += opengl \
multimedia
#ifndef MYGLWIDGET_H
#define MYGLWIDGET_H
#include <QWidget>
#include <QGLWidget>
class QSound;
class MyGLWidget : public QGLWidget
{
Q_OBJECT
public:
explicit MyGLWidget(QWidget *parent = 0);
~MyGLWidget();
protected:
//对3个纯虚函数的重定义
void initializeGL();
void resizeGL(int w, int h);
void paintGL();
void keyPressEvent(QKeyEvent *event); //处理键盘按下事件
private:
void resetObjects(); //重置玩家和敌人信息
void updateData(); //更新下一帧数据
void buildFont(); //创建字体
void killFont(); //删除显示列表
//输出字符串
void glPrint(GLuint x, GLuint y, int set, const char *fmt, ...);
private:
bool fullscreen; //是否全屏显示
bool m_Vline[11][10]; //保存垂直方向的11根线条中,每根线条中的10段是否被走过
bool m_Hline[10][11]; //保存水平方向的11根线条中,每根线条中的10段是否被走过
bool m_Filled; //网格是否被填满
bool m_Gameover; //游戏是否结束
bool m_Anti; //是否反走样
int m_Delay; //敌人的暂停时间
int m_Adjust; //调整速度
int m_Lives; //玩家的生命
int m_Level; //内部的游戏难度等级
int m_Level2; //显示的游戏难度等级
int m_Stage; //游戏的关卡
static const int s_Steps[6]; //用来调节显示的速度
struct object //记录游戏中的对象
{
int fx, fy; //使移动变得平滑
int x, y; //当前游戏者的位置
float spin; //旋转角度
};
object m_Player; //玩家信息
object m_Enemy[9]; //最多9个敌人
object m_Hourglass; //宝物沙漏信息
QString m_FileName[2]; //图片的路径及文件名
GLuint m_Texture[2]; //储存两个纹理
GLuint m_Base; //字符显示列表的开始值
QSound *m_Sound; //保存吃到宝物后的计时音乐
};
#endif // MYGLWIDGET_H
项目文件中,我们增加multimedia部分,这使得我们能够使用QSound等媒体播放对象。
myglwidget.h文件中,首先我们增加了2个布尔变量数组m_Vline和m_Hline,用于记录垂直方向和水平方向各110段线段是否走过。继续是3个布尔变量,当网格被填满时,m_Filled被设置为true而反之则为false;m_Gameover的作用易见,当它的值为true时,游戏结束;m_Anti指出抗锯齿功能是否打开,当设置为true时,该功能是打开着的。
接下来是5个整形变量和1个static const 整形数组,m_Delay用来减缓敌人的行动,其实就是当m_Delay小于某个值时,敌人不能移动(由于画面刷新很快,m_Delay变化也快,你是看不出敌人有很小一段时间没动的)。m_Adjust用来控制玩家和敌人移动的速度,其实就是控制玩家和敌人每一步能走多远,我们通过和下面的s_Steps[]数组配合一起完成这一控制目的。m_Lives保存了玩家的剩余生命值,m_Level保存了游戏内部的等级难度,m_Level2保存了显示出来的游戏难度,m_Stage保存了游戏的关卡(m_Level、m_Level2和m_Stage的区别后面大家会明白的,不用在这里纠结)。m_Steps[]保存了可供m_Adjust选择的数值。
然后我们定义了一个结构体来记录游戏中的对象。fx和fy记录每次在网格上移动我们的英雄和敌人的精确像素位置,x和y则记录着对象即将移动到网格交点是哪个。而最后一个变量spin用来使对象在z轴上选择。定义完后,我们就利用这个结构体创建我们的玩家对象,敌人对象(最多有9个所以是长度为9的数组)和宝物沙漏对象(m_Hourglass)。
还有我们需要载入两个纹理,所以有了m_FileName[2]和m_Texture[2。而m_Base储存字符显示列表的开始值,m_Sound用来指向一个QSound对象,该对象保存了吃到宝物后的计时音乐(注意声明QSound时需要在类前面加上class QSound声明)。最后是5个新的函数的声明,作用大家先看注释留个印象吧,后面会慢慢来介绍解释。
接下来,我们打开myglwidget.cpp,加上声明#include <QTimer>、#include <QSound>、#include <QTime>、#include <QCoreApplication>,在构造函数中对新增变量进行初始化并修改析构函数,具体代码如下:
const int MyGLWidget::s_Steps[] = {1, 2, 4, 5, 10, 20};
MyGLWidget::MyGLWidget(QWidget *parent) :
QGLWidget(parent)
{
fullscreen = false;
setFixedSize(640, 480); //设置固定的窗口大小
for (int i=0; i<11; i++) //初始化每个线段都没被走过
{
for (int j=0; j<11; j++)
{
if (i < 10)
{
m_Hline[i][j] = false;
}
if (j < 10)
{
m_Vline[i][j] = false;
}
}
}
m_Filled = false;
m_Gameover = false;
m_Anti = true;
m_Delay = 0;
m_Adjust = 3;
m_Lives = 5;
m_Level = 1;
m_Level2 = m_Level;
m_Stage = 1;
resetObjects(); //初始化玩家和敌人信息
m_Hourglass.fx = 0; //初始化宝物沙漏信息
m_Hourglass.fy = 0;
m_FileName[0] = "D:/QtOpenGL/QtImage/Font.bmp"; //应根据实际存放图片的路径进行修改
m_FileName[1] = "D:/QtOpenGL/QtImage/Image.bmp";
m_Sound = new QSound("D:/QtOpenGL/QtImage/Freeze.wav");
QTimer *timer = new QTimer(this); //创建一个定时器
//将定时器的计时信号与updateGL()绑定
connect(timer, SIGNAL(tim