#(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;
}
}
~~~
我們有四個以 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);
}
}
~~~
顧名思義,`handleCollisions()`的意思是處理碰撞,也就是所謂的“碰撞檢測”。首先,我們使用`collidingItems()`取得所有碰撞的元素。這個函數的簽名是:
~~~
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`。回憶一下,我們之前編寫的代碼,`Food`的`boundingRect()`要大于其實際值,卻不影響我們的游戲邏輯判斷,這就是原因:因為我們使用的是`Qt::IntersectsItemShape`判斷檢測,這與`boundingRect()`無關。
后面的代碼就很簡單了。我們遍歷所有被碰撞的元素,如果是食物,則進行吃食物的算法,同時將蛇的長度加 1。最后,如果身體包含了頭,那就是蛇吃了自己的身體。
還記得我們在 Food 類中有這么一句:
~~~
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();
}
~~~
`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)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)