Archive:Development/Tutorials/Qt4 Ruby Tutorial/Chapter 11 (zh TW)

From KDE TechBase

Template:I18n/Language Navigation Bar (zh TW)

Template:TutorialBrowser (zh TW)

Giving It a Shot

檔案:

概覽

在這個範例中,我們引入一個計時器來實現動畫的射擊。

一行一行的瀏覽

cannon.rb

CannonField 現在有射擊能力了。

include Math

我們含入(include)Math,因為我們需要 sin()cos() 函數。

@timerCount = 0

@autoShootTimer = Qt::Timer.new(self)
connect(@autoShootTimer, SIGNAL('timeout()'),
         self, SLOT('moveShot()'))

@shootAngle = 0
@shootForce = 0

我們初始化新的私有變數,並且連接 Qt::Timer::timeout() 訊號到 moveShot() 槽。我們將在每次計時器超時(times out)時移動砲彈。

timerCount 會不斷追踪發射後經過的時間。shootAngle 是加農砲發射時的角度,而 shootForce 是加農砲發射時的力量。

def shoot()
  if @autoShootTimer.isActive()
    return
  end;

  @timerCount = 0
  @shootAngle = @currentAngle
  @shootForce = @currentForce
  @autoShootTimer.start(5)
end

除非有砲彈在空中,否則這個函式會發射砲彈。timerCount 重設為零。shootAngleshootForce 變數會被設定為當前加農砲的角度和力量。最後,我們啟動計時器。

def moveShot()
  region = Qt::Region.new(shotRect())
  @timerCount += 1

  shotR = shotRect()

  if shotR.x() > width() || shotR.y() > height()
    @autoShootTimer.stop()
  else
    region = region.unite(Qt::Region.new(shotR))
  end
  update(region)
end

moveShot() 是一個移動砲彈的槽,當Qt::Timer啟動後,每5毫秒被呼叫一次。

它的工作是計算新的位置、更新螢幕上的砲彈到新的位置,並且在必要時停止計時器。

首先,我們建立一個 Qt::Region 來保留舊的 shotRect()Qt::Region 有保留任何區域種類的能力,而我們將在這裡用它來簡化繪圖。shotRect() 會返回砲彈現在位置的矩形。這在稍後會詳細解釋。

然後我們遞增 timerCount,它影響砲彈沿彈道移動的每一步。

接下來,我們取得新的砲彈矩形。

如果砲彈已經越過 widget 右側或底部邊界,那麼我們停止計時器,否則我們加入新的 shotRect()Qt::Region

最後,我們重繪 Qt::Region。這將發出只有一、兩個矩形需要更新的單一的繪圖事件。

def paintEvent(event)
  painter = Qt::Painter.new(self)

  paintCannon(painter)
  if @autoShootTimer.isActive()
    paintShot(painter)
  end

  painter.end()
end

繪圖事件函式比起前面的章節已經簡化。大部分的邏輯操作已經移動到新的 paintShot()paintCannon() 函式。

def paintShot(painter)
  painter.setPen(Qt::NoPen)
  painter.setBrush(Qt::Brush.new(Qt::black))
  painter.drawRect(shotRect())
end

這個私有函式畫出一個黑色填充矩形作為砲彈。

我們省略了 paintCannon() 的實作,它和前面章節 Qt::Widget::paintEvent() 的重新實作相同。

def shotRect()
  gravity = 4.0

  time = @timerCount / 20.0
  velocity = @shootForce
  radians = @shootAngle * 3.14159265 / 180.0

  velx = velocity * cos(radians)
  vely = velocity * sin(radians)
  x0 = (@barrelRect.right() + 5.0) * cos(radians)
  y0 = (@barrelRect.right() + 5.0) * sin(radians)
  x = x0 + velx * time
  y = y0 + vely * time - 0.5 * gravity * time * time

  result = Qt::Rect.new(0, 0, 6, 6)
  result.moveCenter(Qt::Point.new(x.round, height() - 1 - y.round))
  return result
end

這個私有函式計算砲彈的中心點,並且返回封裝砲彈的矩形。除了隨時間推移而增加的 timerCount 外,它還使用加農砲起始的力量和角度。

這個公式使用的是重力場中無摩擦運動的標準牛頓公式。為簡單起見,我們選擇忽略任何愛因斯坦效應(Einstein effect)。

我們在y坐標向上增加的坐標系統中計算中心點。在我們計算出中心點後,我們建構一個大小為6×6的 Qt::Rect 並移動它的中心點到算出的中心點之上。藉由相同的操作,我們把這個點轉換成 widget 的坐標系統(參閱坐標系統)。

t11.rb

唯一增加的是 Shoot 按鈕。

shoot = Qt::PushButton.new(tr('&Shoot'))
shoot.setFont(Qt::Font.new('Times', 18, Qt::Font::Bold))

在建構子中,我們建立並設定了 Shoot 按鈕,就像我們為 Quit 按鈕作的。

connect(shoot, SIGNAL('clicked()'), cannonField, SLOT('shoot()'))

連接 Shoot 按鈕的 clicked() 訊號,到CannonFieldshoot() 槽。

執行應用程式

加農砲可以射擊,但沒有東西會被射中。

練習

使砲彈變成一個填滿的圓。[提示:Qt::Painter::drawEllipse() 可能會有幫助。]

當砲彈在空中時,改變加農砲的顏色。