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

From KDE TechBase
No edit summary
No edit summary
Line 1: Line 1:
{{Template:I18n/Language Navigation Bar_(zh_TW)|Development/Tutorials/Qt4 Ruby Tutorial/Chapter 13}}
{{Template:I18n/Language Navigation Bar_(zh_TW)|Development/Tutorials/Qt4 Ruby Tutorial/Chapter 13}} {{TutorialBrowser_(zh_TW)|
{{TutorialBrowser_(zh_TW)|


series=[[Development/Tutorials/Qt4_Ruby_Tutorial_(zh_TW)|Qt4 Ruby 教學]]|
series=[[Development/Tutorials/Qt4_Ruby_Tutorial_(zh_TW)|Qt4 Ruby 教學]]|
Line 8: Line 7:
pre=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_12_(zh_TW)|教學 12 - Hanging in the Air the Way Bricks Don't]]|
pre=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_12_(zh_TW)|教學 12 - Hanging in the Air the Way Bricks Don't]]|


next=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_14|教學 14 - Facing the Wall]]
next=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_14_(zh_TW)|教學 14 - Facing the Wall]]
}}
}}  
== Game Over ==
[[Image:Qt4_Ruby_Tutorial_Screenshot_13.png|center]]
檔案:
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/lcdrange.rb lcdrange.rb]
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/gamebrd.rb gamebrd.rb]
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/t13.rb t13.rb]
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/cannon.rb cannon.rb]


===概覽===
== Game Over  ==
在這個範例中,我們開始接近一個有分數、真正可以玩的遊戲。


我們給 '''<tt>MyWidget</tt>''' 一個新名字('''<tt>GameBoard</tt>'''),再加上一些槽。並且把它移動到 '''<tt>gamebrd.rb</tt>'''。
[[Image:Qt4 Ruby Tutorial Screenshot 13.png|center]] 檔案:


'''<tt>CannonField</tt>''' 現在有一個遊戲結束狀態。
*[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/lcdrange.rb lcdrange.rb]
*[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/gamebrd.rb gamebrd.rb]
*[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/t13.rb t13.rb]
*[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/cannon.rb cannon.rb]


'''<tt>LCDRange</tt>''' 的佈局問題被修正了。
=== 概覽 ===


===一行一行的瀏覽===
在這個範例中,我們開始接近一個有分數、真正可以玩的遊戲。
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/lcdrange.rb lcdrange.rb]'''


<code ruby>
我們給 '''<tt>MyWidget</tt>''' 一個新名字('''<tt>GameBoard</tt>'''),再加上一些槽。並且把它移動到 '''<tt>gamebrd.rb</tt>'''。
 
'''<tt>CannonField</tt>''' 現在有一個遊戲結束狀態。
 
'''<tt>LCDRange</tt>''' 的佈局問題被修正了。
 
=== 一行一行的瀏覽 ===
 
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/lcdrange.rb lcdrange.rb]'''
 
<code ruby="ruby">
     @label.setSizePolicy(Qt::SizePolicy::Preferred, Qt::SizePolicy::Fixed)
     @label.setSizePolicy(Qt::SizePolicy::Preferred, Qt::SizePolicy::Fixed)
</code>
</code>  


我們設定 [http://doc.qt.nokia.com/latest/qlabel.html Qt::Label] 的大小政策為([http://doc.qt.nokia.com/latest/qsizepolicy.html#Policy-enum Qt::SizePolicy::Preferred]、[http://doc.qt.nokia.com/latest/qsizepolicy.html#Policy-enum Qt::SizePolicy::Fixed])。垂直組件確保標籤將不會垂直伸展或收縮,它會保持在最佳大小(它的 [http://doc.qt.nokia.com/latest/qwidget.html#sizeHint-prop QWidget::sizeHint()])。這解決了在第12章發現的佈局問題。
我們設定 [http://doc.qt.nokia.com/latest/qlabel.html Qt::Label] 的大小政策為([http://doc.qt.nokia.com/latest/qsizepolicy.html#Policy-enum Qt::SizePolicy::Preferred]、[http://doc.qt.nokia.com/latest/qsizepolicy.html#Policy-enum Qt::SizePolicy::Fixed])。垂直組件確保標籤將不會垂直伸展或收縮,它會保持在最佳大小(它的 [http://doc.qt.nokia.com/latest/qwidget.html#sizeHint-prop QWidget::sizeHint()])。這解決了在第12章發現的佈局問題。  


'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/cannon.rb cannon.rb]'''
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/cannon.rb cannon.rb]'''  


'''<tt>CannonField</tt>''' 現在有遊戲結束狀態和一些新功能。
'''<tt>CannonField</tt>''' 現在有遊戲結束狀態和一些新功能。  


<code ruby>
<code ruby="ruby">
signals 'canShoot(bool)'
signals 'canShoot(bool)'
</code>
</code>  


'''<tt>CannonField</tt>''' 的這個新訊號表示 '''<tt>shoot()</tt>''' 槽的有效狀態。我們將使用它啟用(enable)或禁用(disable)下面的 <strong>Shoot</strong> 按鈕。
'''<tt>CannonField</tt>''' 的這個新訊號表示 '''<tt>shoot()</tt>''' 槽的有效狀態。我們將使用它啟用(enable)或禁用(disable)下面的 '''Shoot''' 按鈕。  


<code ruby>
<code ruby="ruby">
@gameEnded = false
@gameEnded = false
</code>
</code>  


這個變數包含了遊戲狀態;'''<tt>true</tt>''' 代表遊戲結束,而 '''<tt>false</tt>''' 代表遊戲還沒結束。(幸運的玩家:-)。
這個變數包含了遊戲狀態;'''<tt>true</tt>''' 代表遊戲結束,而 '''<tt>false</tt>''' 代表遊戲還沒結束。(幸運的玩家:-)。  


<code ruby>
<code ruby="ruby">
def shoot()
def shoot()
   if isShooting()
   if isShooting()
Line 64: Line 67:
   emit canShoot(false)
   emit canShoot(false)
end
end
</code>
</code>  


我們增加了一個新的 '''<tt>isShooting()</tt>''' 函式,所以 '''<tt>shoot()</tt>''' 使用它以取代直接測試。此外,shoot 也會告訴外界,'''<tt>CannonField</tt>''' 現在無法進行射擊。
我們增加了一個新的 '''<tt>isShooting()</tt>''' 函式,所以 '''<tt>shoot()</tt>''' 使用它以取代直接測試。此外,shoot 也會告訴外界,'''<tt>CannonField</tt>''' 現在無法進行射擊。  


<code ruby>
<code ruby="ruby">
def setGameOver()
def setGameOver()
   if @gameEnded
   if @gameEnded
Line 81: Line 84:
   update()
   update()
end
end
</code>
</code>  


這個槽終止遊戲。它必須要在 '''<tt>CannonField</tt>''' 外部呼叫,因為這個 widget 不知道什麼時候終止遊戲。這在組件程式設計中是一項重要的設計原則。我們使組件盡可能靈活,使其在不同規則都可使用(例如,由第一位命中十次的玩家獲勝的多人版本,可以使用相同的 '''<tt>CannonField</tt>''')。
這個槽終止遊戲。它必須要在 '''<tt>CannonField</tt>''' 外部呼叫,因為這個 widget 不知道什麼時候終止遊戲。這在組件程式設計中是一項重要的設計原則。我們使組件盡可能靈活,使其在不同規則都可使用(例如,由第一位命中十次的玩家獲勝的多人版本,可以使用相同的 '''<tt>CannonField</tt>''')。  


如果遊戲已經終止,我們立即返回。如果遊戲繼續,我們停止射擊、設定遊戲結束的標誌,並重繪整個 widget。
如果遊戲已經終止,我們立即返回。如果遊戲繼續,我們停止射擊、設定遊戲結束的標誌,並重繪整個 widget。  


<code ruby>
<code ruby="ruby">
def restartGame()
def restartGame()
   if isShooting()
   if isShooting()
Line 98: Line 101:
   emit canShoot(true)
   emit canShoot(true)
end
end
</code>
</code>  


這槽開始一場新遊戲。如果有砲彈在空中,我們停止射擊。然後我們重設 '''<tt>gameEnded</tt>''' 變數並重繪 widget。
這槽開始一場新遊戲。如果有砲彈在空中,我們停止射擊。然後我們重設 '''<tt>gameEnded</tt>''' 變數並重繪 widget。  


就像 '''<tt>hit()</tt>''' 或 '''<tt>miss()</tt>''','''<tt>moveShot()</tt>''' 也同時發出新的 '''<tt>canShoot(true)</tt>''' 訊號。
就像 '''<tt>hit()</tt>''' 或 '''<tt>miss()</tt>''','''<tt>moveShot()</tt>''' 也同時發出新的 '''<tt>canShoot(true)</tt>''' 訊號。  


在 CannonField::paintEvent() 中的修改:
在 CannonField::paintEvent() 中的修改:  


<code ruby>
<code ruby="ruby">
def paintEvent(event)
def paintEvent(event)
   painter = Qt::Painter.new(self)
   painter = Qt::Painter.new(self)
Line 115: Line 118:
     painter.drawText(rect(), Qt::AlignCenter, tr("Game Over"))
     painter.drawText(rect(), Qt::AlignCenter, tr("Game Over"))
   end
   end
</code>
</code>  


繪圖事件已經增強:如果遊戲結束時,換句話說 '''<tt>gameEnded</tt>''' 是'''<tt>true</tt>''',顯示文字「Game Over」。這裡我們並不費心去檢查矩形的更新。因為遊戲結束了,速度並不重要。
繪圖事件已經增強:如果遊戲結束時,換句話說 '''<tt>gameEnded</tt>''' 是'''<tt>true</tt>''',顯示文字「Game Over」。這裡我們並不費心去檢查矩形的更新。因為遊戲結束了,速度並不重要。  


為了繪製文字,我們首先設定了一支黑色的筆觸,畫筆的顏色是繪製文字時使用。接下來,我們選擇48號粗體 的 Courier 字型。最後,我們繪製文字在 widget 矩形的中心。不幸的是,在某些系統上(尤其是使用 Unicode 字集的 X 伺服器),它可能需要一段時間來載入這麼大的字體。因為 Qt 會暫存字體,所以你只有在字體第一次使用時會注意。
為了繪製文字,我們首先設定了一支黑色的筆觸,畫筆的顏色是繪製文字時使用。接下來,我們選擇48號粗體 的 Courier 字型。最後,我們繪製文字在 widget 矩形的中心。不幸的是,在某些系統上(尤其是使用 Unicode 字集的 X 伺服器),它可能需要一段時間來載入這麼大的字體。因為 Qt 會暫存字體,所以你只有在字體第一次使用時會注意。  


<code ruby>
<code ruby="ruby">
   paintCannon(painter)
   paintCannon(painter)


Line 134: Line 137:
   painter.end()
   painter.end()
end
end
</code>
</code>  


我們只有在射擊時會畫出砲彈,並且只在遊戲時(這是指遊戲還沒有結束的時候)畫出目標。
我們只有在射擊時會畫出砲彈,並且只在遊戲時(這是指遊戲還沒有結束的時候)畫出目標。  


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


This file is new. It contains the '''<tt>GameBoard</tt>''' class, which was last seen as '''<tt>MyWidget</tt>'''.
這個檔案是新加的。它包含 '''<tt>GameBoard</tt>''' 類別,也就是上次看到的 '''<tt>MyWidget</tt>'''


<code ruby>
<code ruby="ruby">
slots 'fire()', 'hit()', 'missed()', 'newGame()'
slots 'fire()', 'hit()', 'missed()', 'newGame()'
</code>
</code>  


We have now added four slots.
我們現在又增加了4個槽。


We have also made some changes in the '''<tt>GameBoard</tt>''' constructor.
我們還對 '''<tt>GameBoard</tt>''' 建構子作了一些修改。


<code ruby>
<code ruby="ruby">
@cannonField = CannonField.new()
@cannonField = CannonField.new()
</code>
</code>  


'''<tt>@cannonField</tt>''' is now a member variable, so we carefully change the constructor to use it.
'''<tt>@cannonField</tt>''' 現在是一個成員變數,所以我們小心地修改建構子以使用它。


<code ruby>
<code ruby="ruby">
connect(@cannonField, SIGNAL('hit()'),
connect(@cannonField, SIGNAL('hit()'),
         self, SLOT('hit()'))
         self, SLOT('hit()'))
connect(@cannonField, SIGNAL('missed()'),
connect(@cannonField, SIGNAL('missed()'),
         self, SLOT('missed()'))
         self, SLOT('missed()'))
</code>
</code>  


This time we want to do something when the shot has hit or missed the target. Thus we connect the '''<tt>hit()</tt>''' and '''<tt>missed()</tt>''' signals of the '''<tt>CannonField</tt>''' to two protected slots with the same names in this class.
當砲彈已經擊中或未擊中目標時,我們想要做些事。因此,我們連接 '''<tt>CannonField</tt>''' '''<tt>hit()</tt>''' '''<tt>missed()</tt>''' 訊號到這個類別中兩個同名的保護槽(protected slots)。


<code ruby>
<code ruby="ruby">
connect(shoot, SIGNAL('clicked()'), self, SLOT('fire()') )
connect(shoot, SIGNAL('clicked()'), self, SLOT('fire()') )
</code>
</code>  


Previously we connected the <strong>Shoot</strong> button's '''<tt>clicked()</tt>''' signal directly to the '''<tt>CannonField</tt>''''s '''<tt>shoot()</tt>''' slot. This time we want to keep track of the number of shots fired, so we connect it to a slot in this class instead.
之前,我們直接連接 '''Shoot''' 按鈕的 '''<tt>clicked()</tt>''' 訊號到'''<tt>CannonField</tt>''' '''<tt>shoot()</tt>''' 槽。這次我們想要紀錄發射的數目,因此我們將它連接到這個類別中的一個槽。


Notice how easy it is to change the behavior of a program when you are working with self-contained components.
請注意,當你使用獨立組件運作時,改變一個程式的行為是多麼地容易。


<code ruby>
<code ruby="ruby">
connect(@cannonField, SIGNAL('canShoot(bool)'),
connect(@cannonField, SIGNAL('canShoot(bool)'),
         shoot, SLOT('setEnabled(bool)'))
         shoot, SLOT('setEnabled(bool)'))
</code>
</code>  


We also use the '''<tt>CannonField</tt>''''s '''<tt>canShoot()</tt>''' signal to enable or disable the <strong>Shoot</strong> button appropriately.
我們還使用 '''<tt>CannonField</tt>''' '''<tt>canShoot()</tt>''' 訊號來適當地啟用或禁用 '''Shoot''' 按鈕。


<code ruby>
<code ruby="ruby">
restart = Qt::PushButton.new(tr('&New Game'))
restart = Qt::PushButton.new(tr('&New Game'))
restart.setFont(Qt::Font.new('Times', 18, Qt::Font::Bold))
restart.setFont(Qt::Font.new('Times', 18, Qt::Font::Bold))


connect(restart, SIGNAL('clicked()'), self, SLOT('newGame()'))
connect(restart, SIGNAL('clicked()'), self, SLOT('newGame()'))
</code>
</code>  


We create, set up, and connect the <strong>New Game</strong> button as we have done with the other buttons. Clicking this button will activate the '''<tt>newGame()</tt>''' slot in this widget.
我們建立、設定,並連接 '''New Game''' 按鈕,就像我們為其他按鈕所做的。按下這個按鈕將會啟動這個 widget 的 '''<tt>newGame()</tt>''' 槽。


<code ruby>
<code ruby="ruby">
@hits = Qt::LCDNumber.new(2)
@hits = Qt::LCDNumber.new(2)
@shotsLeft = Qt::LCDNumber.new(2)
@shotsLeft = Qt::LCDNumber.new(2)
hitsLabel = Qt::Label.new(tr('HITS'))
hitsLabel = Qt::Label.new(tr('HITS'))
shotsLeftLabel = Qt::Label.new(tr('SHOTS LEFT'))
shotsLeftLabel = Qt::Label.new(tr('SHOTS LEFT'))
</code>
</code>  


We create four new widgets, to display the number of hits and shots left.
我們建立四個新的 widget,以顯示擊中(hits)次數,和剩餘砲彈(shots left)。


<code ruby>
<code ruby="ruby">
topLayout = Qt::HBoxLayout.new()
topLayout = Qt::HBoxLayout.new()
topLayout.addWidget(shoot)
topLayout.addWidget(shoot)
Line 207: Line 210:
topLayout.addStretch(1)
topLayout.addStretch(1)
topLayout.addWidget(restart)
topLayout.addWidget(restart)
</code>
</code>  


The top-right cell of the [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] is starting to get crowded. We put a stretch just to the left of the <strong>New Game</strong> button to ensure that this button will always appear on the right side of the window.
[http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] 的右上格開始變擠了。我們把一個 stretch 放在 '''New Game''' 按鈕的左邊,以確保此按鈕總是出現在視窗的右側。


<code ruby>
<code ruby="ruby">
newGame()
newGame()
</code>
</code>  


We're all done constructing the '''<tt>GameBoard</tt>''', so we start it all using '''<tt>newGame()</tt>'''. Although '''<tt>newGame()</tt>''' is a slot, it can also be used as an ordinary function.
我們完成所有的 '''<tt>GameBoard</tt>''' 建設,所以我們使用 '''<tt>newGame()</tt>''' 來開始。雖然 '''<tt>newGame()</tt>''' 是一個槽,它也可以當作普通的函式。


<code ruby>
<code ruby="ruby">
def fire()
def fire()
   if @cannonField.gameOver() || @cannonField.isShooting()
   if @cannonField.gameOver() || @cannonField.isShooting()
Line 226: Line 229:
   @cannonField.shoot()
   @cannonField.shoot()
end
end
</code>
</code>  


This function fires a shot. If the game is over or if there is a shot in the air, we return immediately. We decrement the number of shots left and tell the cannon to shoot.
這個函式會發射一顆砲彈。如果遊戲結束,或有一顆砲彈在空中,我們立即返回。否則我們減少剩餘砲彈數,並告訴加農砲射擊。


<code ruby>
<code ruby="ruby">
def hit()
def hit()
   @hits.display(@hits.intValue() + 1)
   @hits.display(@hits.intValue() + 1)
Line 240: Line 243:
   end
   end
end
end
</code>
</code>  


This slot is activated when a shot has hit the target. We increment the number of hits. If there are no shots left, the game is over. Otherwise, we make the '''<tt>CannonField</tt>''' generate a new target.
這個槽會在砲彈擊中目標時啟動。我們增加擊中次數。如果沒有剩餘砲彈,遊戲結束。否則,我們讓 '''<tt>CannonField</tt>''' 產生一個新的目標。


<code ruby>
<code ruby="ruby">
def missed()
def missed()
   if @shotsLeft.intValue() == 0
   if @shotsLeft.intValue() == 0
Line 250: Line 253:
   end
   end
end
end
</code>
</code>  


This slot is activated when a shot has missed the target. If there are no shots left, the game is over.
這個槽會在砲彈沒打中目標時啟動。如果沒有剩餘砲彈,遊戲結束。


<code ruby>
<code ruby="ruby">
def newGame()
def newGame()
   @shotsLeft.display(15)
   @shotsLeft.display(15)
Line 261: Line 264:
   @cannonField.newTarget()
   @cannonField.newTarget()
end
end
</code>
</code>  
 
這個槽會在使用者按下 '''New Game''' 按鈕時啟動。它也會被建構子呼叫。首先,它設定砲彈數為15。請注意,這是程式中我們唯一設定砲彈數的地方。可以更改它為任何您想要的遊戲規則。接下來,我們重設擊中次數、重新啟動遊戲,並產生一個新的目標。


This slot is activated when the user clicks the <strong>New Game</strong> button. It is also called from the constructor. First it sets the number of shots to 15. Note that this is the only place in the program where we set the number of shots. Change it to whatever you like to change the game rules. Next we reset the number of hits, restart the game, and generate a new target.
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/t13.rb t13.rb]'''


'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t13/t13.rb t13.rb]'''
這個檔案剛剛減肥。'''<tt>MyWidget</tt>''' 離開了,唯一留下的是 '''<tt>main()</tt>''' 函式,它除了名稱以外沒有其它改變。


This file has just been on a diet. '''<tt>MyWidget</tt>''' is gone, and the only thing left is the '''<tt>main()</tt>''' function, unchanged except for the name change.
=== 執行應用程式 ===


===執行應用程式===
加農砲可以射擊目標了。一個新的目標會在舊的被擊中後自動建立。
The cannon can shoot at a target; a new target is automatically created when one has been hit.


Hits and shots left are displayed and the program keeps track of them. The game can end, and there's a button to start a new game.
顯示擊中和剩餘砲彈,並且程式也會紀錄他們。遊戲可以終止,而且有一個按鈕可以開始新遊戲。


===練習===
=== 練習 ===
Add a random wind factor and show it to the user.
加入一個隨機的因子:風。並顯示給使用者。


Make some splatter effects when the shot hits the target.
當砲彈擊中目標時,做出一些飛濺(splatter)的效果。


Implement multiple targets.
實現多個目標。


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

Revision as of 09:57, 16 January 2010

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

Game Over

檔案:

概覽

在這個範例中,我們開始接近一個有分數、真正可以玩的遊戲。

我們給 MyWidget 一個新名字(GameBoard),再加上一些槽。並且把它移動到 gamebrd.rb

CannonField 現在有一個遊戲結束狀態。

LCDRange 的佈局問題被修正了。

一行一行的瀏覽

lcdrange.rb

   @label.setSizePolicy(Qt::SizePolicy::Preferred, Qt::SizePolicy::Fixed)

我們設定 Qt::Label 的大小政策為(Qt::SizePolicy::PreferredQt::SizePolicy::Fixed)。垂直組件確保標籤將不會垂直伸展或收縮,它會保持在最佳大小(它的 QWidget::sizeHint())。這解決了在第12章發現的佈局問題。

cannon.rb

CannonField 現在有遊戲結束狀態和一些新功能。

signals 'canShoot(bool)'

CannonField 的這個新訊號表示 shoot() 槽的有效狀態。我們將使用它啟用(enable)或禁用(disable)下面的 Shoot 按鈕。

@gameEnded = false

這個變數包含了遊戲狀態;true 代表遊戲結束,而 false 代表遊戲還沒結束。(幸運的玩家:-)。

def shoot()

 if isShooting()
   return
 end
 @timerCount = 0
 @shootAngle = @currentAngle
 @shootForce = @currentForce
 @autoShootTimer.start(5)
 emit canShoot(false)

end

我們增加了一個新的 isShooting() 函式,所以 shoot() 使用它以取代直接測試。此外,shoot 也會告訴外界,CannonField 現在無法進行射擊。

def setGameOver()

 if @gameEnded
   return
 end
 if isShooting()
   @autoShootTimer.stop()
 end
 @gameEnded = true
 update()

end

這個槽終止遊戲。它必須要在 CannonField 外部呼叫,因為這個 widget 不知道什麼時候終止遊戲。這在組件程式設計中是一項重要的設計原則。我們使組件盡可能靈活,使其在不同規則都可使用(例如,由第一位命中十次的玩家獲勝的多人版本,可以使用相同的 CannonField)。

如果遊戲已經終止,我們立即返回。如果遊戲繼續,我們停止射擊、設定遊戲結束的標誌,並重繪整個 widget。

def restartGame()

 if isShooting()
   @autoShootTimer.stop()
 end
 @gameEnded = false
 update()
 emit canShoot(true)

end

這槽開始一場新遊戲。如果有砲彈在空中,我們停止射擊。然後我們重設 gameEnded 變數並重繪 widget。

就像 hit()miss()moveShot() 也同時發出新的 canShoot(true) 訊號。

在 CannonField::paintEvent() 中的修改:

def paintEvent(event)

 painter = Qt::Painter.new(self)
 if @gameEnded
   painter.setPen(Qt::black)
   painter.setFont(Qt::Font.new( "Courier", 48, Qt::Font::Bold))
   painter.drawText(rect(), Qt::AlignCenter, tr("Game Over"))
 end

繪圖事件已經增強:如果遊戲結束時,換句話說 gameEndedtrue,顯示文字「Game Over」。這裡我們並不費心去檢查矩形的更新。因為遊戲結束了,速度並不重要。

為了繪製文字,我們首先設定了一支黑色的筆觸,畫筆的顏色是繪製文字時使用。接下來,我們選擇48號粗體 的 Courier 字型。最後,我們繪製文字在 widget 矩形的中心。不幸的是,在某些系統上(尤其是使用 Unicode 字集的 X 伺服器),它可能需要一段時間來載入這麼大的字體。因為 Qt 會暫存字體,所以你只有在字體第一次使用時會注意。

 paintCannon(painter)
 if isShooting()
   paintShot(painter)
 end        
 unless @gameEnded
   paintTarget(painter)
 end
 painter.end()

end

我們只有在射擊時會畫出砲彈,並且只在遊戲時(這是指遊戲還沒有結束的時候)畫出目標。

gamebrd.rb

這個檔案是新加的。它包含 GameBoard 類別,也就是上次看到的 MyWidget

slots 'fire()', 'hit()', 'missed()', 'newGame()'

我們現在又增加了4個槽。

我們還對 GameBoard 建構子作了一些修改。

@cannonField = CannonField.new()

@cannonField 現在是一個成員變數,所以我們小心地修改建構子以使用它。

connect(@cannonField, SIGNAL('hit()'),

       self, SLOT('hit()'))

connect(@cannonField, SIGNAL('missed()'),

       self, SLOT('missed()'))

當砲彈已經擊中或未擊中目標時,我們想要做些事。因此,我們連接 CannonFieldhit()missed() 訊號到這個類別中兩個同名的保護槽(protected slots)。

connect(shoot, SIGNAL('clicked()'), self, SLOT('fire()') )

之前,我們直接連接 Shoot 按鈕的 clicked() 訊號到CannonFieldshoot() 槽。這次我們想要紀錄發射的數目,因此我們將它連接到這個類別中的一個槽。

請注意,當你使用獨立組件運作時,改變一個程式的行為是多麼地容易。

connect(@cannonField, SIGNAL('canShoot(bool)'),

       shoot, SLOT('setEnabled(bool)'))

我們還使用 CannonFieldcanShoot() 訊號來適當地啟用或禁用 Shoot 按鈕。

restart = Qt::PushButton.new(tr('&New Game')) restart.setFont(Qt::Font.new('Times', 18, Qt::Font::Bold))

connect(restart, SIGNAL('clicked()'), self, SLOT('newGame()'))

我們建立、設定,並連接 New Game 按鈕,就像我們為其他按鈕所做的。按下這個按鈕將會啟動這個 widget 的 newGame() 槽。

@hits = Qt::LCDNumber.new(2) @shotsLeft = Qt::LCDNumber.new(2) hitsLabel = Qt::Label.new(tr('HITS')) shotsLeftLabel = Qt::Label.new(tr('SHOTS LEFT'))

我們建立四個新的 widget,以顯示擊中(hits)次數,和剩餘砲彈(shots left)。

topLayout = Qt::HBoxLayout.new() topLayout.addWidget(shoot) topLayout.addWidget(@hits) topLayout.addWidget(hitsLabel) topLayout.addWidget(@shotsLeft) topLayout.addWidget(shotsLeftLabel) topLayout.addStretch(1) topLayout.addWidget(restart)

Qt::GridLayout 的右上格開始變擠了。我們把一個 stretch 放在 New Game 按鈕的左邊,以確保此按鈕總是出現在視窗的右側。

newGame()

我們完成所有的 GameBoard 建設,所以我們使用 newGame() 來開始。雖然 newGame() 是一個槽,它也可以當作普通的函式。

def fire()

 if @cannonField.gameOver() || @cannonField.isShooting()
   return
 end
 @shotsLeft.display(@shotsLeft.intValue() - 1)
 @cannonField.shoot()

end

這個函式會發射一顆砲彈。如果遊戲結束,或有一顆砲彈在空中,我們立即返回。否則我們減少剩餘砲彈數,並告訴加農砲射擊。

def hit()

 @hits.display(@hits.intValue() + 1)
 if @shotsLeft.intValue() == 0
   @cannonField.setGameOver()
 else
   @cannonField.newTarget()
 end

end

這個槽會在砲彈擊中目標時啟動。我們增加擊中次數。如果沒有剩餘砲彈,遊戲結束。否則,我們讓 CannonField 產生一個新的目標。

def missed()

 if @shotsLeft.intValue() == 0
   @cannonField.setGameOver()
 end

end

這個槽會在砲彈沒打中目標時啟動。如果沒有剩餘砲彈,遊戲結束。

def newGame()

 @shotsLeft.display(15)
 @hits.display(0)
 @cannonField.restartGame()
 @cannonField.newTarget()

end

這個槽會在使用者按下 New Game 按鈕時啟動。它也會被建構子呼叫。首先,它設定砲彈數為15。請注意,這是程式中我們唯一設定砲彈數的地方。可以更改它為任何您想要的遊戲規則。接下來,我們重設擊中次數、重新啟動遊戲,並產生一個新的目標。

t13.rb

這個檔案剛剛減肥。MyWidget 離開了,唯一留下的是 main() 函式,它除了名稱以外沒有其它改變。

執行應用程式

加農砲可以射擊目標了。一個新的目標會在舊的被擊中後自動建立。

顯示擊中和剩餘砲彈,並且程式也會紀錄他們。遊戲可以終止,而且有一個按鈕可以開始新遊戲。

練習

加入一個隨機的因子:風。並顯示給使用者。

當砲彈擊中目標時,做出一些飛濺(splatter)的效果。

實現多個目標。