离期末第一场考试之剩下32天了,而oop的期中project还有24天截止。此刻,我们刚刚写完了人生中第一个c++程序,分数类的运算符重载。继承和多态还没学。尽管如此,也是时候作为全裸勇者勇敢地去挑战大魔王了。
通过研究贪吃蛇小游戏的源码,学会用Qt进行游戏开发,并且完成期中Pro报告中挖下的2D Rougelike类小游戏第一关的大坑,并且写下博客给新鲜的学妹学弟留下可以借鉴的学习经验。
Qt5贪吃蛇小游戏源代码下载处
来自Rimond_Jing的Qt5基本安装教程
来自 齐亮,非常棒的一个参考资料
因为目前c++也没学好,Qt也刚开始学,所以本篇博客只适合和我一样什么都不会的人。而且很有可能错误连篇,期待有人能指正。
目前在一遍分析一遍写博客,所以会对内容进行不断地修正
以及非常啰嗦(和废话)
注:纯新手一上来就看总体分析极有可能不懂,看看就好,不懂就跳过去看完全文回来再看。
在本例子中,采取建立不同的界面类(mainWidget,开始界面,和GameWidget,游戏界面),通过接收事件(event,本例是按下按钮),利用信号和槽,来进行界面切换。
感觉并不需要分析:)
#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();
}
头文件分析
#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_OBJECT
public:
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)
}
游戏窗口是这个程序的主体部分,各种复杂的事件(槽和信号机制),状态的更新,动画、碰撞检测都是在这个类(游戏窗口就是一个继承了普通窗口< 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_OBJECT
public:
//构造函数
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
.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)));
该游戏主要用到的就是这两种绘制方式、当然还有更多的绘制方式:参考这里
吐槽:根本不是今日获得的经验,从上周开始写一直拖到现在orz
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。