QT实现简单的2d游戏地图编辑器
我写的这个地图编辑器主要实现了用键盘配合鼠标点击屏幕,间接生成一个二维数组,再将这个二维数组写入到文件中的操作,以直观、方便地对二维数组进行更改,以用作2d游戏的地图数组。
该程序最多向数组中添加19种(0-18)不同的数据。
运行效果:
创建初始地图界面:
地图编辑界面:
将地图数据写入到文件后地图信息界面:
除基本功能外,在程序中还实现了在按下移动鼠标左键时添加数据、按下移动鼠标右键时删除数据、自定义地图大小、使用文件对话框选择要保存的文件路径、将地图文件另存为、查看当前地图信息的功能。
虽然这些功能看似很多,且拥有这些功能的编辑器已经可以将地图数据用在2d小游戏的地图上,但我知道,这些距离一个专业、商业的2d编辑器还有着很远的差距。即使将比较对象换做“成熟好用功能齐全的2d编辑器”,该程序依然差了很多。
比如可以缩放编辑界面大小功能、自定义数值对应自定义颜色功能、添加数值功能、添加图片代表数值功能、添加背景图功能、选区设置数值功能等,这些功能的实现都是很复杂的,也是现在的我没有办法做到的。
编译环境:
Windows Qt 5.9.0 Qt Creator 4.3.0
思路:
将鼠标坐标分为地图宽高相乘个格子的区域,结构数组存储所有格子的格子坐标和对应的数值的信息,鼠标在点击时,将该区域格子在结构数组中的信息根据当前所选键值改变,并将数组中的信息按照颜色对应关系绘制矩形和数字到界面上。
使用键盘的0-9键代表0-9的地图数组中的数值,a-i代表10到18的地图数组中的数值,鼠标右键代表预设的地图默认数值。
下面展示程序的主要代码,如果你想查看项目的所有代码,可以私信博主发给你。
代码:
MainWindow.h 创建初始地图界面类头文件:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
MainWindow.cpp 创建初始地图界面类函数实现:
#include "MainWindow.h"
#include "ui_widget.h"
#include <QPushButton>
#include "mainfrom.h"
#include <QMouseEvent> //鼠标事件
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//设置固定窗口大小
setFixedSize(900,600);
//设置标题
setWindowTitle("2d地图编辑器");
//监听确定按钮点击
connect(ui->pushButton,&QPushButton::clicked,[=](){
this->close(); //当前窗口关闭
//将文本框中的内容转换为数字传入新窗口对象构造函数中
MainFrom *from = new MainFrom(ui->lineEdit->text().toInt(), ui->lineEdit_2->text().toInt(), ui->lineEdit_3->text().toInt());
from->show(); //打开新窗口界面
});
}
Widget::~Widget()
{
delete ui;
}
mainfrom.h 地图编辑界面类头文件:
#ifndef MAINFROM_H
#define MAINFROM_H
#include <QMainWindow>
#include <QPainter> //画家类
#include <QLabel> //标签
#include <QMouseEvent> //鼠标事件
#include <Qvector> //动态数组
class MainFrom : public QMainWindow
{
Q_OBJECT
private:
//绘图事件
void paintEvent(QPaintEvent *);
//鼠标按下事件
void mousePressEvent(QMouseEvent *event);
//鼠标移动事件
void mouseMoveEvent(QMouseEvent *event);
//键盘事件
void keyPressEvent(QKeyEvent *ev);
int MX = 0, MY = 0; //最大宽高
//创建标签控件
QLabel *lab = new QLabel(this);
int mi = 0, mj = 0; //用于循环和确定地图坐标
int key = 0; //用于控制绘制的物体的属性的键值
struct DataAttr //数据值和颜色结构
{
int DKey; //键值
Qt::GlobalColor colorenum; //颜色枚举值
};
//记录所有数据的对象的初始临时数组
//(键值对应数字对应颜色表)
const DataAttr TempDataArr[19] =
{
{0,Qt::darkCyan},{1,Qt::darkMagenta},{2,Qt::black},{3,Qt::darkGray},{4,Qt::gray},
{5,Qt::lightGray},{6,Qt::red},{7,Qt::green},{8,Qt::blue},{9,Qt::cyan},
{10,Qt::magenta},{11,Qt::yellow},{12,Qt::darkRed},{13,Qt::darkGreen},{14,Qt::darkBlue},
{15,Qt::darkCyan},{16,Qt::darkMagenta},{17,Qt::darkYellow},{18,Qt::darkYellow}
};
// QVector<DataAttr> datacolorvec; //颜色对照数据动态数组
DataAttr CurrSeleData = TempDataArr[0]; //记录当前选中的颜色对照结构
struct KeyData //qtkey字符对应结构
{
Qt::Key qtkey;
char ckey;
};
//Qt::key对应字符 数组
const KeyData keyarr[19] =
{
{Qt::Key_0,'0'},{Qt::Key_1,'1'},{Qt::Key_2,'2'},{Qt::Key_3,'3'},{Qt::Key_4,'4'},
{Qt::Key_5,'5'},{Qt::Key_6,'6'},{Qt::Key_7,'7'},{Qt::Key_8,'8'},{Qt::Key_9,'9'},
{Qt::Key_A,'a'},{Qt::Key_B,'b'},{Qt::Key_C,'c'},{Qt::Key_D,'d'},{Qt::Key_E,'e'},
{Qt::Key_F,'f'},{Qt::Key_G,'g'},{Qt::Key_H,'h'},{Qt::Key_I,'i'}
};
int **filemapdataarr = NULL; //存储地图数组数据主要数组
bool filesave = false; //判断文件是否是第一次执行保存操作 false代表还未保存过
QString path = "C:/Users/ASUS/Desktop"; //记录默认保存的文件路径
const char DiaOpenDefa[30] = "./"; //文件对话框默认打开路径
//保存文件函数
void CSaveFileF();
int MapDefault = -1; //记录地图默认值
public:
// explicit MainFrom(QWidget *parent = nullptr);
//构造参数:地图宽高、地图默认值
MainFrom(int MaxWidth, int MaxHeight, int MapDefault);
signals:
public slots:
};
#endif // MAINFROM_H
mainfrom.cpp 地图编辑界面类函数实现:
#include "mainfrom.h"
#include <QMessageBox> //弹出对话框
#include <QCoreApplication> //退出应用程序
#include <QMenuBar> //菜单栏
#include <QFile> //文件读写
#include <QFileDialog> //文件对话框
#include "instructionsuse.h"//使用说明窗口类
#include "keydescription.h" //键位说明窗口类
#include <QMessageBox> //消息对话框
#include <QDebug>
MainFrom::MainFrom(int MaxWidth, int MaxHeight, int MapDefault)
{
this->MapDefault = MapDefault; //将传进来的地图默认值赋给类中的变量,以便于在其他函数使用
if(!MaxWidth || !MaxHeight) //如果两个值都是0则return
{
QMessageBox::critical(this, "错误", "输入错误"); //弹出错误对话框
QCoreApplication::exit(0); //退出应用程序
}
//记录参数
MX = MaxWidth, MY = MaxHeight;
filemapdataarr = new int* [MY]; //数组开辟第二维空间
for(int i = 0; i < MY; i++) //数组开辟第二维空间
filemapdataarr[i] = new int[MX];
//为地图数组赋初值
for(int j = 0 ; j < MY ; j++)
for(int i = 0; i < MX; i++)
filemapdataarr[j][i] = MapDefault;
//根据参数固定窗口大小
setFixedSize(MaxWidth << 5, (MaxHeight << 5) + 32 + 60); //地图高 +菜单栏范围 +提示信息范围
//设置标题
setWindowTitle("编辑器操作界面");
//创建菜单栏
QMenuBar *bar = menuBar();
//将菜单放入到窗口中
setMenuBar(bar);
//设置菜单栏字体属性
bar->setFont(QFont("微软雅黑", 10));
//创建菜单
QMenu* saveMenu = bar->addMenu("文件");
//创建菜单项
saveMenu->addAction("保存");
//监听保存菜单项按钮,执行保存操作
connect(saveMenu,&QMenu::triggered,[=]()
{
//弹出文件对话框(只在第一次保存文件时执行)
if(!filesave)
{
path = QFileDialog::getOpenFileName(this,"打开文件",DiaOpenDefa,"(*.txt)");
CSaveFileF(); //保存文件函数
filesave = true;
}
else
CSaveFileF(); //保存文件函数
});
//创建菜单
QMenu* SaveAsMenu = bar->addMenu("另存为");
//创建菜单项
SaveAsMenu->addAction("另存为");
//监听另存为菜单项
connect(SaveAsMenu,&QMenu::triggered,[=]()
{
path = QFileDialog::getOpenFileName(this,"打开文件",DiaOpenDefa,"(*.txt)"); //弹出对话框选择保存路径
CSaveFileF(); //保存文件函数
});
QMenu* UseMenu = bar->addMenu("使用说明");
UseMenu->addAction("使用说明");
//监听使用说明菜单项
connect(UseMenu,&QMenu::triggered,[=]()
{
InstructionsUse *use = new InstructionsUse();
use->show(); //弹出使用说明窗口
});
QMenu* KeyUseMenu = bar->addMenu("键位说明");
KeyUseMenu->addAction("说明");
//监听键位说明菜单项
connect(KeyUseMenu,&QMenu::triggered,[=]()
{
KeyDescription *kuse = new KeyDescription();
kuse->show(); //弹出使用说明窗口
});
QMenu* MapInformation = bar->addMenu("地图信息");
MapInformation->addAction("地图信息");
//监听地图信息菜单项
connect(MapInformation,&QMenu::triggered,[=]()
{//用消息对话框显示当前地图信息
QMessageBox::information(this, "地图信息",
QString("当前地图宽高:%1 %2n地图默认值:%3n当前选中值:%4")
.arg(MX).arg(MY).arg(this->MapDefault).arg(CurrSeleData.DKey));
});
//设置标签控件属性
lab->move(14, 46); //移动控件
lab->setFont(QFont("微软雅黑", 16)); //设置属性
lab->setText("地图坐标:0 0"); //设置文本
lab->resize(MX << 5, 28); //设置大小
//设置鼠标追踪
setMouseTracking(true);
}
//保存文件函数
void MainFrom::CSaveFileF()
{
qDebug() << "3333333333333333333333333333";
QFile file(path); //创建文件对象
qDebug() << "44444444444444444444444444444444";
//设置打开方式
file.open(QIODevice::WriteOnly); //写入方式打开
qDebug() << "5555555555555555555555555";
//将filemapdataarr中的信息写入到文件中
for(int j = 0; j < MY; j++)
{
for(int i = 0; i < MX; i++)
//参数转换为const char*
file.write(QString("%1, ").arg(filemapdataarr[j][i]).toStdString().data());
file.write("n");
}
qDebug() << "6666666666666666666666666";
//关闭文件
file.close();
qDebug() << "777777777777777777777777777";
qDebug() << "9999999999999999999999999999";
}
//绘图事件
void MainFrom::paintEvent(QPaintEvent *)
{
//创建画家对象,指定绘图设备为this窗口
QPainter painter(this);
//画出格子
for(int i = 0; i < MX; i++)
for(int j = 0; j < MY; j++)
painter.drawRect(QRect(i * 32, j * 32 + 32 + 60, 32, 32));
for(int j = 0; j < MY; j++)
for(int i = 0; i < MX; i++)
{ //画出数组中所有内容
for(auto ti : TempDataArr) //通过判断值,确定要使用显示的颜色
if(ti.DKey == filemapdataarr[j][i])
{
painter.setBrush(QBrush(ti.colorenum)); //使用画刷
break;
}
//如果是默认值且默认值不属于预设值,则使用特定的背景颜色画刷,
if(filemapdataarr[j][i] == MapDefault && (MapDefault < 0 || MapDefault > 18))
painter.setBrush(QBrush(QColor(240,240,240)));
painter.drawRect(QRect(i * 32, j * 32 + 32 + 60, 32, 32)); //画出矩形
//默认值不会显示数字
if(filemapdataarr[j][i] == MapDefault)
continue;
//画出数字
painter.setFont(QFont("黑体", 12, QFont::Bold));
painter.drawText(QRect(i * 32 + 10, j * 32 + 32 + 60 + 8, 30, 20),
QString("%1").arg(filemapdataarr[j][i]));
}
}
//鼠标按下事件
void MainFrom::mousePressEvent(QMouseEvent *event)
{
//左键按下
if (event->button() == Qt::LeftButton)
{
//将当前鼠标点击的数据插入到主要数据数组中
if(CurrSeleData.DKey >= 0 && CurrSeleData.DKey <= 18 && mj >= 0 && mj < MY && mi >= 0 && mi < MX) //加限制条件
filemapdataarr[mj][mi] = CurrSeleData.DKey; //修改当前鼠标坐标的的对应值
update(); //调用绘图函数
}
//右键按下, 删除按下位置的元素
else if (event->button() == Qt::RightButton)
{
filemapdataarr[mj][mi] = this->MapDefault; //修改为默认值
update(); //调用绘图函数
}
}
//鼠标移动事件
void MainFrom::mouseMoveEvent(QMouseEvent *event)
{
//鼠标越界直接返回
if(event->x() < 0 || event->x() > (MX << 5) || event->y() < 0 || event->y() > (MY << 5) + 32 + 60)
return;
for(mi = 0; mi < MX; mi++) //确定鼠标坐标对应地图坐标
for(mj = 0; mj < MY; mj++)
if(event->x() >= (mi << 5) && event->x() < (mi << 5) + 32 &&
event->y() >= (mj << 5) + 32 + 60 && event->y() < (mj << 5) + (32 << 1) + 60 )
{
lab->setText(QString("鼠标坐标:%1 %2").arg(mi + 1).arg(mj + 1)); //更新标签内容
goto L1; //跳出循环
}
L1:
//鼠标左键按下且移动时,也执行插入数据操作
if(event->buttons() & Qt::LeftButton)
{
if(CurrSeleData.DKey >= 0 && CurrSeleData.DKey <= 18 && mj >= 0 && mj < MY && mi >= 0 && mi < MX)
filemapdataarr[mj][mi] = CurrSeleData.DKey; //修改当前鼠标坐标的的对应值
update();
}
//右键同样删除
else if(event->buttons() & Qt::RightButton)
{
filemapdataarr[mj][mi] = this->MapDefault; //修改为默认值
update(); //调用绘图函数
}
}
//键盘事件
void MainFrom::keyPressEvent(QKeyEvent *ev)
{
//组合键CTRL + S 用于保存地图数据快捷键
if ((ev->modifiers() == Qt::ControlModifier) && (ev->key() == Qt::Key_S))
{
//弹出文件对话框(只在第一次保存文件时执行)
if(!filesave)
{
path = QFileDialog::getOpenFileName(this,"打开文件",DiaOpenDefa,"(*.txt)");
CSaveFileF(); //保存文件函数
filesave = true;
return;
}
CSaveFileF(); //保存文件
return;
}
//程序默认对应键值共19个键,0 - 1 a - i
//17种颜色,0和15、1和16、18和17共用一种颜色
int ni; //将键值和表中值对应
for(ni = 0; ni < 19; ni++)
if(ev->key() == keyarr[ni].qtkey)
break;
if(ni >= 19) //没找到键值时,返回
return;
CurrSeleData = TempDataArr[ni]; //将找到的结构赋给记录当前结构的变量
}
欢迎大家提出批评和建议,感谢支持。