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

From KDE TechBase
(Created page with '{{Template:I18n/Language Navigation Bar_(zh_TW)|Development/Tutorials/Qt4 Ruby Tutorial/Chapter 13}} {{TutorialBrowser_(zh_TW)| series=[[Development/Tutorials/Qt4_Ruby_Tutorial_...')
 
No edit summary
Line 44: Line 44:
</code>
</code>


This new signal indicates that the '''<tt>CannonField</tt>''' is in a state where the '''<tt>shoot()</tt>''' slot makes sense. We'll use it below to enable or disable the <strong>Shoot</strong> button.
'''<tt>CannonField</tt>''' 的這個新訊號表示 '''<tt>shoot()</tt>''' 槽的有效狀態。我們將使用它啟用(enable)或禁用(disable)下面的 <strong>Shoot</strong> 按鈕。


<code ruby>
<code ruby>
Line 50: Line 50:
</code>
</code>


This variable contains the game state; '''<tt>true</tt>''' means that the game is over, and '''<tt>false</tt>''' means that a game is going on. Initially, the game is not over (luckily for the player :-).
這個變數包含了遊戲狀態;'''<tt>true</tt>''' 代表遊戲結束,而 '''<tt>false</tt>''' 代表遊戲還沒結束。(幸運的玩家:-)


<code ruby>
<code ruby>
Line 66: Line 66:
</code>
</code>


We added a new '''<tt>isShooting()</tt>''' function, so '''<tt>shoot()</tt>''' uses it instead of testing directly. Also, shoot tells the world that the '''<tt>CannonField</tt>''' cannot shoot now.
我們增加了一個新的 '''<tt>isShooting()</tt>''' 函式,所以 '''<tt>shoot()</tt>''' 使用它以取代直接測試。此外,shoot 也會告訴外界,'''<tt>CannonField</tt>''' 現在無法進行射擊。


<code ruby>
<code ruby>
Line 83: Line 83:
</code>
</code>


This slot ends the game. It must be called from outside '''<tt>CannonField</tt>''', because this widget does not know when to end the game. This is an important design principle in component programming. We choose to make the component as flexible as possible to make it usable with different rules (for example, a multi-player version of this in which the first player to hit ten times wins could use the '''<tt>CannonField</tt>''' unchanged).
這個槽終止遊戲。它必須要在 '''<tt>CannonField</tt>''' 外部呼叫,因為這個 widget 不知道什麼時候終止遊戲。這在組件程式設計中是一項重要的設計原則。我們使組件盡可能靈活,使其在不同規則都可使用(例如,由第一位命中十次的玩家獲勝的多人版本,可以使用相同的 '''<tt>CannonField</tt>''')。


If the game has already been ended we return immediately. If a game is going on we stop the shot, set the game over flag, and repaint the entire widget.
如果遊戲已經終止,我們立即返回。如果遊戲繼續,我們停止射擊、設定遊戲結束的標誌,並重繪整個 widget。


<code ruby>
<code ruby>
Line 100: Line 100:
</code>
</code>


This slot starts a new game. If a shot is in the air, we stop shooting. We then reset the '''<tt>gameEnded</tt>''' variable and repaint the widget.
這槽開始一場新遊戲。如果有砲彈在空中,我們停止射擊。然後我們重設 '''<tt>gameEnded</tt>''' 變數並重繪 widget。


'''<tt>moveShot()</tt>''' too emits the new '''<tt>canShoot(true)</tt>''' signal at the same time as either '''<tt>hit()</tt>''' or '''<tt>miss()</tt>'''.
就像 '''<tt>hit()</tt>''' '''<tt>miss()</tt>''''''<tt>moveShot()</tt>''' 也同時發出新的 '''<tt>canShoot(true)</tt>''' 訊號。


Modifications in CannonField::paintEvent():
CannonField::paintEvent() 中的修改:


<code ruby>
<code ruby>
Line 117: Line 117:
</code>
</code>


The paint event has been enhanced to display the text "Game Over" if the game is over, i.e., '''<tt>gameEnded</tt>''' is '''<tt>true</tt>'''. We don't bother to check the update rectangle here because speed is not critical when the game is over.
繪圖事件已經增強:如果遊戲結束時,換句話說 '''<tt>gameEnded</tt>''' '''<tt>true</tt>''',顯示文字「Game Over」。這裡我們並不費心去檢查矩形的更新。因為遊戲結束了,速度並不重要。


To draw the text we first set a black pen; the pen color is used when drawing text. Next we choose a 48 point bold font from the Courier family. Finally we draw the text centered in the widget's rectangle. Unfortunately, on some systems (especially X servers with Unicode fonts) it can take a while to load such a large font. Because Qt caches fonts, you will notice this only the first time the font is used.
為了繪製文字,我們首先設定了一支黑色的筆觸,畫筆的顏色是繪製文字時使用。接下來,我們選擇48號粗體 的 Courier 字型。最後,我們繪製文字在 widget 矩形的中心。不幸的是,在某些系統上(尤其是使用 Unicode 字集的 X 伺服器),它可能需要一段時間來載入這麼大的字體。因為 Qt 會暫存字體,所以你只有在字體第一次使用時會注意。


<code ruby>
<code ruby>
Line 136: Line 136:
</code>
</code>


We draw the shot only when shooting and the target only when playing (that is, when the game is not ended).
我們只有在射擊時會畫出砲彈,並且只在遊戲時(這是指遊戲還沒有結束的時候)畫出目標。


'''[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]'''

Revision as of 05:08, 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

This file is new. It contains the GameBoard class, which was last seen as MyWidget.

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

We have now added four slots.

We have also made some changes in the GameBoard constructor.

@cannonField = CannonField.new()

@cannonField is now a member variable, so we carefully change the constructor to use it.

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

       self, SLOT('hit()'))

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

       self, SLOT('missed()'))

This time we want to do something when the shot has hit or missed the target. Thus we connect the hit() and missed() signals of the CannonField to two protected slots with the same names in this class.

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

Previously we connected the Shoot button's clicked() signal directly to the CannonField's shoot() 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.

Notice how easy it is to change the behavior of a program when you are working with self-contained components.

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

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

We also use the CannonField's canShoot() signal to enable or disable the Shoot button appropriately.

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

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

We create, set up, and connect the New Game button as we have done with the other buttons. Clicking this button will activate the newGame() slot in this widget.

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

We create four new widgets, to display the number of hits and 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)

The top-right cell of the Qt::GridLayout is starting to get crowded. We put a stretch just to the left of the New Game button to ensure that this button will always appear on the right side of the window.

newGame()

We're all done constructing the GameBoard, so we start it all using newGame(). Although newGame() is a slot, it can also be used as an ordinary function.

def fire()

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

end

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.

def hit()

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

end

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 CannonField generate a new target.

def missed()

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

end

This slot is activated when a shot has missed the target. If there are no shots left, the game is over.

def newGame()

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

end

This slot is activated when the user clicks the New Game 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.

t13.rb

This file has just been on a diet. MyWidget is gone, and the only thing left is the main() 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.

Implement multiple targets.