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

From KDE TechBase
No edit summary
m (Text replace - "</code>" to "</syntaxhighlight>")
Line 35: Line 35:
<code ruby="ruby">
<code ruby="ruby">
     @label.setSizePolicy(Qt::SizePolicy::Preferred, Qt::SizePolicy::Fixed)
     @label.setSizePolicy(Qt::SizePolicy::Preferred, Qt::SizePolicy::Fixed)
</code>  
</syntaxhighlight>  


我們設定 [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章發現的佈局問題。  
Line 45: Line 45:
<code ruby="ruby">
<code ruby="ruby">
signals 'canShoot(bool)'
signals 'canShoot(bool)'
</code>  
</syntaxhighlight>  


'''<tt>CannonField</tt>''' 的這個新訊號表示 '''<tt>shoot()</tt>''' 槽的有效狀態。我們將使用它啟用(enable)或禁用(disable)下面的 '''Shoot''' 按鈕。  
'''<tt>CannonField</tt>''' 的這個新訊號表示 '''<tt>shoot()</tt>''' 槽的有效狀態。我們將使用它啟用(enable)或禁用(disable)下面的 '''Shoot''' 按鈕。  
Line 51: Line 51:
<code ruby="ruby">
<code ruby="ruby">
@gameEnded = false
@gameEnded = false
</code>  
</syntaxhighlight>  


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


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


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


這槽開始一場新遊戲。如果有砲彈在空中,我們停止射擊。然後我們重設 '''<tt>gameEnded</tt>''' 變數並重繪 widget。  
這槽開始一場新遊戲。如果有砲彈在空中,我們停止射擊。然後我們重設 '''<tt>gameEnded</tt>''' 變數並重繪 widget。  
Line 118: Line 118:
     painter.drawText(rect(), Qt::AlignCenter, tr("Game Over"))
     painter.drawText(rect(), Qt::AlignCenter, tr("Game Over"))
   end
   end
</code>  
</syntaxhighlight>  


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


我們只有在射擊時會畫出砲彈,並且只在遊戲時(這是指遊戲還沒有結束的時候)畫出目標。  
我們只有在射擊時會畫出砲彈,並且只在遊戲時(這是指遊戲還沒有結束的時候)畫出目標。  
Line 147: Line 147:
<code ruby="ruby">
<code ruby="ruby">
slots 'fire()', 'hit()', 'missed()', 'newGame()'
slots 'fire()', 'hit()', 'missed()', 'newGame()'
</code>  
</syntaxhighlight>  


我們現在又增加了4個槽。
我們現在又增加了4個槽。
Line 155: Line 155:
<code ruby="ruby">
<code ruby="ruby">
@cannonField = CannonField.new()
@cannonField = CannonField.new()
</code>  
</syntaxhighlight>  


'''<tt>@cannonField</tt>''' 現在是一個成員變數,所以我們小心地修改建構子以使用它。
'''<tt>@cannonField</tt>''' 現在是一個成員變數,所以我們小心地修改建構子以使用它。
Line 164: Line 164:
connect(@cannonField, SIGNAL('missed()'),
connect(@cannonField, SIGNAL('missed()'),
         self, SLOT('missed()'))
         self, SLOT('missed()'))
</code>  
</syntaxhighlight>  


當砲彈已經擊中或未擊中目標時,我們想要做些事。因此,我們連接 '''<tt>CannonField</tt>''' 的 '''<tt>hit()</tt>''' 和 '''<tt>missed()</tt>''' 訊號到這個類別中兩個同名的保護槽(protected slots)。
當砲彈已經擊中或未擊中目標時,我們想要做些事。因此,我們連接 '''<tt>CannonField</tt>''' 的 '''<tt>hit()</tt>''' 和 '''<tt>missed()</tt>''' 訊號到這個類別中兩個同名的保護槽(protected slots)。
Line 170: Line 170:
<code ruby="ruby">
<code ruby="ruby">
connect(shoot, SIGNAL('clicked()'), self, SLOT('fire()') )
connect(shoot, SIGNAL('clicked()'), self, SLOT('fire()') )
</code>  
</syntaxhighlight>  


之前,我們直接連接 '''Shoot''' 按鈕的 '''<tt>clicked()</tt>''' 訊號到'''<tt>CannonField</tt>''' 的 '''<tt>shoot()</tt>''' 槽。這次我們想要紀錄發射的數目,因此我們將它連接到這個類別中的一個槽。
之前,我們直接連接 '''Shoot''' 按鈕的 '''<tt>clicked()</tt>''' 訊號到'''<tt>CannonField</tt>''' 的 '''<tt>shoot()</tt>''' 槽。這次我們想要紀錄發射的數目,因此我們將它連接到這個類別中的一個槽。
Line 179: Line 179:
connect(@cannonField, SIGNAL('canShoot(bool)'),
connect(@cannonField, SIGNAL('canShoot(bool)'),
         shoot, SLOT('setEnabled(bool)'))
         shoot, SLOT('setEnabled(bool)'))
</code>  
</syntaxhighlight>  


我們還使用 '''<tt>CannonField</tt>''' 的 '''<tt>canShoot()</tt>''' 訊號來適當地啟用或禁用 '''Shoot''' 按鈕。
我們還使用 '''<tt>CannonField</tt>''' 的 '''<tt>canShoot()</tt>''' 訊號來適當地啟用或禁用 '''Shoot''' 按鈕。
Line 188: Line 188:


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


我們建立、設定,並連接 '''New Game''' 按鈕,就像我們為其他按鈕所做的。按下這個按鈕將會啟動這個 widget 的 '''<tt>newGame()</tt>''' 槽。
我們建立、設定,並連接 '''New Game''' 按鈕,就像我們為其他按鈕所做的。按下這個按鈕將會啟動這個 widget 的 '''<tt>newGame()</tt>''' 槽。
Line 197: Line 197:
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>  
</syntaxhighlight>  


我們建立四個新的 widget,以顯示擊中(hits)次數,和剩餘砲彈(shots left)。
我們建立四個新的 widget,以顯示擊中(hits)次數,和剩餘砲彈(shots left)。
Line 210: Line 210:
topLayout.addStretch(1)
topLayout.addStretch(1)
topLayout.addWidget(restart)
topLayout.addWidget(restart)
</code>  
</syntaxhighlight>  


[http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] 的右上格開始變擠了。我們把一個 stretch 放在 '''New Game''' 按鈕的左邊,以確保此按鈕總是出現在視窗的右側。
[http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] 的右上格開始變擠了。我們把一個 stretch 放在 '''New Game''' 按鈕的左邊,以確保此按鈕總是出現在視窗的右側。
Line 216: Line 216:
<code ruby="ruby">
<code ruby="ruby">
newGame()
newGame()
</code>  
</syntaxhighlight>  


我們完成所有的 '''<tt>GameBoard</tt>''' 建設,所以我們使用 '''<tt>newGame()</tt>''' 來開始。雖然 '''<tt>newGame()</tt>''' 是一個槽,它也可以當作普通的函式。
我們完成所有的 '''<tt>GameBoard</tt>''' 建設,所以我們使用 '''<tt>newGame()</tt>''' 來開始。雖然 '''<tt>newGame()</tt>''' 是一個槽,它也可以當作普通的函式。
Line 229: Line 229:
   @cannonField.shoot()
   @cannonField.shoot()
end
end
</code>  
</syntaxhighlight>  


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


這個槽會在砲彈擊中目標時啟動。我們增加擊中次數。如果沒有剩餘砲彈,遊戲結束。否則,我們讓 '''<tt>CannonField</tt>''' 產生一個新的目標。
這個槽會在砲彈擊中目標時啟動。我們增加擊中次數。如果沒有剩餘砲彈,遊戲結束。否則,我們讓 '''<tt>CannonField</tt>''' 產生一個新的目標。
Line 253: Line 253:
   end
   end
end
end
</code>  
</syntaxhighlight>  


這個槽會在砲彈沒打中目標時啟動。如果沒有剩餘砲彈,遊戲結束。
這個槽會在砲彈沒打中目標時啟動。如果沒有剩餘砲彈,遊戲結束。
Line 264: Line 264:
   @cannonField.newTarget()
   @cannonField.newTarget()
end
end
</code>  
</syntaxhighlight>  


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

Revision as of 20:56, 29 June 2011

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)

</syntaxhighlight>

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

cannon.rb

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

signals 'canShoot(bool)' </syntaxhighlight>

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

@gameEnded = false </syntaxhighlight>

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

def shoot()

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

end </syntaxhighlight>

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

def setGameOver()

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

end </syntaxhighlight>

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

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

def restartGame()

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

end </syntaxhighlight>

這槽開始一場新遊戲。如果有砲彈在空中,我們停止射擊。然後我們重設 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

</syntaxhighlight>

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

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

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

end </syntaxhighlight>

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

gamebrd.rb

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

slots 'fire()', 'hit()', 'missed()', 'newGame()' </syntaxhighlight>

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

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

@cannonField = CannonField.new() </syntaxhighlight>

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

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

       self, SLOT('hit()'))

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

       self, SLOT('missed()'))

</syntaxhighlight>

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

connect(shoot, SIGNAL('clicked()'), self, SLOT('fire()') ) </syntaxhighlight>

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

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

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

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

</syntaxhighlight>

我們還使用 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()')) </syntaxhighlight>

我們建立、設定,並連接 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')) </syntaxhighlight>

我們建立四個新的 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) </syntaxhighlight>

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

newGame() </syntaxhighlight>

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

def fire()

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

end </syntaxhighlight>

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

def hit()

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

end </syntaxhighlight>

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

def missed()

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

end </syntaxhighlight>

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

def newGame()

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

end </syntaxhighlight>

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

t13.rb

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

執行應用程式

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

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

練習

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

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

實現多個目標。