2月 04, 2009

【翻譯】Qt Tutorial 14 - Facing the Wall

@
tutorials/tutorial/t14/cannonfield.cpp
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 在使用者按下 EnterReturn 時被呼叫。我們也想在 Ctrl+Q 被按下時退出應用程式。這次我們連接到 QWidget::close(),而不是連接到 QCoreApplication::quit()。自從 GameBoard 成為應用程式的主元件以後,它也有與 quit() 有相同的效果。

  Qt::CTRLQt::Key_EnterQt::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++的。

Saitor & Loki 提到...

用出來了
http://imgur.com/NX1W3P6

匿名 提到...

不好意思請問一下,gridlayout裡面的物件可以改變位置嗎,例如說我原本設一個label在(1,1) 那有辦法讓他變到(2,2)嗎?

張貼留言