游戏玩法介绍:
游戏设置关卡二十关,通过选关界面可以选择进入到对应的关卡中。 进入对应关卡之后,点击任意金币,可以使该硬币以及周边(上,下,左, 右)金边翻转。如果硬币都翻转为金币,则游戏胜利。
游戏界面设置:
开始界面:
开始场景中需要自定义一个按钮,点击开始按钮可以实现一个动图弹跳的显示效果。
同时弹出选关窗口,关闭当前窗口。
对于我这个新手来说,这一部分实现的难点是按钮的弹跳效果的实现。
对应代码:
#include "mainScene.h"
#include "mybutton.h"
#include <QPainter>
#include <QPixmap>
#include <QDebug>
#include <QTimer>
#include <QSound>
MainScene::MainScene(QWidget *parent)
: QMainWindow(parent)
{
this->setFixedSize(320, 588);
menu_bar = this->menuBar();
startMenu = new QMenu("开始");
menu_bar->addMenu(startMenu);
exitAction = new QAction("退出");
startMenu->addAction(exitAction);
this->setWindowIcon(QPixmap(":/res/image/Coin0001.png"));
this->setWindowTitle("翻金币");
MyButton * btn = new MyButton(":/res/image/MenuSceneStartButton.png");
btn->setParent(this);
btn->move(this->width()*0.5-btn->width()*0.5, this->height()*0.7);
secondScene = new selectScene();
connect(exitAction, &QAction::triggered, [=](){
this->close();
});
connect(btn, &MyButton::clicked, [=](){
QSound::play(":/res/voice/TapButtonSound.wav");
btn->sink();
btn->rise();
QTimer::singleShot(300, this, [=](){
this->hide();
secondScene->show();
secondScene->setGeometry(this->geometry());
});
});
connect(secondScene, &selectScene::backSignanl, [=](){
QSound::play(":/res/voice/TapButtonSound.wav");
QTimer::singleShot(300, this, [=](){
this->show();
setGeometry(secondScene->geometry());
secondScene->hide();
});
});
}
MainScene::~MainScene()
{
}
void MainScene::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load(":/res/image/MenuSceneBg.png");
painter.drawPixmap(0, 0, this->width(), this->height(), pix);
pix.load(":/res/image/Title.png");
pix = pix.scaled(pix.width()*0.5, pix.height()*0.5);
painter.drawPixmap(30, 30, pix);
}
在初始界面需要构建一个自定义按钮,用于开始按钮,关卡按钮和返回按钮。
开始按钮需要增加弹跳效果,按钮类中使用QPropertyAnimation增加动态效果。
返回按钮则需要点击时切换图片状态,通过重写按钮按下,释放时间实现。
代码实现:
#include "mybutton.h"
#include <QPainter>
#include <QtDebug>
#include <QPropertyAnimation>
#include <QMouseEvent>
MyButton::MyButton(QString orignIcon, QString switchIcon)
{
this->myOrignIcon = orignIcon;
this->mySwitchIcon = switchIcon;
QPixmap *pix = new QPixmap();
bool res = pix->load(myOrignIcon);
if(!res)
{
qDebug() << QString("路径错误%1").arg(orignIcon) << endl;
}
this->setFixedSize(pix->width(), pix->height());
this->setStyleSheet("QPushButton{border:0px}");
this->setIcon(QIcon(orignIcon));
this->setIconSize(QSize(pix->width(), pix->height()));
}
void MyButton::rise()
{
QPropertyAnimation *animation = new QPropertyAnimation(this, "geometry");
animation->setDuration(200);
animation->setStartValue(QRect(QRect(this->x(), this->y()+10, this->width(),this->height())));
animation->setEndValue(QRect(QRect(this->x(), this->y(), this->width(),this->height())));
animation->setEasingCurve(QEasingCurve::OutBounce);
animation->start();
}
void MyButton::sink()
{
QPropertyAnimation *animation = new QPropertyAnimation(this, "geometry");
animation->setDuration(200);
animation->setStartValue(QRect(this->x(), this->y(), this->width(),this->height()));
animation->setEndValue(QRect(this->x(), this->y()+10, this->width(),this->height()));
animation->setEasingCurve(QEasingCurve::OutBounce);
animation->start();
}
void MyButton::mousePressEvent(QMouseEvent *e)
{
if(mySwitchIcon != "")
{
QPixmap *pix = new QPixmap;
bool res = pix->load(mySwitchIcon);
if(!res)
{
qDebug() << QString("路径错误%1").arg(mySwitchIcon) << endl;
}
this->setFixedSize(pix->width(), pix->height());
this->setStyleSheet("QPushButton{border:0px}");
this->setIcon(QIcon(mySwitchIcon));
this->setIconSize(QSize(pix->width(), pix->height()));
}
QPushButton::mousePressEvent(e);
}
void MyButton::mouseReleaseEvent(QMouseEvent *e)
{
if(myOrignIcon != "")
{
QPixmap *pix = new QPixmap;
bool res = pix->load(myOrignIcon);
if(!res)
{
qDebug() << QString("路径错误%1").arg(myOrignIcon) << endl;
}
this->setFixedSize(pix->width(), pix->height());
this->setStyleSheet("QPushButton{border:0px}");
this->setIcon(QIcon(myOrignIcon));
this->setIconSize(QSize(pix->width(), pix->height()));
}
QPushButton::mouseReleaseEvent(e);
}
选关界面:
选关场景的页面设计是比较简单的,使用自定义按钮进行排列。
对应选关按钮需要创建一个游戏场景对象。需要注意的是,在关闭对应关卡的同时也要将创建的游戏场景对象进行删除。
代码实现:
#include "selectscene.h"
#include <QDebug>
#include <QTimer>
#include <QSound>
selectScene::selectScene(QWidget *parent) : QMainWindow(parent)
{
this->setFixedSize(320, 588);
QMenuBar *menu_bar = this->menuBar();
QMenu *start_menu = menu_bar->addMenu("开始");
QAction *exit_action = start_menu->addAction("退出");
this->setWindowIcon(QPixmap(":/res/image/Coin0001.png"));
this->setWindowTitle("翻金币");
backButton = new MyButton(":/res/image/BackButton.png", ":/res/image/BackButtonSelected.png");
backButton->setParent(this);
backButton->move(this->width() - backButton->width(), this->height() - backButton->height());
playScene = nullptr;
//增加选关按钮
for(int i=0; i<20; i++)
{
selectButton[i] = new MyButton(":/res/image/LevelIcon.png");
selectButton[i]->setParent(this);
selectButton[i]->move(25+(i%4)*70, 160+(i/4)*70);
numLabel[i] = new QLabel(QString::number(i+1), this);
numLabel[i]->move(25+(i%4)*70, 160+(i/4)*70);
numLabel[i]->setFixedSize(selectButton[i]->width(), selectButton[i]->height());
numLabel[i]->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
numLabel[i]->setAttribute(Qt::WA_TransparentForMouseEvents);
connect(selectButton[i], &MyButton::clicked, [=](){
QSound::play(":/res/voice/TapButtonSound.wav");
playScene = new PlayScene(i+1);
playScene->show();
playScene->setGeometry(geometry());
this->hide();
connect(playScene, &PlayScene::backSignanl, [=](){
QTimer::singleShot(300, this, [=](){
playScene->hide();
this->show();
setGeometry(playScene->geometry());
delete playScene;
playScene = nullptr;
});
});
});
}
connect(exit_action, &QAction::triggered, [=](){
this->close();
});
connect(backButton, &QPushButton::pressed, [=](){
QSound::play(":/res/voice/BackButtonSound.wav");
emit backSignanl();
});
}
void selectScene::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
QPixmap pix;
pix.load(":/res/image/OtherSceneBg.png");
painter.drawPixmap(0, 0, this->width(), this->height(), pix);
pix.load(":/res/image/Title.png");
painter.drawPixmap((this->width() - pix.width())*0.5, 30, pix);
}
游戏界面:
第一关为例
页面设计同样比较简单,排列金币按钮阵列形式摆放。灰色背景是由QLabel生成的。
功能实现部分,首先需要新定义一个金币类的按钮,点击金币之后可以实现一个(金转银/银转金)翻转效果。通过标志+定时器切换图片实现翻转。
当金币按钮翻转时,禁用掉按钮,防止按钮翻转未完成时进行多次翻转,通过定义标志,重写按钮按下事件就可以实现。
如果游戏胜利,同样需要禁用按钮的功能,通过定义新标志实现。
自定义按钮类的代码实现:
#include "coinbutton.h"
#include <QPainter>
#include <QDebug>
CoinButton::CoinButton(QString img, QWidget *parent)
:QPushButton(parent)
{
myIcon = img;
QPixmap pix;
bool res = pix.load(myIcon);
if(!res)
{
qDebug() << QString("路径错误:%1").arg(myIcon) << endl;
return;
}
this->setFixedSize(pix.width(), pix.height());
this->setStyleSheet("QPushButton{border:0px}");
this->setIcon(QIcon(pix));
this->setIconSize(QSize(pix.width(), pix.height()));
timer = new QTimer;
connect(timer, &QTimer::timeout, [=](){
QPixmap pix;
QString img;
if(position.flag){
img = ":/res/image/Coin000"+QString::number(currentValue++)+".png";
}
else{
img = ":/res/image/Coin000"+QString::number(currentValue--)+".png";
}
bool res = pix.load(img);
if(!res)
{
qDebug() << QString("加载图片1%失败").arg(img) << img << endl;
return ;
}
this->setFixedSize(pix.width(), pix.height());
this->setStyleSheet("QPushButton{border:0px}");
this->setIcon(QIcon(pix));
this->setIconSize(QSize(pix.width(), pix.height()));
if(currentValue < MINVALUE || currentValue > MAXVALUE)
{
position.flag = !position.flag;
timer->stop();
isAnimation = false;
}
});
}
void CoinButton::mousePressEvent(QMouseEvent *e)
{
if(isAnimation || isEnd)
return;
else
QPushButton::mousePressEvent(e);
}
void CoinButton::stateChanged()
{
timer->start(30);
isAnimation = true;
if(position.flag)
{
currentValue = MINVALUE;
}
else
{
currentValue = MAXVALUE;
}
isGold = !isGold;
}
在主游戏界面内,由于不同的关卡内,金币银币翻转布局初始界面是不同的,金币银币的状态设置由另一个专门存储金币状态的数据文件进行保存的。
每一关卡的数据都保存在一个二维4*4数组中,游戏所需要的数据 由20个这样的数据保存,在初始关卡时根据关卡调出对应数组的数据并进行初始化金币。所以数据文件中保存数据 类型为 QMap<int, QVector< QVector>>。
根据游戏的规则,需要重新定义一个按钮数组,用于标记按钮坐标位置,当一个金币翻转,用来判断周围的金币是否可以同时进行翻转。金币翻转的状态(金币?银币)由状态标志判断和更新保存。在完成一次翻转之后判断游戏是否胜利同样由标志位判断。
还有其他的特效小细节设置,由于比较简单,并不进行总结了。
代码实现如下:
#include "playscene.h"
#include <QPainter>
#include <QDebug>
#include <QTimer>
#include <QTime>
#include <QLabel>
#include <QSound>
#include <QPropertyAnimation>
PlayScene::PlayScene(int level, QWidget *parent) : QMainWindow(parent)
{
myLevel = level;
this->setFixedSize(320, 588);
this->setWindowIcon(QPixmap(":/res/image/Coin0001.png"));
QString title = QString("第%1关").arg(myLevel);
this->setWindowTitle(title);
backButton = new MyButton(":/res/image/BackButton.png", ":/res/image/BackButtonSelected.png");
backButton->setParent(this);
backButton->move(this->width() - backButton->width(), this->height() - backButton->height());
QLabel *winLabel = new QLabel(this);
QPixmap successPix;
successPix.load(":/res/image/LevelCompletedDialogBg.png");
winLabel->setPixmap(successPix);
winLabel->setGeometry(0.5*(this->width()-successPix.width()), 0-successPix.height(),successPix.width(), successPix.height());
DataConfig gameData;
for(int i=0; i<4; i++)
{
for(int j=0; j<4; j++)
{
myData[i][j] = gameData.mData[myLevel][i][j];
}
}
connect(backButton, &QPushButton::pressed, [=](){
QSound::play(":/res/voice/BackButtonSound.wav");
emit backSignanl();
});
QPixmap pix;
pix.load(":/res/image/BoardNode.png");
isWin = true;
for(int i=0; i<4; i++)
{
for(int j=0; j<4; j++)
{
QLabel *levelLabel = new QLabel(this);
levelLabel->setFixedSize(pix.width(), pix.height());
levelLabel->setPixmap(pix);
levelLabel->move(57 + i*50, 200 + 50 *j);
QString image;
if (myData[i][j])
image = ":/res/image/Coin0001.png";
else
image = ":/res/image/Coin0008.png";
CoinButton *coinButton = new CoinButton(image, this);
myCoin[i][j] = coinButton;
coinButton->move(59 + i*50, 204 + 50 *j);
coinButton->position.flag = (myData[i][j] == 1)?true:false;
coinButton->isGold = (myData[i][j] == 1)?true:false;
coinButton->isEnd = false;
coinButton->position.posX = i;
coinButton->position.posY = j;
connect(coinButton, &CoinButton::clicked,[=](){
QSound::play(":/res/voice/ConFlipSound.wav");
for(int i=0; i<4; i++)
{
for(int j=0; j<4; j++)
{
myCoin[i][j]->isEnd = true;
}
}
coinButton->stateChanged();
QTimer::singleShot(300,[=](){
if(coinButton->position.posX+1 < 4)
{
myCoin[i+1][j]->stateChanged();
}
if(coinButton->position.posX-1 >= 0)
{
myCoin[i-1][j]->stateChanged();
}
if(coinButton->position.posY+1 < 4)
{
myCoin[i][j+1]->stateChanged();
}
if(coinButton->position.posY-1 >= 0)
{
myCoin[i][j-1]->stateChanged();
}
isWin = true;
for(int i=0; i<4; i++)
{
for(int j=0; j<4; j++)
{
if(!myCoin[i][j]->isGold)
{
isWin = false;
}
}
}
if(!isWin)
{
for(int i=0; i<4; i++)
{
for(int j=0; j<4; j++)
{
myCoin[i][j]->isEnd = false;
}
}
}
else{
QSound::play(":/res/voice/LevelWinSound.wav");
QPropertyAnimation *animation = new QPropertyAnimation(winLabel, "geometry");
animation->setDuration(500);
animation->setStartValue(QRect(0.5*(this->width()-successPix.width()), 0-successPix.height(),successPix.width(), successPix.height()));
animation->setEndValue(QRect(0.5*(this->width()-successPix.width()), successPix.height()+20,successPix.width(), successPix.height()));
animation->setEasingCurve(QEasingCurve::OutBounce);
animation->start();
}
});
});
}
}
showLabel = new QLabel(this);
QFont font("Helvetica [Cronyx]", 20, QFont::Bold);
showLabel->setFont(font);
showLabel->setText(QString("level:%1").arg(myLevel));
showLabel->setGeometry(0, this->height()-50, this->width(), 50);
showLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
}
void PlayScene::paintEvent(QPaintEvent* e)
{
QPainter painter(this);
QPixmap pix;
pix.load(":/res/image/PlayLevelSceneBg.png");
painter.drawPixmap(0, 0, this->width(), this->height(), pix);
pix.load(":/res/image/Title.png");
painter.drawPixmap((this->width() - pix.width())*0.5, 30, pix);
}
全部代码以及相关音频和图片资源有需要的,后期会进行上传。