
Screenshot: Facing the Wall
文件:
•. t14.rb
•. cannon.rb
•. gamebrd.rb
•. lcdrange.rb
这是最后一个示例:一个完整的游戏。
我们加入了键盘快捷键,并且向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事件处理函数。当用户把鼠标指针放置在这个部件上方并且按下鼠标按钮时,就会调用这个函数。
如果当前事件不是由鼠标左键触发的,则我们立即返回。否则,我们检查鼠标指针是否位于炮筒的区域范围之内。如果是的,则将barrelPressed设置为真(true)。
注意,Qt::MouseEvent::pos()函数返回部件本身坐标系统中的一个点坐标。
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事件处理函数。当用户在本部件内部按下鼠标键然后移动鼠标时,就会调用这个函数。(妳可以让Qt即使在没有鼠标按钮被按下的情况下也发送鼠标移动事件。参考Qt::Widget::setMouseTracking()。)
这个事件处理函数会根据鼠标指针的位置来重新放置炮筒的位置。
首先,如果炮筒没有被按下鼠标键,则我们返回。然后,我们获取到鼠标的指针位置。如果鼠标指针超出了部件的左边界或底部边界,我们就调整位置点,使得它仍然位于部件内部。
然后,我们计算出部件的底部连线和部件的左下角到鼠标指针位置处的假想线之间的角度。最后,我们将角度换算成以度为单位,并且设置给加农炮。
别忘记了,setAngle()会重绘加农炮。
def mouseReleaseEvent(event)
if event.button() == Qt::LeftButton
@barrelPressed = false
end
end
如果鼠标是在本部件内部被按下的,那么当用户松开鼠标按钮时,会调用这个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
这个函数返回障碍物的矩形区域。我们将障碍物的底部边界固定在本部件的底部边界处。
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()函数中绘制炮筒时一样,做出相同的转换步骤。首先对坐标系统作平移(translate),然后做旋转(rotate)。
现在,我们需要检查指定的点pos(以部件本身的坐标系坐标来表示)是否位于炮筒的内部。为了实现这一点,我们将变换矩阵反转。被反转的矩阵,会做出与我们在绘制炮筒时相反的变换步骤。我们使用反转之后的矩阵来对点pos 进行映射,如果这个点位于最初的炮筒矩形区域里,则返回真(true)。
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对象。这些对象会拦截发送到某个部件的键盘事件,如果特定的按键被按下则会调用相应的信号槽。注意,一个Qt::Shortcut会成为对应部件的子代对象,当那个部件被销毁时,它也会跟着被销毁。Qt::Shortcut本身并不是部件,并且不会影响到它的亲代部件的外观。
我们定义三个快捷键。我们希望当用户按下回车键(Enter或Return)时调用fire()信号槽。我们还希望当用户按下Ctrl+Q 时程序就退出。这次我们不连接到Qt::CoreApplication::quit(),而是连接到Qt::Widget::close()。因为GameBoard是程序的主部件,所以,这样与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)
现在,当妳按回车键时就会打炮。妳还可以使用鼠标来设置加农炮的角度。障碍物会使得游戏的难度稍微增大。
编写一个空间入侵游戏。
(这个练习是由Igor Rafienko首先完成的。妳可以下载这个游戏。)
新的练习是:编写一个打砖块游戏。
终极指导:继续学习,创建出妳自己的编程艺术杰作吧!
阈值化
HxLauncher: Launch Android applications by voice commands