前一章制作了贪吃蛇游戏的框架,只是实现了一些基本功能,而这一在会进一步完善程序所需要的各项功能,首先看下程序完成后的样子
相较于前一章,个程序添加了一个难度选择框,一个信息显示框,以及三个按钮用于控制游戏的各项功能,对比前一章的程序,这个程序主要多了以下功能
1 游戏结束后可以新建游戏
2 提供难度选择,蛇的移动速度会根据难度的不同而有不同
3 提供了暂停/开始功能
4 吃到10个食物为游戏胜利
5 提供了一个信息显示栏,用于显示游戏的各种游戏状态
实现上述,功能,首先想程序头文件里添加一个QGroupBox,三个QRadioButton,三个QPushButton和一个QLabel
QPushButton* newGame_PushButton;
QPushButton* goOrStop_PushButton;
QPushButton* close_PushButton;
QRadioButton* easy_RadioButton;
QRadioButton* normal_RadioButton;
QRadioButton* hard_RadioButton;
QLabel* info_Label;
QGroupBox* level_GroupBox;
这里省略了构造函数里添加的有关布局的代码,我来逐一看下添加功能的实现,第一步是新建游戏代码,这里通过一个私有槽resetGame()来实现
void Snake::resetGame()
{
snakePath_List.clear(); //清楚保存蛇外形的链表
clock_Timer->stop(); //停止计时器
putFood(); //放置食物和蛇,他们都采用默认值
setSnakeShape();
if(easy_RadioButton->isChecked()) //选择游戏难度,即蛇的移动速度
clock_Timer->setInterval(700);
else if(normal_RadioButton->isChecked())
clock_Timer->setInterval(500);
else if(hard_RadioButton->isChecked())
clock_Timer->setInterval(300);
getFoodNumber_int = 0; //该变量保存吃到食物的个数
currentDirction_enum = GoLeft; //蛇的初始移动方向为向左
gameIsOver_bool = false; //游戏状态为未结束
isMoving_bool = false; //游戏状态为未运行
goOrStop_PushButton->setText(tr("开始"));
info_Label->setText(tr("准备就绪")); //在游戏信息板上显示游戏信息
}
要开始一个新的游戏,需要先把蛇和食物放回原位,蛇还需要变为原来的样子,通过计时器的setInterval()函数来设置游戏的难度,简单:蛇移动间隔为0.7秒,正常:移动间隔为0.5秒,困难:移动间隔为0.3秒,这个类还添加了三个成员变量来控制游戏的状态,其中getFoodNumber_int用于记录已经吃到食物的数量,在新建游戏是这个值会被重置为0,gameIsOver_bool用于标记游戏是否已经结束,当蛇撞墙/吃到自己/吃到10个食物后,变量ganeIsover_bool为被设为false,而新建一个游戏是这个变量会被设为true,最后是变量isMoving_bool,该变量用于标记游戏里蛇是否在前进,新建游戏后尚未开始,蛇处于停止状态,所以这里设为false,当点击开始,蛇开始运动时,这个值会被设为true
下一个功能是游戏的暂停/开始功能,这个功能通过槽keepGoing()来实现
void Snake::keepMoving()
{
if(gameIsOver_bool) //如果游戏结束,则该函数不做任何事
return;
if(isMoving_bool)
{
clock_Timer->stop();
isMoving_bool = false;
goOrStop_PushButton->setText(tr("开始"));
info_Label->setText(tr("游戏暂停"));
}
else
{
clock_Timer->start();
isMoving_bool = true;
goOrStop_PushButton->setText(tr("暂停"));
info_Label->setText(tr("游戏运行中"));
}
}
这个函数的作用是点击“开始”,启动计时器,点击“暂停”,停止计时器,同时在信息显示板上显示游戏的当前状态,需要注意的是这个功能只有在游戏运行过程中才能使用,当游戏已经结束是,该函数不做任何事,也就是说,游戏结束后点击开始/暂停按钮不会有任何改变
最后是游戏胜利条件,吃到10个食物获得胜利,要实现这个功能,需要修改movingFood()函数的功能,改动不多,但考虑到本章内容比较少,为了让文章显得比较长,所以这里贴出完成的movingFood()函数的代码
void Snake::movingFood()
{
if(getFoodNumber_int == 10)
{
info_Label->setText(tr("游戏胜利"));
clock_Timer->stop();
gameIsOver_bool = true;
food_GraphicsRectItem->setPos(-50,-50);//把食物移到view外面
return;
}
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
GridPoint p;
while (true)
{
p.x = qrand() % 20;
p.y = qrand() % 20;
if (!(snakePath_List.contains(p)))
break;
}
putFood(p.x, p.y);
}
变量getFoodNumber_int的计算实在movingSnake()函数里实现的,在发射eatFood()信号前先实现了++getFoodNumber_int功能,所以这里就直接判断是不是已经吃到10个食物了,吃到第11个食物是就判断游戏结束,在信息面板上显示“游戏胜利“,这里有个细节是判断吃到了第11个食物后,不会生产新的食物,也就是说原先的食物不会被移动到一个新的位置,而此时定时器停止,蛇停止运动,食物会位于蛇的最前端,这样略显难看,所以这里把食物移动到QGraphicsView外面,当开始新游戏时,食物会被放到正确的位置上
关于内存:前面说过QGraphicsItem以及继承他的各种item均没有把QObject作为基类,他们无法使用信号于槽,在Qt内存章节中介绍过,当Qt对象销毁时,他会自动销毁他的所有子对象,但这里有个前提条件,就是这些子对象必须是QOject的子类,否则无法被自动销毁。而这里的这个例子里,使用了各种item均不是QObject子类,但对于元素/视图架构,Qt做了特殊处理,当QGraphicsScene销毁时,会自动销毁QGraphicsScene上所有的item,所以就没必要在析构函数里显示的delete每个item了,不过这里有个比较例外的情况,当处于某种需要使用QGraphicsScene的removeItem(QGraphicsItem* item)函数时,被移除scene的item将不会被自动效果,需要在析构函数中手动delete,这个removeItem()函数只是把 item从scene上移除而并没有销毁他。但大多数情况下,需要某个item不出现的时候,会把这个item移动到view显示区域之外,而不是使用removeItem()函数,这样可以避免内存管理上可能出现的问题
完整代码https://pan.baidu.com/s/1gfngtUz