Qt 学习之路 2(33):贪吃蛇游戏(3)

继续前面一章的内容。上次我们讲完了有关蛇的静态部分,也就是绘制部分。现在,我们开始添加游戏控制的代码。首先我们从最简单的四个方向键开始:

 
 
void Snake::moveLeft()
{
head.rx() -= SNAKE_SIZE;
if (head.rx() < -100) {
head.rx() = 100;
}
}

void Snake::moveRight()
{
head.rx() += SNAKE_SIZE;
if (head.rx() > 100) {
head.rx() = -100;
}
}

void Snake::moveUp()
{
head.ry() -= SNAKE_SIZE;
if (head.ry() < -100) {
head.ry() = 100;
}
}

void Snake::moveDown()
{
head.ry() += SNAKE_SIZE;
if (head.ry() > 100) {
head.ry() = -100;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void Snake::moveLeft()
{
    head.rx() -= SNAKE_SIZE;
    if (head.rx() < -100) {
        head.rx() = 100;
    }
}
 
void Snake::moveRight()
{
    head.rx() += SNAKE_SIZE;
    if (head.rx() > 100) {
        head.rx() = -100;
    }
}
 
void Snake::moveUp()
{
    head.ry() -= SNAKE_SIZE;
    if (head.ry() < -100) {
        head.ry() = 100;
    }
}
 
void Snake::moveDown()
{
    head.ry() += SNAKE_SIZE;
    if (head.ry() > 100) {
        head.ry() = -100;
    }
}

我们有四个以 move 开头的函数,内容都很类似:分别以 SNAKE_SIZE 为基准改变头部坐标,然后与场景边界比较,大于边界值时,设置为边界值。这么做的结果是,当蛇运动到场景最右侧时,会从最左侧出来;当运行到场景最上侧时,会从最下侧出来。

然后我们添加一个比较复杂的函数,借此,我们可以看出 Graphics View Framework 的强大之处:

 
 
void Snake::handleCollisions()
{
QList collisions = collidingItems();

// Check collisions with other objects on screen
foreach (QGraphicsItem *collidingItem, collisions) {
if (collidingItem->data(GD_Type) == GO_Food) {
// Let GameController handle the event by putting another apple
controller.snakeAteFood(this, (Food *)collidingItem);
growing += 1;
}
}

// Check snake eating itself
if (tail.contains(head)) {
controller.snakeAteItself(this);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Snake::handleCollisions()
{
    QList collisions = collidingItems();
 
    // Check collisions with other objects on screen
    foreach (QGraphicsItem *collidingItem, collisions) {
        if (collidingItem->data(GD_Type) == GO_Food) {
            // Let GameController handle the event by putting another apple
            controller.snakeAteFood(this, (Food *)collidingItem);
            growing += 1;
        }
    }
 
    // Check snake eating itself
    if (tail.contains(head)) {
        controller.snakeAteItself(this);
    }
}

顾名思义,handleCollisions()的意思是处理碰撞,也就是所谓的“碰撞检测”。首先,我们使用collidingItems()取得所有碰撞的元素。这个函数的签名是:

 
 
QList<QGraphicsItem *> QGraphicsItem::collidingItems(
Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const
1
2
QList<QGraphicsItem *> QGraphicsItem::collidingItems(
        Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const

该函数返回与这个元素碰撞的所有元素。Graphcis View Framework 提供了四种碰撞检测的方式:

  • Qt::ContainsItemShape:如果被检测物的形状(shape())完全包含在检测物内,算做碰撞;
  • Qt::IntersectsItemShape:如果被检测物的形状(shape())与检测物有交集,算做碰撞;
  • Qt::ContainsItemBoundingRect:如果被检测物的包含矩形(boundingRect())完全包含在检测物内,算做碰撞;
  • Qt::IntersectsItemBoundingRect:如果被检测物的包含矩形(boundingRect())与检测物有交集,算做碰撞。

注意,该函数默认是Qt::IntersectsItemShape。回忆一下,我们之前编写的代码,FoodboundingRect()要大于其实际值,却不影响我们的游戏逻辑判断,这就是原因:因为我们使用的是Qt::IntersectsItemShape判断检测,这与boundingRect()无关。

后面的代码就很简单了。我们遍历所有被碰撞的元素,如果是食物,则进行吃食物的算法,同时将蛇的长度加 1。最后,如果身体包含了头,那就是蛇吃了自己的身体。

还记得我们在 Food 类中有这么一句:

 
 
setData(GD_Type, GO_Food);
1
setData(GD_Type, GO_Food);

QGraphicsItem::setData()以键值对的形式设置元素的自定义数据。所谓自定义数据,就是对应用程序有所帮助的用户数据。Qt 不会使用这种机制来存储数据,因此你可以放心地将所需要的数据存储到元素对象。例如,我们在Food的构造函数中,将GD_Type的值设置为GO_Food。那么,这里我们取出GD_Type,如果其值是GO_Food,意味着这个QGraphicsItem就是一个Food,因此我们可以将其安全地进行后面的类型转换,从而完成下面的代码。

下面是advance()函数的代码:

 
 
void Snake::advance(int step)
{
if (!step) {
return;
}
if (tickCounter++ % speed != 0) {
return;
}
if (moveDirection == NoMove) {
return;
}

if (growing > 0) {
QPointF tailPoint = head;
tail << tailPoint;
growing -= 1;
} else {
tail.takeFirst();
tail << head;
}

switch (moveDirection) {
case MoveLeft:
moveLeft();
break;
case MoveRight:
moveRight();
break;
case MoveUp:
moveUp();
break;
case MoveDown:
moveDown();
break;
}

setPos(head);
handleCollisions();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void Snake::advance(int step)
{
    if (!step) {
        return;
    }
    if (tickCounter++ % speed != 0) {
        return;
    }
    if (moveDirection == NoMove) {
        return;
    }
 
    if (growing > 0) {
        QPointF tailPoint = head;
        tail << tailPoint;
        growing -= 1;
    } else {
        tail.takeFirst();
        tail << head;
    }
 
    switch (moveDirection) {
        case MoveLeft:
            moveLeft();
            break;
        case MoveRight:
            moveRight();
            break;
        case MoveUp:
            moveUp();
            break;
        case MoveDown:
            moveDown();
            break;
    }
 
    setPos(head);
    handleCollisions();
}

QGraphicsItem::advance()函数接受一个 int 作为参数。这个 int 代表该函数被调用的时间。QGraphicsItem::advance()函数会被QGraphicsScene::advance()函数调用两次:第一次时这个 int 为 0,代表即将开始调用;第二次这个 int 为 1,代表已经开始调用。在我们的代码中,我们只使用不为 0 的阶段,因此当 !step 时,函数直接返回。

tickCounter实际是我们内部的一个计时器。我们使用 speed 作为蛇的两次动作的间隔时间,直接影响到游戏的难度。speed 值越大,两次运动的间隔时间越大,游戏越简单。这是因为随着 speed 的增大,tickCounter % speed != 0 的次数响应越多,刷新的次数就会越少,蛇运动得越慢。

moveDirection显然就是运动方向,当是 NoMove 时,函数直接返回。

growing是正在增长的方格数。当其大于 0 时,我们将头部追加到尾部的位置,同时减少一个方格;当其小于 0 时,我们删除第一个,然后把头部添加进去。我们可以把 growing 看做即将发生的变化。比如,我们将 growing 初始化为 7。第一次运行advance()时,由于 7 > 1,因此将头部追加,然后 growing 减少 1。直到 growing 为 0,此时,蛇的长度不再发生变化,直到我们吃了一个食物。

下面是相应的方向时需要调用对应的函数。最后,我们设置元素的坐标,同时检测碰撞。

Qt 学习之路 2(33):贪吃蛇游戏(3)的更多相关文章

  1. Qt 学习之路 2(32):贪吃蛇游戏(2)

    Qt 学习之路 2(32):贪吃蛇游戏(2) 豆子 2012年12月27日 Qt 学习之路 2 55条评论 下面我们继续上一章的内容.在上一章中,我们已经完成了地图的设计,当然是相当简单的.在我们的游 ...

  2. Qt 学习之路 2(34):贪吃蛇游戏(4)

    Qt 学习之路 2(34):贪吃蛇游戏(4) 豆子 2012年12月30日 Qt 学习之路 2 73条评论 这将是我们这个稍大一些的示例程序的最后一部分.在本章中,我们将完成GameControlle ...

  3. Qt 学习之路 2(31):贪吃蛇游戏(1)

    Qt 学习之路 2(31):贪吃蛇游戏(1) 豆子 2012年12月18日 Qt 学习之路 2 41条评论 经过前面一段时间的学习,我们已经了解到有关 Qt 相当多的知识.现在,我们将把前面所讲过的知 ...

  4. 《Qt 学习之路 2》目录

    <Qt 学习之路 2>目录 <Qt 学习之路 2>目录  豆子  2012年8月23日  Qt 学习之路 2  177条评论 <Qt 学习之路 2>目录 序 Qt ...

  5. Qt 学习之路 2(71):线程简介

    Qt 学习之路 2(71):线程简介 豆子 2013年11月18日 Qt 学习之路 2 30条评论 前面我们讨论了有关进程以及进程间通讯的相关问题,现在我们开始讨论线程.事实上,现代的程序中,使用线程 ...

  6. Qt 学习之路 2(69):进程

    Qt 学习之路 2(69):进程 豆子 2013年11月9日 Qt 学习之路 2 15条评论 进程是操作系统的基础之一.一个进程可以认为是一个正在执行的程序.我们可以把进程当做计算机运行时的一个基础单 ...

  7. Qt 学习之路 2(67):访问网络(3)

    Qt 学习之路 2(67):访问网络(3) 豆子 2013年11月5日 Qt 学习之路 2 16条评论 上一章我们了解了如何使用我们设计的NetWorker类实现我们所需要的网络操作.本章我们将继续完 ...

  8. Qt 学习之路 2(66):访问网络(2)

    Home / Qt 学习之路 2 / Qt 学习之路 2(66):访问网络(2) Qt 学习之路 2(66):访问网络(2)  豆子  2013年10月31日  Qt 学习之路 2  27条评论 上一 ...

  9. Qt 学习之路 2(63):使用 QJson 处理 JSON

    Home / Qt 学习之路 2 / Qt 学习之路 2(63):使用 QJson 处理 JSON Qt 学习之路 2(63):使用 QJson 处理 JSON  豆子  2013年9月9日  Qt ...

随机推荐

  1. MFC小程序

    1.将菜单栏归零,工具栏放在窗口低部,加载自己新建的工具栏 CMainFrame::OnCreate()函数中 this->SetMenu(0); 2.将窗口初始化为最大化 APP类中:m_pM ...

  2. ArcGIS JS API实现的距离测量与面积量算

    转自https://www.cnblogs.com/deliciousExtra/p/5490937.html

  3. iframe 模拟ajax文件上传and formdata ajax 文件上传

    对于文件上传 有好多种方式,一直想总结 文件上传的方法 今天就来写下 iframe  的文件上传的代码 本人语言表达能里有限,不多说了 直接上代码. 首先看 总体页面. 总共就三个文件. 实际上也就是 ...

  4. SpringBoot26 利用 Ribbon + RestTemplate 调用远程服务资源

    1 RestTemplate扫盲 借助 RestTemplate,Spring应用能够方便地使用REST资源  2 准备 创建三个springCloud项目 >Eureaka : 服务注册中心 ...

  5. =面试题:java面试基本方向 背1 有用 项目二技术学完再看

    一.Java基础 1. 集合框架A)集合中泛型优点? 将运行期的ClaasCastException 转到编译期异常.  泛型还提供通配符 1)HashMap---允许一个键为null,允许多个值为n ...

  6. 数据库连接池DBUtils

    安装 pip3 install DBUtils DBUtils是Python的一个用于实现数据库连接池的模块. 此连接池有两种连接模式: 模式一:为每个线程创建一个连接,线程即使调用了close方法, ...

  7. linux命令下载安装软件

    在ubuntu下获取对应内核源码命令 Ubuntu的包管理系统,为您提供了一种高效快捷的软件管理方式,您只要知道您需要什么软件就可以了,甚至不需要关心它存放在网络上的哪一台服务器中,而且绝大多数的软件 ...

  8. App常用性能测试工具清单

    APP的CPU,内存,耗电,流量测试工具 APP的CPU,内存,耗电,流量测试工具下载地址,后续文章会介绍如何使用Emmagee.itest.gt APP应用的CPU,内存,耗电,流量调查(可和同类产 ...

  9. 无限级分类及生成json数据

    第一步,先去数据库查询类别数据,然后交给生成json数据的函数处理,代码如下: /*生成类别JSON数据*/ public function wirteJson(){ $dataInfo = \thi ...

  10. ABP源码uml类图

    陆陆续续学习ABP框架有一段时间了,阳光铭睿的入门教程和HK Zhang的源码分析文章对我的学习帮助都很大.之所以会花这么大工夫去学习ABP.看ABP的源代码,一是因为本人对于DDD也非常有兴趣,AB ...