目录
前言
这是基于QT开发一款推箱子小游戏,游戏规则大致是利用方向控制键(W:上、 S:下、 A :左、D:右)将箱子推进对应的洞坑中,将所有的箱子推进去了,游戏就结束了。在游戏整个过程中,人物不能穿过障碍物,不能拉箱子等操作。
一、开发准备
对于这款游戏,大致需要有以下部分:地图,角色,主控制对象。其中地图元素包括路、障碍物等;角色元素包括了角色的位置,方向等;控制对象负责整个游戏控制工作,如控制角色移动、地图和角色元素绘制地点等。对于一款游戏而言,我们先需要了解我们对于整个项目组成的各个部分,先将对应对象或者元素做出,在结合整个调试。
二、实现过程
1.地图
首先地图元素里面以下部分:路、障碍物、箱子、洞、进洞。这里可以用一个枚举变量表示这些元素。
enum MapElement
{
Road,
Wall,
Box,
Point,
InPoint,
};
地图初始化时,我们需要读取每一个关的地图所包含的元素,这时候我就需要地图读取资源函数,将信息读出来,储存在一个二维数组当中(这里选取二维数组原因是:利用二维数组很好表示一个2D地图元素的横坐标和纵坐标),这时我们可以根据读取文件或者二维数组的内容(这里我们直接用定义全局变量的二维数组定义地图资源,也可以在项目下利用文件定义),显示不同关卡的地图样式,从而设定不同难度级别。
//读取地图资源
bool Map::ReadMap(void)
{
for(int i = 0;i < map_Row;i++)
{
for(int j = 0;j < map_Col;j++)//遍历列
{
map_Map[i][j] = map_Grade1[i][j];//这里可以根据关卡不同,设定不同二维数组
}
}
return true;
}
将地图资源读取完后,我们就需要对所有的地图元素进行绘制,利用不同图片将路,路、障碍物等绘出,这里是利用QT图形绘制功能,将地图各种图片对象绘制出来,这里需要注意几点:1、如果你的地图中各元素对象图片不一样大小,这时就需要对坐标进行一些处理,下面代码就有体现了这一点;2 可以对绘制图片进行优化,我这里在地图开始绘制前就把路给铺好,在下面遍历循环时,就不需要绘制路的图片,还可以利用新旧是否变换,进行是否需要重绘。
void Map::DrawMap(QPainter* mPainter)
{
//全部铺路
for(int i = 0;i < map_Row;i++)//行
{
for(int j = 0;j < map_Col;j++)//列
{
QString imgUrl = ":/new/prefix1/images/block.gif";
mPainter->drawImage(35*j,35*i,QImage(imgUrl));
}
}
for(int i = 0;i < map_Row;i++)//行
{
for(int j = 0;j < map_Col;j++)//列
{
// qDebug()<<mMap->map_Map[i][j];
QString imgUrl;
switch (map_Map[i][j])
{
case Map::Road:
imgUrl = ":/new/prefix1/images/block.gif";
break;
case Map::Wall:
imgUrl = ":/new/prefix1/images/wall.png";
break;
case Map::Box:
imgUrl = ":/new/prefix1/images/box.png";
break;
case Map::Point:
imgUrl = ":/new/prefix1/images/ball.png";
break;
case Map::InPoint:
imgUrl = ":/new/prefix1/images/box.png";
break;
}
int x = QImage(imgUrl).width() < 35 ? 35*j+2:35*j;
int y = QImage(imgUrl).height()>35 ? 35*i-(QImage(imgUrl).height() - 35):35*i;
if (imgUrl != ":/new/prefix1/images/block.gif")//如果不是路就绘画
{
mPainter->drawImage(x,y,QImage(imgUrl));
}
}
}
}
2.读入数据
角色包括了角色的移动和绘制,以及角色的绘制。我这里是将角色图片分为四张图片,上、下、左、右,这样就可以显示对应角色方向时,呈现给我们一种视觉上位置变换效果,这里代码比较简单,直接放出整段代码。
#include "role.h"
Role::Role(QObject *parent) : QObject(parent)
{
R_Row = 8;
R_Col = 8;
R_Dir = Down;
}
void Role::DrawRole(QPainter* mPainter,int Dir) //绘制角色
{
//确地角色的显示方位
QString imgUrl;
switch (Dir)
{
case Up:
imgUrl = ":/new/prefix1/images/up.png";
break;
case Down:
imgUrl = ":/new/prefix1/images/down.png";
break;
case Left:
imgUrl = ":/new/prefix1/images/left.png";
break;
case Right:
imgUrl = ":/new/prefix1/images/right.png";
break;
}
int x = 35*R_Col - (QImage(imgUrl).width() - 35)/2;
int y = 35*R_Row - (QImage(imgUrl).height() - 35);
mPainter->drawImage(x,y,QImage(imgUrl));
}
//角色移动
void Role::MoveRole(int D_Row,int D_Col)
{
R_Row += D_Row;//横坐标
R_Col += D_Col;//纵坐标
}
#ifndef ROLE_H
#define ROLE_H
#include <QObject>
#include <QObject>
#include <QPoint>
#include <QPainter>
class Role : public QObject
{
Q_OBJECT
public:
explicit Role(QObject *parent = nullptr);
enum
{
Up,
Down,
Left,
Right,
};
int R_Row;
int R_Col;
int R_Dir;
void DrawRole(QPainter* mPainter,int Dir); //绘制角色
void MoveRole(int D_Row,int D_Col);//角色移动
signals:
};
#endif // ROLE_H
3.主控制对象
主控制对象则是控制整个代码的运作,创建对象,控制规则等,这里采用了 Widget作为空窗口。首先初始化地图元素和人物元素。
//初始化地图元素
mMap = new Map(this);//创建一个游戏地图对象
mMap->ReadMap();
mMap->map_pos = QPoint(300,400);
//初始化人物元素
mRole = new Role();
创建画家和定时器对象,画家负责整个资源的绘制过程,定时器则负责定时更新绘制间隔和判断胜利的时间间隔。
mPainter = new QPainter(this);//创建画家
mTimer = new QTimer(this);
mTimer->start(10);
connect(mTimer,&QTimer::timeout,[this](){this->update();});
mTimer1 = new QTimer(this);
mTimer1->start(1000);
connect(mTimer1,&QTimer::timeout,[this](){this->IsWin();});
推箱子逻辑是下面代码,就是判断人物前方是否有墙等情况,用层if else语句列举出来各种情况,从而改变对应地图元素。
//碰撞函数
void Widget::IsCollide(int _dRow,int _dCol)
{
int newRow = mRole->R_Row + _dRow;
int newCol = mRole->R_Col + _dCol;
if (mMap->map_Map[newRow][newCol] == 1)//判断人物前方是否墙
{
mRole->MoveRole(0,0);
}
else if (mMap->map_Map[newRow][newCol] == 2)//判断前方是否箱子
{
if (mMap->map_Map[newRow+_dRow][newCol+_dCol] == 0)//判断箱子前方是否路
{
mMap->map_Map[newRow][newCol] = 0;//把箱子变成路元素
mMap->map_Map[newRow+_dRow][newCol+_dCol] = 2;//把路变成箱子元素
mRole->MoveRole(_dRow,_dCol);
}
else if (mMap->map_Map[newRow+_dRow][newCol+_dCol] == 3)//判断箱子前方是否洞
{
mMap->map_Map[newRow][newCol] = 0;//把箱子变成路元素
mMap->map_Map[newRow+_dRow][newCol+_dCol] = 4;//把洞变成进洞元素
mRole->MoveRole(_dRow,_dCol);
}
}
else if (mMap->map_Map[newRow][newCol] == 4) //判断前方是否进洞元素
{
if (mMap->map_Map[newRow+_dRow][newCol+_dCol] == 0)//判断进洞前方是否路
{
mMap->map_Map[newRow][newCol] = 3;//把进洞变成洞元素
mMap->map_Map[newRow+_dRow][newCol+_dCol] = 2;//把路变成箱子元素
mRole->MoveRole(_dRow,_dCol);
}
}
else//否则进行角色移动
{
mRole->MoveRole(_dRow,_dCol);
}
}
判断是否胜利规则,则是用了扫描整个二维数组是否有箱子元素,如果没有则为胜利,反之则还未结束。
//判断是否胜利
void Widget::IsWin(void)
{
int temp = 0;
for(int i = 0;i < mMap->map_Row;i++)
{
for (int j = 0; j < mMap->map_Col; j++)
{
if (mMap->map_Map[i][j] == 3)//如果有洞元素。怎证明游戏还没有结束
{
temp++;
}
}
}
if (temp == 0)//游戏结束
{
// 创建一个消息框
QMessageBox msgBox;
msgBox.setWindowTitle("恭喜过关");
msgBox.setText("请选择选项");
// 添加确定和取消按钮
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
// 设置中文文本
msgBox.setButtonText(QMessageBox::Ok,"下一关!");
msgBox.setButtonText(QMessageBox::Cancel,"重玩本关");
// 显示消息框并获取按钮点击结果
int ret = msgBox.exec();
if (ret == QMessageBox::Ok)
{
// 点击了确定按钮
// 执行相应的操作
// return;
}
else if (ret == QMessageBox::Cancel) {
// 点击了取消按钮
// 执行相应的操作
// return;
}
}
else//游戏继续
{
}
}
总结
回看整个项目实现过程,其实难度不大,但一些想法和实现筹备是有一定门口的,所有这就凸显出教程的意义,可以给你灵感,快速上手一个项目。回到项目本身,贴出的代码还有没有实现关卡的级别选择,这时可以创建一个三维数组,储存2D地图二维数组,还有一些细节上可以处理。(本项目用到的资源和代码都在链接里,有需要可以下载,如何觉得还不错的话,记得点个赞噢)资料https://download.csdn.net/download/angelxiaolei/88584631?spm=1001.2014.3001.5503