.tutorials/tutorial/t11/cannonfield.h
.tutorials/tutorial/t11/lcdrange.cpp
.tutorials/tutorial/t11/lcdrange.h
.tutorials/tutorial/t11/main.cpp
.tutorials/tutorial/t11/t11.pro
在這個範例裡,我們採用了計時器來實現動畫的射擊(shoot)。
Line by Line Walkthrough
t11/cannonfield.h
CannonField 現在有射擊能力了。
void shoot();
假如砲彈不在空中,呼叫這個 slot 將會使加農砲射擊。
private slots: void moveShot();
當砲彈在空中時,這個私有的(private) slot 會使用 QTimer 來移動砲彈。
private: void paintShot(QPainter &painter);
這個私有函式會畫出砲彈。
QRect shotRect() const;
假如砲彈在空中,這個私有函式會返回它的封裝矩形;否則傳回的是一個未定義的矩型。
int timerCount; QTimer *autoShootTimer; float shootAngle; float shootForce; };
這些私有變數包含了描述砲彈的資訊。timerCount 紀錄了砲彈開火之後經過的時間。shootAngle 為加農砲的角度,shootForce 是加農砲射擊時的力量。
t11/cannonfield.cpp
#include <math.h>
因為我們需要 sin() 及 cos() 函式,所以我們引入了 <math.h>。(另一個選擇是引入比較新式的 <cmath> 標頭檔。不幸的是,一些 Unix 平台仍然沒有正確的支援它。)
CannonField::CannonField(QWidget *parent) : QWidget(parent) { currentAngle = 45; currentForce = 0; timerCount = 0; autoShootTimer = new QTimer(this); connect(autoShootTimer, SIGNAL(timeout()), this, SLOT(moveShot())); shootAngle = 0; shootForce = 0; setPalette(QPalette(QColor(250, 250, 200))); setAutoFillBackground(true); }
我們初始化我們的新私有變數,並連接 QTimer::timeout() signal 到我們的 moveShot() slot。我們將在每次計時器超時(time out)時移動砲彈。
void CannonField::shoot() { if (autoShootTimer->isActive()) return; timerCount = 0; shootAngle = currentAngle; shootForce = currentForce; autoShootTimer->start(5); }
除非砲彈在空中,否則這個函式會射出一個砲彈。timerCount 被重置為 0。shootAngle 與 shootForce 變數會被設成當前的加農砲角度以及力量。最後,我們啟動計時器。
void CannonField::moveShot() { QRegion region = shotRect(); ++timerCount; QRect shotR = shotRect(); if (shotR.x() > width() || shotR.y() > height()) { autoShootTimer->stop(); } else { region = region.unite(shotR); } update(region); }
moveShot() 是一個移動砲彈的 slot,當 QTimer 啟動後,其每 5 毫秒會被呼叫一次。
它的任務是計算新的砲彈位置,將螢幕的砲彈更新到新的位置。而且,如果需要的話,停止計時器。
首先我們建立一個 QRegion 來保留舊的 shotRect()。一個 QRegion 可以保留任何種類的區域(region),而且我們可以在這裡使用它來簡化繪圖。shotRect() 返回砲彈現在位置的矩形。這在稍後會詳細解釋。
現在我們遞增 timerCount,它實現了砲彈在軌道中移動的每一步。
接下來我們取得新的砲彈矩形。
假如砲彈越過了元件右邊或底部的邊界,我們就停止計時器,否則我們將新的 shotRect() 加入到 QRegion。
最後,我們重繪 QRegion。這將會送出一個只有一個或兩個矩形需要更新的單一繪圖事件。
void CannonField::paintEvent(QPaintEvent * /* event */) { QPainter painter(this); paintCannon(painter); if (autoShootTimer->isActive()) paintShot(painter); }
繪圖事件函式比起先前的章節被簡化了。許多邏輯操作被移到新的 paintShot() 和 paintCannon() 函式。
void CannonField::paintShot(QPainter &painter) { painter.setPen(Qt::NoPen); painter.setBrush(Qt::black); painter.drawRect(shotRect()); }
這個私有函式畫出一個使用黑色填滿的矩型作為砲彈。
我們把 paintCannon() 的實現丟到一旁;它與先前章節 QWidget::paintEvent() 的重新實現相同。
QRect CannonField::shotRect() const { const double gravity = 4; double time = timerCount / 20.0; double velocity = shootForce; double radians = shootAngle * 3.14159265 / 180; double velx = velocity * cos(radians); double vely = velocity * sin(radians); double x0 = (barrelRect.right() + 5) * cos(radians); double y0 = (barrelRect.right() + 5) * sin(radians); double x = x0 + velx * time; double y = y0 + vely * time - 0.5 * gravity * time * time; QRect result(0, 0, 6, 6); result.moveCenter(QPoint(qRound(x), height() - 1 - qRound(y))); return result; }
這個私有函式計算出砲彈的中心點,並返回砲彈的封裝矩形。除了隨著時間經過而增加的 timerCount 之外,它還使用了加農砲的最初力量與角度。
這個公式使用的是重力場下無摩擦力移動的標準牛頓公式。為了簡單起見,我們選擇忽略任何愛因斯坦理論。
我們在一個 y 坐標向上遞增的座標系統中計算中心點。在我們計算出中心點之後,我們建構了一個大小為 6 x 6 的 QRect,並移動它的中心點到計算出來的中心點之上。藉由同樣的操作,我們將這個點轉換成元件的座標系統(參閱 The Coordinate System)。
qRound() 函式是一個定義在 <QtGlobal> (被所有 Qt 標頭檔所引入)裡的行內(inline)函式。qRound() 會將一個雙精度浮點數(double)轉換為最接近的整數。
t11/main.cpp
class MyWidget : public QWidget { public: MyWidget(QWidget *parent = 0); };
唯一加入的東西是 Shoot 按紐。
QPushButton *shoot = new QPushButton(tr("&Shoot")); shoot->setFont(QFont("Times", 18, QFont::Bold));
在建構子中,我們建立並設定 Shoot 按鈕,就像我們為 Quit 按紐所做的
connect(shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()));
將 Shoot 按鈕的 clicked() signal 連結到 CannonField 的 shoot() slot。
Running the Application
加農砲能射擊了,不過沒有任何東西會被射中。
Exercises
將砲彈變成一個被填滿的圓。[提示:QPainter::drawEllipse() 可能會有幫助。]
當砲彈在空中時,改變加農砲的顏色。
來源:Qt Tutorial 11 - Giving It a Shot
版本:4.4.3
2 回覆:
最近一直在看板主翻譯的這一系列 Qt 教學
已經看完 10 章了
學起來很爽呢
多謝版大!
barrelRect 是什麼?
張貼留言