* t13/lcdrange.h包含LCDRange類別定義。
* t13/lcdrange.cpp包含LCDRange類別實現。
* t13/cannon.h包含CannonField類別定義。
* t13/cannon.cpp包含CannonField類別實現。
* t13/gamebrd.h包含GameBoard類別定義。
* t13/gamebrd.cpp包含GameBoard類別實現。
* t13/main.cpp包含MyWidget和main。
lcdrange.h
class LCDRange : public QWidget
注意,現在LCDRange不是繼承QVBox,而是繼承QWidget
而我們選用功能強大且有一點困難的QVBoxLayout來管理我們的視窗元件
lcdrange.cpp
#include <qlayout.h>
我們現在需要qlayout來管理我們的API(Application Programming Interface)
LCDRange::LCDRange( QWidget *parent, const char *name ) : QWidget( parent, name )
之前lcdrange是繼承QVBox,
現在改繼承QWidget,並且由layout管理視窗元件
QVBoxLayout * l = new QVBoxLayout( this );
我們建立一個QVBoxLayout,其所有的設定值都是用預設的,其parent是this(也就是QWidget)
用來管理此視窗的子元件
l->addWidget( lcd, 1 );
加入lcd進入視窗元件中,並且,它的stretch factor是1,表示會延展
若有一個是1另一個是2,則最後的長寬比是1比2
l->addWidget( slider );
l->addWidget( label );
再加入兩個元件,其延展因數是預設值的0
這個伸展控制是QVBoxLayout(和QHBoxLayout,和QGridLayout)所提供的,
而像QVBox這樣的類別卻不提供。在這種情況下我們讓QLCDNumber可以伸展,而其它的不可以。
cannon.h
新增了遊戲結束和新的函數
bool gameOver() const { return gameEnded; }
若遊戲結束則回傳TRUE,否則回傳FALSE
void setGameOver();
void restartGame();
這是兩個新增的函數,分別是設定「遊戲結束」與「遊戲重新開始」
void canShoot( bool );
這一個函數是用來控制shoot的按紐是否有效
bool gameEnded;
這一個非公開變數是用來記錄遊戲是否結束,TRUE為結束;FALSE則還沒有結束
cannon.cpp
gameEnded = FALSE;
一開始在建構子中,設定遊戲尚未結束
void CannonField::shoot()
{
if ( isShooting() )
return;
timerCount = 0;
shoot_ang = ang;
shoot_f = f;
autoShootTimer->start( 50 );
emit canShoot( FALSE );
}
我們現在改用isShooting這一個函數代替isActive
並且由shoot觸發canShoot不能再發設子彈
void CannonField::setGameOver()
{
if ( gameEnded )
return;
if ( isShooting() )
autoShootTimer->stop();
gameEnded = TRUE;
repaint();
}
我們把游戲結束的這一個函數獨立出來,以便可以讓外面呼叫
當此副程式被呼叫時,
則停止timer的運行,把gameEnded的標誌設定為TRUE
並重畫
void CannonField::restartGame()
{
if ( isShooting() )
autoShootTimer->stop();
gameEnded = FALSE;
repaint();
emit canShoot( TRUE );
}
如果炮彈還在空中,則停止timer計時,設定標誌為FALSE,並且重畫
就像hit()或miss()一樣,moveShot()同時也發射新的canShoot(TRUE)信號。
void CannonField::paintEvent( QPaintEvent *e )
{
QRect updateR = e->rect();
QPainter p( this );
if ( gameEnded ) {
p.setPen( black );
p.setFont( QFont( "Courier", 48, QFont::Bold ) );
p.drawText( rect(), AlignCenter, "Game Over" );
}
先把目前要更新的區域放在updateR中,
若遊戲結束則設定字型與要顯示的字放在中間
if ( updateR.intersects( cannonRect() ) )
paintCannon( &p );
if ( isShooting() && updateR.intersects( shotRect() ) )
paintShot( &p );
if ( !gameEnded && updateR.intersects( targetRect() ) )
paintTarget( &p );
}
我們只有在炸彈在空中的時候,才會畫炮彈
gamebrd.h
這是一個新的文件,可以把它想成是最底下的一個白板,就包含左邊的輸入介面,與右邊的顯示介面
protected slots:
void fire();
void hit();
void missed();
void newGame();
我們增加了四個slot,只能讓內部使用與繼承的類別內部使用
gamebrd.cpp
這一個文件是新的,是用來做為MyWidget中的gameboard類別的實作
cannonField = new CannonField( this, "cannonField" );
在gamebrd類別的建構子中宣告一個CannonField的物件
connect( cannonField, SIGNAL(hit()),this, SLOT(hit()) );
connect( cannonField, SIGNAL(missed()),this, SLOT(missed()) );
CannonField類別中有一個signal hit(),且GameBoard類別中也有一個slot hit()
當CannonField觸發hit()時,會使得GameBoard類別中的hit()同時運作(使命中的lcd加1,且若炮彈數為0時,則遊戲結束)
同理
當CannonField類別中的missed()運作時,GameBoard類別中的missed()同時運作(,若炮彈為0時,則遊戲結束)
connect( shoot, SIGNAL(clicked()), SLOT(fire()) );
當shoot的pushbutton被點後,本物件的fire()會同時被執行(把炮彈送出去[cannonField->shoot()],並且把炮彈減一)
connect( cannonField, SIGNAL(canShoot(bool)),shoot, SLOT(setEnabled(bool)) );
用canShoot來決定shoot的pushbutton按鈕生效和失效
QPushButton *restart = new QPushButton( "&New Game", this, "newgame" );
restart->setFont( QFont( "Times", 18, QFont::Bold ) );
connect( restart, SIGNAL(clicked()), this, SLOT(newGame()) );
設定一個新按紐來決定否是重新一個新的遊戲
hits = new QLCDNumber( 2, this, "hits" );
shotsLeft = new QLCDNumber( 2, this, "shotsleft" );
QLabel *hitsL = new QLabel( "HITS", this, "hitsLabel" );
QLabel *shotsLeftL = new QLabel( "SHOTS LEFT", this, "shotsleftLabel" );
這裡我們建立四個視窗元件,這裡說明我們在GameBoard類別建構子中才宣告hitsL與shotsLeftL指標主要是因為,我們沒有要對它們做任何改變
當GameBoard視窗元件被銷毀的時候,Qt將會刪除它們,並且佈局類會適當地重新定義它們的大小。
換句話說,當我們宣告指標在標頭檔時,當視窗元件被銷毀時,Qt不會刪除它?!
QHBoxLayout *topBox = new QHBoxLayout;
grid->addLayout( topBox, 0, 1 );
topBox->addWidget( shoot );
topBox->addWidget( hits );
topBox->addWidget( hitsL );
topBox->addWidget( shotsLeft );
topBox->addWidget( shotsLeftL );
topBox->addStretch( 1 );
topBox->addWidget( restart );
我們在右上的補足了一些視窗元件,並且讓它展現出適當的大小
並且在new game的左邊增加了一個可以延展的元件,當視窗變大時,new game 與其它元件的距離就會變大
newGame();
}
在完成視窗元件的配置之後,我們直接以newGame函數開始執行
void GameBoard::fire()
{
if ( cannonField->gameOver() || cannonField->isShooting() )
return;
shotsLeft->display( shotsLeft->intValue() - 1 );
cannonField->shoot();
}
此函數會使得炮彈被送出
void GameBoard::hit()
{
hits->display( hits->intValue() + 1 );
if ( shotsLeft->intValue() == 0 )
cannonField->setGameOver();
else
cannonField->newTarget();
}
當CannonField確定擊中目標時,會通知此函數,並且增加lcd上顯示的命中次數
當炮彈沒有了,則遊戲結束了
void GameBoard::missed()
{
if ( shotsLeft->intValue() == 0 )
cannonField->setGameOver();
}
當CannonField確定沒有命中目標時,通知此函數,則減少lcd上顯示的剩餘炮彈數,
當炮彈沒有了,則遊戲結束了
void GameBoard::newGame()
{
shotsLeft->display( 15 );
hits->display( 0 );
cannonField->restartGame();
cannonField->newTarget();
}
當new game的按紐被按下去時,則把lcd上顯示的命中次數歸0與剩下炮彈個數恢復成15
main.cpp
刪掉一些宣告MyWidget的程式碼,並且把它移到gamebrd中
沒有留言:
張貼留言