分享一个用Qt软件去写一个贪吃蛇小游戏,学习qt,在这里你可以学到
- qt的自定义信号与槽
- qt线程使用
- qt中容器qlist,qvector的使用
- qt样式简单应用
- 自定义数据结构
- 设计思路
快来复制粘贴代码运行一下吧!
键盘操作说明: ↑W,↓S,←A,→D按键来控制蛇的方向,按下U实现snake的加速,按下P实现snake暂停移动
废话不多说,直接动手分析写代码,代码我有比较详细的注释
头文件:qsnake.h
#ifndef QSNAKE_H
#define QSNAKE_H
#include <QLabel>
#include <QThread>
#include <QObject>
//定义snake一些样式
#define BOYD_STYLE "background-color: rgb(138, 226, 5);border-radius: 10px;"
#define HEAD_STYLE "background-color: rgb(234, 7, 5);border-radius: 10px;"
#define TAIL_STYLE "background-color: rgb(23, 27, 45);border-radius: 10px;"
#define FROG_STYLE1 "background-color: rgb(100, 255, 0);border-radius: 10px;"
#define FROG_STYLE2 "background-color: rgb(50, 255, 100);border-radius: 2px;"
#define AREA_STYLE "background-color: rgba(220, 220, 220,125);"
//定义一些信号码
#define SNAKE_NULL -1
#define SNAKE_OVER 0 //结束
#define IS_WALL 1 //撞墙
#define IS_BODY 2 //撞身体
#define FULL_AREA 3 //满屏幕
#define EATEN 4 //吃掉食物
#define SNAKE_MOVE 5 //移到
//采用label形式显示节点
typedef QLabel showType;
typedef struct
{
enum nodetype{head,body,tail};
nodetype type;//节点类型
showType *disp;//节点显示方式 disp属于显示窗口,不允许在PlaySanke中修改,可以通过信号把指针和需要的参数送回主窗口修改
QString style;//节点显示样式
int x;
int y;
int step; //节点移动步长或者边长或者半径
}snakeNode,*snakeNodePtr;
//采用链表形式存放节点,可以采用list vector或者自定义容器存放节点
//容器类需要有实现:frist(),last(),at(),size()等模板类容器具有的特性
typedef QList<snakeNodePtr> snakeType;
//定义一个管理蛇属性的对象类
class PlaySnake : public QThread
{
Q_OBJECT
public:
enum Direction{Up,Down,Left,Right};
//构造函数,parent为snake的显示窗口
PlaySnake(QWidget *parent = nullptr);
~PlaySnake(){}
//设置蛇到活动区域,相对于主窗口parent,边界必须是step到整数倍
void setRect(int w,int h, int x=0 ,int y=0);
//设置移动速度值
void setSpeed(int speed){g_speed = speed;}
//设置加速标志
void setSpeedFlag(bool flag){g_speedFlag = flag;}
//设置移动方向
void setDirection(Direction indirect = Right);
//设置移动步长
void setStep(int instep){step = instep;}
//设置蛇操作对象
void setSnake(snakeType *tsnake){snake = tsnake;}
//设置青蛙操作对象
void setFrog(snakeNodePtr frog){this->frog = frog;}
//移动
void move(int step_x ,int step_y);
//设置节点样式
void setStyle(QString style,snakeNode::nodetype nodetype);
bool eating();
//判断位置
bool isBody();
bool isWall();
bool isBody(int x ,int y);
bool isWall(int x ,int y);
//新建一个青蛙位置
bool creatFrog(int &x , int &y);
void stop(){runing = false;}
void setpause(bool pause){this->pause = pause;}
signals:
//由主窗口更新显示,更新节点样式或者节点位置
void updataStyle(showType *lab, QString style);
void updataNode(showType *lab, QString style,int x,int y ,int step);
//当青蛙被吃,重新更新
bool eaten(int x,int y,int step);
//信号触发码
int snakeMsg(int code);
protected:
virtual void run();
private:
int g_speed;
bool g_speedFlag;
bool step_flag;
bool runing;
bool pause;
Direction direct;
int step;
snakeType *snake;
snakeNodePtr frog;
int area_w;
int area_h;
int area_x;
int area_y;
QWidget *parent;
};
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
#endif // QSNAKE_H
在头文件中,我们定义了一个PlaySnake的类,这个类主要是用于操控snake列表的节点位置,snake的每一个节点包含位置,节点类型,显示方式,样式,还有节点的移到步伐,也是节点一个格子的大小。PlaySnake类提供蛇的移动,设置移到方向,判断自身状态,吃掉青蛙等等一些方法。PlaySnake类是继承QThread的,实现多线程运行。PlaySnake的parent主窗口用于显示snake.
源码文件: qsnake.cpp
#include <QDebug>
#include <QRandomGenerator>
#include "qsnake.h"
PlaySnake::PlaySnake(QWidget *parent)
{
this->parent = parent;
g_speed = 10;
g_speedFlag=false;
runing = true;
pause = false;
snake = nullptr;
frog = nullptr;
direct = Right;
step = 10;
area_x = 0;
area_y = 0;
if(parent != nullptr)
{
area_h = (parent->height()/step)*step;
area_w = (parent->width()/step)*step;
}
else
{
area_h = (100/step)*step;
area_w = (100/step)*step;
}
}
void PlaySnake::setRect(int w, int h, int x, int y)
{
area_w = (w/step)*step;
area_h = (h/step)*step;
area_x = (x/step)*step;
area_y = (y/step)*step;
}
void PlaySnake::setDirection(PlaySnake::Direction indirect)
{
//限定蛇不能后退
bool flag = (direct == Up && indirect == Down) ||
(direct == Left && indirect == Right)||
(direct == Down && indirect == Up) ||
(direct == Right && indirect == Left) ;
if(flag)
return;
//最小步伐限定,每次改变方向前,要等待上一次移动完成
//避免移动一步,方向改变两次,造成矛盾
if(step_flag)
{
direct = indirect;
step_flag = false;
}
}
//采用蛇头变蛇体,蛇尾变蛇头,蛇尾前一个节点变蛇尾方式交替,实现蛇到移动
//实际上每次移动只操作了三个节点
void PlaySnake::move(int step_x, int step_y)
{
if(snake->size() < 3)
return;
snakeNodePtr body = snake->at(snake->size()-2);
snakeNodePtr head = snake->first();
snakeNodePtr tail = snake->takeLast();
QString tmph = head->style;
QString tmpt = tail->style;
QString tmpb = body->style;
int x = head->x + step_x;
int y = head->y + step_y;
tail->x = x;
tail->y = y;
tail->type = snakeNode::head;
tail->style = tmph;
head->type = snakeNode::body;
head->style = tmpb;
body->type = snakeNode::tail;
body->style = tmpt;
emit updataNode(body->disp,tmpt,body->x,body->y,body->step);
emit updataNode(tail->disp,tmph,tail->x,tail->y,tail->step);
emit updataNode(head->disp,tmpb,head->x,head->y,head->step);
snake->prepend(tail);
}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
void PlaySnake::setStyle(QString style, snakeNode::nodetype nodetype)
{
if(nodetype == snakeNode::nodetype::head)
{
emit updataStyle(snake->first()->disp,style);
return;
}
if(nodetype == snakeNode::nodetype::tail)
{
emit updataStyle(snake->last()->disp,style);
return;
}
foreach (snakeNodePtr var, *snake) {
if(var->type == nodetype)
emit updataStyle(var->disp,style);
}
return;
}
bool PlaySnake::eating()
{
if(!frog)
return false;
snakeNodePtr head = snake->first();
snakeNodePtr body = snake->at(snake->size()-2);
snakeNodePtr tail = snake->last();
int s_x = head->x;
int s_y = head->y;
int f_x = frog->x;
int f_y = frog->y;
//吃到青蛙把青蛙变成蛇尾
if(s_x == f_x && s_y == f_y)
{
frog->style = tail->style;
frog->type = tail->type;
frog->x = tail->x;
frog->y = tail->y;
tail->type = body->type;
tail->style = body->style;
snake->append(frog);
emit updataNode(tail->disp,tail->style,tail->x,tail->y,tail->step);
emit updataNode(frog->disp,frog->style,frog->x,frog->y,frog->step);
int x=0 , y = 0;
if(creatFrog(x,y))
emit eaten(x,y,step);
else
return false;
return true;
}
else
{
return false;
}
}
bool PlaySnake::isBody()
{
snakeNodePtr head = snake->first();
foreach (snakeNodePtr var, *snake) {
if(var->type == snakeNode::head)
continue;
if(var->x == head->x && var->y == head->y)
return true;
}
return false;
}
bool PlaySnake::isBody(int x , int y)
{
foreach (snakeNodePtr var, *snake) {
if(var->x == x && var->y == y)
return true;
}
return false;
}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
bool PlaySnake::isWall()
{
if(snake->isEmpty())
return false;
snakeNodePtr first = snake->first();
//snake右移和下移需要多+step,节点(正方形)左上角代表实际坐标x,y
int x = direct == Direction::Right ? first->x+step : first->x;
int y = direct == Direction::Down ? first->y+step : first->y;
return x > area_w+area_x || y > area_h+area_y || x < area_x || y < area_y;
}
bool PlaySnake::isWall(int x , int y)
{
return x > area_w+area_x || y > area_h+area_y || x < area_x || y < area_y;
}
bool PlaySnake::creatFrog(int &x, int &y)
{
int a_w = (area_w/step);
int a_h = (area_h/step);
int count = 0;
int area_size = a_w * a_h ;
//用于标记区域内那些区域被sanke占用
bool *area = new bool[area_size] ;
//创建一个合法的frog坐标,无法创建返回false;
do
{
//限定随机区域范围,主窗口区域大小,并且为step的整数倍
x = (QRandomGenerator::global()->bounded(area_x,area_w+area_x)/step) * step;
y = (QRandomGenerator::global()->bounded(area_y,area_h+area_y)/step) * step;
if(!isBody(x,y)) return true;
//蛇身体已经占满区域时无法创建返回false
int i = x/step;
int j = y/step;
if(!area[i*a_w+j*a_h])
{
area[i*a_w+j*a_h] = true;
count++;
}
}while(count < area_size - 1);
runing = false;
delete [] area;
emit snakeMsg(FULL_AREA);
return false;
}
void PlaySnake::run()
{
int tmpspeed = 0;
int speed = 0;
int x = 0;
int y = 0;
int t_s = 0;
bool change = true;
//创建初始位置青蛙
if(creatFrog(x,y))
emit eaten(x,y,step);
while (runing) {
QThread::msleep(5ll);
if(++t_s > 50)
{
t_s = 0;
change = !change;
//让frog闪烁起来
if(frog != nullptr)
emit updataStyle(frog->disp,change?FROG_STYLE2:FROG_STYLE1);
}
//暂停
if(pause) continue;
//加速标志
if(!g_speedFlag){
tmpspeed = g_speed;
}
else {
tmpspeed = g_speed/20;
}
if(++speed < tmpspeed)
continue;
speed = 0;
x = 0;
y = 0;
if(snake->isEmpty())
continue;
//方向选择
switch (direct) {
case Direction::Up:
y -= step;
break;
case Direction::Down:
y += step;
break;
case Direction::Left:
x -= step;
break;
case Direction::Right:
x += step;
break;
}
//移到
move(x,y);
emit snakeMsg(SNAKE_MOVE);
step_flag = true;
//判断是否吃掉青蛙
if(eating()){
emit snakeMsg(EATEN);
continue;
}
//判断是否撞墙
if(isWall())
emit snakeMsg(IS_WALL);
if(isBody())
emit snakeMsg(IS_BODY);
}
}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
从qsnake源码上看,snake对象是从外面传进来的,PlaySnake类只负责对其进行操作,判断自身状态,然后通过触发自定义信号,让主线程窗口去修改其显示位置和显示样式,实现了snake的移到和吃食物。这是我们需要创建一个主窗口,用来显示蛇
头文件:mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMainWindow>
#include <QLabel>
#include <QThread>
#include <QObject>
#include "qsnake.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
//通过键盘按钮操作snake的方向和加速
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
public slots :
//主窗口PlaySnake请求更新样式接收槽
void updataStyle(showType *lab, QString style);
//PlaySnake请求更新显示节点位置样式大小
void updataNode(showType *lab, QString style,int x,int y ,int step);
//更新frog
void updataFrog(int x ,int y,int step);
//信息码
void dealSnakeMsg(int code);
private:
showType *showtype;
showType *area;
PlaySnake *playsnake;
snakeType *snake;
};
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
#endif // MAINWINDOW_H
主窗口的头文件我们准备了几个方法函数,void keyPressEvent(QKeyEvent *event)和keyReleaseEvent(QKeyEvent *event)用于读取键盘按下事件函数,这里用到↑W,↓S,←A,→D按键来控制蛇的方向,按下U实现snake的加速,按下P实现snake暂停移动,槽函数,updataStyle用来更新PlaySnake发送updataStyle更新节点的样式显示,updataNode更新节点显示状态,updataFrog更新设置frog,dealSankeMsg处理snake一些状态码,在qsnake.h中有定义。
源码文件:mainwindow.cpp
#include "mainwindow.h"
#include <QKeyEvent>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setGeometry(100,50,800,600);
//snake活动范围
int area_x = 100;
int area_y = 50;
int area_h = 500;
int area_w = 600;
//步长与速度值
int step = 20;
int speed =100;
//显示snake在主窗口的活动区域
area = new showType(this);
area->setStyleSheet(AREA_STYLE);
area->setGeometry((area_x/step)*step,(area_y/step)*step,(area_w/step)*step,(area_h/step)*step);
//初始化sanke
snake = new snakeType;
//初始长度
int init_size = 3;
for(int i = 0 ; i < init_size ; i++)
{
//snake 所有坐标必须是step的整数倍
snakeNodePtr snakenode = new snakeNode;
showtype = new showType(this);
snakenode->x = (area_x/step)*step+i*step;
snakenode->y = (area_y/step)*step;
snakenode->step = step;
snakenode->style = BOYD_STYLE;
snakenode->type = snakeNode::body;
snakenode->disp = showtype;
if(i == 0)
{
snakenode->type = snakeNode::tail;
snakenode->style = TAIL_STYLE;
}
if(i == (init_size-1))
{
snakenode->type = snakeNode::head;
snakenode->style = HEAD_STYLE;
}
snakenode->disp->setStyleSheet(snakenode->style);
snakenode->disp->setGeometry(snakenode->x,snakenode->y,snakenode->step,snakenode->step);
snake->prepend(snakenode);
}
//创建sanke操作对象
playsnake = new PlaySnake(this);
//连接接受playsnake发出的信号和处理的槽函数
connect(playsnake,&PlaySnake::updataStyle,this,&MainWindow::updataStyle);
connect(playsnake,&PlaySnake::updataNode,this,&MainWindow::updataNode);
connect(playsnake,&PlaySnake::eaten,this,&MainWindow::updataFrog);
connect(playsnake,&PlaySnake::snakeMsg,this,&MainWindow::dealSnakeMsg);
playsnake->setSpeed(speed);
playsnake->setStep(step);
playsnake->setRect(area_w,area_h,area_x,area_y);
playsnake->setSnake(snake);
playsnake->start();
}
//释放内存
MainWindow::~MainWindow()
{
playsnake->stop();
playsnake->wait();
if(snake)
{
foreach (snakeNodePtr var, *snake) {
delete var->disp;
var->disp = nullptr;
delete var;
var = nullptr;
}
delete snake;
snake = nullptr;
}
delete playsnake;
delete area;
area = nullptr;
playsnake = nullptr;
}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
void MainWindow::keyPressEvent(QKeyEvent *event)
{
static bool pause = false;
switch (event->key()) {
case Qt::Key_U :
playsnake->setSpeedFlag(true);
break;
case Qt::Key_W :
case Qt::Key_Up :
playsnake->setDirection(PlaySnake::Up);
break;
case Qt::Key_S :
case Qt::Key_Down :
playsnake->setDirection(PlaySnake::Down);
break;
case Qt::Key_D :
case Qt::Key_Right :
playsnake->setDirection(PlaySnake::Right);
break;
case Qt::Key_A :
case Qt::Key_Left :
playsnake->setDirection(PlaySnake::Left);
break;
case Qt::Key_P :
pause = !pause;
playsnake->setpause(pause);
break;
}
}
void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
if(event->key() == Qt::Key_U)
{
playsnake->setSpeedFlag(false);
}
}
void MainWindow::updataStyle(showType *lab, QString style)
{
lab->setStyleSheet(style);
lab->raise();
this->update();
}
void MainWindow::updataNode(showType *lab, QString style,int x,int y ,int step)
{
lab->setStyleSheet(style);
lab->setGeometry(x,y,step,step);
lab->raise();
this->update();
}
void MainWindow::updataFrog(int x, int y,int step)
{
QString style =FROG_STYLE1;
snakeNodePtr frog = new snakeNode;
frog->x = x;
frog->y = y;
frog->disp = new showType(this);
frog->style = style;
frog->step = step;
frog->type = snakeNode::head;
frog->disp->setGeometry(frog->x,frog->y,frog->step,frog->step);
frog->disp->setStyleSheet(frog->style);
frog->disp->show();
playsnake->setFrog(frog);
}
void MainWindow::dealSnakeMsg(int code)
{
switch (code) {
case IS_WALL:
qDebug()<<"IS_WALL";
break;
case IS_BODY:
qDebug()<<"IS_BODY";
break;
case FULL_AREA:
playsnake->stop();
playsnake->wait();
qDebug()<<"FULL_AREA";
break;
case EATEN:
qDebug()<<"EATEN";
break;
default:
break;
}
}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
dealSnakeMsg(int code)可以根据code作出一些处理措施,比如分数增加,速度加快,撞墙的时候结束游戏等等,这里就不去实现了,留给大家发挥的余地。
pro文件: snake.pro
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = snake_t
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
CONFIG += c++11
SOURCES += \
main.cpp \
mainwindow.cpp \
qsnake.cpp
HEADERS += \
mainwindow.h \
qsnake.h
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
# 博客:booinon
# https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
运行指导:
把上面的源码和头文件内容分别复制下来,粘贴到对应名字的文件,然后用Qt,打开snake.pro文件运行即可。或者,自己创建一条qt工程,把源码贴进去也是可以的。