悲傷的現實
離期末第一場考試之剩下32天了,而oop的期中project還有24天截止。此刻,我們剛剛寫完了人生中第一個c++程序,分數類的運算符重載。繼承和多態還沒學。儘管如此,也是時候作爲全裸勇者勇敢地去挑戰大魔王了。
目標
通過研究貪吃蛇小遊戲的源碼,學會用Qt進行遊戲開發,並且完成期中Pro報告中挖下的2D Rougelike類小遊戲第一關的大坑,並且寫下博客給新鮮的學妹學弟留下可以借鑑的學習經驗。
資料
Qt5貪吃蛇小遊戲源代碼下載處
來自Rimond_Jing的Qt5基本安裝教程
來自 齊亮,非常棒的一個參考資料
高能預警
因爲目前c++也沒學好,Qt也剛開始學,所以本篇博客只適合和我一樣什麼都不會的人。而且很有可能錯誤連篇,期待有人能指正。
目前在一遍分析一遍寫博客,所以會對內容進行不斷地修正
以及非常囉嗦(和廢話)
總體分析
注:純新手一上來就看總體分析極有可能不懂,看看就好,不懂就跳過去看完全文回來再看。
界面轉換
在本例子中,採取建立不同的界面類(mainWidget,開始界面,和GameWidget,遊戲界面),通過接收事件(event,本例是按下按鈕),利用信號和槽,來進行界面切換。
main函數分析
感覺並不需要分析:)
#include "mainWidget.h"
//自己寫的主窗口的頭文件
#include <QApplication>
//一個基礎的類,所有工程(pro文件)都要include。
//有時候include這個文件會報錯,可能是因爲是Qt4的代碼的原因
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
mainWidget w;
w.show();
return a.exec();
}
主窗口分析:mainWidget
頭文件分析
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include "GameWidget.h"
#include <QWidget>
#include <QIcon>
#include <QPalette>
#include <QBrush>
#include <QPixmap>
#include <QPushButton>
#include <QMessageBox>
#include <QLabel>
#include <QFont>
class mainWidget : public QWidget//繼承了QWidget類
{
Q_OBJECT
//只要有槽和信號機制,就要寫Q_OBJECT
public:
mainWidget(QWidget *parent = 0);
~mainWidget();
//void resizeEvent(QResizeEvent *);
private:
QPushButton *startbtn;//一個按鈕,用鼠標點擊後會開始遊戲
QPushButton *exitbtn;//一個按鈕,用鼠標點擊後會退出
GameWidget *g;
QLabel *label;
signals://信號,mainWidget不會發出信號
public slots://可以接收所有信號的公共槽
void exitSlot();//用來接收退出信號的槽
void startSlot();//用來接收開始信號的槽
};
#endif // MAINWIDGET_H
.cpp文件分析
註釋的都是可以在自己的程序中使用的函數
#include "mainWidget.h"
mainWidget::mainWidget(QWidget *parent): QWidget(parent)
{
this->resize(480,270);//resize函數,用來設置mainWidget這個窗口的大小
this->setMaximumSize(480,270);
this->setWindowIcon(QIcon(":/new/prefix1/img/icon.png"));//設置ICON
this->setWindowTitle("貪吃蛇");
QPalette palette;
palette.setBrush(QPalette::Background,QBrush(QPixmap(":/new/prefix1/img/back.jpg").scaled(this->size())));
this->setPalette(palette);
startbtn=new QPushButton(this);
startbtn->setIcon(QIcon(":/new/prefix1/img/start.png"));
startbtn->setIconSize(QSize(75,75));
startbtn->setGeometry(QRect(250,170,75,75));
startbtn->setFlat(true);
exitbtn=new QPushButton(this);
exitbtn->setIcon(QIcon(":/new/prefix1/img/quit.png"));
exitbtn->setIconSize(QSize(70,70));
exitbtn->setGeometry(QRect(350,170,70,70));
exitbtn->setFlat(true);
//設置說明標籤
QFont font;
font.setFamily("Consolas");
font.setBold(true);
font.setPixelSize(13);
label=new QLabel(this);
label->setText("遊戲說明:貪吃蛇遊戲可使用按鈕或者w a s d控制蛇的走動");
label->setFont(font);
label->setGeometry(QRect(10,10,400,50));
connect(exitbtn,SIGNAL(clicked()),this,SLOT(exitSlot()));
connect(startbtn,SIGNAL(clicked()),this,SLOT(startSlot()));
}
mainWidget::~mainWidget()
{
delete startbtn;
delete exitbtn;
}
void mainWidget::exitSlot()
{
if(QMessageBox::question(this,"退出遊戲","是否退出當前遊戲",QMessageBox::Yes|QMessageBox::No)==QMessageBox::Yes)
{
delete this;
exit(0);
}
}
void mainWidget::startSlot()
{
g=new GameWidget(this);
g->show();
}
//重寫resizeEvent
/*void mainWidget::resizeEvent(QResizeEvent *)
{
QPalette palette;
palette.setBrush(QPalette::Background,QBrush(QPixmap("img/back.jpg").scaled(this->size())));
this->setPalette(palette);
startbtn->setGeometry(QRect(this->size().width()-230,this->size().height()-100,75,75));
exitbtn->setGeometry(QRect(this->size().width()-130,this->size().height()-100,70,70));
}*/
QMessageBox的初始化及效果
void mainWidget::exitSlot()
{
if(QMessageBox::question(this,"退出遊戲","是否退出當前遊戲",QMessageBox::Yes|QMessageBox::No)==QMessageBox::Yes)
{
delete this;
exit(0);//直接結束進程
}
//生成一個彈窗。question的初始化爲(鏈接的窗口,"標題","示意文字",QMessageBox::Yes|QMessageBox::No)
}
遊戲窗口分析:GameWidgit
遊戲窗口是這個程序的主體部分,各種複雜的事件(槽和信號機制),狀態的更新,動畫、碰撞檢測都是在這個類(遊戲窗口就是一個繼承了普通窗口< Widget >的派生類)中完成的。
頭文件分析
#ifndef GAMEWIDGET_H
#define GAMEWIDGET_H
#include <QWidget>
//既然繼承自QWidget,肯定需要include相關的頭文件
#include <QPalette>
#include <QIcon>
//這些是爲了讓你在顯示背景圖片和窗口圖標,一般做遊戲(自己設計UI)都要用
#include <QPushButton>
//這個庫在你有鼠標事件的事後非常好用
#include <QPainter>
//如果有動畫,需要包含QPainter庫(如果只是顯示圖片可以用其他方法,但遊戲一般都有動畫)
#include <QDebug>
//主要用於調試代碼,類似於std::cout的替代品,支持QT的數據類型。
#include <QTime>
#include <QTimer>
//和計時有關的庫,做遊戲也一般都要用到
#include <QMessageBox>
//如果你的遊戲有彈出框,就要包含(非必須)
#include <QKeyEvent>
//如果你的遊戲可以用鍵盤操縱,需要包含
#include <QLabel>
//可以用來顯示字(也可以顯示圖片)
#include <QFont>
//如果你要用Qt繪製字(而不是用自己設計的UI)時,需要包含
#include <QSound>
//音頻相關,遊戲一般必須
class GameWidget : public QWidget
{
Q_OBJECT
public:
//構造函數
explicit GameWidget(QWidget *parent = 0);
~GameWidget();
//繪製事件,遊戲中非常重要的函數
void paintEvent(QPaintEvent *);
//鍵盤事件
void keyPressEvent(QKeyEvent *);
private:
//按鈕相當於鼠標事件
QPushButton *upbtn;
QPushButton *leftbtn;
QPushButton *downbtn;
QPushButton *rightbtn;
QPushButton *startbtn;
QPushButton *returnbtn;
//記錄蛇目前運動方向
int direction;
//用來記錄蛇xy座標,可以看出最大能得100分
int snake[100][2];
int snake1[100][2];
//計算吃過了幾個食物
int foodcount;
//計時器!和動畫的幀數也有關
QTimer *timer;
//食物的屬性
int foodx,foody;
int score;
int level;
QLabel *scorelabel;
QLabel *levellabel;
QLabel *scoreshow;
QLabel *levelshow;
QString str1,str2;
QSound *sound;
QSound *sound1;
signals:
//因爲有鍵盤事件(KeyPressEvent),鍵盤事件要發射不同的信號
void UpSignal();
void DownSignal();
void LeftSignal();
void RightSignal();
public slots:
//一般來說有幾個按鈕就要有幾個槽函數
void upbtnSlot();
void leftbtnSlot();
void rightbtnSlot();
void downbtnSlot();
void startbtnSlot();
void returnbtnSlot();
//本例的主函數,和timer結合使用(比較複雜,之後會詳細講)
void timeoutSlot();
};
#endif // GAMEWIDGET_H
.c文件分析
因爲GameWidget略長,且有一些部分和MainWidget用法一致,就不放上來全部代碼(想要自己運行的可以從之前附上的鏈接下載,記得只適用於QT5)
注: 需要include相應的庫文件
1.如何讓你的遊戲發出聲音
首先要載入音頻
//聲音區:載入音頻,注意音頻文件要加入.qrc文件中
sound=new QSound(":/listen/img/5611.wav");
sound1=new QSound(":/listen/img/die.wav");
在什麼時候播放聲音,使用if語句判斷,一般是一個可以update()的槽函數中(本例爲timeoutSlot(),見下文)
sound->play();
2.如何即時顯示得分
str1=QString::number(score);//轉化爲字符串
scoreshow=new QLabel(this);//用Label輸出
scoreshow->setFont(font);
scoreshow->setGeometry(QRect(385,1,60,30));
scoreshow->setText(str1);
3.如何設置交互(信號和槽機制)
//設置按鈕操作
connect(leftbtn,SIGNAL(clicked()),this,SLOT(leftbtnSlot()));
connect(rightbtn,SIGNAL(clicked()),this,SLOT(rightbtnSlot()));
connect(upbtn,SIGNAL(clicked()),this,SLOT(upbtnSlot()));
connect(downbtn,SIGNAL(clicked()),this,SLOT(downbtnSlot()));
connect(startbtn,SIGNAL(clicked()),this,SLOT(startbtnSlot()));
connect(returnbtn,SIGNAL(clicked()),this,SLOT(returnbtnSlot()));
//設置鍵盤操作
connect(this,SIGNAL(UpSignal()),upbtn,SLOT(click()));
connect(this,SIGNAL(DownSignal()),downbtn,SLOT(click()));
connect(this,SIGNAL(LeftSignal()),leftbtn,SLOT(click()));
connect(this,SIGNAL(RightSignal()),rightbtn,SLOT(click()));
connect的使用
connect(發射信號的類,SIGNAL(信號函數),接收信號的類,SLOT(槽函數));
按鈕操作的實現
QPushButton這個類中有click()的信號函數和槽函數,如果發生了鼠標點擊事件,被點擊的Button就發射click()信號,因爲我們用connect將這個信號和GameWidget中的槽函數(比如leftbtnSlot())連接起來,GameWidget就能接收到這個信號並且調用相應的槽函數。
鍵盤操作同理,因爲KeyPressEvent是我們自己寫在GameWidget類中,所以爲 ‘this’ 指針。這次connect是把鍵盤事件和按鈕聯繫了起來。
4.如何設置隨機數
QTime t;
t= QTime::currentTime();
qsrand(t.msec()+t.second()*1000);
5.如何update程序
timer=new QTimer(this);
timer->setInterval(500);//設置時間間隔
connect(timer,SIGNAL(timeout()),this,SLOT(timeoutSlot()));
該程序設置了一個timer(一個定時觸發器,參考這裏),並且設置了時間間隔,每過一次時間間隔發送一個timeout()信號。
而GameWidget類中定義了timeoutSlot()槽函數,每過一定時間間隔被調用,執行update()操作,來更新界面狀態,下文會講
6.(重點)timeoutSlot分析
因爲這一段很長,也只寫出代碼結構(需要完整代碼見開頭鏈接)
void GameWidget::timeoutSlot()
{
//判斷是否吃到食物的代碼塊(判斷蛇的座標和食物的座標是否重合)
//其中包含,發出聲音、更新食物座標(隨機更新,食物不能出現在蛇身上)、如果分數到達一定的檔次,就提升遊戲等級(改變時間間隔)
memcpy(snake1,snake,sizeof(snake));//不能直接對目前顯示的數組進行操作,因此一開始就定義了兩個數組存放當前狀態和改變後的狀態,用memcpy拷貝
//實現蛇的遊動
for(int i=foodcount;i>=1;i--)
{
snake[i][0]=snake[i-1][0];
snake[i][1]=snake[i-1][1];
}
switch(direction)
{
case UP:snake[0][1]--;break;
case DOWN:snake[0][1]++;break;
case LEFT:snake[0][0]--;break;
case RIGHT:snake[0][0]++;break;
}
//判斷蛇是否撞到自身的代碼塊,如果撞到,顯示遊戲結束的彈窗,並重置遊戲
//判斷蛇是否撞到牆體的代碼塊
//最關鍵,調用update()函數,更新窗口狀態
this->update();
}
memcpy輔助理解
update輔助理解
其中的碰撞檢測機制是通過判斷xy座標是否相同來進行的。
Qt工具書上似乎有其他碰撞檢測的辦法,挖個坑
7.(重點)paintEvent分析
函數聲明
void GameWidget::paintEvent(QPaintEvent *);
要定義一個繪製器painter
QPainter painter(this);
畫一個小方格(長方形)的代碼(該遊戲畫了很多很多小方格),只摘取一個:j,i是座標,20, 20是方格的大小
painter.drawRect(j,i,20,20);
讀取素材繪製
foodx要乘20的原因是這是相對座標,而不是絕對座標
painter.drawImage(foodx*20,foody*20,QImage(":/new/prefix1/img/apple.png").scaled(QSize(20,20)));
該遊戲主要用到的就是這兩種繪製方式、當然還有更多的繪製方式:參考這裏
今日學習獲得的一些經驗
- Qt4和Qt5雖然用法差別不大,但是Qt4的代碼在Qt5上很難跑通。Qt4的遊戲源代碼值得參考,但要自己嘗試運行,還是去找Qt5的源代碼吧。
- Qt的各種參考書和博客中給的代碼多是模板,不要直接放在Qt中運行
- Qt中讀取資源(圖片、音頻etc)都要把文件加入qrc文件中以獲取相對路徑
吐槽:根本不是今日獲得的經驗,從上週開始寫一直拖到現在orz