* t11/lcdrange.h包含LCDRange類別定義。
* t11/lcdrange.cpp包含LCDRange類別實現。
* t11/cannon.h包含CannonField類別定義。
* t11/cannon.cpp包含CannonField類別實現。
* t11/main.cpp包含MyWidget和main。
cannon.h
現在炮台有射擊能力了
void shoot();
當炮彈沒有在空中時,使用這一個slots射擊炮彈
private slots:
void moveShot();
當炮彈在空中時,這一個private slots function使用timer來移動炮彈的位置
private:
void paintShot( QPainter * );
透過這一個private function來畫在空中移動的炮彈
QRect shotRect() const;
當炮彈在空中時,這一個private function回傳它所佔用的空間的矩形
若離開了顯示畫面,回傳一個沒有定義的矩形
int timerCount;
QTimer * autoShootTimer;
float shoot_ang;
float shoot_f;
};
這是包留炮彈射出去時的資訊,
timerCount表示射出炮彈後所經過的時間,
shoot_ang表示炮彈射出去時的角度
shoot_f表示炮彈射出去的力量
cannon.cpp
CannonField::CannonField( QWidget *parent, const char *name ) : QWidget( parent, name )
{
ang = 45;
f = 0;
timerCount = 0;
autoShootTimer = new QTimer( this, "movement handler" );
connect( autoShootTimer, SIGNAL(timeout()), this, SLOT(moveShot()) );
shoot_ang = 0;
shoot_f = 0;
setPalette( QPalette( QColor( 250, 250, 200) ) );
}
初始化我們的非公開的變數,並且新增一個timer,當timer發出timeout時,執行moveShot的function
void CannonField::shoot()
{
if ( autoShootTimer->isActive() )
return;
timerCount = 0;
shoot_ang = ang;
shoot_f = f;
autoShootTimer->start( 50 );
}
只有當炮彈不在空中時,此函數才有效果,
炮彈射擊後所經過的時間歸零;並且記錄炮彈射出去時的角度與力量;並開始timer,每50ms一個timerout
void CannonField::moveShot()
這一個function是用來畫出當炮彈在空中時所應該在的位置
此函數的任務就是計算並且畫出炮彈的位置,當QTimer啟動時,每50ms會被呼叫一次,
還有決定是否要停止QTimer的執行
QRegion r( shotRect() );
首先,QRegion可以保留任何Region,我們透過QRegion來保留舊的炮彈位置。
timerCount++;
增加炮彈射出後的時間
QRect shotR = shotRect();
shotRect()函數會傳回炮彈的矩形,這之間包括炮彈的新位置
if ( shotR.x() > width() || shotR.y() > height() )
autoShootTimer->stop();
else
r = r.unite( QRegion( shotR ) );
repaint( r ); // 會呼叫paintEvent()函數
當炮彈新的位置移動到下面或是右邊的邊界時,
我們就停止QTimer的執行,
否則我們則添加新的shotRect()到QRegion中
最後我們重畫QRegion
而r.unite( QRegion( shotR ) ),是表示舊的炮彈與新的炮彈的聯集形成的區域
repaint( r )要求要重畫r這一個區域
void CannonField::paintEvent( QPaintEvent *e )
{
QRect updateR = e->rect();
QPainter p( this );
if ( updateR.intersects( cannonRect() ) )
paintCannon( &p );
if ( autoShootTimer->isActive() && updateR.intersects( shotRect() ) ) // 很重要,當autoShootTimer被stop後,炮彈就不會被畫出來了
paintShot( &p );
}
實作畫圖事件,當執行repaint時,則會呼叫paintEvent
QRect updateR = e->rect();
rect()的解示是說傳回需要更新的部分。
若要更新的部分與炮台有交集的話,就呼叫paintCannon()
若要更新的部分與炮彈有交集的話,就呼叫paintShot()
QPainter p( this );
把QPainter加入,要繪製的設備是this,也就是QWidget
if ( updateR.intersects( cannonRect() ) )
paintCannon( &p );
updateR表示需要更新的部分,而cannonRect()回傳的是放置炮台的這一個方塊區域
先判斷是否兩個有交集,若有交集則執行paintCannon,更新炮台畫面
if ( autoShootTimer->isActive() && updateR.intersects( shotRect() ) )
paintShot( &p );
若炮彈在空中,且需要更新的部分與新的炮彈位置有交集,則重畫炮彈的圖示
void CannonField::paintShot( QPainter *p )
{
p->setBrush( black );
p->setPen( NoPen );
p->drawRect( shotRect() );
}
這一個非公開函數是在準備畫炮彈,黑色的炮彈、沒有邊界線、並且傳回炮彈的位置
void CannonField::paintCannon( QPainter *p )
{
QRect cr = cannonRect(); // 回傳左下角炮台的矩型
QPixmap pix( cr.size() ); // 設定一個pixmap的大小與左下角的炮台一樣的大小
pix.fill( this, cr.topLeft() ); // 把這一個大小的pixmap左上角座標設定成左下角炮台的左上角座標
QPainter tmp( &pix ); // 把pix設定為等一下要畫在上面的設備
tmp.setBrush( blue ); // 設定為藍色
tmp.setPen( NoPen ); // 沒有外框
tmp.translate( 0, pix.height() - 1 ); // 座標點往下移pix.height()-1
tmp.drawPie( QRect( -35,-35, 70, 70 ), 0, 90*16 ); // 在一個方型[QRect( -35,-35, 70, 70 )]中畫一個起始為0度,共90度的扇型
tmp.rotate( -ang ); // 依新的原點旋轉
tmp.drawRect( barrelRect ); // 畫炮筒
tmp.end(); // 結束pixmap的畫圖,也就是說已畫在pixmap設備上了
// 因為,上面的是tmp.end();所以,下面是p,跟上面不同,不需要p.begin();
p->drawPixmap( cr.topLeft(), pix ); // 把pixmap畫到qprinter(這裡指的是CannonField)
}
paintCannon()與前一章中的paintEvent()不同的地方在於tmp.end()後,p不需要begin();前一章因為是同樣個p,所以,當end後,要把pixmap畫到畫布上,必需要begin
const QRect barrelRect(33, -4, 15, 8);
我上ppt了問了一下高手,了解這一行的意思了。
定義barrelRect為一個QRect
並使用(33, -4, 15, 8)初始化barrelRect
(33,-4,15,8)是建構元的參數
const不是#define的意思 他代表barrelRect是constant
也就是回傳的QRect是不能被修改的。
反正,關鍵字barrelRect就是QRect(33,-4,15,8)的這一個物件。
QRect CannonField::cannonRect() const
{
QRect r( 0, 0, 50, 50 );
r.moveBottomLeft( rect().bottomLeft() );
return r;
}
這裡的rect()應該是this.rect()的省略
QRect CannonField::shotRect() const
{
const double gravity = 4;
double time = timerCount / 4.0;
double velocity = shoot_f;
double radians = shoot_ang*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 r = QRect( 0, 0, 6, 6 );
r.moveCenter( QPoint( qRound(x), height() - 1 - qRound(y) ) );
return r;
}
計算炮彈球中心的位置,使用到重力加速度、射出炮彈的角度、力量和射出去後經過的時間
moveCenter(QPoint &)設置方塊的中心點,而大小不改變
qRound()函數是一個在qglobal.h中定義的inline function(被其它所有Qt頭文件包含)。
qRound()把一個雙精度實數變為最接近的整數。
main.cpp
QPushButton *shoot = new QPushButton( "&Shoot", this, "shoot" );
shoot->setFont( QFont( "Times", 18, QFont::Bold ) );
增加一個shoot的按鈕
connect( shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()) );
把Shoot按鈕的clicked() signal和CannonField的shoot() slot連接起來。
homework:
用一個填充的圓來表示炮彈:用QPainter::drawEllipse()
用一個改變顏色的球:p->setBrush( QColor(rand()%255,rand()%255,rand()%255 ));
沒有留言:
張貼留言