Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14: Difference between revisions

    From KDE TechBase
    m (Text replace - "</code>" to "</syntaxhighlight>")
    (add translate tags)
    Line 1: Line 1:
    <languages />
    {{Template:I18n/Language Navigation Bar|Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14}}
    {{Template:I18n/Language Navigation Bar|Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14}}
    {{TutorialBrowser|
    series=[[Development/Tutorials/Qt4_Ruby_Tutorial|Qt4 Ruby Tutorial]]|
    name=Facing the Wall|


    pre=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_13|Tutorial 13 - Game Over]]
    {{<translate>TutorialBrowser</translate>|
    series=[[Special:myLanguage/Development/Tutorials/Qt4_Ruby_Tutorial|<translate>Qt4 Ruby Tutorial</translate>]]|
    name=<translate>Facing the Wall</translate>|
    pre=[[Special:myLanguage/Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_13|<translate>Tutorial 13 - Game Over</translate>]]
    }}
    }}
    <translate>
    == Facing the Wall ==
    == Facing the Wall ==
    </translate>
    <translate>
    [[Image:Qt4_Ruby_Tutorial_Screenshot_14.png|center]]
    [[Image:Qt4_Ruby_Tutorial_Screenshot_14.png|center]]
    </translate>
    <translate>
    Files:
    Files:
    </translate>
    * [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t14/lcdrange.rb lcdrange.rb]
    * [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t14/lcdrange.rb lcdrange.rb]
    * [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]
    Line 16: Line 22:
    * [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]


    <translate>
    === Overview ===
    === Overview ===
    This is the final example: a complete game.
    This is the final example: a complete game.


    Line 22: Line 30:


    === Line by Line Walkthrough ===
    === Line by Line Walkthrough ===
    </translate>
    '''[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]'''


    <translate>
    The '''<tt>CannonField</tt>''' can now receive mouse events to make the user aim the barrel by clicking on it and dragging. '''<tt>CannonField</tt>''' also has a barrier wall.
    The '''<tt>CannonField</tt>''' can now receive mouse events to make the user aim the barrel by clicking on it and dragging. '''<tt>CannonField</tt>''' also has a barrier wall.
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 30: Line 41:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    This line has been added to the constructor. Initially, the mouse is not pressed on the barrel.
    This line has been added to the constructor. Initially, the mouse is not pressed on the barrel.
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 37: Line 50:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    Now that we have a barrier, there are three ways to miss. We test for the third, too. (In '''<tt>moveShot()</tt>'''.)
    Now that we have a barrier, there are three ways to miss. We test for the third, too. (In '''<tt>moveShot()</tt>'''.)
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 51: Line 66:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    This is a Qt event handler. It is called when the user presses a mouse button when the mouse cursor is over the widget.
    This is a Qt event handler. It is called when the user presses a mouse button when the mouse cursor is over the widget.


    Line 56: Line 72:


    Notice that the [http://doc.qt.nokia.com/latest/qmouseevent.html#pos Qt::MouseEvent::pos()] function returns a point in the widget's coordinate system.
    Notice that the [http://doc.qt.nokia.com/latest/qmouseevent.html#pos Qt::MouseEvent::pos()] function returns a point in the widget's coordinate system.
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 78: Line 95:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    This is another Qt event handler. It is called when the user already has pressed the mouse button inside this widget and then moves/drags the mouse. (You can make Qt send mouse move events even when no buttons are pressed. See [http://doc.qt.nokia.com/latest/qwidget.html#mouseTracking-prop Qt::Widget::setMouseTracking()].)
    This is another Qt event handler. It is called when the user already has pressed the mouse button inside this widget and then moves/drags the mouse. (You can make Qt send mouse move events even when no buttons are pressed. See [http://doc.qt.nokia.com/latest/qwidget.html#mouseTracking-prop Qt::Widget::setMouseTracking()].)


    Line 87: Line 105:


    Remember that '''<tt>setAngle()</tt>''' redraws the cannon.
    Remember that '''<tt>setAngle()</tt>''' redraws the cannon.
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 96: Line 115:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    This Qt event handler is called whenever the user releases a mouse button and it was pressed inside this widget.
    This Qt event handler is called whenever the user releases a mouse button and it was pressed inside this widget.


    Line 101: Line 121:


    The paint event has one extra line:
    The paint event has one extra line:
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 106: Line 127:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    '''<tt>paintBarrier()</tt>''' does the same sort of thing as '''<tt>paintShot()</tt>''', '''<tt>paintTarget()</tt>''', and '''<tt>paintCannon()</tt>'''.
    '''<tt>paintBarrier()</tt>''' does the same sort of thing as '''<tt>paintShot()</tt>''', '''<tt>paintTarget()</tt>''', and '''<tt>paintCannon()</tt>'''.
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 116: Line 139:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    This function paints the barrier as a rectangle filled with yellow and with a black outline.
    This function paints the barrier as a rectangle filled with yellow and with a black outline.
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 124: Line 149:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    This function returns the rectangle of the barrier. We fix the bottom edge of the barrier to the bottom edge of the widget.
    This function returns the rectangle of the barrier. We fix the bottom edge of the barrier to the bottom edge of the widget.
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 136: Line 163:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    This function returns '''<tt>true</tt>''' if the point is in the barrel; otherwise it returns '''<tt>false</tt>'''.
    This function returns '''<tt>true</tt>''' if the point is in the barrel; otherwise it returns '''<tt>false</tt>'''.


    Line 143: Line 171:


    Now we need to check whether the point '''<tt>pos</tt>''' (in widget coordinates) lies inside the barrel. To do this, we invert the transformation matrix. The inverted matrix performs the inverse transformation that we used when drawing the barrel. We map the point '''<tt>pos</tt>''' using the inverted matrix and return '''<tt>true</tt>''' if it is inside the original barrel rectangle.
    Now we need to check whether the point '''<tt>pos</tt>''' (in widget coordinates) lies inside the barrel. To do this, we invert the transformation matrix. The inverted matrix performs the inverse transformation that we used when drawing the barrel. We map the point '''<tt>pos</tt>''' using the inverted matrix and return '''<tt>true</tt>''' if it is inside the original barrel rectangle.
    </translate>


    '''[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]'''
    Line 151: Line 180:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    We create and set up a [http://doc.qt.nokia.com/latest/qframe.html Qt::Frame], and set its frame style. This results in a 3D frame around the '''<tt>CannonField</tt>'''.
    We create and set up a [http://doc.qt.nokia.com/latest/qframe.html Qt::Frame], and set its frame style. This results in a 3D frame around the '''<tt>CannonField</tt>'''.
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 162: Line 193:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    Here we create and set up three [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] objects. These objects intercept keyboard events to a widget and call slots if certain keys are pressed. Note that a [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] object is a child of a widget and will be destroyed when that widget is destroyed. [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] itself is not a widget and has no visible effect on its parent.
    Here we create and set up three [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] objects. These objects intercept keyboard events to a widget and call slots if certain keys are pressed. Note that a [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] object is a child of a widget and will be destroyed when that widget is destroyed. [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] itself is not a widget and has no visible effect on its parent.


    Line 167: Line 199:


    Qt::CTRL, Qt::Key_Enter, Qt::Key_Return, and Qt::Key_Q are all constants declared in the Qt namespace. Unfortunately, in the current version of qtruby, they need to be converted to integers before we can use them in our shortcuts.
    Qt::CTRL, Qt::Key_Enter, Qt::Key_Return, and Qt::Key_Q are all constants declared in the Qt namespace. Unfortunately, in the current version of qtruby, they need to be converted to integers before we can use them in our shortcuts.
    </translate>


    <syntaxhighlight lang="ruby">
    <syntaxhighlight lang="ruby">
    Line 182: Line 215:
    </syntaxhighlight>
    </syntaxhighlight>


    <translate>
    We give '''<tt>cannonBox</tt>''' its own [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout], and we add '''<tt>CannonField</tt>''' to that layout. This implicitly makes '''<tt>CannonField</tt>''' a child of '''<tt>cannonBox</tt>'''. Because nothing else is in the box, the effect is that the [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout] will put a frame around the '''<tt>CannonField</tt>'''. We put '''<tt>cannonBox</tt>''', not '''<tt>CannonField</tt>''', in the grid layout.
    We give '''<tt>cannonBox</tt>''' its own [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout], and we add '''<tt>CannonField</tt>''' to that layout. This implicitly makes '''<tt>CannonField</tt>''' a child of '''<tt>cannonBox</tt>'''. Because nothing else is in the box, the effect is that the [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout] will put a frame around the '''<tt>CannonField</tt>'''. We put '''<tt>cannonBox</tt>''', not '''<tt>CannonField</tt>''', in the grid layout.
    </translate>


    <translate>
    === Running the Application ===
    === Running the Application ===
    The cannon now shoots when you press Enter. You can also position the cannon's angle using the mouse. The barrier makes it a little more challenging to play the game. We also have a nice looking frame around the '''<tt>CannonField</tt>'''.
    The cannon now shoots when you press Enter. You can also position the cannon's angle using the mouse. The barrier makes it a little more challenging to play the game. We also have a nice looking frame around the '''<tt>CannonField</tt>'''.


    === Exercises ===
    === Exercises ===
    Write a space invaders game.
    Write a space invaders game.


    Line 195: Line 233:


    [[Category:Ruby]]
    [[Category:Ruby]]
    </translate>

    Revision as of 23:52, 1 July 2011

    Other languages:


    Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14


    Facing the Wall
    Tutorial Series   Qt4 Ruby Tutorial
    Previous   Tutorial 13 - Game Over
    What's Next   n/a
    Further Reading   n/a

    Facing the Wall

    Files:

    Overview

    This is the final example: a complete game.

    We add keyboard accelerators and introduce mouse events to CannonField. We put a frame around the CannonField and add a barrier (wall) to make the game more challenging.

    Line by Line Walkthrough

    cannon.rb

    The CannonField can now receive mouse events to make the user aim the barrel by clicking on it and dragging. CannonField also has a barrier wall.

    @barrelPressed = false
    

    This line has been added to the constructor. Initially, the mouse is not pressed on the barrel.

    elsif shotR.x() > width() || shotR.y() > height() ||
        shotR.intersects(barrierRect())
    

    Now that we have a barrier, there are three ways to miss. We test for the third, too. (In moveShot().)

    def mousePressEvent(event)
      unless event.button() == Qt::LeftButton
        return
      end
    
      if barrelHit(event.pos())
        @barrelPressed = true
      end
    end
    

    This is a Qt event handler. It is called when the user presses a mouse button when the mouse cursor is over the widget.

    If the event was not generated by the left mouse button, we return immediately. Otherwise, we check if the position of the mouse cursor is within the cannon's barrel. If it is, we set barrelPressed to true.

    Notice that the Qt::MouseEvent::pos() function returns a point in the widget's coordinate system.

    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
    

    This is another Qt event handler. It is called when the user already has pressed the mouse button inside this widget and then moves/drags the mouse. (You can make Qt send mouse move events even when no buttons are pressed. See Qt::Widget::setMouseTracking().)

    This handler repositions the cannon's barrel according to the position of the mouse cursor.

    First, if the barrel is not pressed, we return. Next, we fetch the mouse cursor's position. If the mouse cursor is to the left or below the widget, we adjust the point to be inside the widget.

    Then we calculate the angle between the bottom edge of the widget and the imaginary line between the bottom-left corner of the widget and the cursor position. Finally we set the cannon's angle to the new value converted to degrees.

    Remember that setAngle() redraws the cannon.

    def mouseReleaseEvent(event)
      if event.button() == Qt::LeftButton
        @barrelPressed = false
      end
    end
    

    This Qt event handler is called whenever the user releases a mouse button and it was pressed inside this widget.

    If the left button is released, we can be sure that the barrel is no longer pressed.

    The paint event has one extra line:

    paintBarrier(painter)
    

    paintBarrier() does the same sort of thing as paintShot(), paintTarget(), and paintCannon().

    def paintBarrier( painter )
      painter.setBrush(Qt::Brush.new(Qt::yellow))
      painter.setPen(Qt::Color.new(Qt::black))
      painter.drawRect(barrierRect())
    end
    

    This function paints the barrier as a rectangle filled with yellow and with a black outline.

    def barrierRect()
      return Qt::Rect.new(145, height() - 100, 15, 99)
    end
    

    This function returns the rectangle of the barrier. We fix the bottom edge of the barrier to the bottom edge of the 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
    

    This function returns true if the point is in the barrel; otherwise it returns false.

    Here we use the class Qt::Matrix. Qt::Matrix defines a coordinate system mapping. It can perform the same transformations as the Qt::Painter.

    Here we perform the same transformation steps as we do when drawing the barrel in the paintCannon() function. First we translate the coordinate system and then we rotate it.

    Now we need to check whether the point pos (in widget coordinates) lies inside the barrel. To do this, we invert the transformation matrix. The inverted matrix performs the inverse transformation that we used when drawing the barrel. We map the point pos using the inverted matrix and return true if it is inside the original barrel rectangle.

    gamebrd.rb

    cannonBox = Qt::Frame.new()
    cannonBox.setFrameStyle(Qt::Frame::WinPanel | Qt::Frame::Sunken)
    

    We create and set up a Qt::Frame, and set its frame style. This results in a 3D frame around the CannonField.

    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()'))
    

    Here we create and set up three Qt::Shortcut objects. These objects intercept keyboard events to a widget and call slots if certain keys are pressed. Note that a Qt::Shortcut object is a child of a widget and will be destroyed when that widget is destroyed. Qt::Shortcut itself is not a widget and has no visible effect on its parent.

    We define three shortcut keys. We want the fire() slot to be called when the user presses Enter or Return. We also want the application to quit when key Ctrl+Q is pressed. Instead of connecting to Qt::CoreApplication::quit(), we connect to Qt::Widget::close() this time. Since the GameBoard is the application's main widget, this has the same effect as QCoreApplication::quit().

    Qt::CTRL, Qt::Key_Enter, Qt::Key_Return, and Qt::Key_Q are all constants declared in the Qt namespace. Unfortunately, in the current version of qtruby, they need to be converted to integers before we can use them in our shortcuts.

    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)
    

    We give cannonBox its own Qt::VBoxLayout, and we add CannonField to that layout. This implicitly makes CannonField a child of cannonBox. Because nothing else is in the box, the effect is that the Qt::VBoxLayout will put a frame around the CannonField. We put cannonBox, not CannonField, in the grid layout.

    Running the Application

    The cannon now shoots when you press Enter. You can also position the cannon's angle using the mouse. The barrier makes it a little more challenging to play the game. We also have a nice looking frame around the CannonField.

    Exercises

    Write a space invaders game.

    The new exercise is: Write a Breakout game.

    Final exhortation: Go forth now and create masterpieces of the programming art!