Archive:Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14 (zh CN): Difference between revisions
(Created page with '{{Template:I18n/Language Navigation Bar_(zh_CN)|Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14}} {{TutorialBrowser_(zh_CN)| series=[[Development/Tutorials/Qt4_Ruby_Tutorial_...') |
m (AnneW moved page Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14 (zh CN) to Archive:Development/Tutorials/Qt4 Ruby Tutorial/Chapter 14 (zh CN) without leaving a redirect: Obsolete) |
||
(2 intermediate revisions by one other user not shown) | |||
Line 26: | Line 26: | ||
'''<tt>CannonField</tt>''' 现在可以接收鼠标事件,让用户透过按下并拖动炮管来瞄准。'''<tt>CannonField</tt>''' 也有障碍墙了。 | '''<tt>CannonField</tt>''' 现在可以接收鼠标事件,让用户透过按下并拖动炮管来瞄准。'''<tt>CannonField</tt>''' 也有障碍墙了。 | ||
< | <syntaxhighlight lang="ruby"> | ||
@barrelPressed = false | @barrelPressed = false | ||
</ | </syntaxhighlight> | ||
这行加入到建构子。最初,鼠标没有在炮管上按下。 | 这行加入到建构子。最初,鼠标没有在炮管上按下。 | ||
< | <syntaxhighlight lang="ruby"> | ||
elsif shotR.x() > width() || shotR.y() > height() || | elsif shotR.x() > width() || shotR.y() > height() || | ||
shotR.intersects(barrierRect()) | shotR.intersects(barrierRect()) | ||
</ | </syntaxhighlight> | ||
现在,我们有一个障碍,也就是第三种不击中的方法。我们也测试第三种。(在 '''<tt>moveShot()</tt>'''。) | 现在,我们有一个障碍,也就是第三种不击中的方法。我们也测试第三种。(在 '''<tt>moveShot()</tt>'''。) | ||
< | <syntaxhighlight lang="ruby"> | ||
def mousePressEvent(event) | def mousePressEvent(event) | ||
unless event.button() == Qt::LeftButton | unless event.button() == Qt::LeftButton | ||
Line 49: | Line 49: | ||
end | end | ||
end | end | ||
</ | </syntaxhighlight> | ||
这是一个 Qt 事件处理器。当光标在 widget 上,且用户按下鼠标按钮时,它会被呼叫。 | 这是一个 Qt 事件处理器。当光标在 widget 上,且用户按下鼠标按钮时,它会被呼叫。 | ||
Line 57: | Line 57: | ||
请注意,[http://doc.qt.nokia.com/latest/qmouseevent.html#pos Qt::MouseEvent::pos()] 函式会返回一个 Widget 坐标系统中的点。 | 请注意,[http://doc.qt.nokia.com/latest/qmouseevent.html#pos Qt::MouseEvent::pos()] 函式会返回一个 Widget 坐标系统中的点。 | ||
< | <syntaxhighlight lang="ruby"> | ||
def mouseMoveEvent(event) | def mouseMoveEvent(event) | ||
unless @barrelPressed | unless @barrelPressed | ||
Line 76: | Line 76: | ||
setAngle((rad * 180 / 3.14159265).round()) | setAngle((rad * 180 / 3.14159265).round()) | ||
end | end | ||
</ | </syntaxhighlight> | ||
这是另一个 Qt 事件处理器。当用户已经在这个 widget 内按下鼠标按钮,然后移动/拖动鼠标时被呼叫。(即使没有任何按键被按下,您也可以让 Qt 发送鼠标移动事件。参阅 [http://doc.qt.nokia.com/latest/qwidget.html#mouseTracking-prop Qt::Widget::setMouseTracking()]。) | 这是另一个 Qt 事件处理器。当用户已经在这个 widget 内按下鼠标按钮,然后移动/拖动鼠标时被呼叫。(即使没有任何按键被按下,您也可以让 Qt 发送鼠标移动事件。参阅 [http://doc.qt.nokia.com/latest/qwidget.html#mouseTracking-prop Qt::Widget::setMouseTracking()]。) | ||
Line 88: | Line 88: | ||
记得用 '''<tt>setAngle()</tt>''' 重绘加农炮。 | 记得用 '''<tt>setAngle()</tt>''' 重绘加农炮。 | ||
< | <syntaxhighlight lang="ruby"> | ||
def mouseReleaseEvent(event) | def mouseReleaseEvent(event) | ||
if event.button() == Qt::LeftButton | if event.button() == Qt::LeftButton | ||
Line 94: | Line 94: | ||
end | end | ||
end | end | ||
</ | </syntaxhighlight> | ||
无论用户何时释放鼠标按钮,且它是在这个 widget 里面按下时,这个 Qt 事件处理器被呼叫。 | 无论用户何时释放鼠标按钮,且它是在这个 widget 里面按下时,这个 Qt 事件处理器被呼叫。 | ||
Line 102: | Line 102: | ||
绘图事件还多了一行: | 绘图事件还多了一行: | ||
< | <syntaxhighlight lang="ruby"> | ||
paintBarrier(painter) | paintBarrier(painter) | ||
</ | </syntaxhighlight> | ||
'''<tt>paintBarrier()</tt>''' 与 '''<tt>paintShot()</tt>'''、'''<tt>paintTarget()</tt>''',和 '''<tt>paintCannon()</tt>''' 作同类的事情。 | '''<tt>paintBarrier()</tt>''' 与 '''<tt>paintShot()</tt>'''、'''<tt>paintTarget()</tt>''',和 '''<tt>paintCannon()</tt>''' 作同类的事情。 | ||
< | <syntaxhighlight lang="ruby"> | ||
def paintBarrier( painter ) | def paintBarrier( painter ) | ||
painter.setBrush(Qt::Brush.new(Qt::yellow)) | painter.setBrush(Qt::Brush.new(Qt::yellow)) | ||
Line 114: | Line 114: | ||
painter.drawRect(barrierRect()) | painter.drawRect(barrierRect()) | ||
end | end | ||
</ | </syntaxhighlight> | ||
这个函式绘制一个填充黄色和黑色外框的矩形作为障碍。 | 这个函式绘制一个填充黄色和黑色外框的矩形作为障碍。 | ||
< | <syntaxhighlight lang="ruby"> | ||
def barrierRect() | def barrierRect() | ||
return Qt::Rect.new(145, height() - 100, 15, 99) | return Qt::Rect.new(145, height() - 100, 15, 99) | ||
end | end | ||
</ | </syntaxhighlight> | ||
这个函式返回障碍的矩形。我们把障碍的底边固定在 widget 的底边。 | 这个函式返回障碍的矩形。我们把障碍的底边固定在 widget 的底边。 | ||
< | <syntaxhighlight lang="ruby"> | ||
def barrelHit(pos) | def barrelHit(pos) | ||
matrix = Qt::Matrix.new() | matrix = Qt::Matrix.new() | ||
Line 134: | Line 134: | ||
return @barrelRect.contains(matrix.map(pos)) | return @barrelRect.contains(matrix.map(pos)) | ||
end | end | ||
</ | </syntaxhighlight> | ||
如果点在炮管里,这个函式返回 '''<tt>true</tt>''';否则返回 '''<tt>false</tt>'''。 | 如果点在炮管里,这个函式返回 '''<tt>true</tt>''';否则返回 '''<tt>false</tt>'''。 | ||
Line 146: | Line 146: | ||
'''[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]''' | ||
< | <syntaxhighlight lang="ruby"> | ||
cannonBox = Qt::Frame.new() | cannonBox = Qt::Frame.new() | ||
cannonBox.setFrameStyle(Qt::Frame::WinPanel | Qt::Frame::Sunken) | cannonBox.setFrameStyle(Qt::Frame::WinPanel | Qt::Frame::Sunken) | ||
</ | </syntaxhighlight> | ||
我们建立及设定了一个 [http://doc.qt.nokia.com/latest/qframe.html Qt::Frame],并设定框架的风格。这样的结果是一个围绕 '''<tt>CannonField</tt>''' 的3D框架。 | 我们建立及设定了一个 [http://doc.qt.nokia.com/latest/qframe.html Qt::Frame],并设定框架的风格。这样的结果是一个围绕 '''<tt>CannonField</tt>''' 的3D框架。 | ||
< | <syntaxhighlight lang="ruby"> | ||
Qt::Shortcut.new(Qt::KeySequence.new(Qt::Key_Enter.to_i), | Qt::Shortcut.new(Qt::KeySequence.new(Qt::Key_Enter.to_i), | ||
self, SLOT('fire()')) | self, SLOT('fire()')) | ||
Line 160: | Line 160: | ||
Qt::Shortcut.new(Qt::KeySequence.new(Qt::CTRL.to_i + Qt::Key_Q.to_i), | Qt::Shortcut.new(Qt::KeySequence.new(Qt::CTRL.to_i + Qt::Key_Q.to_i), | ||
self, SLOT('close()')) | self, SLOT('close()')) | ||
</ | </syntaxhighlight> | ||
这里,我们建立并设定三个 [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] 物件。这些对象会撷取(intercept)键盘事件到 widget,并在某些键被按下时呼叫槽。请注意,[http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] 物件是 widget 的子 widget。且在 widget 毁灭时,也会被毁灭。[http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] 本身不是一个 widget,在 widget 中也没有可见的影响。 | 这里,我们建立并设定三个 [http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] 物件。这些对象会撷取(intercept)键盘事件到 widget,并在某些键被按下时呼叫槽。请注意,[http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] 物件是 widget 的子 widget。且在 widget 毁灭时,也会被毁灭。[http://doc.qt.nokia.com/latest/qshortcut.html Qt::Shortcut] 本身不是一个 widget,在 widget 中也没有可见的影响。 | ||
Line 168: | Line 168: | ||
Qt::CTRL、Qt::Key_Enter、Qt::Key_Return,和 Qt::Key_Q 都是宣告在 Qt 名称空间里的常数。不幸的是,在 qtruby 的当前版本,他们需要转换为整数,才能使用它们在我们的快捷键。 | Qt::CTRL、Qt::Key_Enter、Qt::Key_Return,和 Qt::Key_Q 都是宣告在 Qt 名称空间里的常数。不幸的是,在 qtruby 的当前版本,他们需要转换为整数,才能使用它们在我们的快捷键。 | ||
< | <syntaxhighlight lang="ruby"> | ||
leftLayout = Qt::VBoxLayout.new() | leftLayout = Qt::VBoxLayout.new() | ||
leftLayout.addWidget(angle) | leftLayout.addWidget(angle) | ||
Line 180: | Line 180: | ||
gridLayout.setColumnStretch(1, 10) | gridLayout.setColumnStretch(1, 10) | ||
setLayout(gridLayout) | setLayout(gridLayout) | ||
</ | </syntaxhighlight> | ||
我们给 '''<tt>cannonBox</tt>''' 它自己的 [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout],并且加入 '''<tt>CannonField</tt>''' 到布局中。这间接使得 '''<tt>CannonField</tt>''' 成为 '''<tt>cannonBox</tt>''' 的一个子 widget。因为没有其它东西在这个盒子里,结果就是 [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout] 会放置一个环绕着 '''<tt>CannonField</tt>''' 的框架。我们放置 '''<tt>cannonBox</tt>''',而不是 '''<tt>CannonField</tt>''',到网格布局中。 | 我们给 '''<tt>cannonBox</tt>''' 它自己的 [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout],并且加入 '''<tt>CannonField</tt>''' 到布局中。这间接使得 '''<tt>CannonField</tt>''' 成为 '''<tt>cannonBox</tt>''' 的一个子 widget。因为没有其它东西在这个盒子里,结果就是 [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout] 会放置一个环绕着 '''<tt>CannonField</tt>''' 的框架。我们放置 '''<tt>cannonBox</tt>''',而不是 '''<tt>CannonField</tt>''',到网格布局中。 |
Latest revision as of 16:03, 23 June 2013
Template:I18n/Language Navigation Bar (zh CN) Template:TutorialBrowser (zh CN)
Facing the Wall
档案:
概览
这是最后的范例:一个完整的游戏。
我们加上键盘快捷键(accelerator)和引入鼠标事件到 CannonField。我们放置了一个环绕 CannonField 的外框,并加上一个障碍(墙),使游戏更具挑战性。
一行一行的浏览
CannonField 现在可以接收鼠标事件,让用户透过按下并拖动炮管来瞄准。CannonField 也有障碍墙了。
@barrelPressed = false
这行加入到建构子。最初,鼠标没有在炮管上按下。
elsif shotR.x() > width() || shotR.y() > height() ||
shotR.intersects(barrierRect())
现在,我们有一个障碍,也就是第三种不击中的方法。我们也测试第三种。(在 moveShot()。)
def mousePressEvent(event)
unless event.button() == Qt::LeftButton
return
end
if barrelHit(event.pos())
@barrelPressed = true
end
end
这是一个 Qt 事件处理器。当光标在 widget 上,且用户按下鼠标按钮时,它会被呼叫。
如果事件不是鼠标左键产生的,我们立即返回。否则,我们检查光标的位置是否在加农炮的炮管内。假如是,我们就设定 barrelPressed 为 true。
请注意,Qt::MouseEvent::pos() 函式会返回一个 Widget 坐标系统中的点。
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
这是另一个 Qt 事件处理器。当用户已经在这个 widget 内按下鼠标按钮,然后移动/拖动鼠标时被呼叫。(即使没有任何按键被按下,您也可以让 Qt 发送鼠标移动事件。参阅 Qt::Widget::setMouseTracking()。)
这个处理器会根据光标的位置,重新定位炮管。
首先,如果炮管没有被按下,我们返回。下一步,我们取得光标的位置。如果光标移到 widget 的左边或底部,我们调整这个点到 widget 的内部。
然后,我们计算 widget 底边,与 widget 左下角和光标位置构成假想线之间的角度。最后,我们设定加农炮的角度为换算成度的新值。
记得用 setAngle() 重绘加农炮。
def mouseReleaseEvent(event)
if event.button() == Qt::LeftButton
@barrelPressed = false
end
end
无论用户何时释放鼠标按钮,且它是在这个 widget 里面按下时,这个 Qt 事件处理器被呼叫。
如果左边的按钮被释放,我们可以确定炮管已不再被按住了。
绘图事件还多了一行:
paintBarrier(painter)
paintBarrier() 与 paintShot()、paintTarget(),和 paintCannon() 作同类的事情。
def paintBarrier( painter )
painter.setBrush(Qt::Brush.new(Qt::yellow))
painter.setPen(Qt::Color.new(Qt::black))
painter.drawRect(barrierRect())
end
这个函式绘制一个填充黄色和黑色外框的矩形作为障碍。
def barrierRect()
return Qt::Rect.new(145, height() - 100, 15, 99)
end
这个函式返回障碍的矩形。我们把障碍的底边固定在 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
如果点在炮管里,这个函式返回 true;否则返回 false。
这里我们使用 Qt::Matrix 类别。Qt::Matrix 定义了一个坐标系统映像。它可以执行与 Qt::Painter 相同的转换。
这里,我们执行与 paintCannon() 函式中绘制炮管的同样转换步骤。首先,我们转移坐标系,然后旋转。
现在我们需要检查点 pos(在 widget 坐标)是否位于炮管内。为了做到这一点,我们倒转这个转移矩阵。倒转矩阵执行了我们使用在绘制炮管的倒转转移。我们使用倒转矩阵来映射点 pos。如果它在原来炮管的矩形里面,返回 true。
cannonBox = Qt::Frame.new()
cannonBox.setFrameStyle(Qt::Frame::WinPanel | Qt::Frame::Sunken)
我们建立及设定了一个 Qt::Frame,并设定框架的风格。这样的结果是一个围绕 CannonField 的3D框架。
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()'))
这里,我们建立并设定三个 Qt::Shortcut 物件。这些对象会撷取(intercept)键盘事件到 widget,并在某些键被按下时呼叫槽。请注意,Qt::Shortcut 物件是 widget 的子 widget。且在 widget 毁灭时,也会被毁灭。Qt::Shortcut 本身不是一个 widget,在 widget 中也没有可见的影响。
我们定义了三个快捷键。我们希望当使用者按下 Enter 或 Return 时,呼叫 fire() 槽。我们也希望当按下 Ctrl+Q 键时,应用程序退出。这次我们连接到 Qt::Widget::close(),而不是 Qt::CoreApplication::quit()。由于 GameBoard 是应用程序主要的 widget,这样做与 QCoreApplication::quit() 有同样的效果。
Qt::CTRL、Qt::Key_Enter、Qt::Key_Return,和 Qt::Key_Q 都是宣告在 Qt 名称空间里的常数。不幸的是,在 qtruby 的当前版本,他们需要转换为整数,才能使用它们在我们的快捷键。
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)
我们给 cannonBox 它自己的 Qt::VBoxLayout,并且加入 CannonField 到布局中。这间接使得 CannonField 成为 cannonBox 的一个子 widget。因为没有其它东西在这个盒子里,结果就是 Qt::VBoxLayout 会放置一个环绕着 CannonField 的框架。我们放置 cannonBox,而不是 CannonField,到网格布局中。
执行应用程序
现在加农炮会在您按下 Enter 时射击。您还可以使用鼠标定位加农炮的角度。障碍使得游戏变得更具挑战性。我们也有一个好看的外框围绕CannonField。
练习
写一个太空入侵者游戏。
新的练习是:写一个打砖块游戏。
最后的忠告:继续往前走,并创造程序设计艺术的杰作!