Archive:Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14 (zh TW): Difference between revisions

From KDE TechBase
No edit summary
 
(5 intermediate revisions by 2 users not shown)
Line 26: Line 26:
'''<tt>CannonField</tt>''' 現在可以接收滑鼠事件,讓使用者透過按下並拖動砲管來瞄準。'''<tt>CannonField</tt>''' 也有障礙牆了。
'''<tt>CannonField</tt>''' 現在可以接收滑鼠事件,讓使用者透過按下並拖動砲管來瞄準。'''<tt>CannonField</tt>''' 也有障礙牆了。


<code ruby>
<syntaxhighlight lang="ruby">
@barrelPressed = false
@barrelPressed = false
</code>
</syntaxhighlight>


這行加入到建構子。最初,滑鼠沒有在砲管上按下。
這行加入到建構子。最初,滑鼠沒有在砲管上按下。


<code ruby>
<syntaxhighlight lang="ruby">
elsif shotR.x() > width() || shotR.y() > height() ||
elsif shotR.x() > width() || shotR.y() > height() ||
     shotR.intersects(barrierRect())
     shotR.intersects(barrierRect())
</code>
</syntaxhighlight>


現在,我們有一個障礙,也就是第三種不擊中的方法。我們也測試第三種。(在 '''<tt>moveShot()</tt>'''。)
現在,我們有一個障礙,也就是第三種不擊中的方法。我們也測試第三種。(在 '''<tt>moveShot()</tt>'''。)


<code ruby>
<syntaxhighlight lang="ruby">
def mousePressEvent(event)
def mousePressEvent(event)
   unless event.button() == Qt::LeftButton
   unless event.button() == Qt::LeftButton
Line 49: Line 49:
   end
   end
end
end
</code>
</syntaxhighlight>


這是一個 Qt 事件處理器。當游標在 widget 上,且使用者按下滑鼠按鈕時,它會被呼叫。
這是一個 Qt 事件處理器。當游標在 widget 上,且使用者按下滑鼠按鈕時,它會被呼叫。
Line 57: Line 57:
請注意,[http://doc.qt.nokia.com/latest/qmouseevent.html#pos Qt::MouseEvent::pos()] 函式會返回一個 Widget 坐標系統中的點。
請注意,[http://doc.qt.nokia.com/latest/qmouseevent.html#pos Qt::MouseEvent::pos()] 函式會返回一個 Widget 坐標系統中的點。


<code ruby>
<syntaxhighlight lang="ruby">
def mouseMoveEvent(event)
def mouseMoveEvent(event)
   unless @barrelPressed
   unless @barrelPressed
Line 76: Line 76:
   setAngle((rad * 180 / 3.14159265).round())
   setAngle((rad * 180 / 3.14159265).round())
end
end
</code>
</syntaxhighlight>


這是另一個 Qt 事件處理器。當使用者已經在這個 widget 內按下滑鼠按鈕,然後移動/拖動滑鼠時被呼叫。(即使沒有任何按鍵被按下,您也可以讓 Qt 發送滑鼠移動事件。參閱 [http://doc.qt.nokia.com/latest/qwidget.html#mouseTracking-prop Qt::Widget::setMouseTracking()]。)
這是另一個 Qt 事件處理器。當使用者已經在這個 widget 內按下滑鼠按鈕,然後移動/拖動滑鼠時被呼叫。(即使沒有任何按鍵被按下,您也可以讓 Qt 發送滑鼠移動事件。參閱 [http://doc.qt.nokia.com/latest/qwidget.html#mouseTracking-prop Qt::Widget::setMouseTracking()]。)


這個處理器根據游標的位置,重新定位砲管。
這個處理器會根據游標的位置,重新定位砲管。


First, if the barrel is not pressed, we return. Next, we fetch the mouse cursor's position. If the mouse cursor is to the left or below the widget, we adjust the point to be inside the widget.
首先,如果砲管沒有被按下,我們返回。下一步,我們取得游標的位置。如果游標移到 widget 的左邊或底部,我們調整這個點到 widget 的內部。


Then we calculate the angle between the bottom edge of the widget and the imaginary line between the bottom-left corner of the widget and the cursor position. Finally we set the cannon's angle to the new value converted to degrees.
然後,我們計算 widget 底邊,與 widget 左下角和游標位置構成假想線之間的角度。最後,我們設定加農砲的角度為換算成度的新值。


Remember that '''<tt>setAngle()</tt>''' redraws the cannon.
記得用 '''<tt>setAngle()</tt>''' 重繪加農砲。


<code ruby>
<syntaxhighlight lang="ruby">
def mouseReleaseEvent(event)
def mouseReleaseEvent(event)
   if event.button() == Qt::LeftButton
   if event.button() == Qt::LeftButton
Line 94: Line 94:
   end
   end
end
end
</code>
</syntaxhighlight>


This Qt event handler is called whenever the user releases a mouse button and it was pressed inside this widget.
無論使用者何時釋放滑鼠按鈕,且它是在這個 widget 裡面按下時,這個 Qt 事件處理器被呼叫。


If the left button is released, we can be sure that the barrel is no longer pressed.
如果左邊的按鈕被釋放,我們可以確定砲管已不再被按住了。


The paint event has one extra line:
繪圖事件還多了一行:


<code ruby>
<syntaxhighlight lang="ruby">
paintBarrier(painter)
paintBarrier(painter)
</code>
</syntaxhighlight>


'''<tt>paintBarrier()</tt>''' does the same sort of thing as '''<tt>paintShot()</tt>''', '''<tt>paintTarget()</tt>''', and '''<tt>paintCannon()</tt>'''.
'''<tt>paintBarrier()</tt>''' '''<tt>paintShot()</tt>''''''<tt>paintTarget()</tt>''',和 '''<tt>paintCannon()</tt>''' 作同類的事情。


<code ruby>
<syntaxhighlight lang="ruby">
def paintBarrier( painter )
def paintBarrier( painter )
   painter.setBrush(Qt::Brush.new(Qt::yellow))
   painter.setBrush(Qt::Brush.new(Qt::yellow))
Line 114: Line 114:
   painter.drawRect(barrierRect())
   painter.drawRect(barrierRect())
end
end
</code>
</syntaxhighlight>


This function paints the barrier as a rectangle filled with yellow and with a black outline.
這個函式繪製一個填充黃色和黑色外框的矩形作為障礙。


<code ruby>
<syntaxhighlight lang="ruby">
def barrierRect()
def barrierRect()
   return Qt::Rect.new(145, height() - 100, 15, 99)
   return Qt::Rect.new(145, height() - 100, 15, 99)
end
end
</code>
</syntaxhighlight>


This function returns the rectangle of the barrier. We fix the bottom edge of the barrier to the bottom edge of the widget.
這個函式返回障礙的矩形。我們把障礙的底邊固定在 widget 的底邊。


<code ruby>
<syntaxhighlight lang="ruby">
def barrelHit(pos)
def barrelHit(pos)
   matrix = Qt::Matrix.new()
   matrix = Qt::Matrix.new()
Line 134: Line 134:
   return @barrelRect.contains(matrix.map(pos))
   return @barrelRect.contains(matrix.map(pos))
end
end
</code>
</syntaxhighlight>


This function returns '''<tt>true</tt>''' if the point is in the barrel; otherwise it returns '''<tt>false</tt>'''.
如果點在砲管裡,這個函式返回 '''<tt>true</tt>''';否則返回 '''<tt>false</tt>'''


Here we use the class [http://doc.qt.nokia.com/latest/qmatrix.html Qt::Matrix]. [http://doc.qt.nokia.com/latest/qmatrix.html Qt::Matrix] defines a coordinate system mapping. It can perform the same transformations as the [http://doc.qt.nokia.com/latest/qpainter.html Qt::Painter].
這裡我們使用 [http://doc.qt.nokia.com/latest/qmatrix.html Qt::Matrix] 類別。[http://doc.qt.nokia.com/latest/qmatrix.html Qt::Matrix] 定義了一個坐標系統映射。它可以執行與 [http://doc.qt.nokia.com/latest/qpainter.html Qt::Painter] 相同的轉換。


Here we perform the same transformation steps as we do when drawing the barrel in the '''<tt>paintCannon()</tt>''' function. First we translate the coordinate system and then we rotate it.
這裡,我們執行與 '''<tt>paintCannon()</tt>''' 函式中繪製砲管的同樣轉換步驟。首先,我們轉移坐標系,然後旋轉。


Now we need to check whether the point '''<tt>pos</tt>''' (in widget coordinates) lies inside the barrel. To do this, we invert the transformation matrix. The inverted matrix performs the inverse transformation that we used when drawing the barrel. We map the point '''<tt>pos</tt>''' using the inverted matrix and return '''<tt>true</tt>''' if it is inside the original barrel rectangle.
現在我們需要檢查點 '''<tt>pos</tt>'''(在 widget 坐標)是否位於砲管內。為了做到這一點,我們倒轉這個轉移矩陣。倒轉矩陣執行了我們使用在繪製砲管的倒轉轉移。我們使用倒轉矩陣來映射點 '''<tt>pos</tt>'''。如果它在原來砲管的矩形裡面,返回 '''<tt>true</tt>'''


'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t14/gamebrd.rb gamebrd.rb]'''
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t14/gamebrd.rb gamebrd.rb]'''


<code ruby>
<syntaxhighlight lang="ruby">
cannonBox = Qt::Frame.new()
cannonBox = Qt::Frame.new()
cannonBox.setFrameStyle(Qt::Frame::WinPanel | Qt::Frame::Sunken)
cannonBox.setFrameStyle(Qt::Frame::WinPanel | Qt::Frame::Sunken)
</code>
</syntaxhighlight>


We create and set up a [http://doc.qt.nokia.com/latest/qframe.html Qt::Frame], and set its frame style. This results in a 3D frame around the '''<tt>CannonField</tt>'''.
我們建立及設定了一個 [http://doc.qt.nokia.com/latest/qframe.html Qt::Frame],並設定框架的風格。這樣的結果是一個圍繞 '''<tt>CannonField</tt>''' 的3D框架。


<code ruby>
<syntaxhighlight lang="ruby">
Qt::Shortcut.new(Qt::KeySequence.new(Qt::Key_Enter.to_i),
Qt::Shortcut.new(Qt::KeySequence.new(Qt::Key_Enter.to_i),
                 self, SLOT('fire()'))
                 self, SLOT('fire()'))
Line 160: Line 160:
Qt::Shortcut.new(Qt::KeySequence.new(Qt::CTRL.to_i + Qt::Key_Q.to_i),
Qt::Shortcut.new(Qt::KeySequence.new(Qt::CTRL.to_i + Qt::Key_Q.to_i),
                 self, SLOT('close()'))
                 self, SLOT('close()'))
</code>
</syntaxhighlight>


Here we create and set up three [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] objects. These objects intercept keyboard events to a widget and call slots if certain keys are pressed. Note that a [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] object is a child of a widget and will be destroyed when that widget is destroyed. [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] itself is not a widget and has no visible effect on its parent.
這裡,我們建立並設定三個 [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] 物件。這些物件會擷取(intercept)鍵盤事件到 widget,並在某些鍵被按下時呼叫槽。請注意,[http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] 物件是 widget 的子 widget。且在 widget 毀滅時,也會被毀滅。[http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] 本身不是一個 widget,在 widget 中也沒有可見的影響。


We define three shortcut keys. We want the '''<tt>fire()</tt>''' slot to be called when the user presses Enter or Return. We also want the application to quit when key Ctrl+Q is pressed. Instead of connecting to [http://doc.qt.nokia.com/latest/qcoreapplication.html#quit Qt::CoreApplication::quit()], we connect to [http://doc.qt.nokia.com/latest/qwidget.html#close Qt::Widget::close()] this time. Since the '''<tt>GameBoard</tt>''' is the application's main widget, this has the same effect as [http://doc.qt.nokia.com/latest/qcoreapplication.html#quit QCoreApplication::quit()].
我們定義了三個快捷鍵。我們希望當使用者按下 Enter 或 Return 時,呼叫 '''<tt>fire()</tt>''' 槽。我們也希望當按下 Ctrl+Q 鍵時,應用程式退出。這次我們連接到 [http://doc.qt.nokia.com/latest/qwidget.html#close Qt::Widget::close()],而不是 [http://doc.qt.nokia.com/latest/qcoreapplication.html#quit Qt::CoreApplication::quit()]。由於 '''<tt>GameBoard</tt>''' 是應用程式主要的 widget,這樣做與 [http://doc.qt.nokia.com/latest/qcoreapplication.html#quit QCoreApplication::quit()] 有同樣的效果。


Qt::CTRL, Qt::Key_Enter, Qt::Key_Return, and Qt::Key_Q are all constants declared in the Qt namespace. Unfortunately, in the current version of qtruby, they need to be converted to integers before we can use them in our shortcuts.
Qt::CTRL、Qt::Key_Enter、Qt::Key_Return,和 Qt::Key_Q 都是宣告在 Qt 名稱空間裡的常數。不幸的是,在 qtruby 的當前版本,他們需要轉換為整數,才能使用它們在我們的快捷鍵。


<code ruby>
<syntaxhighlight lang="ruby">
leftLayout = Qt::VBoxLayout.new()
leftLayout = Qt::VBoxLayout.new()
leftLayout.addWidget(angle)
leftLayout.addWidget(angle)
Line 180: Line 180:
gridLayout.setColumnStretch(1, 10)
gridLayout.setColumnStretch(1, 10)
setLayout(gridLayout)
setLayout(gridLayout)
</code>
</syntaxhighlight>


We give '''<tt>cannonBox</tt>''' its own [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout], and we add '''<tt>CannonField</tt>''' to that layout. This implicitly makes '''<tt>CannonField</tt>''' a child of '''<tt>cannonBox</tt>'''. Because nothing else is in the box, the effect is that the [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout] will put a frame around the '''<tt>CannonField</tt>'''. We put '''<tt>cannonBox</tt>''', not '''<tt>CannonField</tt>''', in the grid layout.
我們給 '''<tt>cannonBox</tt>''' 它自己的 [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout],並且加入 '''<tt>CannonField</tt>''' 到佈局中。這間接使得 '''<tt>CannonField</tt>''' 成為 '''<tt>cannonBox</tt>''' 的一個子 widget。因為沒有其它東西在這個盒子裡,結果就是 [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout] 會放置一個環繞著 '''<tt>CannonField</tt>''' 的框架。我們放置 '''<tt>cannonBox</tt>''',而不是 '''<tt>CannonField</tt>''',到網格佈局中。


===執行應用程式===
===執行應用程式===
The cannon now shoots when you press Enter. You can also position the cannon's angle using the mouse. The barrier makes it a little more challenging to play the game. We also have a nice looking frame around the '''<tt>CannonField</tt>'''.
現在加農砲會在您按下 Enter 時射擊。您還可以使用滑鼠定位加農砲的角度。障礙使得遊戲變得更具挑戰性。我們也有一個好看的外框圍繞'''<tt>CannonField</tt>'''


===練習===
===練習===
Write a space invaders game.
寫一個太空入侵者遊戲。


(This exercise was first done by [mailto:[email protected] Igor Rafienko]. You can [http://heim.ifi.uio.no/~igorr/download.html download his game].)
新的練習是:寫一個打磚塊遊戲。


The new exercise is: Write a Breakout game.
最後的忠告:繼續往前走,並創造程式設計藝術的傑作!
 
Final exhortation: Go forth now and create masterpieces of the programming art!


[[Category:Ruby]]
[[Category:Ruby]]

Latest revision as of 16:03, 23 June 2013

Template:I18n/Language Navigation Bar (zh TW) Template:TutorialBrowser (zh TW)

Facing the Wall

檔案:

概覽

這是最後的範例:一個完整的遊戲。

我們加上鍵盤快捷鍵(accelerator)和引入滑鼠事件到 CannonField。我們放置了一個環繞 CannonField 的外框,並加上一個障礙(牆),使遊戲更具挑戰性。

一行一行的瀏覽

cannon.rb

CannonField 現在可以接收滑鼠事件,讓使用者透過按下並拖動砲管來瞄準。CannonField 也有障礙牆了。

@barrelPressed = false

這行加入到建構子。最初,滑鼠沒有在砲管上按下。

elsif shotR.x() > width() || shotR.y() > height() ||
    shotR.intersects(barrierRect())

現在,我們有一個障礙,也就是第三種不擊中的方法。我們也測試第三種。(在 moveShot()。)

def mousePressEvent(event)
  unless event.button() == Qt::LeftButton
    return
  end

  if barrelHit(event.pos())
    @barrelPressed = true
  end
end

這是一個 Qt 事件處理器。當游標在 widget 上,且使用者按下滑鼠按鈕時,它會被呼叫。

如果事件不是滑鼠左鍵產生的,我們立即返回。否則,我們檢查游標的位置是否在加農砲的砲管內。假如是,我們就設定 barrelPressed 為 true。

請注意,Qt::MouseEvent::pos() 函式會返回一個 Widget 坐標系統中的點。

def mouseMoveEvent(event)
  unless @barrelPressed
    return
  end

  pos = event.pos();

  if pos.x() <= 0
    pos.setX(1)
  end

  if pos.y() >= height()
    pos.setY(height() - 1)
  end

  rad = atan2((rect().bottom() - pos.y()), pos.x())
  setAngle((rad * 180 / 3.14159265).round())
end

這是另一個 Qt 事件處理器。當使用者已經在這個 widget 內按下滑鼠按鈕,然後移動/拖動滑鼠時被呼叫。(即使沒有任何按鍵被按下,您也可以讓 Qt 發送滑鼠移動事件。參閱 Qt::Widget::setMouseTracking()。)

這個處理器會根據游標的位置,重新定位砲管。

首先,如果砲管沒有被按下,我們返回。下一步,我們取得游標的位置。如果游標移到 widget 的左邊或底部,我們調整這個點到 widget 的內部。

然後,我們計算 widget 底邊,與 widget 左下角和游標位置構成假想線之間的角度。最後,我們設定加農砲的角度為換算成度的新值。

記得用 setAngle() 重繪加農砲。

def mouseReleaseEvent(event)
  if event.button() == Qt::LeftButton
    @barrelPressed = false
  end
end

無論使用者何時釋放滑鼠按鈕,且它是在這個 widget 裡面按下時,這個 Qt 事件處理器被呼叫。

如果左邊的按鈕被釋放,我們可以確定砲管已不再被按住了。

繪圖事件還多了一行:

paintBarrier(painter)

paintBarrier()paintShot()paintTarget(),和 paintCannon() 作同類的事情。

def paintBarrier( painter )
  painter.setBrush(Qt::Brush.new(Qt::yellow))
  painter.setPen(Qt::Color.new(Qt::black))
  painter.drawRect(barrierRect())
end

這個函式繪製一個填充黃色和黑色外框的矩形作為障礙。

def barrierRect()
  return Qt::Rect.new(145, height() - 100, 15, 99)
end

這個函式返回障礙的矩形。我們把障礙的底邊固定在 widget 的底邊。

def barrelHit(pos)
  matrix = Qt::Matrix.new()
  matrix.translate(0, height())
  matrix.rotate(-@currentAngle)
  matrix = matrix.inverted()
  return @barrelRect.contains(matrix.map(pos))
end

如果點在砲管裡,這個函式返回 true;否則返回 false

這裡我們使用 Qt::Matrix 類別。Qt::Matrix 定義了一個坐標系統映射。它可以執行與 Qt::Painter 相同的轉換。

這裡,我們執行與 paintCannon() 函式中繪製砲管的同樣轉換步驟。首先,我們轉移坐標系,然後旋轉。

現在我們需要檢查點 pos(在 widget 坐標)是否位於砲管內。為了做到這一點,我們倒轉這個轉移矩陣。倒轉矩陣執行了我們使用在繪製砲管的倒轉轉移。我們使用倒轉矩陣來映射點 pos。如果它在原來砲管的矩形裡面,返回 true

gamebrd.rb

cannonBox = Qt::Frame.new()
cannonBox.setFrameStyle(Qt::Frame::WinPanel | Qt::Frame::Sunken)

我們建立及設定了一個 Qt::Frame,並設定框架的風格。這樣的結果是一個圍繞 CannonField 的3D框架。

Qt::Shortcut.new(Qt::KeySequence.new(Qt::Key_Enter.to_i),
                 self, SLOT('fire()'))
Qt::Shortcut.new(Qt::KeySequence.new(Qt::Key_Return.to_i),
                 self, SLOT('fire()'))
Qt::Shortcut.new(Qt::KeySequence.new(Qt::CTRL.to_i + Qt::Key_Q.to_i),
                 self, SLOT('close()'))

這裡,我們建立並設定三個 Qt::Shortcut 物件。這些物件會擷取(intercept)鍵盤事件到 widget,並在某些鍵被按下時呼叫槽。請注意,Qt::Shortcut 物件是 widget 的子 widget。且在 widget 毀滅時,也會被毀滅。Qt::Shortcut 本身不是一個 widget,在 widget 中也沒有可見的影響。

我們定義了三個快捷鍵。我們希望當使用者按下 Enter 或 Return 時,呼叫 fire() 槽。我們也希望當按下 Ctrl+Q 鍵時,應用程式退出。這次我們連接到 Qt::Widget::close(),而不是 Qt::CoreApplication::quit()。由於 GameBoard 是應用程式主要的 widget,這樣做與 QCoreApplication::quit() 有同樣的效果。

Qt::CTRL、Qt::Key_Enter、Qt::Key_Return,和 Qt::Key_Q 都是宣告在 Qt 名稱空間裡的常數。不幸的是,在 qtruby 的當前版本,他們需要轉換為整數,才能使用它們在我們的快捷鍵。

leftLayout = Qt::VBoxLayout.new()
leftLayout.addWidget(angle)
leftLayout.addWidget(force)

gridLayout = Qt::GridLayout.new()
gridLayout.addWidget(quit, 0, 0)
gridLayout.addLayout(topLayout, 0, 1)
gridLayout.addLayout(leftLayout, 1, 0)
gridLayout.addWidget(@cannonField, 1, 1, 2, 1)
gridLayout.setColumnStretch(1, 10)
setLayout(gridLayout)

我們給 cannonBox 它自己的 Qt::VBoxLayout,並且加入 CannonField 到佈局中。這間接使得 CannonField 成為 cannonBox 的一個子 widget。因為沒有其它東西在這個盒子裡,結果就是 Qt::VBoxLayout 會放置一個環繞著 CannonField 的框架。我們放置 cannonBox,而不是 CannonField,到網格佈局中。

執行應用程式

現在加農砲會在您按下 Enter 時射擊。您還可以使用滑鼠定位加農砲的角度。障礙使得遊戲變得更具挑戰性。我們也有一個好看的外框圍繞CannonField

練習

寫一個太空入侵者遊戲。

新的練習是:寫一個打磚塊遊戲。

最後的忠告:繼續往前走,並創造程式設計藝術的傑作!