.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 是什麼?
張貼留言