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

    From KDE TechBase
    (Created page with '{{Template:I18n/Language Navigation Bar_(zh_CN)|Development/Tutorials/Qt4 Ruby Tutorial/Chapter 13}} {{TutorialBrowser_(zh_CN)| series=[[Development/Tutorials/Qt4_Ruby_Tutorial...')
     
    m (Text replace - "</code>" to "</syntaxhighlight>")
    Line 34: Line 34:
    <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 44: Line 44:
    <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 50: Line 50:
    <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 66: Line 66:
       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 83: Line 83:
       update()
       update()
    end
    end
    </code>  
    </syntaxhighlight>  


    这个槽终止游戏。它必须要在 '''<tt>CannonField</tt>''' 外部呼叫,因为这个 widget 不知道什么时候终止游戏。这在组件程序设计中是一项重要的设计原则。我们使组件尽可能灵活,使其在不同规则都可使用(例如,由第一位命中十次的玩家获胜的多人版本,可以使用相同的 '''<tt>CannonField</tt>''')。  
    这个槽终止游戏。它必须要在 '''<tt>CannonField</tt>''' 外部呼叫,因为这个 widget 不知道什么时候终止游戏。这在组件程序设计中是一项重要的设计原则。我们使组件尽可能灵活,使其在不同规则都可使用(例如,由第一位命中十次的玩家获胜的多人版本,可以使用相同的 '''<tt>CannonField</tt>''')。  
    Line 100: Line 100:
       emit canShoot(true)
       emit canShoot(true)
    end
    end
    </code>  
    </syntaxhighlight>  


    这槽开始一场新游戏。如果有炮弹在空中,我们停止射击。然后我们重设 '''<tt>gameEnded</tt>''' 变量并重绘 widget。  
    这槽开始一场新游戏。如果有炮弹在空中,我们停止射击。然后我们重设 '''<tt>gameEnded</tt>''' 变量并重绘 widget。  
    Line 117: Line 117:
         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 136: Line 136:
       painter.end()
       painter.end()
    end
    end
    </code>  
    </syntaxhighlight>  


    我们只有在射击时会画出炮弹,并且只在游戏时(这是指游戏还没有结束的时候)画出目标。  
    我们只有在射击时会画出炮弹,并且只在游戏时(这是指游戏还没有结束的时候)画出目标。  
    Line 146: Line 146:
    <code ruby="ruby">
    <code ruby="ruby">
    slots 'fire()', 'hit()', 'missed()', 'newGame()'
    slots 'fire()', 'hit()', 'missed()', 'newGame()'
    </code>  
    </syntaxhighlight>  


    我们现在又增加了4个槽。
    我们现在又增加了4个槽。
    Line 154: Line 154:
    <code ruby="ruby">
    <code ruby="ruby">
    @cannonField = CannonField.new()
    @cannonField = CannonField.new()
    </code>  
    </syntaxhighlight>  


    '''<tt>@cannonField</tt>''' 现在是一个成员变量,所以我们小心地修改建构子以使用它。
    '''<tt>@cannonField</tt>''' 现在是一个成员变量,所以我们小心地修改建构子以使用它。
    Line 163: Line 163:
    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 169: Line 169:
    <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 178: Line 178:
    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 187: Line 187:


    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 196: Line 196:
    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 209: Line 209:
    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 215: Line 215:
    <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 228: Line 228:
       @cannonField.shoot()
       @cannonField.shoot()
    end
    end
    </code>  
    </syntaxhighlight>  


    这个函式会发射一颗炮弹。如果游戏结束,或有一颗炮弹在空中,我们立即返回。否则我们减少剩余炮弹数,并告诉加农炮射击。
    这个函式会发射一颗炮弹。如果游戏结束,或有一颗炮弹在空中,我们立即返回。否则我们减少剩余炮弹数,并告诉加农炮射击。
    Line 242: Line 242:
       end
       end
    end
    end
    </code>  
    </syntaxhighlight>  


    这个槽会在炮弹击中目标时启动。我们增加击中次数。如果没有剩余炮弹,游戏结束。否则,我们让 '''<tt>CannonField</tt>''' 产生一个新的目标。
    这个槽会在炮弹击中目标时启动。我们增加击中次数。如果没有剩余炮弹,游戏结束。否则,我们让 '''<tt>CannonField</tt>''' 产生一个新的目标。
    Line 252: Line 252:
       end
       end
    end
    end
    </code>  
    </syntaxhighlight>  


    这个槽会在炮弹没打中目标时启动。如果没有剩余炮弹,游戏结束。
    这个槽会在炮弹没打中目标时启动。如果没有剩余炮弹,游戏结束。
    Line 263: Line 263:
       @cannonField.newTarget()
       @cannonField.newTarget()
    end
    end
    </code>  
    </syntaxhighlight>  


    这个槽会在使用者按下 '''New Game''' 按钮时启动。它也会被建构子呼叫。首先,它设定炮弹数为15。请注意,这是程序中我们唯一设定炮弹数的地方。可以更改它为任何您想要的游戏规则。接下来,我们重设击中次数、重新启动游戏,并产生一个新的目标。
    这个槽会在使用者按下 '''New Game''' 按钮时启动。它也会被建构子呼叫。首先,它设定炮弹数为15。请注意,这是程序中我们唯一设定炮弹数的地方。可以更改它为任何您想要的游戏规则。接下来,我们重设击中次数、重新启动游戏,并产生一个新的目标。

    Revision as of 20:51, 29 June 2011

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

    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)的效果。

    实现多个目标。