目錄
內(nèi)容簡(jiǎn)介
項(xiàng)目要求
項(xiàng)目實(shí)現(xiàn)
素材導(dǎo)入
核心思路
思路的轉(zhuǎn)變:從main到mainwindow
如何讓游戲動(dòng)起來(lái)
如何設(shè)計(jì)一個(gè)物體類
如何從鍵盤輸入操作
如何繪制圖片?
如何初始化
項(xiàng)目源碼
內(nèi)容簡(jiǎn)介
該項(xiàng)目實(shí)現(xiàn)了基于Qt的FlappyBird動(dòng)畫游戲開發(fā),我會(huì)從素材導(dǎo)入開始帶大家熟悉Qt開發(fā)的全過(guò)程,該CrashCourse偏向于對(duì)C++有一定基礎(chǔ),并且了解Qt基本運(yùn)作原理的同學(xué)
項(xiàng)目要求
該應(yīng)用是一個(gè)飛鳥躲避障礙的游戲,剛開始除以待機(jī)狀態(tài)按下space后飛鳥就開始不斷下落,玩家需要通過(guò)按space讓小鳥跳躍來(lái)保持高度穿過(guò)隨機(jī)生成的障礙,右上角會(huì)記錄小鳥的得分,即經(jīng)過(guò)障礙的個(gè)數(shù),隨著游戲的進(jìn)行,障礙速度變快,障礙密度增加,飛鳥在拍打翅膀和越過(guò)障礙時(shí)會(huì)有提示音,碰撞和游戲結(jié)束時(shí)也會(huì)有相應(yīng)的音效,以提高反饋感,在游戲結(jié)束后,會(huì)彈出gameover的字樣,再次按下space后游戲會(huì)重新開始,計(jì)數(shù)也會(huì)清零。
項(xiàng)目實(shí)現(xiàn)
素材導(dǎo)入
素材導(dǎo)入有兩種方法:
1.直接以創(chuàng)建qt resource file導(dǎo)入
2.在導(dǎo)入后轉(zhuǎn)化為二進(jìn)制形式文件,第二種較復(fù)雜,因?yàn)槊看翁砑有沦Y源后都需要進(jìn)行一遍二進(jìn)制化操作,但相應(yīng)的資源大小會(huì)被壓縮很多,在資源文教較大時(shí)可考慮存為二進(jìn)制形式
這里只講第一種方法,至于二進(jìn)制化可以參考這個(gè)博主的文章。
先從github,csdn等網(wǎng)站上找到flaggybird所需的資源放在同一文件夾里,然后移動(dòng)該文件夾到項(xiàng)目所在目錄
右鍵flappybird(項(xiàng)目名)點(diǎn)擊添加新文件,選Qt,選Qt Resource File,取名字,
點(diǎn)擊添加,添加前綴,刪除前綴到只剩下/,然后點(diǎn)添加,添加文件,選中資源文件夾,拖動(dòng)選取所有資源
這樣就完成了資源的導(dǎo)入,需要使用時(shí)只要記住資源所在目錄,然后宏定義替換掉即可
注意:如果出現(xiàn)以下情況
說(shuō)明資源文件太大,要轉(zhuǎn)化為二進(jìn)制文件,也就是方法二,在上面有詳細(xì)方法的鏈接,這里再粘貼一遍
至此就完成了素材的導(dǎo)入工作,在需要用到圖片或音頻的時(shí)候只需要通過(guò)宏定義就可以調(diào)用了,后面會(huì)給出具體宏定義的代碼,在config.h的代碼段里
config.h
#ifndef CONFIG_H
#define CONFIG_H
/********** 游戲配置數(shù)據(jù) **********/
#define GAME_WIDTH 864 //寬度
#define GAME_HEIGHT 512 //高度
#define GAME_TITLE "FlaggyBird v1.0" //標(biāo)題
#define GAME_RES_PATH "./bird.rcc" //rcc文件路徑
#define GAME_ICON ":/res/favicon.ico"
/********** 背景配置數(shù)據(jù) **********/
#define STARTMAP_PATH ":/res/message.png"
#define MAP1_PATH ":/res/background-day.png"
#define MAP2_PATH ":/res/background-night.png"
#define GAME_RATE 10 //刷新間隔,幀率 單位毫秒
#define bgm ":/res/BGM.wav"
/********** 地基配置數(shù)據(jù) **********/
#define BASE_PATH ":/res/base.png"
#define BASE_SPEED 1
#define BASE_Y 470
/********** 小鳥配置數(shù)據(jù) **********/
#define BIRD_UP_PATH ":/res/bluebird-upflap.png"
#define BIRD_MID_PATH ":/res/bluebird-midflap.png"
#define BIRD_DOWN_PATH ":/res/bluebird-downflap.png"
#define BIRD_DOWN_SPEED 6
#define BIRD_UP_SPEED 60
#define BIRD_FLAP1_SOUND ":/res/swoosh.wav"
#define BIRD_FLAP2_SOUND ":/res/wing.wav"
#define BIRD_DEAD_SPEED 9
#define BIRD_DEAD_PATH ":/res/bluebird-dead.png"
#define BIRD_DROP_SOUND ":/res/die.wav"
#define BIRD_SCORE_SOUND ":/res/point.wav"
/********** 管道配置數(shù)據(jù) **********/
#define TUBE_SPEED 1
#define TUBE_PATH ":/res/pipe-green.png"
#define TUBE_PATH2 ":/res/pipe-greendown.png"
#define TUBE_INTERVAL 220
#define TUBE_NUM 50
/********** 死亡配置數(shù)據(jù) **********/
#define GAMEOVER_PATH ":/res/gameover.png"
#define CRASH_PATH ":/res/hit.wav"
/********** 得分配置數(shù)據(jù) **********/
#define S_0 ":/res/0.png"
#define S_1 ":/res/1.png"
#define S_2 ":/res/2.png"
#define S_3 ":/res/3.png"
#define S_4 ":/res/4.png"
#define S_5 ":/res/5.png"
#define S_6 ":/res/6.png"
#define S_7 ":/res/7.png"
#define S_8 ":/res/8.png"
#define S_9 ":/res/9.png"
#endif // CONFIG_H
核心思路
思路的轉(zhuǎn)變:從main到mainwindow
大多數(shù)大一沒有接觸過(guò)c++項(xiàng)目的小伙伴可能依然習(xí)慣于上課所學(xué)的int main(),然后在main函數(shù)里進(jìn)行對(duì)象的調(diào)用并實(shí)現(xiàn)程序主體內(nèi)容,但這里更推薦在mainwindow里實(shí)現(xiàn),因?yàn)橛行r(shí)候一個(gè)游戲會(huì)需要不同窗口,比如我室友的坦克大戰(zhàn)游戲,需要一個(gè)單獨(dú)的完結(jié)動(dòng)畫窗口,main函數(shù)通常用來(lái)調(diào)用不同的窗口,而不是每個(gè)窗口里的具體內(nèi)容,因此雖然本程序是單窗口的,但更推薦在mainwindow.h里include其它類的.h并創(chuàng)建其它類的對(duì)象,在mainwindow.cpp里實(shí)現(xiàn)游戲代碼的主體內(nèi)容,main里只創(chuàng)建mainwindow的對(duì)象w,然后w.show()顯示
總而言之,最好把mainwindow視作程序的主體部分而不是main
如何讓游戲動(dòng)起來(lái)
其實(shí)游戲的技術(shù)本質(zhì)和動(dòng)畫一樣,都類似于小時(shí)候看到的翻頁(yè)連環(huán)畫,在每一頁(yè)小紙片上畫上連續(xù)的動(dòng)作內(nèi)容,通過(guò)快速地翻動(dòng)紙片,就可以看到連續(xù)的動(dòng)畫了
這里的紙片就是游戲里的幀(也許是連續(xù)地幾幀,為了方便,這里粗略地把一次屏幕刷新叫做一幀),通過(guò)快速地屏幕刷新,欺騙我們的眼睛,使其可以看到連續(xù)的畫面,唯一不同的是,游戲是需要根據(jù)玩家鍵盤和鼠標(biāo)輸入的指令,進(jìn)行實(shí)時(shí)地反饋,調(diào)整下一幀里物體的動(dòng)作或位置,因此,我們可以只需要考慮每一幀的時(shí)候,各個(gè)物體會(huì)做出什么操作(用QTimer類實(shí)現(xiàn)),以及玩家輸入指令的時(shí)候,物體會(huì)做出什么操作(用keyPressEvent實(shí)現(xiàn))
void MainWindow::initial() {
//初始化窗口大小
setFixedSize(GAME_WIDTH,GAME_HEIGHT);
//設(shè)置窗口標(biāo)題
setWindowTitle(GAME_TITLE);
//設(shè)置圖標(biāo)資源
setWindowIcon(QIcon(GAME_ICON));
//設(shè)置定時(shí)器幀率
Timer.setInterval(GAME_RATE);
//播放音樂
if(gameover.state==0){
QSound::play(bgm);
}
//抽象時(shí)鐘
count=0;
//其它變量
point=0;
start=0;
gameover.state=0;
//重新開始
recover();
//開始游戲
if(judge==0){
PlayGame();
}
}
這里的 Timer.setinterval(GAME_RATE),GAME_RATE是在config.h(宏定義匯總)里定義的屏幕每幾幀刷新一次,其數(shù)值為10,這樣Timer會(huì)每10幀發(fā)出一次名為timeout的信號(hào)
void MainWindow::PlayGame(){
//啟動(dòng)定時(shí)器
Timer.start();
//監(jiān)聽定時(shí)器
connect(&Timer,&QTimer::timeout,[=](){
//更新游戲中元素的坐標(biāo)
updatePosition();
//碰撞檢測(cè)
crash();
//抽象計(jì)時(shí)器
if(start==1&&gameover.state==0){
count++;
}
//管道刷新器
moretube();
//刷新計(jì)分器
score();
//重新繪制圖片
update();
//變速器
speedup();
});
}
在PlayGame()函數(shù)被調(diào)用時(shí)即開始游戲時(shí),會(huì)啟動(dòng)Timer開始按每10幀發(fā)出一次timeout的信號(hào)
再通過(guò)連接槽函數(shù)connect(connect函數(shù)在本程序不會(huì)多次用到,如果感興趣可以查閱槽函數(shù)的相關(guān)內(nèi)容,通俗理解為類似細(xì)胞釋放的化學(xué)信號(hào)和另一個(gè)細(xì)胞上的受體就可以了,通過(guò)connect函數(shù)連接)將timeout信號(hào)和mainwindow連起來(lái),這樣每當(dāng)mainwindow的對(duì)象收到timeout信號(hào)的時(shí)候,就會(huì)調(diào)用updatePosition()函數(shù),也就是進(jìn)行一次刷新(可通俗理解為連環(huán)畫的翻頁(yè))
void MainWindow::updatePosition(){
base.baseposition();
if(start==1){
if(gameover.state==1){
bird.dead();
}else{
bird.birdposition();
}
for(int j=0;j<TUBE_NUM;j++){
tube[j].updateposition();
}
}
}
?在mainwindow.cpp中自定義了updatePosition()函數(shù)功能如上,分別進(jìn)行base的位置刷新,bird的位置刷新,以及tube的位置刷新,當(dāng)然,刷新鳥位置的前提是,游戲開始了且游戲還沒輸,因此添加了兩個(gè)判斷語(yǔ)句來(lái)防止玩家沒按空格開始游戲鳥就開始下墜,或者游戲已經(jīng)結(jié)束鳥還沒有播放死亡動(dòng)畫的情況,最后的tube,因?yàn)楣艿烙卸鄠€(gè),需要使用循環(huán)語(yǔ)句刷新所以管道的位置。
在理解如何讓游戲動(dòng)起來(lái)后,其實(shí)剩下的就只是輕松愉悅地具體實(shí)現(xiàn)每一幀(每一次刷新)各個(gè)物體所做的操作和鍵盤鍵入操作,這種翻頁(yè)連環(huán)畫讓游戲動(dòng)起來(lái)的方法同樣適用于坦克大戰(zhàn),貪吃蛇等動(dòng)態(tài)游戲中
如何設(shè)計(jì)一個(gè)物體類
游戲中的不同物體其實(shí)就是不同的類,通過(guò)定義類里的數(shù)據(jù)和函數(shù),再在mainwindow.h中創(chuàng)建對(duì)象,就可以實(shí)現(xiàn)在mainwindow. cpp里
舉個(gè)最簡(jiǎn)單的例子:實(shí)現(xiàn)地基
老師希望的地基是向后滾動(dòng)的,并且鳥撞上后會(huì)死亡
base.h
#ifndef BASE_H
#define BASE_H
#include<QPixmap>
#include<QRect>
class Base
{
public:
Base();
//地基圖像
QPixmap base;
//地基圖像位置
int base_X1;
int base_X2;
int base_X3;
int base_X4;
int speed;
//地基矩形邊框
QRect base_rect;
//地基位置更新
void baseposition();
};
#endif // BASE_H
?base.cpp
#include "base.h"
#include "config.h"
Base::Base()
{
//地基資源加載
base.load(BASE_PATH);
//地基位置
base_X1=0;
base_X2=288;
base_X3=576;
base_X4=864;
//地基矩形
base_rect.setWidth(3*base.width());
base_rect.setHeight(base.height());
base_rect.moveTo(0,BASE_Y);
speed=BASE_SPEED;
}
//地基位置更新
void Base::baseposition(){
base_X1-=speed;
if(base_X1<=-288){
base_X1=0;
}
base_X2-=speed;
if(base_X2<=0){
base_X2=288;
}
base_X3-=speed;
if(base_X3<=288){
base_X3=576;
}
base_X4-=speed;
if(base_X4<=576){
base_X4=864;
}
}
創(chuàng)建一個(gè)地基類,關(guān)鍵要考慮兩個(gè)事情,一個(gè)是地基的位置,一個(gè)是地基的動(dòng)態(tài)
地基位置:
地基是需要有實(shí)體的,因?yàn)樾枰袛帏B是否撞上了地基,所以在設(shè)置地基位置變量時(shí),不僅要設(shè)定地基圖像位置base_X還要設(shè)定地基實(shí)體矩陣的位置base_rect(這里矩陣變量利用了Qt提供的類QRect來(lái)簡(jiǎn)化程序,如果不清楚任何Qt提供函數(shù)和類的使用規(guī)則,都可以在軟件的幫助里選索引,然后進(jìn)行查找)
這里可能會(huì)有小伙伴疑問(wèn)為什么有4個(gè)base_X以及為什么base_rect需要*3,照理說(shuō)是只需要2個(gè)base_X(為了實(shí)現(xiàn)滾動(dòng))且base_rect不需要乘3的,這是因?yàn)檎业降牡鼗夭氖秦Q屏游戲素材,不夠?qū)挘圆⑴欧胖昧?個(gè)來(lái)增加寬度(這樣看起來(lái)是一個(gè)更長(zhǎng)的地基)
地基動(dòng)態(tài):
地基的動(dòng)態(tài)在之前翻頁(yè)連環(huán)畫方法中已經(jīng)提到,只需要考慮具體每一幀地基會(huì)執(zhí)行什么操作,在base.cpp里具體實(shí)現(xiàn)了地基的位置更新函數(shù)baseposition(),其實(shí)就是簡(jiǎn)單地讓每個(gè)地基倒退speed的距離,當(dāng)每個(gè)地基完全偏離開始位置時(shí),恢復(fù)到原位重新開始,這樣就可以實(shí)現(xiàn)循環(huán)往復(fù)且圖像連續(xù)移動(dòng)的感覺
(在做飛機(jī)大戰(zhàn)時(shí),背景圖片需要向下移動(dòng),這時(shí)同理需要兩個(gè)背景圖片同時(shí)向下移動(dòng),當(dāng)每個(gè)圖片完全偏離開始位置時(shí),重新開始,這樣雖然有兩個(gè)圖片,但肉眼只能看到一個(gè),形成了一種背景圖片連續(xù)向下移動(dòng)的感覺)
base.load(BASE_PATH):
這個(gè)是用來(lái)畫圖的,這里的base是一個(gè)QPixmap類型的變量,而不是base類,具體會(huì)在之后mainwindow里的painter函數(shù)使用到,可以暫時(shí)理解為一個(gè)圖片變量,通過(guò)load就加載了圖片,以及圖片長(zhǎng)度寬度等信息
至此,我們已經(jīng)能夠獨(dú)立地創(chuàng)建一個(gè)物體類,bird類和tube類大同小異,關(guān)鍵是搞清楚他們的圖片位置和實(shí)體矩陣位置,以及每一幀的操作
bird.h
#ifndef BIRD_H
#define BIRD_H
#include<QPixmap>
#include<QRect>
#include<QSound>
class Bird
{
public:
Bird();
//拍打翅膀
void setposition(int x,int y);
//按幀率修改小鳥位置和狀態(tài)
void birdposition();
//小鳥死亡
void dead();
public:
//小鳥資源
QPixmap birdmap;
//小鳥坐標(biāo)
int m_x;
int m_y;
//小鳥矩形邊框(碰撞判定)
QRect rec;
//小鳥動(dòng)作
int state;
int i;
int sound;
};
#endif // BIRD_H
bird.cpp
#include "bird.h"
#include"config.h"
Bird::Bird()
{
birdmap.load(BIRD_UP_PATH);
m_x=GAME_WIDTH *0.1;
m_y=GAME_HEIGHT*0.5;
rec.setWidth(birdmap.width());
rec.setHeight(birdmap.height());
rec.moveTo(m_x,m_y);
state=0;
i=0;
sound=0;
}
void Bird::setposition(int x,int y){
m_x=x;
m_y=y;
rec.moveTo(m_x,m_y);
}
void Bird::birdposition(){
i++;
if(i%3==0){
m_y+=BIRD_DOWN_SPEED;
state++;
}
switch(state%3)
{
case 0:
birdmap.load(BIRD_UP_PATH);
break;
case 1:
birdmap.load(BIRD_MID_PATH);
break;
case 2:
birdmap.load(BIRD_DOWN_PATH);
break;
}
rec.moveTo(m_x,m_y);
}
void Bird::dead(){
birdmap.load(BIRD_DEAD_PATH);
m_x-=TUBE_SPEED;
m_y+=BIRD_DEAD_SPEED;
if(sound==0){
QSound::play(CRASH_PATH);
QSound::play(BIRD_DROP_SOUND);
sound=1;
}
}
tube.h?
#ifndef TUBE_H
#define TUBE_H
#include"config.h"
#include<QPixmap>
#include<QRect>
#include<QtGlobal>
#include <cstdlib>
class Tube
{
public:
Tube();
void updateposition();//更新管道坐標(biāo)
QPixmap tube;//管道資源
QPixmap rtube;//上管道資源
QRect rec;//下管道實(shí)體矩形
int m_x;//下管道坐標(biāo)x
int m_y;//下管道坐標(biāo)y
QRect rrec;//上管道實(shí)體矩形
int m_rx;//上管道坐標(biāo)
int m_ry;//上管道坐標(biāo)
bool exist;
int speed;
};
#endif // TUBE_H
tube.cpp
#include "tube.h"
Tube::Tube()
{
tube.load(TUBE_PATH);//加載管道資源
rec.setWidth(tube.width());//設(shè)置實(shí)體矩形寬高
rec.setHeight(tube.height());
rtube.load(TUBE_PATH2);//加載上管道資源
rrec.setWidth(rtube.width());//設(shè)置上管道實(shí)體矩陣
rrec.setHeight(rtube.height());
m_y=( qrand() % (GAME_HEIGHT-100 - 180 + 1) ) + 160;//利用隨機(jī)數(shù)生成初始y坐標(biāo)
m_x=GAME_WIDTH;//設(shè)置初始X坐標(biāo)
rec.moveTo(m_x,m_y);//設(shè)置初始矩形位置
m_rx=GAME_WIDTH;
m_ry=m_y-rtube.height()-180;
rrec.moveTo(m_rx,m_ry);//設(shè)置上矩形初始位置
speed=TUBE_SPEED;
exist=false;
}
void Tube::updateposition(){
if(exist==true){
m_x-=speed;
m_rx-=speed;
rec.moveTo(m_x,m_y);
rrec.moveTo(m_rx,m_ry);
}
}
?(這段直接放代碼了,因?yàn)槎际莊lappybird的具體操作不太具有普適性,有時(shí)間會(huì)補(bǔ)上)
如何從鍵盤輸入操作
Qt自帶一個(gè)叫Event的東西,叫事件?,事件里的操作會(huì)在程序運(yùn)行時(shí)被一直重復(fù)的執(zhí)行(也就是實(shí)時(shí)反饋,無(wú)論何時(shí)按下鍵盤,總會(huì)響應(yīng)),注意這里keypressEvent和QKeyEvent是大小寫都不能改的,是Qt提供的庫(kù)函數(shù),第一行算是鐵模板,除了mainwindow和e基本是沒法修改的,在函數(shù)內(nèi)部e可以理解為指向鍵盤的指針,通過(guò)->key()可以訪問(wèn)到當(dāng)前鍵盤鍵入的內(nèi)容
void MainWindow::keyPressEvent(QKeyEvent *e){
int y=bird.m_y;
if(e->key() == Qt::Key_Space){
y-=BIRD_UP_SPEED;
if(gameover.state==0){
QSound::play(BIRD_FLAP2_SOUND);
}
if(start==0){
start=1;
QSound::play(BIRD_FLAP1_SOUND);
}
}
bird.setposition(bird.m_x,y);
if(gameover.state==1){
if(e->key() == Qt::Key_Space){
judge=1;
initial();
}
}
}
如何繪制圖片?
Qt提供了一個(gè)叫paintEvent的事件,和鍵盤事件一樣,會(huì)在程序運(yùn)行過(guò)程中不斷被執(zhí)行,所以可以利用paintEvent來(lái)實(shí)現(xiàn)實(shí)時(shí)更新游戲畫面的效果,前兩行代碼除了mainwindow和painter基本也是鐵模板,通過(guò)painter.drawPixmap(對(duì)象x位置,對(duì)象y位置,對(duì)象的圖片)來(lái)繪圖,其中對(duì)象的圖片在對(duì)象的類里會(huì)用load函數(shù)提前存為圖片變量,通過(guò)實(shí)時(shí)更新x和y的位置,就可以達(dá)到圖像的移動(dòng)
void MainWindow::paintEvent(QPaintEvent *){
QPainter painter(this);
//繪制地圖
if(point%20<10){
painter.drawPixmap(0,map.map_posY,map.map1);
painter.drawPixmap(288,map.map_posY,map.map1);
painter.drawPixmap(576,map.map_posY,map.map1);
}else{
painter.drawPixmap(0,map.map_posY,map.map2);
painter.drawPixmap(288,map.map_posY,map.map2);
painter.drawPixmap(576,map.map_posY,map.map2);
}
if(start==0){
painter.drawPixmap(330,90,map.startmap);
}
//繪制地基
painter.drawPixmap(base.base_X1,BASE_Y,base.basemap);
painter.drawPixmap(base.base_X2,BASE_Y,base.basemap);
painter.drawPixmap(base.base_X3,BASE_Y,base.basemap);
painter.drawPixmap(base.base_X4,BASE_Y,base.basemap);
//繪制小鳥
painter.drawPixmap(bird.m_x,bird.m_y,bird.birdmap);
//繪制管道
for(int j=0;j<TUBE_NUM;j++){
if(tube[j].exist==true){
painter.drawPixmap(tube[j].m_x,tube[j].m_y,tube[j].tube);
painter.drawPixmap(tube[j].m_rx,tube[j].m_ry,tube[j].rtube);
}
}
如何初始化
至此,程序的大體就已經(jīng)完成,我們?cè)O(shè)計(jì)出了各個(gè)類,他們都有各自的位置更新函數(shù),把他們放在mainwindow的updatePosition函數(shù)里,這樣每次刷新時(shí)各個(gè)物體的位置就會(huì)更新,同時(shí),鍵盤事件和繪圖事件也使我們能夠?qū)崟r(shí)改變物體位置并更新物體圖像位置,形成連續(xù)的畫面,但是如何啟動(dòng)呢,這需要initial函數(shù)來(lái)初始化,在其中調(diào)用playgame函數(shù)以啟動(dòng)時(shí)鐘,時(shí)鐘一旦啟動(dòng),游戲也就開始了
void MainWindow::initial() {
//初始化窗口大小
setFixedSize(GAME_WIDTH,GAME_HEIGHT);
//設(shè)置窗口標(biāo)題
setWindowTitle(GAME_TITLE);
//設(shè)置圖標(biāo)資源
setWindowIcon(QIcon(GAME_ICON));
//設(shè)置定時(shí)器幀率
Timer.setInterval(GAME_RATE);
//播放音樂
if(gameover.state==0){
QSound::play(bgm);
}
//抽象時(shí)鐘
count=0;
//其它變量
point=0;
start=0;
gameover.state=0;
//重新開始
recover();
//開始游戲
if(judge==0){
PlayGame();
}
}
void MainWindow::PlayGame(){
//啟動(dòng)定時(shí)器
Timer.start();
//監(jiān)聽定時(shí)器
connect(&Timer,&QTimer::timeout,[=](){
//更新游戲中元素的坐標(biāo)
updatePosition();
//碰撞檢測(cè)
crash();
//抽象計(jì)時(shí)器
if(start==1&&gameover.state==0){
count++;
}
//管道刷新器
moretube();
//刷新計(jì)分器
score();
//重新繪制圖片
update();
//變速器
speedup();
});
}
項(xiàng)目源碼
flappybird.pro
#-------------------------------------------------
#
# Project created by QtCreator 2022-07-12T18:14:43
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = flappybird
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp \
map.cpp \
base.cpp \
bird.cpp \
tube.cpp \
gameover.cpp \
score.cpp
HEADERS += \
mainwindow.h \
config.h \
map.h \
base.h \
bird.h \
tube.h \
gameover.h \
score.h
FORMS += \
mainwindow.ui
QT += core gui multimedia
RC_FILE = exe_ico.rc
?config.h
#ifndef CONFIG_H
#define CONFIG_H
/********** 游戲配置數(shù)據(jù) **********/
#define GAME_WIDTH 864 //寬度
#define GAME_HEIGHT 512 //高度
#define GAME_TITLE "FlaggyBird v1.0" //標(biāo)題
#define GAME_RES_PATH "./bird.rcc" //rcc文件路徑
#define GAME_ICON ":/res/favicon.ico"
/********** 背景配置數(shù)據(jù) **********/
#define STARTMAP_PATH ":/res/message.png"
#define MAP1_PATH ":/res/background-day.png"
#define MAP2_PATH ":/res/background-night.png"
#define GAME_RATE 10 //刷新間隔,幀率 單位毫秒
#define bgm ":/res/BGM.wav"
/********** 地基配置數(shù)據(jù) **********/
#define BASE_PATH ":/res/base.png"
#define BASE_SPEED 1
#define BASE_Y 470
/********** 小鳥配置數(shù)據(jù) **********/
#define BIRD_UP_PATH ":/res/bluebird-upflap.png"
#define BIRD_MID_PATH ":/res/bluebird-midflap.png"
#define BIRD_DOWN_PATH ":/res/bluebird-downflap.png"
#define BIRD_DOWN_SPEED 6
#define BIRD_UP_SPEED 60
#define BIRD_FLAP1_SOUND ":/res/swoosh.wav"
#define BIRD_FLAP2_SOUND ":/res/wing.wav"
#define BIRD_DEAD_SPEED 9
#define BIRD_DEAD_PATH ":/res/bluebird-dead.png"
#define BIRD_DROP_SOUND ":/res/die.wav"
#define BIRD_SCORE_SOUND ":/res/point.wav"
/********** 管道配置數(shù)據(jù) **********/
#define TUBE_SPEED 1
#define TUBE_PATH ":/res/pipe-green.png"
#define TUBE_PATH2 ":/res/pipe-greendown.png"
#define TUBE_INTERVAL 220
#define TUBE_NUM 50
/********** 死亡配置數(shù)據(jù) **********/
#define GAMEOVER_PATH ":/res/gameover.png"
#define CRASH_PATH ":/res/hit.wav"
/********** 得分配置數(shù)據(jù) **********/
#define S_0 ":/res/0.png"
#define S_1 ":/res/1.png"
#define S_2 ":/res/2.png"
#define S_3 ":/res/3.png"
#define S_4 ":/res/4.png"
#define S_5 ":/res/5.png"
#define S_6 ":/res/6.png"
#define S_7 ":/res/7.png"
#define S_8 ":/res/8.png"
#define S_9 ":/res/9.png"
#endif // CONFIG_H
base.h
#ifndef BASE_H
#define BASE_H
#include<QPixmap>
#include<QRect>
class Base
{
public:
Base();
//地基圖像
QPixmap base;
//地基圖像位置
int base_X1;
int base_X2;
int base_X3;
int base_X4;
int speed;
//地基矩形邊框
QRect base_rect;
//地基位置更新
void baseposition();
};
#endif // BASE_H
base.cpp?
#include "base.h"
#include "config.h"
Base::Base()
{
//地基資源加載
base.load(BASE_PATH);
//地基位置
base_X1=0;
base_X2=288;
base_X3=576;
base_X4=864;
//地基矩形
base_rect.setWidth(3*base.width());
base_rect.setHeight(base.height());
base_rect.moveTo(0,BASE_Y);
speed=BASE_SPEED;
}
//地基位置更新
void Base::baseposition(){
base_X1-=speed;
if(base_X1<=-288){
base_X1=0;
}
base_X2-=speed;
if(base_X2<=0){
base_X2=288;
}
base_X3-=speed;
if(base_X3<=288){
base_X3=576;
}
base_X4-=speed;
if(base_X4<=576){
base_X4=864;
}
}
bird.h
#ifndef BIRD_H
#define BIRD_H
#include<QPixmap>
#include<QRect>
#include<QSound>
class Bird
{
public:
Bird();
//拍打翅膀
void setposition(int x,int y);
//按幀率修改小鳥位置和狀態(tài)
void birdposition();
//小鳥死亡
void dead();
public:
//小鳥資源
QPixmap birdmap;
//小鳥坐標(biāo)
int m_x;
int m_y;
//小鳥矩形邊框(碰撞判定)
QRect rec;
//小鳥動(dòng)作
int state;
int i;
int sound;
};
#endif // BIRD_H
?bird.cpp
#include "bird.h"
#include"config.h"
Bird::Bird()
{
birdmap.load(BIRD_UP_PATH);
m_x=GAME_WIDTH *0.1;
m_y=GAME_HEIGHT*0.5;
rec.setWidth(birdmap.width());
rec.setHeight(birdmap.height());
rec.moveTo(m_x,m_y);
state=0;
i=0;
sound=0;
}
void Bird::setposition(int x,int y){
m_x=x;
m_y=y;
rec.moveTo(m_x,m_y);
}
void Bird::birdposition(){
i++;
if(i%3==0){
m_y+=BIRD_DOWN_SPEED;
state++;
}
switch(state%3)
{
case 0:
birdmap.load(BIRD_UP_PATH);
break;
case 1:
birdmap.load(BIRD_MID_PATH);
break;
case 2:
birdmap.load(BIRD_DOWN_PATH);
break;
}
rec.moveTo(m_x,m_y);
}
void Bird::dead(){
birdmap.load(BIRD_DEAD_PATH);
m_x-=TUBE_SPEED;
m_y+=BIRD_DEAD_SPEED;
if(sound==0){
QSound::play(CRASH_PATH);
QSound::play(BIRD_DROP_SOUND);
sound=1;
}
}
?gameover.h
#ifndef GAMEOVER_H
#define GAMEOVER_H
#include"config.h"
#include<QPixmap>
#include<QSound>
class Gameover
{
public:
Gameover();
QPixmap gameovermap;
int state;
};
#endif // GAMEOVER_H
?gameover.cpp
#include "gameover.h"
Gameover::Gameover()
{
state=0;
gameovermap.load(GAMEOVER_PATH);
}
mainwindow.h?
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include"config.h"
#include <QIcon>
#include"map.h"
#include<QPaintEvent>
#include<QPainter>
#include<QTimer>
#include"base.h"
#include"bird.h"
#include"tube.h"
#include"gameover.h"
#include"score.h"
#include <QAbstractButton>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void initial();
void paintEvent(QPaintEvent *);//繪圖事件
void updatePosition();//以幀率為周期更新坐標(biāo)
void keyPressEvent(QKeyEvent *);//鍵盤事件
void crash();//碰撞檢測(cè)
void GameOver();//游戲結(jié)束
void moretube();//管道刷新器
void score();//得分
void PlayGame();//啟動(dòng)游戲,開啟定時(shí)器對(duì)象
void recover();//回復(fù)初始位置
void speedup();//變速器
public:
Map map;//地圖對(duì)象
QTimer Timer;//定時(shí)器對(duì)象
Base base;//地基對(duì)象
Bird bird;//小鳥對(duì)象
Gameover gameover;//結(jié)束對(duì)象
Tube tube[60];//管道對(duì)象
Score scored;//計(jì)分對(duì)象
int count;//計(jì)時(shí)器
int point;
int start;
int judge=0;
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
?mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
initial();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::initial() {
//初始化窗口大小
setFixedSize(GAME_WIDTH,GAME_HEIGHT);
//設(shè)置窗口標(biāo)題
setWindowTitle(GAME_TITLE);
//設(shè)置圖標(biāo)資源
setWindowIcon(QIcon(GAME_ICON));
//設(shè)置定時(shí)器幀率
Timer.setInterval(GAME_RATE);
//播放音樂
if(gameover.state==0){
QSound::play(bgm);
}
//抽象時(shí)鐘
count=0;
//其它變量
point=0;
start=0;
gameover.state=0;
//重新開始
recover();
//開始游戲
if(judge==0){
PlayGame();
}
}
void MainWindow::paintEvent(QPaintEvent *){
QPainter painter(this);
//繪制地圖
if(point%20<10){
painter.drawPixmap(0,map.map_posY,map.map1);
painter.drawPixmap(288,map.map_posY,map.map1);
painter.drawPixmap(576,map.map_posY,map.map1);
}else{
painter.drawPixmap(0,map.map_posY,map.map2);
painter.drawPixmap(288,map.map_posY,map.map2);
painter.drawPixmap(576,map.map_posY,map.map2);
}
if(start==0){
painter.drawPixmap(330,90,map.startmap);
}
//繪制地基
painter.drawPixmap(base.base_X1,BASE_Y,base.base);
painter.drawPixmap(base.base_X2,BASE_Y,base.base);
painter.drawPixmap(base.base_X3,BASE_Y,base.base);
painter.drawPixmap(base.base_X4,BASE_Y,base.base);
//繪制小鳥
painter.drawPixmap(bird.m_x,bird.m_y,bird.birdmap);
//繪制管道
for(int j=0;j<TUBE_NUM;j++){
if(tube[j].exist==true){
painter.drawPixmap(tube[j].m_x,tube[j].m_y,tube[j].tube);
painter.drawPixmap(tube[j].m_rx,tube[j].m_ry,tube[j].rtube);
}
}
//繪制得分
switch(point%10){
case 0:
painter.drawPixmap(820,20,scored.S0);
break;
case 1:
painter.drawPixmap(820,20,scored.S1);
break;
case 2:
painter.drawPixmap(820,20,scored.S2);
break;
case 3:
painter.drawPixmap(820,20,scored.S3);
break;
case 4:
painter.drawPixmap(820,20,scored.S4);
break;
case 5:
painter.drawPixmap(820,20,scored.S5);
break;
case 6:
painter.drawPixmap(820,20,scored.S6);
break;
case 7:
painter.drawPixmap(820,20,scored.S7);
break;
case 8:
painter.drawPixmap(820,20,scored.S8);
break;
case 9:
painter.drawPixmap(820,20,scored.S9);
break;
}
switch(point/10){
case 0:
painter.drawPixmap(788,20,scored.S0);
break;
case 1:
painter.drawPixmap(788,20,scored.S1);
break;
case 2:
painter.drawPixmap(788,20,scored.S2);
break;
case 3:
painter.drawPixmap(788,20,scored.S3);
break;
case 4:
painter.drawPixmap(788,20,scored.S4);
break;
case 5:
painter.drawPixmap(788,20,scored.S5);
break;
case 6:
painter.drawPixmap(788,20,scored.S6);
break;
case 7:
painter.drawPixmap(788,20,scored.S7);
break;
case 8:
painter.drawPixmap(788,20,scored.S8);
break;
case 9:
painter.drawPixmap(786,20,scored.S9);
break;
}
//繪制gameover
if(gameover.state==1){
painter.drawPixmap(320,200,gameover.gameovermap);
}
}
void MainWindow::PlayGame(){
//啟動(dòng)定時(shí)器
Timer.start();
//監(jiān)聽定時(shí)器
connect(&Timer,&QTimer::timeout,[=](){
//更新游戲中元素的坐標(biāo)
updatePosition();
//碰撞檢測(cè)
crash();
//抽象計(jì)時(shí)器
if(start==1&&gameover.state==0){
count++;
}
//管道刷新器
moretube();
//刷新計(jì)分器
score();
//重新繪制圖片
update();
//變速器
speedup();
});
}
void MainWindow::updatePosition(){
base.baseposition();
if(start==1){
if(gameover.state==1){
bird.dead();
}else{
bird.birdposition();
}
for(int j=0;j<TUBE_NUM;j++){
tube[j].updateposition();
}
}
}
void MainWindow::keyPressEvent(QKeyEvent *e){
int y=bird.m_y;
if(e->key() == Qt::Key_Space){
y-=BIRD_UP_SPEED;
if(gameover.state==0){
QSound::play(BIRD_FLAP2_SOUND);
}
if(start==0){
start=1;
QSound::play(BIRD_FLAP1_SOUND);
}
}
bird.setposition(bird.m_x,y);
if(gameover.state==1){
if(e->key() == Qt::Key_Space){
judge=1;
initial();
}
}
}
void MainWindow::crash(){
if(bird.rec.intersects(base.base_rect)){
gameover.state=1;
}
for(int j=0;j<TUBE_NUM;j++){
if(bird.rec.intersects(tube[j].rec)){
gameover.state=1;
}
if(bird.rec.intersects(tube[j].rrec)){
gameover.state=1;
}
}
}
void MainWindow:: moretube(){
int w=count/(TUBE_INTERVAL-point*4);
tube[w].exist=true;
}
void MainWindow::score(){
if(bird.m_x>=tube[point].m_x&&bird.m_x<=tube[point].m_x+tube[point].rec.width()&&gameover.state==0){
point++;
QSound::play(BIRD_SCORE_SOUND);
}
}
void MainWindow::speedup(){
for(int i=0;i<TUBE_NUM;i++){
tube[i].speed=point*0.2+TUBE_SPEED ;
}
base.speed=point*0.2+TUBE_SPEED;
}
void MainWindow:: recover(){
bird.birdmap.load(BIRD_UP_PATH);
for(int i=0;i<TUBE_NUM;i++){
tube[i].m_x=GAME_WIDTH;
tube[i].m_rx=GAME_WIDTH;
tube[i].rec.moveTo(tube[i].m_x,tube[i].m_y);
tube[i].rrec.moveTo(tube[i].m_rx,tube[i].m_ry);
tube[i].exist=false;
}
bird.m_x=GAME_WIDTH *0.1;
bird.m_y=GAME_HEIGHT*0.5;
bird.rec.moveTo(bird.m_x,bird.m_y);
bird.sound=0;
}
map.h
#ifndef MAP_H
#define MAP_H
#include<QPixmap>
class Map
{
public:
QPixmap map1;
QPixmap map2;
QPixmap startmap;
int map_posY;
Map();
};
#endif // MAP_H
?map.cpp
#include "map.h"
#include"config.h"
Map::Map()
{
map1.load(MAP1_PATH);
map2.load(MAP2_PATH);
startmap.load(STARTMAP_PATH);
map_posY=0;
}
?score.h
#ifndef SCORE_H
#define SCORE_H
#include<QPixmap>
#include"config.h"
class Score
{
public:
Score();
QPixmap S0;
QPixmap S1;
QPixmap S2;
QPixmap S3;
QPixmap S4;
QPixmap S5;
QPixmap S6;
QPixmap S7;
QPixmap S8;
QPixmap S9;
};
#endif // SCORE_H
?score.cpp
#include "score.h"
Score::Score()
{
S0.load(S_0);
S1.load(S_1);
S2.load(S_2);
S3.load(S_3);
S4.load(S_4);
S5.load(S_5);
S6.load(S_6);
S7.load(S_7);
S8.load(S_8);
S9.load(S_9);
}
tube.h文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-408162.html
#ifndef TUBE_H
#define TUBE_H
#include"config.h"
#include<QPixmap>
#include<QRect>
#include<QtGlobal>
#include <cstdlib>
class Tube
{
public:
Tube();
void updateposition();//更新管道坐標(biāo)
QPixmap tube;//管道資源
QPixmap rtube;//上管道資源
QRect rec;//下管道實(shí)體矩形
int m_x;//下管道坐標(biāo)x
int m_y;//下管道坐標(biāo)y
QRect rrec;//上管道實(shí)體矩形
int m_rx;//上管道坐標(biāo)
int m_ry;//上管道坐標(biāo)
bool exist;
int speed;
};
#endif // TUBE_H
?tube.cpp文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-408162.html
#include "tube.h"
Tube::Tube()
{
tube.load(TUBE_PATH);//加載管道資源
rec.setWidth(tube.width());//設(shè)置實(shí)體矩形寬高
rec.setHeight(tube.height());
rtube.load(TUBE_PATH2);//加載上管道資源
rrec.setWidth(rtube.width());//設(shè)置上管道實(shí)體矩陣
rrec.setHeight(rtube.height());
m_y=( qrand() % (GAME_HEIGHT-100 - 180 + 1) ) + 160;//利用隨機(jī)數(shù)生成初始y坐標(biāo)
m_x=GAME_WIDTH;//設(shè)置初始X坐標(biāo)
rec.moveTo(m_x,m_y);//設(shè)置初始矩形位置
m_rx=GAME_WIDTH;
m_ry=m_y-rtube.height()-180;
rrec.moveTo(m_rx,m_ry);//設(shè)置上矩形初始位置
speed=TUBE_SPEED;
exist=false;
}
void Tube::updateposition(){
if(exist==true){
m_x-=speed;
m_rx-=speed;
rec.moveTo(m_x,m_y);
rrec.moveTo(m_rx,m_ry);
}
}
到了這里,關(guān)于通關(guān)大一編程實(shí)踐,用C++基礎(chǔ)和Qt實(shí)現(xiàn)FlappyBird小游戲的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!