easyx devc++ 开发具有导出文件夹DLC的瓦片地图编辑器

时间:2024-02-16 21:53:07

 1. F1保存为 DLC 输出为当前文件目录下。

2. 瓦片的边框利用绘制到 -1 -1 把边框绘制到外部

3. 利用 +1 实现网格保留

#include <graphics.h>
#include <stdio.h>
#include <string.h>
#include <io.h>
// 保存游戏地图贴图数据
void savegamemap(int** map,int gamemapi,int gamemapj,IMAGE* pentable,int pixnum);
// 使用关键字 inline 声明为内联函数,减少贴图函数频繁调用的开销导致的卡顿。
// 缓冲区纹理映射函数:bkmesh 映射目标,map 映射总网格,pentable:纹理集,bkmeshmapi,bkmeshmapj:映射起始点,tilenum:横,纵映射的数量,pixnum:一个映射块的边长,单位:像素。
inline void freshmesh(IMAGE* bkmesh, int** map, IMAGE* pentable, int bkmeshmapi, int bkmeshmapj, int tilenum, int pixnum)
{
	int pennumber = 0;										// 暂存每一次循环的映射代号
	IMAGE pen = NULL;										// 所找到的纹理
	int left = 0;											// 这是每次循环所找到的纹理对应映射地址
	int top = 0;
	SetWorkingImage(bkmesh);								// 设置绘图目标为游戏背景采样区,刷新采样区,刷新寄存区
	for (int i = bkmeshmapi; i < bkmeshmapi + tilenum; i++)
	{
		left = 0;
		for (int j = bkmeshmapj; j < bkmeshmapj + tilenum; j++)
		{
			pennumber = map[i][j];							// 读取游戏大地图数组序号
			pen = pentable[pennumber];						// 根据序号查找对应贴图
			putimage(left, top, &pen);						// 把贴图画到采样区
			left += pixnum+1;									// 往右移动,准备下一次绘制位置,+1 留出白色背景 
		}
		top += pixnum+1;										// 往下移动,准备下一次绘制位置,+1 留出白色背景 
	}
	SetWorkingImage();
}
// 在纹理映射函数中产生的图片中截图,但此为演示参数作用,此处并未优化。
inline void freshbk(IMAGE* bk, IMAGE* bkmesh, int gamex, int gamey, int bkmeshmapi, int bkmeshmapj, int tilenum, int pixnum)
{
	SetWorkingImage(bkmesh);
	getimage(bk, gamex - bkmeshmapj * pixnum, gamey - bkmeshmapi * pixnum, tilenum * pixnum, tilenum * pixnum);
	SetWorkingImage();
}
// 在屏幕显示截图
inline void showbk(IMAGE* bk, int bkdeskx, int bkdesky)
{
	SetWorkingImage();
	putimage(bkdeskx, bkdesky, bk);
}
// 在屏幕上显示缓冲区
inline void showbkmesh(IMAGE* bkmesh, int bkmeshdeskx, int bkmeshdesky)
{
	SetWorkingImage();
	putimage(bkmeshdeskx, bkmeshdesky, bkmesh);
}
// 初始化游戏地图
int** initmap(int wide,int high)
{
	int**map = new int* [high];						// 二维数组动态初始化,先给二级指针挂上一个长度为 10 的指针数组
	for (int i = 0; i < high ; i++)
	{
		map[i] = new int[wide];						// 然后数组里的每个指针都挂上一个长度为 10 的 int 类型数组
	}
	for (int i = 0; i <high; i++)
	{
		for (int j = 0; j < wide; j++)
		{
			map[i][j] = 1;						// 初始化游戏大地图 map 的参数,参数范围 3
		}
	}
	return map;
}
int main()
{
	initgraph(1640, 980, 1);
	setbkcolor(GREEN);
	cleardevice();
	IMAGE* bk;									// 背景图片寄存区
	bk = new IMAGE(270, 270);
	IMAGE* bkmesh;								// 背景图片采样区
	bkmesh = new IMAGE(270 * 3, 270 * 3);
	int** map;									// 游戏大地图数组,记录着整个游戏背景的贴图信息,而在运行过程中,选取部分区域的数字,对照序号与贴图,实现游戏背景绘制。其余没有选中的区域就是压缩的空间。
	int pixnum;									// 一个正方形瓦片的边长。单位:像素
	int bkgameleft;								// 背景图片寄存区左上角坐标,是在游戏里的像素坐标。(0,0)可以理解为游戏大地图的左上角顶点。
	int bkgametop;
	int bkmeshgameleft;							// 背景图片采样区左上角坐标,是在游戏里的像素坐标。
	int bkmeshgametop;
	int bkmeshmapi;								// 背景图片采样区左上角所对应的 map 数组序号。从 map[0][0]开始,按照 map[i][j],其中 bkmeshmapi=bkmeshtop/pixnum
	int bkmeshmapj;
	int bkdeskleft;								// 规定在屏幕上显示游戏背景寄存区,此处记录其左上角在屏幕上的像素坐标
	int bkdesktop;
	int bkmeshdeskleft;							// 规定在屏幕上显示游戏背景采样区,此处记录其左上角在屏幕上的像素坐标
	int bkmeshdesktop;
	pixnum = 30;									// 进行初始化,规定各位置具体数字
	bkdeskleft = 200;								// 游戏背景左上角将会在屏幕的(200,200) 处
	bkdesktop = 200;
	bkgameleft = 0;
	bkgametop = 0;
	bkmeshdeskleft = 700;							// 游戏背景缓冲区左上角将会在屏幕的(700,0)处
	bkmeshdesktop = 0;
	bkmeshgameleft = 0;
	bkmeshgametop = 0;
	bkmeshmapi = bkmeshgametop / pixnum;
	bkmeshmapj = bkmeshgameleft / pixnum;
	map=initmap(1000,1000);							// 初始化一个有1000*1000个贴图的地图
	int pentableleft;								// 忘了初始化调色盘了,这里设置调色盘左上角在屏幕的坐标
	int pentabletop;
	IMAGE* pentable;								// 调色板其实就是贴图数组
	pentableleft = 0;								// 调色盘左上角将会在屏幕的(0,0)处
	pentabletop = 0;
	pentable = new IMAGE[10];
	for (int i = 0; i < 10; i++)
	{
		pentable[i] = IMAGE(30, 30);
		SetWorkingImage(&pentable[i]);					// 给调色板绘制颜色
		setfillcolor(RGB(i * 20, i * 20, i * 20));		// 这里初始化调色盘的颜色
		fillrectangle(-1, -1, pixnum, pixnum);			// 在调色板上绘制颜色(纹理) 要从 -1,-1 开始绘制,把边框画到外部,不保留边框。 
	}
	int left;												// 初始化绘制采样区所需的坐标,相对于采样区,(0,0)就是采样区左上角顶点坐标
	int top;
	left = 0;
	top = 0;
	//	往缓冲区刷入贴图
	SetWorkingImage(bkmesh);								// 设置绘图目标为游戏背景采样区,刷新采样区,刷新寄存区
	setbkcolor(RGB(200,200,200));
	cleardevice(); 
	for (int i = bkmeshmapi; i < bkmeshmapi + 30; i++)
	{
		left = 0;
		for (int j = bkmeshmapj; j < bkmeshmapj + 30; j++)
		{
			int pennumber = map[i][j];						// 读取游戏大地图数组序号
			IMAGE pen = pentable[pennumber];				// 根据序号查找对应贴图
			putimage(left, top, &pen);						// 把贴图画到采样区
			left += pixnum+1;								// 往右移动,准备下一次绘制位置, 此处 +1 是增加白色边框,方便定位 
		}
		top += pixnum+1;									// 往下移动,准备下一次绘制位置,此处 +1 是增加白色边框,方便定位 
	}
	getimage(bk, bkgameleft, bkgametop, 270, 270);			// 从刚刚绘制好的采样区取样,刷新游戏背景寄存区。
	//	开始往屏幕上绘图
	SetWorkingImage();															// 设置电脑屏幕为绘制对象
	for (int j = 0; j < 10; j++)
	{
		putimage(pentableleft + 10, pentabletop + j * 30, &pentable[j]); 		// 绘制绘图板
	}
	putimage(bkdeskleft, bkdesktop, bk);										// 绘制游戏背景
	putimage(bkmeshdeskleft, bkmeshdesktop, bkmesh); 							// 显示游戏背景缓冲区
	// 此时绘制完成,以上 刷贴图,采样,粘贴就是实现 RPG 游戏大地图的压缩
	//	开始检测鼠标键盘功能
	int drawflag;															// 设置长按 flag
	int drawoldmx;															// 记录上一次绘制时的鼠标坐标,用于检测是否重复点击相同像素,来减少重复绘制
	int drawoldmy;
	int drawx;																// 画笔在游戏里的位置,单位像素
	int drawy;
	int olddrawi;															// 记录上一次绘制的瓦片,判断是否需要重新绘制
	int olddrawj;
	int drawsmallflag;														// 在 drawflag=1 时,检测是否刷新
	drawsmallflag=0;
	olddrawi=0;
	olddrawj=0;
	drawx = 0;
	drawy = 0;
	drawflag = 0;
	drawoldmx = 0;
	drawoldmy = 0;
	int pentake;															// 设置不绘制时贴图代号为 -1
	pentake = 0;															// 默认黑色笔刷
	int draftoldmx;															// 记录刚刚拖拽时的鼠标的位置,用于坐标变换计算位移
	int draftoldmy;
	int draftoldgamex;														// 记录刚刚拖拽时的游戏地图位置,用于坐标变换计算新的游戏坐标
	int draftoldgamey;
	int draftflag;															// 设置拖拽 flag
	draftoldmx = 0;
	draftoldmy = 0;
	draftoldgamex = 0;
	draftoldgamey = 0;
	draftflag = 0;
	int moveflag;															// 是否键盘控制移动
	int flag_x;																// 记录位移
	int flag_y;
	int speed;																// 键盘控制视口的移动速度
	speed=5;
	flag_x=0;
	flag_y=0;
	moveflag=0;
	int mousex;																// 记录鼠标位置
	int mousey;
	mousex=0;
	mousey=0;
	int oldbkmeshgamex;														// 判断拖拽时是否需要刷新 bkmesh 网格
	int oldbkmeshgamey;
	ExMessage m;
	while (1)
	{
		while (peekmessage(&m, EX_KEY | EX_MOUSE))		// 一次性处理完鼠标消息,参考自 https://codebus.cn/zhaoh/handle-mouse-messages-correctly
		{
			switch (m.message)
			{
				case WM_LBUTTONDOWN:																				// 鼠标左键按下,有两种情况,一是选择贴图,另外就是绘制贴图
					if (drawflag==0&&m.x > bkdeskleft && m.y > bkdesktop && m.x < bkdeskleft + 300 && m.y < bkdesktop + 300)		// 如果之前不是长按状态	且按下左键时,鼠标在游戏背景区域内
					{
						drawflag = 1;																				// 才记录为正在绘制的状态
						mousex=m.x;
						mousey=m.y;
					}
					else if (drawflag==0&&draftflag==0&&m.x > 0 && m.y > 0 && m.x < 30 && m.y < 300)
						pentake = m.y / 30; 																		// 选择贴图对应的代号
					break;
				case WM_LBUTTONUP:
					drawflag = 0;
					drawsmallflag=0;
					olddrawi=0;
					olddrawj=0;
					break;
				case WM_RBUTTONDOWN:																				// 鼠标右键拖动
					if (draftflag==0&&m.x > bkdeskleft && m.y > bkdesktop && m.x < bkdeskleft + 270 && m.y < bkdesktop + 270)
					{
						draftflag = 1;
						draftoldmx = m.x;																			// 记录鼠标坐标
						draftoldmy = m.y;
						mousex=m.x;
						mousey=m.y;
						draftoldgamex = bkgameleft;																	// 记录游戏背景寄存区左上角坐标
						draftoldgamey = bkgametop;
					}
					break;
				case WM_RBUTTONUP:
					draftflag = 0;
					bkgameleft = draftoldgamex - (m.x - draftoldmx);												// bkgameleft - draftoldgamex =- (m.x - draftoldmx)
					bkgametop = draftoldgamey - (m.y - draftoldmy);													// bkgametop - draftoldgamey =- (m.y - draftoldmy)
					break;
				case WM_KEYDOWN:
					switch (m.vkcode)					// 键盘移动控制
					{
						case 0x41:						// A
							flag_x-=speed;
							moveflag=1;
							break;
						case 0x57:						// W
							flag_y-=speed;
							break;
						case 0x44:						// D
							flag_x+=speed;
							break;
						case 0x53:						// S
							flag_y+=speed;
							break;
						case VK_F1:
							savegamemap(map,300,300,pentable,pixnum);
							break;
					}
					break;
				case WM_KEYUP:
					switch(m.vkcode)
					{
						case 0x41:						// A
							flag_x=0;
							break;
						case 0x57:						// W
							flag_y=0;
							break;
						case 0x44:						// D
							flag_x=0;
							break;
						case 0x53:						// S
							flag_y=0;
							break;
					}
					break;
				case WM_MOUSEMOVE:
					if(mousex!=m.x||mousey!=m.y)
					{
						mousex=m.x;
						mousey=m.y;
					}
					break;
			}
		}
//		开始根据指令运行坐标变化
		if (draftflag == 1)
		{
			bkgameleft = draftoldgamex - (mousex - draftoldmx);											// bkgameleft-draftoldgamex=-(mousex-draftoldmx)
			bkgametop = draftoldgamey - (mousey - draftoldmy);											// bkgametop-draftoldgamey=-(mousey - draftoldmy)
		}
		else if (drawflag == 1&&flag_x==0&&flag_y==0 &&(drawoldmx!=mousex||drawoldmy!=mousey)&& mousex > bkdeskleft && mousey > bkdesktop && mousex < bkdeskleft + 300 && mousey < bkdesktop + 300)
		{
			// 注意不要越界,否则 gamex 为负数,导致数组越界闪退。
			// 通过实现坐标变换与赋值达到修改游戏大地图(数组)
			moveflag=0;
			drawoldmx = mousex;
			drawoldmy = mousey;
			drawx = bkgameleft + (mousex - bkdeskleft);										// drawx-bkgameleft=mousex-bkdeskleft	横坐标方向移动距离相同
			drawy = bkgametop + (mousey - bkdesktop);										// drawy-bkgametop=mousey-bkdesktop	纵坐标方向移动距离相同
			if(olddrawi!=drawy/pixnum||olddrawj!=drawx/pixnum)
			{
				drawsmallflag=1;
				olddrawi=drawy / pixnum;
				olddrawj=drawx / pixnum;
				map[olddrawi][olddrawj] = pentake;											// 注意 map[y][x],而不是 map[x][y],因为判断第几行,是通过 y 来控制上下移动的,判断第几列,是通过 x 左右移动的。
			}
			else
			{
				drawsmallflag=0;															// 检测到是上一次绘制的瓦片,则不再刷新贴图与缓冲区。
			}
		}	//	对绘制进行分类计算数据,剥离特殊情况的重复绘制,仅仅是 flag_x,或者flag_y不为零时取消重复绘制判断。
		else if(drawflag == 1 && mousex > bkdeskleft && mousey > bkdesktop && mousex < bkdeskleft + 300 && mousey < bkdesktop + 300)
		{
			moveflag=1;
			while(flag_x<-10)																// 限制在合适速度范围
				flag_x+=10;
			while(flag_x>10)
				flag_x-=10;
			while(flag_y<-10)
				flag_y+=10;
			while(flag_y>10)
				flag_y-=10;
			bkgameleft+=flag_x;																// 更新游戏背景寄存区左上角坐标
			bkgametop+=flag_y;
			drawx = bkgameleft + (mousex - bkdeskleft);										// drawx-bkgameleft=m.x-bkdeskleft	横坐标方向移动距离相同
			drawy = bkgametop + (mousey - bkdesktop);										// drawy-bkgametop=m.y-bkdesktop	纵坐标方向移动距离相同
			if(olddrawi!=drawy/pixnum||olddrawj!=drawx/pixnum)
			{
				drawsmallflag=1;
				olddrawi=drawy / pixnum;
				olddrawj=drawx / pixnum;
				map[olddrawi][olddrawj] = pentake;											// 注意 map[y][x],而不是 map[x][y],因为判断第几行,是通过 y 来控制上下移动的,判断第几列,是通过 x 左右移动的。
			}
			else
			{
				drawsmallflag=0;															// 检测到是上一次绘制的瓦片,则不再刷新贴图与缓冲区。
			}
		}
		else if(drawflag==0&&flag_x!=0||flag_y!=0)
		{
			moveflag=1;
			while(flag_x<-10)																// 限制在合适速度范围
				flag_x+=10;
			while(flag_x>10)
				flag_x-=10;
			while(flag_y<-10)
				flag_y+=10;
			while(flag_y>10)
				flag_y-=10;
			bkgameleft+=flag_x;
			bkgametop+=flag_y;
		}
		else
		{
			// 既不绘制也不移动也不拖拽
		}
		// 根据计算出的坐标数据进行绘制,分多种情况分别绘制,减少函数重复调用与无效调用
		if(drawsmallflag==1&&moveflag==0)
		{
			freshmesh(bkmesh, map, pentable, bkmeshmapi, bkmeshmapj, 27, 30);			// 刷新,重新映射,其实就是开头初始化的代码,这里是给了一个封装示例,但未进行性能优化
			freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, 9, 30);
			showbk(bk, bkdeskleft, bkdesktop);
			showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);
		}
		else if(moveflag==1&&drawsmallflag==1)
		{
			if(bkgameleft<0)															// 网格越界检测并调整
				bkgameleft=0;
			if(bkgametop<0)
				bkgametop=0;
			if(bkgameleft>30*300)
				bkgameleft=30*300;
			if(bkgametop>30*300)
				bkgametop=30*300;
			while (bkgameleft < bkmeshgameleft)											// 更新游戏采样区坐标,一些简单换算
				bkmeshgameleft -=270;
			while (bkgametop <bkmeshgametop )
				bkmeshgametop -= 270;
			while(bkgameleft>bkmeshgameleft+270+270)
				bkmeshgameleft += 270;
			while(bkgametop>bkmeshgametop+270+270)
				bkmeshgametop += 270;
			bkmeshmapi = bkmeshgametop / pixnum;
			bkmeshmapj = bkmeshgameleft / pixnum;
			freshmesh(bkmesh, map, pentable, bkmeshmapi, bkmeshmapj, 27, 30);
			freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, 9, 30);
			showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);
			showbk(bk, bkdeskleft, bkdesktop);
		}
		else if(moveflag==1)															// 分类渲染, drawflag==0 时,再选择性刷新缓冲区
		{
			if(bkgameleft<0)															// 网格越界检测并调整
				bkgameleft=0;
			if(bkgametop<0)
				bkgametop=0;
			if(bkgameleft>30*300)
				bkgameleft=30*300;
			if(bkgametop>30*300)
				bkgametop=30*300;
			while (bkgameleft < bkmeshgameleft)											// 更新游戏采样区坐标,一些简单换算,由于频繁调用函数在这里产生了明显的卡顿影响,所以这里就不再封装成函数
				bkmeshgameleft -=270;
			while (bkgametop <bkmeshgametop )
				bkmeshgametop -= 270;
			while(bkgameleft>bkmeshgameleft+270+270)
				bkmeshgameleft += 270;
			while(bkgametop>bkmeshgametop+270+270)
				bkmeshgametop += 270;
			bkmeshmapi = bkmeshgametop / pixnum;
			bkmeshmapj = bkmeshgameleft / pixnum;
			if(oldbkmeshgamex!=bkmeshgameleft||oldbkmeshgamey!=bkmeshgametop)			// 判断是否更新采样区
			{
				freshmesh(bkmesh, map, pentable, bkmeshmapi, bkmeshmapj, 27, 30);
				oldbkmeshgamex=bkmeshgameleft;
				oldbkmeshgamey=bkmeshgametop;
				showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);
			}
			freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, 9, 30);
			showbk(bk, bkdeskleft, bkdesktop);
		}
		else if(draftflag)																// 分类渲染-只拖拽
		{
			if(bkgameleft<0)															// 网格越界检测并调整
				bkgameleft=0;
			if(bkgametop<0)
				bkgametop=0;
			if(bkgameleft>30*300)
				bkgameleft=30*300;
			if(bkgametop>30*300)
				bkgametop=30*300;
			while (bkgameleft < bkmeshgameleft)											// 更新游戏采样区坐标,一些简单换算
				bkmeshgameleft -=270;
			while (bkgametop <bkmeshgametop )
				bkmeshgametop -= 270;
			while(bkgameleft>bkmeshgameleft+270+270)
				bkmeshgameleft += 270;
			while(bkgametop>bkmeshgametop+270+270)
				bkmeshgametop += 270;
			bkmeshmapi = bkmeshgametop / pixnum;
			bkmeshmapj = bkmeshgameleft / pixnum;
			if(oldbkmeshgamex!=bkmeshgameleft||oldbkmeshgamey!=bkmeshgametop)			// 判断是否更新采样区
			{
				freshmesh(bkmesh, map, pentable, bkmeshmapi, bkmeshmapj, 27, 30);
				oldbkmeshgamex=bkmeshgameleft;
				oldbkmeshgamey=bkmeshgametop;
				showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);
			}
			freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, 9, 30);
			showbk(bk, bkdeskleft, bkdesktop);
		}
		Sleep(20);																		// 休眠 20 毫秒,减少 CPU 占用
	}
	return 0;
}
// 边绘制边移动:最大:边界 2.30GHz 3%占用,内部 2.25Ghz。仅绘制:1.8GHz, 拖拽:0.99GHz,旧瓦片绘制检测,又减少重复绘制,显著平稳,峰值降低
void savegamemap(int** map,int gamemapi,int gamemapj,IMAGE* pentable,int pixnum)
{
	FILE* fp;
	int i=0;
	int j=0;
	char dirpath[400]={'\0'};
	char filepath[400]={'\0'};
	for(i=0;i<100;i++){
		sprintf(dirpath,"DLC%d",i);
		if(access(dirpath,0)==-1){										// 检查 DLC 是否存在,不存在为 -1 
			break;
		}
	}
	mkdir(dirpath);														// 创建文件夹 
	char filename[400]="gamemap.txt";
	const char* next="./";
	strcat(filepath,dirpath);
	strcat(filepath,next);
	strcat(filepath,filename);
	fp=fopen(filepath,"w+");											// 创建 .txt 文件 
	fprintf(fp,"注意此导出的游戏文件为按键 F1 后自动生成 修改汉语字符或者英文字符 或导致该 .txt 文件不可读取\n");
	fprintf(fp,"warning this saved gamefile is automatically create after F1 pressed change Chinesse character or English character lead to read failed");
	fprintf(fp,"pixnum %d\n",pixnum);									// 正方形瓦片贴图边长信息:单位:像素 
	fprintf(fp,"imagenum %d\n",10);										// 瓦片个数 
	char c[400]={'\0'};
	for(i=0;i<10;i++){
		sprintf(c,"tile_%d.png",i);
		fprintf(fp,"%s\n",c);
	} 
	fprintf(fp,"gamemapi %d gamemapj %d\n",gamemapi,gamemapj);
	for(i=0; i<gamemapi; i++)
	{
		fprintf(fp,"%d",map[i][j]);
		for(j=0; j<gamemapj; j++)
		{
			fprintf(fp," %d",map[i][j]);
		}
		fprintf(fp,"\n");
	}
	fclose(fp);
	char imagename[400]= {'\0'};
	char imagepath[400]={'\0'}; 
	for(i=0; i<10; i++)
	{
		sprintf(imagename,"tile_%d.png",i);							// 数字转字符串
		strcpy(imagepath,dirpath);									// 函数直接从头开始粘贴,自动清除开头的字符 
		strcat(imagepath,next);
		strcat(imagepath,imagename);
		saveimage(imagepath,&pentable[i]);							// 批量导出贴图
	}
}