#(34):貪吃蛇游戲(4)
這將是我們這個稍大一些的示例程序的最后一部分。在本章中,我們將完成`GameController`中有關用戶控制的相關代碼。
首先,我們來給`GameController`添加一個事件過濾器:
~~~
bool GameController::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
handleKeyPressed((QKeyEvent *)event);
return true;
} else {
return QObject::eventFilter(object, event);
}
}
~~~
回憶一下,我們使用`QGraphicsScene`作為游戲場景。為什么不直接繼承`QGprahicsScene`,重寫其`keyPressEvent()`函數呢?這里的考慮是:第一,我們不想只為重寫一個鍵盤事件而繼承`QGraphicScene`。這不符合面向對象設計的要求。繼承首先應該有“是一個(is-a)”的關系。我們將游戲場景繼承`QGraphcisScene`當然滿足這個關系,無可厚非。但是,繼承還有一個“特化”的含義,我們只想控制鍵盤事件,并沒有添加其它額外的代碼,因此感覺并不應該作此繼承。第二,我們希望將表示層與控制層分離:明明已經有了`GameController`,顯然,這是一個用于控制游戲的類,那么,為什么鍵盤控制還要放在場景中呢?這豈不將控制與表現層耦合起來了嗎?基于以上兩點考慮,我們選擇不繼承`QGraphicsScene`,而是在`GameController`中為場景添加事件過濾器,從而完成鍵盤事件的處理。下面我們看看這個`handleKeyPressed()`函數是怎樣的:
~~~
void GameController::handleKeyPressed(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Left:
snake->setMoveDirection(Snake::MoveLeft);
break;
case Qt::Key_Right:
snake->setMoveDirection(Snake::MoveRight);
break;
case Qt::Key_Up:
snake->setMoveDirection(Snake::MoveUp);
break;
case Qt::Key_Down:
snake->setMoveDirection(Snake::MoveDown);
break;
}
}
~~~
這段代碼并不復雜:只是設置蛇的運動方向。記得我們在前面的代碼中,已經為蛇添加了運動方向的控制,因此,我們只需要修改這個狀態,即可完成對蛇的控制。由于前面我們已經在蛇的對象中完成了相應控制的代碼,因此這里的游戲控制就是這么簡單。接下來,我們要完成游戲邏輯:吃食物、生成新的食物以及咬到自己這三個邏輯:
~~~
void GameController::snakeAteFood(Snake *snake, Food *food)
{
scene.removeItem(food);
delete food;
addNewFood();
}
~~~
首先是蛇吃到食物。如果蛇吃到了食物,那么,我們將食物從場景中移除,然后添加新的食物。為了避免內存泄露,我們需要在這里 delete 食物,以釋放占用的空間。當然,你應該想到,我們肯定會在`addNewFood()`函數中使用 new 運算符重新生成新的食物。
~~~
void GameController::addNewFood()
{
int x, y;
do {
x = (int) (qrand() % 100) / 10;
y = (int) (qrand() % 100) / 10;
x *= 10;
y *= 10;
} while (snake->shape().contains(snake->mapFromScene(QPointF(x + 5, y + 5))));
Food *food = new Food(x , y);
scene.addItem(food);
}
~~~
在`addNewFood()`代碼中,我們首先計算新的食物的坐標:使用一個循環,直到找到一個不在蛇身體中的坐標。為了判斷一個坐標是不是位于蛇的身體上,我們利用蛇的`shape()`函數。需要注意的是,`shape()`返回元素坐標系中的坐標,而我們計算而得的 x,y 坐標位于場景坐標系,因此我們必須利用`QGraphicsItem::mapFromScene()`將場景坐標系映射為元素坐標系。當我們計算出食物坐標后,我們在堆上重新創建這個食物,并將其添加到游戲場景。
~~~
void GameController::snakeAteItself(Snake *snake)
{
QTimer::singleShot(0, this, SLOT(gameOver()));
}
void GameController::gameOver()
{
scene.clear();
snake = new Snake(*this);
scene.addItem(snake);
addNewFood();
}
~~~
如果蛇咬到了它自己,游戲即宣告結束。因此,我們直接調用`gameOver()`函數。這個函數將場景清空,然后重新創建蛇并增加第一個食物。為什么我們不直接調用`gameOver()`函數,而是利用`QTimer`調用呢(希望你沒有忘記`QTimer::singleShot(0, ...)`的用法)?這是因為,我們不應該在一個 update 操作中去清空整個場景。因此我們使用`QTimer`,在 update 事件之后完成這個操作。
至此,我們已經把這個簡單的貪吃蛇游戲全部完成。最后我們來看一下運行結果:
[](http://files.devbean.net/images/2012/12/snake-demo.png)
文末的附件中是我們當前的全部代碼。如果你檢查下這部分代碼,會發現我們其實還沒有完成整個游戲:`Wall`對象完全沒有實現,難度控制也沒有完成。當然,通過我們的講解,希望你已經理解了我們設計的原則以及各部分代碼之間的關系。如果感興趣,可以繼續完成這部分代碼。豆子在 github 上面創建了一個代碼庫,如果你感覺自己的改進比較成功,或者希望與大家分享,歡迎 clone 倉庫提交代碼!
- (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(續)