成品展示

项目分析:
基本元素如下

小蛇的设计,初始大小蛇头占一个方块,蛇身占两个方块。
关于小蛇的移动,采用蛇头前进方向增加一个方块,蛇尾减掉一个方块的实现方法。
在移动过程中不能立即向前进反方向移动(如正在向左前进的小蛇,按下向右的按键应当无效果),当小蛇吃到食物后,蛇身自增,并记录得分。
当小蛇碰到边界处 或 自身时游戏结束。
食物随机生成。

工程创建

游戏背景图片准备

小游戏的设计采用的是“穿越方案”,到达边界后会从另一边出现。并未设置墙壁碰撞检测。下图墙壁仅为图片,在代码实现过程中可根据需求添加碰撞检测。

主体框架预览

主要函数分析

按键处理

void Widget::keyPressEvent(QKeyEvent* event){switch(event->key()){case Qt::Key_Up:if(this->moveFlag != DIR_DOWN){this->moveFlag = DIR_UP;}break;case Qt::Key_Down:if(this->moveFlag != DIR_UP){this->moveFlag = DIR_DOWN;}break;case Qt::Key_Right:if(this->moveFlag != DIR_LEFT){this->moveFlag = DIR_RIGHT;}break;case Qt::Key_Left:if(this->moveFlag != DIR_RIGHT){this->moveFlag = DIR_LEFT;}break;case Qt::Key_Space:if(this->gameStart == false){this->gameStart = true;//游戏开始,启动定时器this->timer->start(TIME);}else{this->gameStart = false;this->timer->stop();}break;default:break;}}

渲染绘图

//渲染void Widget::paintEvent(QPaintEvent* et){QPainter painter(this);QPen pen;QBrush brush; //背景图片QPixmap pix;pix.load("D:/snake/snake_b1.png");painter.drawPixmap(0,0,820,460,pix);//画蛇pen.setColor(Qt::black);brush.setColor(Qt::darkMagenta);brush.setStyle(Qt::SolidPattern);painter.setPen(pen);painter.setBrush(brush);for(int i = 0; irewardNode);//绘制得分QFont font("方正舒体",17,QFont::ExtraLight,false);painter.setFont(font);painter.drawText((this->width() - 75),this->height()- 427, QString::number(this->score));if(checkContact()){painter.drawText((this->width() - 300)/2,(this->height()-30)/2,QString("小蛇不小心咬到了自己,游戏结束!!!"));this->timer->stop();}QWidget::paintEvent(et);}

槽函数

void Widget::timeout(){int count = 1;//蛇头是否 触碰到食物if(snake[0].intersects(this->rewardNode)){addNewReword();score++;count++;}while(count--){switch(this->moveFlag){case DIR_UP://蛇上方加方格addTop();break;case DIR_DOWN://蛇下方加方格addDown();break;case DIR_LEFT://蛇左边加空格addLeft();break;case DIR_RIGHT://蛇右边加空格addRight();break;default:break;}}//删除尾部方块deleteLast();update();}

蛇运动

以向上运动为例:

情况1:蛇头运动越界,从另一端“穿越”回游戏界面。

情况2:正常运动

运动实现原理:

向上增加方块,代码实现:

void Widget::addTop(){QPointF leftTop;//左上QPointF rightBotom;//右下//方案1:如果越界(蛇头碰到墙体) -- 游戏失败//方案2:穿墙(上越界,从下边界出来)if(snake[0].y() - NODEHEIGHT height() - NODEHEIGHT);rightBotom = QPoint(snake[0].x()+NODEHEIGHT,this->height());}else{//正常增加方块leftTop = QPointF(snake[0].x(),snake[0].y()-NODEHEIGHT);rightBotom = snake[0].topRight();}snake.insert(0,QRectF(leftTop,rightBotom));}

整体代码

widget.h

#ifndef WIDGET_H#define WIDGET_H#include #include #include QT_BEGIN_NAMESPACEnamespace Ui {class Widget;}QT_END_NAMESPACEtypedef enum DIR{DIR_LEFT,DIR_RIGHT,DIR_UP,DIR_DOWN}dir_t;//超时时间 单位毫秒#define TIME 100#define NODEHEIGHT 20//y轴边界#define BORDER_X 300//x轴边界#define BORDER_Y 500class Widget : public QWidget{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget(); //槽函数protected slots://定时器启动 触发该槽函数void timeout();protected://上 -- 蛇身增长void addTop();//下 -- 蛇身增长void addDown();//左 -- 蛇身增长void addRight();//右 -- 蛇身增长void addLeft();//删除蛇尾方块void deleteLast();//随机生成食物void addNewReword();//渲染void paintEvent(QPaintEvent* event);//蛇头碰到蛇身判断bool checkContact();//按键处理 -- 重写void keyPressEvent(QKeyEvent* event);private:Ui::Widget *ui;//记录当前头部朝向int moveFlag = DIR_UP;//游戏是否开始bool gameStart = false;//记录蛇身体QList snake;//定时器QTimer* timer;//食物QRectF rewardNode;//积分int score;};#endif // WIDGET_H

widget.cpp

#include "widget.h"#include "ui_widget.h"#include #include #include #include Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget){ui->setupUi(this);//初始化定时器this->timer = new QTimer();//信号和槽connect(timer,SIGNAL( timeout() ),this,SLOT( timeout() ));//调整窗口带下this->resize(820,460);//初始化蛇身QPointF leftTop = QPointF(200,200);QPointF rightBotom = QPointF(220,220);snake.insert(0,QRectF(leftTop,rightBotom));//初始化食物addNewReword();//初始化积分this->score = 0;addTop();addTop();}Widget::~Widget(){delete ui;}void Widget::keyPressEvent(QKeyEvent* event){switch(event->key()){case Qt::Key_Up:if(this->moveFlag != DIR_DOWN){this->moveFlag = DIR_UP;}break;case Qt::Key_Down:if(this->moveFlag != DIR_UP){this->moveFlag = DIR_DOWN;}break;case Qt::Key_Right:if(this->moveFlag != DIR_LEFT){this->moveFlag = DIR_RIGHT;}break;case Qt::Key_Left:if(this->moveFlag != DIR_RIGHT){this->moveFlag = DIR_LEFT;}break;case Qt::Key_Space:if(this->gameStart == false){this->gameStart = true;//游戏开始,启动定时器this->timer->start(TIME);}else{this->gameStart = false;this->timer->stop();}break;default:break;}}//渲染void Widget::paintEvent(QPaintEvent* et){QPainter painter(this);QPen pen;QBrush brush; //背景图片QPixmap pix;pix.load("D:/snake/snake_b1.png");painter.drawPixmap(0,0,820,460,pix);//画蛇pen.setColor(Qt::black);brush.setColor(Qt::darkMagenta);brush.setStyle(Qt::SolidPattern);painter.setPen(pen);painter.setBrush(brush);for(int i = 0; irewardNode);//绘制得分QFont font("方正舒体",17,QFont::ExtraLight,false);painter.setFont(font);painter.drawText((this->width() - 75),this->height()- 427, QString::number(this->score));if(checkContact()){painter.drawText((this->width() - 300)/2,(this->height()-30)/2,QString("小蛇不小心咬到了自己,游戏结束!!!"));this->timer->stop();}QWidget::paintEvent(et);}void Widget::addTop(){QPointF leftTop;//左上QPointF rightBotom;//右下//方案1:如果越界(蛇头碰到墙体) -- 游戏失败//方案2:穿墙(上越界,从下边界出来)if(snake[0].y() - NODEHEIGHT height() - NODEHEIGHT);rightBotom = QPoint(snake[0].x()+NODEHEIGHT,this->height());}else{//正常增加方块leftTop = QPointF(snake[0].x(),snake[0].y()-NODEHEIGHT);rightBotom = snake[0].topRight();}snake.insert(0,QRectF(leftTop,rightBotom));}//向下增加方块void Widget::addDown(){QPointF leftTop;QPointF rightBotom;if(snake[0].y() + NODEHEIGHT*2 > this->height())//越界{//修正坐标leftTop = QPointF(snake[0].x(),0);rightBotom = QPointF(snake[0].x()+NODEHEIGHT,NODEHEIGHT);}else{//leftTop = snake[0].bottomLeft();rightBotom = snake[0].bottomRight() + QPointF(0,NODEHEIGHT);}snake.insert(0,QRectF(leftTop,rightBotom));}void Widget::addLeft(){QPointF leftTop;QPointF rightBotom;if(snake[0].x() - NODEHEIGHT width()-NODEHEIGHT,snake[0].y());}else{leftTop = snake[0].topLeft() - QPointF(NODEHEIGHT,0);}rightBotom = leftTop + QPointF(NODEHEIGHT,NODEHEIGHT);snake.insert(0,QRectF(leftTop,rightBotom));}void Widget::addRight(){QPointF leftTop;QPointF rightBotom;if(snake[0].x() + NODEHEIGHT*2 > this->width())//越界{//修正坐标leftTop = QPointF(0,snake[0].y());}else{//leftTop = snake[0].topRight();}rightBotom = leftTop + QPointF(NODEHEIGHT,NODEHEIGHT);snake.insert(0,QRectF(leftTop,rightBotom));}void Widget::deleteLast(){snake.removeLast();}void Widget::timeout(){int count = 1;//蛇头是否 触碰到食物if(snake[0].intersects(this->rewardNode)){addNewReword();score++;count++;}while(count--){switch(this->moveFlag){case DIR_UP://蛇上方加方格addTop();break;case DIR_DOWN://蛇下方加方格addDown();break;case DIR_LEFT://蛇左边加空格addLeft();break;case DIR_RIGHT://蛇右边加空格addRight();break;default:break;}}//删除尾部方块deleteLast();update();}//随机生成食物void Widget::addNewReword(){rewardNode = QRectF(qrand()%(this->width()/20) * 20,qrand()% (this->height()/20) * 20,NODEHEIGHT,NODEHEIGHT);}bool Widget::checkContact(){for(int i = 0;i<snake.length();++i){for(int j = i+1;j<snake.length();++j){if(snake[i] == snake[j]){return true;}}}return false;}