.tutorials/tutorial/t14/cannonfield.h
.tutorials/tutorial/t14/gameboard.cpp
.tutorials/tutorial/t14/gameboard.h
.tutorials/tutorial/t14/lcdrange.cpp
.tutorials/tutorial/t14/lcdrange.h
.tutorials/tutorial/t14/main.cpp
.tutorials/tutorial/t14/t14.pro
這是最後一個範例:一個完整的遊戲。
我們加入鍵盤快捷鍵(accelerator),並將滑鼠事件引進到 CannonField。我們放置了一個環繞著 CannonField 的外框,並且加了一個障礙物(牆)使這個遊戲更具挑戰性。
Line by Line Walkthrough
t14/cannonfield.h
CannonField 現在可以接收滑鼠事件,讓使用者藉由點選並拖曳(drag)砲管來瞄準。CannonField 也有了一道障礙牆。
protected: void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event);
除了熟悉的事件處理器以外,CannonField 引入了三個滑鼠事件處理器。名稱說明了一切。
void paintBarrier(QPainter &painter);
這個私有函式畫出障礙牆。
QRect barrierRect() const;
這個私有函式返回障礙物的封裝矩形。
bool barrelHit(const QPoint &pos) const;
這個私有函式檢查某個點是否在加農砲的砲管內部。
bool barrelPressed;
若是使用者在砲管上按下滑鼠,並且沒有釋放它,這個私有變數的值為 true。
t14/cannonfield.cpp
barrelPressed = false;
這一行被加入到建構子裡。最初,滑鼠並沒有在砲管上被按下。
} else if (shotR.x() > width() || shotR.y() > height() || shotR.intersects(barrierRect())) {
現在我們有了一個障礙物,也就是第三種不擊中目標的方式。我們也做了第三種測試。
void CannonField::mousePressEvent(QMouseEvent *event) { if (event->button() != Qt::LeftButton) return; if (barrelHit(event->pos())) barrelPressed = true; }
這是一個 Qt 事件處理器。當滑鼠指標在元件上,且使用者按下滑鼠按鈕時,它就會被呼叫。
假如這個事件不是由左鍵觸發的,我們立刻返回。除此之外,我們確認滑鼠指標是否在加農砲的砲管之中。假如是,我們就將 barrelPressed 設為 true。
注意到 QMouseEvent::pos() 函式會返回一個在元件座標系統裡的點。
void CannonField::mouseMoveEvent(QMouseEvent *event) { if (!barrelPressed) return; QPoint pos = event->pos(); if (pos.x() <= 0) pos.setX(1); if (pos.y() >= height()) pos.setY(height() - 1); double rad = atan(((double)rect().bottom() - pos.y()) / pos.x()); setAngle(qRound(rad * 180 / 3.14159265)); }
這是另一個 Qt 事件處理器。它會在使用者已經在這個元件內部按下滑鼠按鈕,並移動 / 拖曳滑鼠時被呼叫。(你還可以使 Qt 在沒有按鈕被按下時送出滑鼠移動事件。參見 QWidget::setMouseTracking()。)
這個處理器根據滑鼠指標的位置重新調整加農砲砲管的位置。
首先,假如砲管沒有被按下,我們返回。接下來,我們取得滑鼠指標的位置。假如滑鼠指標移到了元件左邊或是底部,我們調整這個點到元件內部。
然後我們計算元件底邊,與元件左下角和指標位置構成的虛線之間的角度。最後,我們將加農砲的角度設為被轉換成度數的新值。
記得要用 setAngle() 重繪加農砲。
void CannonField::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) barrelPressed = false; }
無論使用者在何時釋放滑鼠按鈕,且滑鼠按鈕是在元件內部被按下時,這個 Qt 事件處理器就會被呼叫。
假如左鍵被釋放,我們可以確定砲管不再被按下。
繪圖事件有了額外的一行:
paintBarrier(painter);
paintBarrier() 與 paintShot()、paintTarget()、和 paintCannon() 做的是一樣的事。
void CannonField::paintBarrier(QPainter &painter) { painter.setPen(Qt::black); painter.setBrush(Qt::yellow); painter.drawRect(barrierRect()); }
這個私有函式畫出以一個黑色邊緣、以黃色填滿的矩形作為障礙物。
QRect CannonField::barrierRect() const { return QRect(145, height() - 100, 15, 99); }
這個私有函式返回障礙物的矩形。我們將障礙物的底邊固定在元件底邊。
bool CannonField::barrelHit(const QPoint &pos) const { QMatrix matrix; matrix.translate(0, height()); matrix.rotate(-currentAngle); matrix = matrix.inverted(); return barrelRect.contains(matrix.map(pos)); }
如果點在砲管裡,這個函式會返回 true;否則返回 false。
這裡我們使用了 QMatrix 類別。QMatrix 定義了一個座標系統映射。它可以完成與 QPainter 一樣的轉換。
這裡我們完成了與我們在 paintCannon() 函式中描繪砲管所做的相同轉換步驟。首先我們轉化座標系統,然後我們旋轉它。
現在我們需要檢查點 pos (在元件座標中)是否位於砲管中。為了做到這件事,我們倒轉這個轉換矩陣。倒轉的矩陣執行了我們描繪砲管所使用的倒轉轉換。我們藉由倒轉的矩陣來映射點 pos。並且,若是它在最初的砲管矩形內部,則返回 true。
t14/gameboard.cpp
QFrame *cannonBox = new QFrame; cannonBox->setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
我們建立及設定一個 QFrame,並設定好它的框架風格(style)。這樣的結果是一個環繞 CannonField 的 3D 框架。
(void) new QShortcut(Qt::Key_Enter, this, SLOT(fire())); (void) new QShortcut(Qt::Key_Return, this, SLOT(fire())); (void) new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close()));
這裡我們建立及設定三個 QShortcut 物件。這些物件擷取(intercept)鍵盤事件到一個元件中,並在某幾個鍵被按下時呼叫 slot。注意,一個 QShortcut 物件是一個元件的子元件,且會在這個元件毀滅時被毀滅。QShortcut 自己並不是一個元件,且在它的父元件中沒有任何可看見的效果。
我們定義了三個快速鍵(shortcut key)。我們想要讓 fire() slot 在使用者按下 Enter 或 Return 時被呼叫。我們也想在 Ctrl+Q 被按下時退出應用程式。這次我們連接到 QWidget::close(),而不是連接到 QCoreApplication::quit()。自從 GameBoard 成為應用程式的主元件以後,它也有與 quit() 有相同的效果。
Qt::CTRL、Qt::Key_Enter、Qt::Key_Return、與 Qt::Key_Q 全都是宣告在 Qt 名稱空間裡的常數。
QVBoxLayout *cannonLayout = new QVBoxLayout; cannonLayout->addWidget(cannonField); cannonBox->setLayout(cannonLayout); QGridLayout *gridLayout = new QGridLayout; gridLayout->addWidget(quit, 0, 0); gridLayout->addLayout(topLayout, 0, 1); gridLayout->addLayout(leftLayout, 1, 0); gridLayout->addWidget(cannonBox, 1, 1, 2, 1); gridLayout->setColumnStretch(1, 10); setLayout(gridLayout);
我們給了 cannonBox 它自己的 QVBoxLayout,並且把 cannonField 加入到版面配置中。這將間接使得 cannonField 成為 cannonBox 的子元件。因為沒有其它東西在這個盒子裡,所以結果就是 QVBoxLayout 會放置一個環繞著 CannonField 的框架。我們放置 cannonBox,而非 cannonField,到網格版面配置中。
Running the Application
現在加農砲會在你按下 Enter 時射擊。你也可以使用滑鼠把加農砲調到適當的角度。障礙物使得玩遊戲多了點挑戰性。我們也有一個看起來還不錯的外框環繞著 CannonField。
Exercises
寫一個太空入侵者的遊戲。
(這個練習首先被 Igor Rafienko 做出來了。你可以下載他的遊戲。)
新的練習是:寫一個打磚塊(Breakout)遊戲。
最後的忠告:繼續往前走,並創造程式設計藝術的傑作!
來源:Qt Tutorial 14 - Facing the Wall
版本:4.4.3
3 回覆:
蠻有趣的。
請問您知道哪還有類似的中文遊戲教學嗎?
只要是用C++的。
用出來了
http://imgur.com/NX1W3P6
不好意思請問一下,gridlayout裡面的物件可以改變位置嗎,例如說我原本設一個label在(1,1) 那有辦法讓他變到(2,2)嗎?
張貼留言