Archive:Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14 (zh TW): Difference between revisions
(Created page with '{{Template:I18n/Language Navigation Bar_(zh_TW)|Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14}} {{TutorialBrowser_(zh_TW)| series=[[Development/Tutorials/Qt4_Ruby_Tutorial_...') |
m (AnneW moved page Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14 (zh TW) to Archive:Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14 (zh TW) without leaving a redirect: Obsolete) |
||
(6 intermediate revisions by 2 users not shown) | |||
Line 17: | Line 17: | ||
===概覽=== | ===概覽=== | ||
這是最後的範例:一個完整的遊戲。 | |||
我們加上鍵盤快捷鍵(accelerator)和引入滑鼠事件到 '''<tt>CannonField</tt>'''。我們放置了一個環繞 '''<tt>CannonField</tt>''' 的外框,並加上一個障礙(牆),使遊戲更具挑戰性。 | |||
===一行一行的瀏覽=== | ===一行一行的瀏覽=== | ||
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t14/cannon.rb cannon.rb]''' | '''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t14/cannon.rb cannon.rb]''' | ||
'''<tt>CannonField</tt>''' 現在可以接收滑鼠事件,讓使用者透過按下並拖動砲管來瞄準。'''<tt>CannonField</tt>''' 也有障礙牆了。 | |||
< | <syntaxhighlight lang="ruby"> | ||
@barrelPressed = false | @barrelPressed = false | ||
</ | </syntaxhighlight> | ||
這行加入到建構子。最初,滑鼠沒有在砲管上按下。 | |||
< | <syntaxhighlight lang="ruby"> | ||
elsif shotR.x() > width() || shotR.y() > height() || | elsif shotR.x() > width() || shotR.y() > height() || | ||
shotR.intersects(barrierRect()) | shotR.intersects(barrierRect()) | ||
</ | </syntaxhighlight> | ||
現在,我們有一個障礙,也就是第三種不擊中的方法。我們也測試第三種。(在 '''<tt>moveShot()</tt>'''。) | |||
< | <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 | ||
</ | </syntaxhighlight> | ||
這是一個 Qt 事件處理器。當游標在 widget 上,且使用者按下滑鼠按鈕時,它會被呼叫。 | |||
如果事件不是滑鼠左鍵產生的,我們立即返回。否則,我們檢查游標的位置是否在加農砲的砲管內。假如是,我們就設定 '''<tt>barrelPressed</tt>''' 為 true。 | |||
請注意,[http://doc.qt.nokia.com/latest/qmouseevent.html#pos Qt::MouseEvent::pos()] 函式會返回一個 Widget 坐標系統中的點。 | |||
< | <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 | ||
</ | </syntaxhighlight> | ||
這是另一個 Qt 事件處理器。當使用者已經在這個 widget 內按下滑鼠按鈕,然後移動/拖動滑鼠時被呼叫。(即使沒有任何按鍵被按下,您也可以讓 Qt 發送滑鼠移動事件。參閱 [http://doc.qt.nokia.com/latest/qwidget.html#mouseTracking-prop Qt::Widget::setMouseTracking()]。) | |||
這個處理器會根據游標的位置,重新定位砲管。 | |||
首先,如果砲管沒有被按下,我們返回。下一步,我們取得游標的位置。如果游標移到 widget 的左邊或底部,我們調整這個點到 widget 的內部。 | |||
然後,我們計算 widget 底邊,與 widget 左下角和游標位置構成假想線之間的角度。最後,我們設定加農砲的角度為換算成度的新值。 | |||
記得用 '''<tt>setAngle()</tt>''' 重繪加農砲。 | |||
< | <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 | ||
</ | </syntaxhighlight> | ||
無論使用者何時釋放滑鼠按鈕,且它是在這個 widget 裡面按下時,這個 Qt 事件處理器被呼叫。 | |||
如果左邊的按鈕被釋放,我們可以確定砲管已不再被按住了。 | |||
繪圖事件還多了一行: | |||
< | <syntaxhighlight lang="ruby"> | ||
paintBarrier(painter) | paintBarrier(painter) | ||
</ | </syntaxhighlight> | ||
'''<tt>paintBarrier()</tt>''' | '''<tt>paintBarrier()</tt>''' 與 '''<tt>paintShot()</tt>'''、'''<tt>paintTarget()</tt>''',和 '''<tt>paintCannon()</tt>''' 作同類的事情。 | ||
< | <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 | ||
</ | </syntaxhighlight> | ||
這個函式繪製一個填充黃色和黑色外框的矩形作為障礙。 | |||
< | <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 | ||
</ | </syntaxhighlight> | ||
這個函式返回障礙的矩形。我們把障礙的底邊固定在 widget 的底邊。 | |||
< | <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 | ||
</ | </syntaxhighlight> | ||
如果點在砲管裡,這個函式返回 '''<tt>true</tt>''';否則返回 '''<tt>false</tt>'''。 | |||
這裡我們使用 [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] 相同的轉換。 | |||
這裡,我們執行與 '''<tt>paintCannon()</tt>''' 函式中繪製砲管的同樣轉換步驟。首先,我們轉移坐標系,然後旋轉。 | |||
現在我們需要檢查點 '''<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]''' | ||
< | <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) | ||
</ | </syntaxhighlight> | ||
我們建立及設定了一個 [http://doc.qt.nokia.com/latest/qframe.html Qt::Frame],並設定框架的風格。這樣的結果是一個圍繞 '''<tt>CannonField</tt>''' 的3D框架。 | |||
< | <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()')) | ||
</ | </syntaxhighlight> | ||
這裡,我們建立並設定三個 [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 中也沒有可見的影響。 | |||
我們定義了三個快捷鍵。我們希望當使用者按下 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:: | Qt::CTRL、Qt::Key_Enter、Qt::Key_Return,和 Qt::Key_Q 都是宣告在 Qt 名稱空間裡的常數。不幸的是,在 qtruby 的當前版本,他們需要轉換為整數,才能使用它們在我們的快捷鍵。 | ||
< | <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) | ||
</ | </syntaxhighlight> | ||
我們給 '''<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>''',到網格佈局中。 | |||
===執行應用程式=== | ===執行應用程式=== | ||
現在加農砲會在您按下 Enter 時射擊。您還可以使用滑鼠定位加農砲的角度。障礙使得遊戲變得更具挑戰性。我們也有一個好看的外框圍繞'''<tt>CannonField</tt>'''。 | |||
===練習=== | ===練習=== | ||
寫一個太空入侵者遊戲。 | |||
新的練習是:寫一個打磚塊遊戲。 | |||
最後的忠告:繼續往前走,並創造程式設計藝術的傑作! | |||
[[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 的外框,並加上一個障礙(牆),使遊戲更具挑戰性。
一行一行的瀏覽
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。
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。
練習
寫一個太空入侵者遊戲。
新的練習是:寫一個打磚塊遊戲。
最後的忠告:繼續往前走,並創造程式設計藝術的傑作!