Linux环境QT5.9+OpenGL绘制图形与渲染(21)详解系列

铁木 发表于 2022/06/18 21:51:23 2022/06/18
【摘要】 这是一个系列的博客,OpenGL是基于计算机图形学为基础发展出来的一个分支,必须理解清楚的是“向量”,由数学向量与C语言结合发展出了shader language,shader封装再结合C++就是UE4和UE5图形引擎了。向量vector(不是C++里面的vector容器啊,不要搞混了),最重要的就是引入数学矩阵Matrix,一个矩阵就是一个向量,即将向量计算变成矩阵变换,同时一个向量vec...


这是一个系列的博客,OpenGL是基于计算机图形学为基础发展出来的一个分支,必须理解清楚的是“向量”,由数学向量与C语言结合发展出了shader language,shader封装再结合C++就是UE4和UE5图形引擎了。向量vector(不是C++里面的vector容器啊,不要搞混了),最重要的就是引入数学矩阵Matrix,一个矩阵就是一个向量,即将向量计算变成矩阵变换,同时一个向量vector可以等于两个矩阵计算的结果。

这是向量A,换算成矩阵的样子

矩阵的乘法满足于以下运算律:

结合律:(AB)C = A(BC)

左分配律:(A + B)C = AC + BC

右分配律:C(A + B) = CA + CB
矩阵乘法不满足交换律:

由此我们明白了,矩阵Matrix与向量Vector的关系,但是还没搞明白OpenGL与shader的关系。

OpenGL有vertex shader 和 fragment shader等过程,这些就是封装过的shader在OpenGL里面使用。

关于纹理滤波的问题
线性插值滤波(GL_LINEAR)==的纹理贴图,这需要机器有相当高的处理能力,但是看起来效果会很好;

最临近值滤波(GL_NEAREST),它只占用很小的处理能力,看起来效果会比较差,但是使用它因为不占用资源,工程在很快和很慢的机器上都可以正常运行;也可以混合使用线性插值滤波和最临近值滤波,纹理看起来效果会好一些;

Mipmap,这是一种创建纹理的新方法;您可能会注意到当图像在屏幕上变得很小的时候,很多细节将会丢失,刚才还很不错的图案变得很难看;当您告诉OPenGL创建一个mipmaped纹理时,OPenGL将选择它已经创建的外观最佳的纹理(带有很多细节)来绘制,而不仅仅是缩放原先的图像(这将导致细节丢失)。

关于光照
(1)当不开启光照时,使用顶点颜色来产生整个表面的颜色。

用glShadeModel可以设置表面内部像素颜色产生的方式。GL_FLAT/GL_SMOOTH.

(2)一般而言,开启光照后,在场景中至少需要有一个光源(GL_LIGHT0.。.GL_LIGHT7)

通过glEnable(GL_LIGHT0) glDisable(GL_LIGHT0) 来开启和关闭指定的光源。

— 全局环境光 —

GLfloat gAmbient[] = {0.6, 0,6, 0,6, 1.0};

glLightModelfv(GL_LIGHT_MODEL_AMBIENT, gAmbient);

(3)设置光源的光分量 – 环境光/漫色光/镜面光

默认情况下,GL_LIGHT0.。.GL_LIGHT7 的GL_AMBIENT值为(0.0, 0.0, 0.0, 1.0);

GL_LIGHT0的GL_DIFFUSE和GL_SPECULAR值为(1.0, 1.0, 1.0, 1.0),

GL_LIGHT1.。.GL_LIGHT7 的GL_DIFFUSE和GL_SPECULAR值为(0.0, 0.0, 0.0, 0.0)。

GLfloat lightAmbient[] = {1.0, 1.0, 1.0, 1.0};

GLfloat lightDiffuse[] = {1.0, 1.0, 1.0, 1.0};

GLfloat lightSpecular[] = {0.5, 0.5, 0.5, 1.0};

glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);

glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);

glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpecular);

(4)设置光源的位置和方向

– 平行光 – 没有位置只有方向

GLfloat lightPosiTIon[] = {8.5, 5.0, -2.0, 0.0}; // w=0.0

glLightfv(GL_LIGHT0, GL_POSITION, lightPosiTIon);

– 点光源 – 有位置没有方向

GLfloat lightPosiTIon[] = {8.5, 5.0, -2.0, 1.0}; // w不为0

glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);

– 聚光灯 – 有位置有方向
GLfloat lightPosition[] = {-6.0, 1.0, 3.0, 1.0}; // w不为0

glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);

GLfloat lightDirection[] = {1.0, 1.0, 0.0};

glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, lightDirection); // 聚光灯主轴方向 spot direction

glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0); // cutoff角度 spot cutoff

** 平行光不会随着距离d增加而衰减,但点光源和聚光灯会发生衰减。

attenuation为衰变系数,系数值越大,衰变越快。

默认情况下,c=1.0, l=0.0, q=0.0
glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 2.0); // c 系数

glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0); // l 系数

glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.5); // q 系数


具体编程代码,请查看附件,仅供学习交流。


.h文件
#ifndef MYGLWIDGET_H
#define MYGLWIDGET_H

#include <QOpenGLWidget>
#include <QKeyEvent>
#include <GL/glu.h>
#include <QMessageBox>
#include <QApplication>
#include <QSound>
#include <QOpenGLExtraFunctions>

class MyGLWidget : public QOpenGLWidget
{
Q_OBJECT

public:
//接下来需要一个结构来记录游戏中的对象。fx和fy每次在网格上移动英雄和敌人一些较小的象素,以创建一个平滑的动画效果。
//x和y则记录着对象处于网格的那个交点上。
//上下左右各有11个点,因此x和y可以是0到10之间的任意值。这也是为什么需要fx和fy的原因。
//考虑如果只能够在上下和左右方向的11个点间移动的话,我们的英雄不得不
//在各个点间跳跃前进。这样显然是不够平滑美观的。
//最后一个变量spin用来使对象在Z轴上旋转。
struct Object // 记录游戏中的对象
{
int fx, fy; // 使移动变得平滑
int x , y; // 当前游戏者的位置
float spin; // 旋转方向
Object()
{
fx = 0;
fy = 0;
x = 0;
y = 0;
spin = 0.0f;
}
};

MyGLWidget(QWidget *parent = nullptr);
~MyGLWidget();

protected:
void resizeGL(int w, int h);
void initializeGL();
void paintGL();
void keyPressEvent(QKeyEvent *event);
void timerEvent(QTimerEvent *event);

private:
void resetObjects();
void loadGLTexture();
void buildFont();
void glPrint(GLuint x, GLuint y, int set, const char *string);
void resetLines();

private:
bool m_show_full_screen;

//bool类型的变量,vline保存了组成游戏网格垂直方向上的121条线,上下水平各11条。hline保存了水平方向上的 121条线,
//用ap来检查A键是否已经按下。
//当网格被填满时, filled被设置为TRUE而反之则为FALSE。gameover这个变量的作用显而易见,当他的值为TRUE时,游戏结束。
//anti指出抗锯齿功能是否打开,当设置为TRUE时,该功能是打开着的。
bool m_vline[11][10];        // 保存垂直方向的11根线条中,每根线条中的10段是否被走过
bool m_hline[10][11];        //保存水平方向的11根线条中,每根线条中的10段是否被走过
bool m_filled;               // 网格是否被填满
bool m_gameover;             // 游戏是否结束
bool m_anti;                 // 是否启用反走样

//delay 是一个计数器,用他来减慢那些坏蛋的动作。当delay的值大于某一个馈值的时候,敌人才可以行动,此时delay将被重置。
int m_delay;
//把lives的值设置成5,这样英雄一出场就拥有5条命。level是一个内部变量,用来指出当前游戏的难度。
//当然,这并不是你在屏幕上所看到的那个Level。变量level2开始的时候和Level拥有相同的值,但是随着你技能的提高,这个值也会增加。
//当你成功通过难度3之后,这个值也将在难度3上停止增加。level 是一个用来表示游戏难度的内部变量,stage才是用来记录当前游戏关卡的变量。
int m_lives;         // 玩家的生命
int m_level;         // 内部游戏的等级
int m_stage;         // 游戏的关卡

//既然已经为玩家,敌人,甚至是秘密武器。设置了结构体,那么同样的,为了表现刚刚创设的结构体的功能和特性,
//也可以为此设置新的结构体。
//为玩家创设结构体之下的第一条直线。基本上将会为玩家提供fx,fy,x,y和spin值几种不同的结构体。
//通过增加这些直线,仅需查看玩家的x值就很容易取得玩家的位置,同时也可以通过增加玩家的旋转度来改变玩家的spin值。
//第二条直线略有不同。因为同一屏幕我们可以同时拥有至多15个敌人。需要为每个敌人创造上面所提到的可变量。
//通过设置一个有15个敌人的组来实现这个目标,如第一个敌人的位置被设定为敌人(0).x.第二个敌人的位置为(1),x等等
//第三条直线使得为宝物创设结构体实现了可能。宝物是一个会时不时在屏幕上出现的沙漏。需要通过沙漏来追踪x和y值。
//但是因为沙漏的位置是固定的所以不需要寻找最佳位置,而通过为程序后面的其他物品寻找好的可变量来实现(如fx和fy)
Object m_player;       // 玩家信息
Object m_enemy[9];     // 最多9个敌人的信息
Object m_hourglass;    // 宝物信息

GLuint  m_texture[2];  // 字符纹理
GLuint  m_base;        // 字符显示列表的开始值

QSound  m_freezeSound;

};
#endif // MYGLWIDGET_H




.cpp文件

#include “myglwidget.h”

//直线,反走样,正投影,计时,基本的音效和一个简单的游戏逻辑。
//花了两天的时间写代码,并用了两周的时间写这份HTML文件
//结尾你将获得一个叫"amidar"的游戏,你的任务是走完所有的直线。
//这个程序有了一个基本游戏的一切要素,关卡,生命值,声音和一个游戏道具。

MyGLWidget::MyGLWidget(QWidget *parent) : QOpenGLWidget(parent),
m_show_full_screen(false), m_filled(false), m_gameover(false),
m_anti(true), m_lives(5), m_level(1), m_stage(1), m_freezeSound(":/voice/Freeze.wav")
{
showNormal();
resetObjects();
resetLines();
startTimer(15);
}

MyGLWidget::~MyGLWidget()
{
glDeleteTextures(2, &m_texture[0]);
glDeleteLists(m_base, 256);
}

//下面的代码基本没有变化,只是把透视投影变为了正投影
void MyGLWidget::resizeGL(int w, int h)
{
if (h==0)
{
h=1;
}
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f,w,h,0.0f,-1.0f,1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

//初始化的代码和前面的代码相比没有什么改变
void MyGLWidget::initializeGL()
{
loadGLTexture();
buildFont();
glShadeModel(GL_SMOOTH);
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glClearDepth(1.0f);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

//首先清空缓存,接着绑定字体的纹理,绘制游戏的提示字符串
void MyGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, m_texture[0]); // 选择字符纹理
glColor3f(1.0f,0.5f,1.0f);
glPrint(207, 24, 0, “GRID CRAZY”) ; // 绘制游戏名称"GRID CRAZY"
glColor3f(1.0f,1.0f,0.0f);
QString levelStr = QString(“Level:%1”).arg(m_level);
QString stagesTR = QString(“Stage:%1”).arg(m_stage);
glPrint(20,20,1,levelStr.toLatin1().data()); // 绘制当前的级别
glPrint(20,40,1,stagesTR.toLatin1().data()); // 绘制当前级别的关卡
//现在检测游戏是否结束,如果游戏结束绘制"Gmae over"并提示玩家按空格键重新开始
if (m_gameover) // 游戏是否结束
{
glColor3ub(qrand()%255, qrand()%255, qrand()%255); // 随机选择一种颜色
glPrint(472,20,1, “GAME OVER”); // 绘制 GAME OVER 字符串到屏幕
glPrint(456,40,1, “PRESS SPACE”); // 提示玩家按空格键重新开始
}
//在屏幕的右上角绘制玩家的剩余生命
for (int i = 0, iend = m_lives - 1; i < iend; i++) //循环绘制玩家的剩余生命
{
glLoadIdentity();
glTranslatef(490+(i40.0f),40.0f,0.0f); // 移动到屏幕右上角
glRotatef(-m_player.spin,0.0f,0.0f,1.0f); // 旋转绘制的生命图标
glColor3f(0.0f,1.0f,0.0f); // 绘制玩家生命
glBegin(GL_LINES); // 绘制玩家图标
glVertex2d(-5,-5);
glVertex2d( 5, 5);
glVertex2d( 5,-5);
glVertex2d(-5, 5);
glEnd();
glRotatef(-m_player.spin
0.5f,0.0f,0.0f,1.0f);
glColor3f(0.0f,0.75f,0.0f);
glBegin(GL_LINES);
glVertex2d(-7, 0);
glVertex2d( 7, 0);
glVertex2d( 0,-7);
glVertex2d( 0, 7);
glEnd();
}
//下面来绘制网格,设置变量filled为TRUE,这告诉程序填充网格。
//接着把线的宽度设置为2,并把线的颜色设置为蓝色,接着检测线断是否被走过,如果走过设置颜色为白色。
m_filled = true; // 在测试前,把填充变量设置为TRUE
glLineWidth(2.0f); // 设置线宽为2.0f
glDisable(GL_LINE_SMOOTH); // 禁用反走样
glLoadIdentity();
for (int i = 0, iend = 11; i < iend; i++) // 循环11根线
{
for (int j = 0, jend = 11; j < jend; j++) // 循环每根线的线段
{
glColor3f(0.0f,0.5f,1.0f); // 设置线为蓝色
if (m_hline[i][j]) // 是否走过
{
glColor3f(1.0f,1.0f,1.0f); // 是,设线为白色
}
if (i<10) // 绘制水平线
{
if (!m_hline[i][j]) // 如果当前线段没有走过,则不填充
{
m_filled = false;
}
glBegin(GL_LINES); // 绘制当前的线段
glVertex2d(20+(i60),70+(j40));
glVertex2d(80+(i60),70+(j40));
glEnd();
}
//下面的代码绘制垂直的线段
glColor3f(0.0f,0.5f,1.0f); // 设置线为蓝色
if (m_vline[i][j]) // 是否走过
{
glColor3f(1.0f,1.0f,1.0f); // 是,设线为白色
}
if (j<10) // 绘制垂直线
{
if (!m_vline[i][j]) // 如果当前线段没有走过,则不填充
{
m_filled = false;
}
glBegin(GL_LINES); // 绘制当前的线段
glVertex2d(20+(i60),70+(j40));
glVertex2d(20+(i60),110+(j40));
glEnd();
}

        //接下来检测长方形的四个边是否都被走过,如果被走过就绘制一个带纹理的四边形。
        //用下图来解释这个检测过程
        //如果对于垂直线vline的相邻两个边都被走过,并且水平线hline的相邻两个边也被走过,那么可以绘制这个四边形了。
        //可以使用循环检测每一个四边形,代码如下:
        glEnable(GL_TEXTURE_2D);				     // 使用纹理映射
        glColor3f(1.0f,1.0f,1.0f);				     // 设置为白色
        glBindTexture(GL_TEXTURE_2D, m_texture[1]);	     // 绑定纹理
        if ((i<10) && (j<10))				            // 绘制走过的四边形
        {
            // 这个四边形是否被走过
            if (m_hline[i][j] && m_hline[i][j+1] && m_vline[i][j] && m_vline[i+1][j])
            {
                glBegin(GL_QUADS);			            // 是,则绘制它
                    glTexCoord2f(float(i/10.0f)+0.1f,1.0f-(float(j/10.0f)));
                    glVertex2d(20+(i*60)+59,(70+j*40+1));
                    glTexCoord2f(float(i/10.0f),1.0f-(float(j/10.0f)));
                    glVertex2d(20+(i*60)+1,(70+j*40+1));
                    glTexCoord2f(float(i/10.0f),1.0f-(float(j/10.0f)+0.1f));
                    glVertex2d(20+(i*60)+1,(70+j*40)+39);
                    glTexCoord2f(float(i/10.0f)+0.1f,1.0f-(float(j/10.0f)+0.1f));
                    glVertex2d(20+(i*60)+59,(70+j*40)+39);
                glEnd();
            }
        }
        glDisable(GL_TEXTURE_2D);
    }
}
glLineWidth(1.0f);

//下面的代码用来设置是否启用直线反走样
if (m_anti)					                  // 是否启用反走样
{
    glEnable(GL_LINE_SMOOTH);
}
//为了使游戏变得简单些,添加了一个时间停止器,当你吃掉它时,可以让追击的你的敌人停下来。
//下面的代码用来绘制一个时间停止器。
if (m_hourglass.fx==1)
{
    glLoadIdentity();
    glTranslatef(20.0f+(m_hourglass.x*60),70.0f+(m_hourglass.y*40),0.0f);
    glRotatef(m_hourglass.spin,0.0f,0.0f,1.0f);
    glColor3ub(qrand()%255,qrand()%255,qrand()%255);
    glBegin(GL_LINES);
        glVertex2d(-5,-5);
        glVertex2d( 5, 5);
        glVertex2d( 5,-5);
        glVertex2d(-5, 5);
        glVertex2d(-5, 5);
        glVertex2d( 5, 5);
        glVertex2d(-5,-5);
        glVertex2d( 5,-5);
    glEnd();
}

//接下来绘制玩家
glLoadIdentity();
glTranslatef(m_player.fx+20.0f, m_player.fy+70.0f, 0.0f);	// 设置玩家的位置
glRotatef(m_player.spin, 0.0f, 0.0f, 1.0f);                   // 旋转动画
glColor3f(0.0f,1.0f,0.0f);
glBegin(GL_LINES);
    glVertex2d(-5,-5);
    glVertex2d( 5, 5);
    glVertex2d( 5,-5);
    glVertex2d(-5, 5);
glEnd();

//绘制玩家的显示效果,让它看起来更好看些(其实没用)
glRotatef(m_player.spin*0.5f, 0.0f, 0.0f, 1.0f);
glColor3f(0.0f,0.75f,0.0f);
glBegin(GL_LINES);
    glVertex2d(-7, 0);
    glVertex2d( 7, 0);
    glVertex2d( 0,-7);
    glVertex2d( 0, 7);
glEnd();

//接下来绘制追击玩家的敌人
for (int i = 0, iend = m_stage*m_level; i < iend; i++)
{
    glLoadIdentity();
    glTranslatef(m_enemy[i].fx+20.0f, m_enemy[i].fy+70.0f, 0.0f);
    glColor3f(1.0f,0.5f,0.5f);
    glBegin(GL_LINES);
        glVertex2d( 0,-7);
        glVertex2d(-7, 0);
        glVertex2d(-7, 0);
        glVertex2d( 0, 7);
        glVertex2d( 0, 7);
        glVertex2d( 7, 0);
        glVertex2d( 7, 0);
        glVertex2d( 0,-7);
    glEnd();

    //下面的代码绘制敌人的显示效果,让其更好看。
    glRotatef(m_enemy[i].spin,0.0f,0.0f,1.0f);
    glColor3f(1.0f,0.0f,0.0f);
    glBegin(GL_LINES);
        glVertex2d(-7,-7);
        glVertex2d( 7, 7);
        glVertex2d(-7, 7);
        glVertex2d( 7,-7);
    glEnd();
}

}

void MyGLWidget::keyPressEvent(QKeyEvent event)
{
switch(event->key())
{
case Qt::Key_F2:
{
m_show_full_screen = !m_show_full_screen;
if(m_show_full_screen)
{
showFullScreen();
}
else
{
showNormal();
}
update();
break;
}
case Qt::Key_Escape:
{
qApp->exit();
break;
}
case Qt::Key_A:
{
//按A键切换是否启用反走样
m_anti = !m_anti;
break;
}
//使用上,下,左,右控制玩家的位置
case Qt::Key_Up:
{
if (m_player.y > 0 && m_player.fx==m_player.x
60 && m_player.fy==m_player.y40)
{
m_player.y–;
m_vline[m_player.x][m_player.y] = true;
}
break;
}
case Qt::Key_Down:
{
if (m_player.y<10 && m_player.fx==m_player.x
60 && m_player.fy==m_player.y40)
{
m_vline[m_player.x][m_player.y] = true;
m_player.y++;
}
break;
}
case Qt::Key_Left:
{
if (m_player.x>0 && m_player.fx==m_player.x
60 && m_player.fy==m_player.y40)
{
m_player.x–;
m_hline[m_player.x][m_player.y] = true;
}
break;
}
case Qt::Key_Right:
{
if (m_player.x<10 && m_player.fx==m_player.x
60 && m_player.fy==m_player.y*40)
{
m_hline[m_player.x][m_player.y] = true;
m_player.x++;
}
break;
}
case Qt::Key_Space:
{
if(m_gameover)
{
//如果游戏结束,按空格开始新的一局游戏
m_gameover = false; // 开始新的一局
m_filled = true; // 重置所有的变量
m_level=1;
m_stage=0;
m_lives=5;
}
break;
}
}
QOpenGLWidget::keyPressEvent(event);
}

void MyGLWidget::timerEvent(QTimerEvent event)
{
if (!m_gameover) // 如果游戏没有结束,则进行游戏循环
{
for(int i = 0, iend = m_stage
m_level; i < iend; i++)
{
//根据玩家的位置,让敌人追击玩家
if ((m_enemy[i].x<m_player.x) && (m_enemy[i].fy==m_enemy[i].y40))
{
m_enemy[i].x++;
}
if ((m_enemy[i].x>m_player.x) && (m_enemy[i].fy==m_enemy[i].y
40))
{
m_enemy[i].x–;
}
if ((m_enemy[i].y<m_player.y) && (m_enemy[i].fx==m_enemy[i].x60))
{
m_enemy[i].y++;
}
if ((m_enemy[i].y>m_player.y) && (m_enemy[i].fx==m_enemy[i].x
60))
{
m_enemy[i].y–;
}
//如果时间停止器的显示时间结束,而玩家又没有吃到,那么重置计时计算器。
if (m_delay > (3 - m_level) && (m_hourglass.fx!=2)) // 如果没有吃到时间停止器
{
m_delay=0; // 重置时间停止器
for (int j = 0, jend = m_stagem_level; j < jend; j++) // 循环设置每个敌人的位置
{
//下面的代码调整每个敌人的位置,并绘制它们的显示效果
if (m_enemy[j].fx<m_enemy[j].x
60)
{
m_enemy[j].fx+=5;
m_enemy[j].spin+=5;
}
if (m_enemy[j].fx>m_enemy[j].x60)
{
m_enemy[j].fx-=5;
m_enemy[j].spin-=5;
}
if (m_enemy[j].fy<m_enemy[j].y
40)
{
m_enemy[j].fy+=5;
m_enemy[j].spin+=5;
}
if (m_enemy[j].fy>m_enemy[j].y40)
{
m_enemy[j].fy-=5;
m_enemy[j].spin-=5;
}
}
}
//如果敌人的位置和玩家的位置相遇,这玩家死亡,开始新的一局
if ((m_enemy[i].fx==m_player.fx) && (m_enemy[i].fy==m_player.fy))
{
m_lives–; // 如果是,生命值减1
if (m_lives==0) // 如果生命值为0,则游戏结束
{
m_gameover= true;
}
resetObjects(); // 如果生命值为0,则游戏结束
QSound::play(":/voice/Die.wav"); // 播放死亡的音乐
}
}
//调整玩家的位置,让动画看起来跟自然
if (m_player.fx<m_player.x
60)
{
m_player.fx+=5;
}
if (m_player.fx>m_player.x60)
{
m_player.fx-=5;
}
if (m_player.fy<m_player.y
40)
{
m_player.fy+=5;
}
if (m_player.fy>m_player.y40)
{
m_player.fy-=5;
}
}
//如果顺利通过本关,播放通关音乐,并提高游戏难度,开始新的一局
if (m_filled) // 所有网格是否填满
{
QSound::play(":/voice/Complete.wav");
m_stage++; // 增加游戏难度
if(m_stage>3) // 如果当前的关卡大于3,则进入到下一个大的关卡?
{
m_stage=1; // 重置当前的关卡
m_level++; // 增加大关卡的值
if (m_level>3)
{
m_level=3; // 如果大关卡大于3,则不再增加
m_lives++; // 完成一局给玩家奖励一条生命
if (m_lives>5) // 如果玩家有5条生命,则不再增加
{
m_lives=5;
}
}
}
//进入到下一关卡,重置所有的游戏变量
resetObjects();
resetLines();
}
//如果玩家吃到时间停止器,记录这一信息
if ((m_player.fx==m_hourglass.x
60) && (m_player.fy==m_hourglass.y40) && (m_hourglass.fx==1))
{
// 播放一段声音
m_freezeSound.setLoops(999);
m_freezeSound.play();
m_hourglass.fx=2; // 设置fx为2,表示吃到时间停止器
m_hourglass.fy=0; // 设置fy为0
}
//显示玩家的动画效果
m_player.spin+=0.5f
5; // 旋转动画
if(m_player.spin > 360.0f)
{
m_player.spin-=360;
}
//显示时间停止器的动画
m_hourglass.spin-=0.25f*5; // 旋转动画
if (m_hourglass.spin<0.0f)
{
m_hourglass.spin+=360.0f;
}
//下面的代码计算何时出现一个时间停止计数器
m_hourglass.fy+=5; // 增加fy的值,当他大于一定的时候,产生时间停止计数器
if ((m_hourglass.fx==0) && (m_hourglass.fy>6000/m_level))
{
QSound::play(":/voice/Hourglass.wav");
m_hourglass.x=qrand()%10+1;
m_hourglass.y=qrand()%11;
m_hourglass.fx=1; //fx=1表示时间停止器出现
m_hourglass.fy=0;
}

//如果玩家没有拾取时间停止器,则过一段时间后,它自动消失
if ((m_hourglass.fx==1) && (m_hourglass.fy>6000/m_level))
{
    m_hourglass.fx=0;					// 消失后重置时间停止器
    m_hourglass.fy=0;
}
//如果玩家吃到时间停止器,在时间停止停止阶段播放一段音乐,过一段时间停止播放音乐
if ((m_hourglass.fx==2) && (m_hourglass.fy>500+(500*m_level)))
{
    m_freezeSound.stop();
    m_hourglass.fx=0;					// 重置变量
    m_hourglass.fy=0;
}
//增加敌人的延迟计数器的值,这个值用来更新敌人的运动
m_delay++;						// 增加敌人的延迟计数器的值
update();
QOpenGLWidget::timerEvent(event);

}

//在下面的代码里,把玩家重置在屏幕的左上角,而给敌人设置一个随机的位置。
void MyGLWidget::resetObjects()
{
m_player.x=0; // 把玩家置于左上角
m_player.y=0;
m_player.fx=0;
m_player.fy=0;
//接着给敌人一个随机的开始位置,敌人的数量等于难度乘上当前关卡号。记着,难度最大是3,而最多有3关。因此敌人最多有9个。
for (int i = 0, iend = m_stagem_level; i < iend; i++) // 循环随即放置所有的敌人
{
m_enemy[i].x = 5 + qrand()%6;
m_enemy[i].y = qrand()%11;
m_enemy[i].fx = m_enemy[i].x
60;
m_enemy[i].fy = m_enemy[i].y*40;
}
}

void MyGLWidget::loadGLTexture()
{
QVector<QImage> images;
images.push_back(QImage(":/image/Font.bmp"));
images.push_back(QImage(":/image/Image.bmp"));
for(int i = 0, iend = images.count(); i < iend; i++)
{
images[i] = images[i].convertToFormat(QImage::Format_RGB888);
images[i] = images[i].mirrored();
}

glGenTextures(2, &m_texture[0]);
for(int i = 0, iend = images.count(); i < iend ;i++)
{
    glBindTexture(GL_TEXTURE_2D, m_texture[i]);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, images[i].width(), images[i].height(),
                 0, GL_RGB, GL_UNSIGNED_BYTE, images[i].bits());
}

}

//下面的代码建立了显示列表。对于字体的显示。在这里把字体图象分成16×16个单元共256个字符。
void MyGLWidget::buildFont()
{
m_base = glGenLists(256);
glBindTexture(GL_TEXTURE_2D, m_texture[0]);
for (int i = 0, iend = 256; i < iend; i++)
{
float cx=float(i%16)/16.0f;
float cy=float(i/16)/16.0f;
glNewList(m_base+i,GL_COMPILE);
glBegin(GL_QUADS);
glTexCoord2f(cx,1.0f-cy-0.0625f);
glVertex2d(0,16);
glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f);
glVertex2i(16,16);
glTexCoord2f(cx+0.0625f,1.0f-cy);
glVertex2i(16,0);
glTexCoord2f(cx,1.0f-cy);
glVertex2i(0,0);
glEnd();
glTranslated(15,0,0);
glEndList();
}
}

//函数没有做太多改变。唯一的改动是它可以打印变量了。把代码列出这样你可以容易看到改动的地方。
//请注意,在这里激活了纹理并且重置了视图矩阵。如果set被置1的话,字体将被放大。这样做是希望可以在屏幕上显示大一点的字符。
//在一切结束后,禁用纹理。
void MyGLWidget::glPrint(GLuint x, GLuint y, int set, const char string)
{
if (set>1)
{
set = 1;
}
glEnable(GL_TEXTURE_2D);
glLoadIdentity();
glTranslated(x,y,0);
glListBase(m_base-32+(128
set));
if(set==0)
{
glScalef(1.5f,2.0f,1.0f);
}
glCallLists(strlen(string),GL_UNSIGNED_BYTE, string);
glDisable(GL_TEXTURE_2D);
}

void MyGLWidget::resetLines()
{
for (int i = 0, iend = 11; i<iend; i++)
{
for (int j = 0, jend = 11; j<jend; j++)
{
if (i<10)
{
m_hline[i][j] = false;
}
if (j<10)
{
m_vline[i][j] = false;
}
}
}
}

//它开始于一个简单的直线教程,结束与一个小型的游戏。希望它能给你一些有用的信息,
//你们中大部分喜欢那些基于“贴图”的游戏,但这些将教会你关于游戏更多的东西。
//尽量去注释每一行代码,知道程序运行的一切细节,但把它表达出来又是另一回事。如果你有更好的表达能力,



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。