【游戏制作】 从零开始的Qt5贪吃蛇代码分析

来源:互联网 发布:java web书籍 编辑:程序博客网 时间:2024/04/30 13:19

悲伤的现实

离期末第一场考试之剩下32天了,而oop的期中project还有24天截止。此刻,我们刚刚写完了人生中第一个c++程序,分数类的运算符重载。继承和多态还没学。尽管如此,也是时候作为全裸勇者勇敢地去挑战大魔王了。

目标

通过研究贪吃蛇小游戏的源码,学会用Qt进行游戏开发,并且完成期中Pro报告中挖下的2D Rougelike类小游戏第一关的大坑,并且写下博客给新鲜的学妹学弟留下可以借鉴的学习经验。

资料

Qt5贪吃蛇小游戏源代码下载处
来自Rimond_Jing的Qt5基本安装教程
来自 齐亮,非常棒的一个参考资料

高能预警

因为目前c++也没学好,Qt也刚开始学,所以本篇博客只适合和我一样什么都不会的人。而且很有可能错误连篇,期待有人能指正。
目前在一遍分析一遍写博客,所以会对内容进行不断地修正

以及非常啰嗦(和废话)

总体分析

注:纯新手一上来就看总体分析极有可能不懂,看看就好,不懂就跳过去看完全文回来再看。

界面转换

在本例子中,采取建立不同的界面类(mainWidget,开始界面,和GameWidget,游戏界面),通过接收事件(event,本例是按下按钮),利用信号和槽,来进行界面切换。

main函数分析

感觉并不需要分析:)

#include "mainWidget.h"//自己写的主窗口的头文件#include <QApplication>//一个基础的类,所有工程(pro文件)都要include。//有时候include这个文件会报错,可能是因为是Qt4的代码的原因int main(int argc, char *argv[]){    QApplication a(argc, argv);    mainWidget w;    w.show();    return a.exec();}

主窗口分析:mainWidget

头文件分析

#ifndef MAINWIDGET_H#define MAINWIDGET_H#include "GameWidget.h"#include <QWidget>#include <QIcon>#include <QPalette>#include <QBrush>#include <QPixmap>#include <QPushButton>#include <QMessageBox>#include <QLabel>#include <QFont>class mainWidget : public QWidget//继承了QWidget类{    Q_OBJECT//只要有槽和信号机制,就要写Q_OBJECTpublic:    mainWidget(QWidget *parent = 0);    ~mainWidget();    //void resizeEvent(QResizeEvent *);private:    QPushButton *startbtn;//一个按钮,用鼠标点击后会开始游戏    QPushButton *exitbtn;//一个按钮,用鼠标点击后会退出    GameWidget *g;    QLabel *label;signals://信号,mainWidget不会发出信号public slots://可以接收所有信号的公共槽    void exitSlot();//用来接收退出信号的槽    void startSlot();//用来接收开始信号的槽};#endif // MAINWIDGET_H

.cpp文件分析
注释的都是可以在自己的程序中使用的函数

#include "mainWidget.h"mainWidget::mainWidget(QWidget *parent): QWidget(parent){    this->resize(480,270);//resize函数,用来设置mainWidget这个窗口的大小    this->setMaximumSize(480,270);    this->setWindowIcon(QIcon(":/new/prefix1/img/icon.png"));//设置ICON    this->setWindowTitle("贪吃蛇");    QPalette palette;    palette.setBrush(QPalette::Background,QBrush(QPixmap(":/new/prefix1/img/back.jpg").scaled(this->size())));    this->setPalette(palette);    startbtn=new QPushButton(this);    startbtn->setIcon(QIcon(":/new/prefix1/img/start.png"));    startbtn->setIconSize(QSize(75,75));    startbtn->setGeometry(QRect(250,170,75,75));    startbtn->setFlat(true);    exitbtn=new QPushButton(this);    exitbtn->setIcon(QIcon(":/new/prefix1/img/quit.png"));    exitbtn->setIconSize(QSize(70,70));    exitbtn->setGeometry(QRect(350,170,70,70));    exitbtn->setFlat(true);    //设置说明标签    QFont font;    font.setFamily("Consolas");    font.setBold(true);    font.setPixelSize(13);    label=new QLabel(this);    label->setText("游戏说明:贪吃蛇游戏可使用按钮或者w a s d控制蛇的走动");    label->setFont(font);    label->setGeometry(QRect(10,10,400,50));    connect(exitbtn,SIGNAL(clicked()),this,SLOT(exitSlot()));    connect(startbtn,SIGNAL(clicked()),this,SLOT(startSlot()));}mainWidget::~mainWidget(){    delete startbtn;    delete exitbtn;}void mainWidget::exitSlot(){    if(QMessageBox::question(this,"退出游戏","是否退出当前游戏",QMessageBox::Yes|QMessageBox::No)==QMessageBox::Yes)    {        delete this;        exit(0);    }}void mainWidget::startSlot(){    g=new GameWidget(this);    g->show();}//重写resizeEvent/*void mainWidget::resizeEvent(QResizeEvent *){    QPalette palette;    palette.setBrush(QPalette::Background,QBrush(QPixmap("img/back.jpg").scaled(this->size())));    this->setPalette(palette);    startbtn->setGeometry(QRect(this->size().width()-230,this->size().height()-100,75,75));    exitbtn->setGeometry(QRect(this->size().width()-130,this->size().height()-100,70,70));}*/

QMessageBox的初始化及效果

void mainWidget::exitSlot(){    if(QMessageBox::question(this,"退出游戏","是否退出当前游戏",QMessageBox::Yes|QMessageBox::No)==QMessageBox::Yes)    {        delete this;        exit(0);//直接结束进程    }    //生成一个弹窗。question的初始化为(链接的窗口,"标题","示意文字",QMessageBox::Yes|QMessageBox::No)}

QMessageBox

游戏窗口分析:GameWidgit

游戏窗口是这个程序的主体部分,各种复杂的事件(槽和信号机制),状态的更新,动画、碰撞检测都是在这个类(游戏窗口就是一个继承了普通窗口< Widget >的派生类)中完成的。

头文件分析

#ifndef GAMEWIDGET_H#define GAMEWIDGET_H#include <QWidget>//既然继承自QWidget,肯定需要include相关的头文件#include <QPalette>#include <QIcon>//这些是为了让你在显示背景图片和窗口图标,一般做游戏(自己设计UI)都要用#include <QPushButton>//这个库在你有鼠标事件的事后非常好用#include <QPainter>//如果有动画,需要包含QPainter库(如果只是显示图片可以用其他方法,但游戏一般都有动画)#include <QDebug>//主要用于调试代码,类似于std::cout的替代品,支持QT的数据类型。#include <QTime>#include <QTimer>//和计时有关的库,做游戏也一般都要用到#include <QMessageBox>//如果你的游戏有弹出框,就要包含(非必须)#include <QKeyEvent>//如果你的游戏可以用键盘操纵,需要包含#include <QLabel>//可以用来显示字(也可以显示图片)#include <QFont>//如果你要用Qt绘制字(而不是用自己设计的UI)时,需要包含#include <QSound>//音频相关,游戏一般必须class GameWidget : public QWidget{    Q_OBJECTpublic:    //构造函数    explicit GameWidget(QWidget *parent = 0);    ~GameWidget();    //绘制事件,游戏中非常重要的函数    void paintEvent(QPaintEvent *);    //键盘事件    void keyPressEvent(QKeyEvent *);private:    //按钮相当于鼠标事件    QPushButton *upbtn;    QPushButton *leftbtn;    QPushButton *downbtn;    QPushButton *rightbtn;    QPushButton *startbtn;    QPushButton *returnbtn;    //记录蛇目前运动方向    int direction;    //用来记录蛇xy坐标,可以看出最大能得100分    int snake[100][2];    int snake1[100][2];    //计算吃过了几个食物    int foodcount;    //计时器!和动画的帧数也有关    QTimer *timer;    //食物的属性    int foodx,foody;    int score;    int level;    QLabel *scorelabel;    QLabel *levellabel;    QLabel *scoreshow;    QLabel *levelshow;    QString str1,str2;    QSound *sound;    QSound *sound1;signals://因为有键盘事件(KeyPressEvent),键盘事件要发射不同的信号    void UpSignal();    void DownSignal();    void LeftSignal();    void RightSignal();public slots:    //一般来说有几个按钮就要有几个槽函数    void upbtnSlot();    void leftbtnSlot();    void rightbtnSlot();    void downbtnSlot();    void startbtnSlot();    void returnbtnSlot();    //本例的主函数,和timer结合使用(比较复杂,之后会详细讲)    void timeoutSlot();};#endif // GAMEWIDGET_H

QDebug的说明

.c文件分析
因为GameWidget略长,且有一些部分和MainWidget用法一致,就不放上来全部代码(想要自己运行的可以从之前附上的链接下载,记得只适用于QT5
注: 需要include相应的库文件

1.如何让你的游戏发出声音
首先要载入音频

    //声音区:载入音频,注意音频文件要加入.qrc文件中    sound=new QSound(":/listen/img/5611.wav");    sound1=new QSound(":/listen/img/die.wav");

在什么时候播放声音,使用if语句判断,一般是一个可以update()的槽函数中(本例为timeoutSlot(),见下文)

sound->play();

2.如何即时显示得分

    str1=QString::number(score);//转化为字符串    scoreshow=new QLabel(this);//用Label输出    scoreshow->setFont(font);    scoreshow->setGeometry(QRect(385,1,60,30));    scoreshow->setText(str1);

3.如何设置交互(信号和槽机制)

    //设置按钮操作    connect(leftbtn,SIGNAL(clicked()),this,SLOT(leftbtnSlot()));    connect(rightbtn,SIGNAL(clicked()),this,SLOT(rightbtnSlot()));    connect(upbtn,SIGNAL(clicked()),this,SLOT(upbtnSlot()));    connect(downbtn,SIGNAL(clicked()),this,SLOT(downbtnSlot()));    connect(startbtn,SIGNAL(clicked()),this,SLOT(startbtnSlot()));    connect(returnbtn,SIGNAL(clicked()),this,SLOT(returnbtnSlot()));    //设置键盘操作    connect(this,SIGNAL(UpSignal()),upbtn,SLOT(click()));    connect(this,SIGNAL(DownSignal()),downbtn,SLOT(click()));    connect(this,SIGNAL(LeftSignal()),leftbtn,SLOT(click()));    connect(this,SIGNAL(RightSignal()),rightbtn,SLOT(click()));

connect的使用

connect(发射信号的类,SIGNAL(信号函数),接收信号的类,SLOT(槽函数));

按钮操作的实现
QPushButton这个类中有click()的信号函数和槽函数,如果发生了鼠标点击事件,被点击的Button就发射click()信号,因为我们用connect将这个信号和GameWidget中的槽函数(比如leftbtnSlot())连接起来,GameWidget就能接收到这个信号并且调用相应的槽函数。
键盘操作同理,因为KeyPressEvent是我们自己写在GameWidget类中,所以为 ‘this’ 指针。这次connect是把键盘事件和按钮联系了起来。

4.如何设置随机数

    QTime t;    t= QTime::currentTime();    qsrand(t.msec()+t.second()*1000);

5.如何update程序

    timer=new QTimer(this);    timer->setInterval(500);//设置时间间隔    connect(timer,SIGNAL(timeout()),this,SLOT(timeoutSlot()));

该程序设置了一个timer(一个定时触发器,参考这里),并且设置了时间间隔,每过一次时间间隔发送一个timeout()信号。
而GameWidget类中定义了timeoutSlot()槽函数,每过一定时间间隔被调用,执行update()操作,来更新界面状态,下文会讲

6.(重点)timeoutSlot分析
因为这一段很长,也只写出代码结构(需要完整代码见开头链接)

void GameWidget::timeoutSlot(){    //判断是否吃到食物的代码块(判断蛇的坐标和食物的坐标是否重合)    //其中包含,发出声音、更新食物坐标(随机更新,食物不能出现在蛇身上)、如果分数到达一定的档次,就提升游戏等级(改变时间间隔)    memcpy(snake1,snake,sizeof(snake));//不能直接对目前显示的数组进行操作,因此一开始就定义了两个数组存放当前状态和改变后的状态,用memcpy拷贝    //实现蛇的游动    for(int i=foodcount;i>=1;i--)    {        snake[i][0]=snake[i-1][0];        snake[i][1]=snake[i-1][1];    }    switch(direction)    {    case UP:snake[0][1]--;break;    case DOWN:snake[0][1]++;break;    case LEFT:snake[0][0]--;break;    case RIGHT:snake[0][0]++;break;    }    //判断蛇是否撞到自身的代码块,如果撞到,显示游戏结束的弹窗,并重置游戏    //判断蛇是否撞到墙体的代码块    //最关键,调用update()函数,更新窗口状态    this->update();}

memcpy辅助理解
update辅助理解
其中的碰撞检测机制是通过判断xy坐标是否相同来进行的。
Qt工具书上似乎有其他碰撞检测的办法,挖个坑

7.(重点)paintEvent分析
函数声明

void GameWidget::paintEvent(QPaintEvent *);

要定义一个绘制器painter

QPainter painter(this);

画一个小方格(长方形)的代码(该游戏画了很多很多小方格),只摘取一个:j,i是坐标,20, 20是方格的大小

painter.drawRect(j,i,20,20);

读取素材绘制
foodx要乘20的原因是这是相对坐标,而不是绝对坐标

painter.drawImage(foodx*20,foody*20,QImage(":/new/prefix1/img/apple.png").scaled(QSize(20,20)));

该游戏主要用到的就是这两种绘制方式、当然还有更多的绘制方式:参考这里

今日学习获得的一些经验

  • Qt4和Qt5虽然用法差别不大,但是Qt4的代码在Qt5上很难跑通。Qt4的游戏源代码值得参考,但要自己尝试运行,还是去找Qt5的源代码吧。
  • Qt的各种参考书和博客中给的代码多是模板,不要直接放在Qt中运行
  • Qt中读取资源(图片、音频etc)都要把文件加入qrc文件中以获取相对路径

吐槽:根本不是今日获得的经验,从上周开始写一直拖到现在orz

end

0 0
原创粉丝点击