找回密码
 立即注册
收起左侧

Qt学习之路第33篇 贪吃蛇游戏(3)

0
回复
14345
查看
[复制链接]
累计签到:3 天
连续签到:1 天
来源: 2013-9-9 15:28:23 显示全部楼层 |阅读模式

马上注册,查看详细内容!注册请先查看:注册须知

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
版权声明

该文章原创于Qter开源社区(www.qter.org),作者devbean,博客www.devbean.net,转载请注明出处!


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

  1. void Snake::moveLeft()
  2. {
  3.     head.rx() -= SNAKE_SIZE;
  4.     if (head.rx() < -100) {
  5.         head.rx() = 100;
  6.     }
  7. }

  8. void Snake::moveRight()
  9. {
  10.     head.rx() += SNAKE_SIZE;
  11.     if (head.rx() > 100) {
  12.         head.rx() = -100;
  13.     }
  14. }

  15. void Snake::moveUp()
  16. {
  17.     head.ry() -= SNAKE_SIZE;
  18.     if (head.ry() < -100) {
  19.         head.ry() = 100;
  20.     }
  21. }

  22. void Snake::moveDown()
  23. {
  24.     head.ry() += SNAKE_SIZE;
  25.     if (head.ry() > 100) {
  26.         head.ry() = -100;
  27.     }
  28. }
复制代码


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



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

  1. void Snake::handleCollisions()
  2. {
  3.     QList collisions = collidingItems();

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

  12.     // Check snake eating itself
  13.     if (tail.contains(head)) {
  14.         controller.snakeAteItself(this);
  15.     }
  16. }
复制代码

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

  1. QList<QGraphicsItem *> QGraphicsItem::collidingItems(
  2.         Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const
复制代码

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

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

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


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


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

  1. setData(GD_Type, GO_Food);
复制代码

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


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

  1. void Snake::advance(int step)
  2. {
  3.     if (!step) {
  4.         return;
  5.     }
  6.     if (tickCounter++ % speed != 0) {
  7.         return;
  8.     }
  9.     if (moveDirection == NoMove) {
  10.         return;
  11.     }

  12.     if (growing > 0) {
  13.         QPointF tailPoint = head;
  14.         tail << tailPoint;
  15.         growing -= 1;
  16.     } else {
  17.         tail.takeFirst();
  18.         tail << head;
  19.     }

  20.     switch (moveDirection) {
  21.         case MoveLeft:
  22.             moveLeft();
  23.             break;
  24.         case MoveRight:
  25.             moveRight();
  26.             break;
  27.         case MoveUp:
  28.             moveUp();
  29.             break;
  30.         case MoveDown:
  31.             moveDown();
  32.             break;
  33.     }

  34.     setPos(head);
  35.     handleCollisions();
  36. }
复制代码

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,此时,蛇的长度不再发生变化,直到我们吃了一个食物。



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





参与人数 1人气 +1 收起 理由
恒星shephered + 1

查看全部评分总评分 : 人气 +1

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了