成品展示
项目分析:
🐍基本元素如下
🐍小蛇的设计,初始大小蛇头占一个方块,蛇身占两个方块。
🐍关于小蛇的移动,采用蛇头前进方向增加一个方块,蛇尾减掉一个方块的实现方法。
🐍在移动过程中不能立即向前进反方向移动(如正在向左前进的小蛇,按下向右的按键应当无效果),当小蛇吃到食物后,蛇身自增,并记录得分。
🐍当小蛇碰到边界处 或 自身时游戏结束。
🐍食物随机生成。
工程创建
游戏背景图片准备
小游戏的设计采用的是"穿越方案",到达边界后会从另一边出现。并未设置墙壁碰撞检测。下图墙壁仅为图片,在代码实现过程中可根据需求添加碰撞检测。
主体框架预览
主要函数分析
按键处理
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; i<snake.length();++i)
{
painter.drawRect(snake[i]);
}
//画 食物
pen.setColor(Qt::red);
brush.setColor(Qt::red);
brush.setStyle(Qt::SolidPattern);
painter.setPen(pen);
painter.setBrush(brush);
painter.drawEllipse(this->rewardNode);
//绘制得分
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 < 0)
{
//方案1:游戏直接结束
//方案2:穿墙
leftTop = QPoint(snake[0].x(),this->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 <QWidget>
#include <QList>
#include <Qtime>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
typedef 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 500
class Widget : public QWidget
{
Q_OBJECT
public:
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<QRectF> snake;
//定时器
QTimer* timer;
//食物
QRectF rewardNode;
//积分
int score;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QKeyEvent>
#include <QTimer>
#include <QPainter>
#include <QPaintEvent>
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; i<snake.length();++i)
{
painter.drawRect(snake[i]);
}
//画 食物
pen.setColor(Qt::red);
brush.setColor(Qt::red);
brush.setStyle(Qt::SolidPattern);
painter.setPen(pen);
painter.setBrush(brush);
painter.drawEllipse(this->rewardNode);
//绘制得分
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 < 0)
{
//方案1:游戏直接结束
//方案2:穿墙
leftTop = QPoint(snake[0].x(),this->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 < 0)//越界
{
//修正坐标
leftTop = QPointF(this->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;
}