Editing While Playing 使用 Easyx 开发的 RPG 地图编辑器 tilemap eaitor

AWSD移动画布

鼠标右键长按拖拽

鼠标左键长按绘制

可以边拖拽边移动画布边绘制。

F1 导出 DLC

F2 导入DLC

author:

民用级脑的研发记录

1309602336@qq.com

开发环境:

内置 easyx 的 devc++ 5.11 或者 VS 2022

TDM GCC 4.9.2 64-bit

c++11及以上都可运行

windows 环境运行

源代码可复制粘贴直接跑

代码原理:

  1. 多操作同时进行:

使用标志位,一个是用于判断执行哪些模块,另一方面是通过不同标志位的合并拆分,实现条理运行代码。正如一个是 flag-x 和flag_y这两个参数同时为零和moveflag=0;而moveflag成为控制后续选择渲染步骤的控制变量。

以及想象中的模块代码是什么样子的,比仅仅是表面的标志位区分,但是由于标志位局限于所属模块,不能确定此模块运行时,其他模块的作用,而且有时标志位无法设置,导致没有标志位,就需要按情况不断拆分合并代码,最后发现按三个条件的8种情况满足情况。实际上是功能的实现,如是否绘制和是否按键移动,导致的如下四个排列组合

绘制而键盘控制画布不移动,

绘制而健康控制画布移动

不绘制而键盘控制画布不移动,

不绘制而键盘控制画布移动

而这四种组合,对应的坐标数据运算和贴图,都有各自不同的优化,这也是在把得到的功能重新拆分,在把多个if if if 的判断中多次修改,发现可以按找如上的排列组合,全部变成 if else if else这样的形式,而这就是主函数里三组 选择 分支产生的由来。也,只找出必须的运算,来理顺各个层次之间的联系,实现精确优化。

考虑到拖拽时鼠标和画布同时移动,导致无法绘制,于是就可以单独处理鼠标拖拽,解决键盘控制画布移动和鼠标拖拽移动,这里是鼠标拖拽优先级高,在实现原理上,其实是由于鼠标拖拽需要暂存鼠标坐标和画布原坐标,而键盘控制改变当前原坐标,不会改变拖拽时暂存的数据,而鼠标拖拽计算出的数据是在键盘影响完,重新写入数据,所以键盘不会对鼠标移动产生影响。因为标志位的检测

因为拖拽的检测放在最后,所以只有四种情况都检测完毕,才会执行拖拽检测,

而控制位导致绘制时不能拖拽,绘制时鼠标坐标和画布坐标同时移动也会导致绘制失败,所以,四种情况里,穷举一下,有绘制不能拖拽,有键盘移动,不能拖拽。绘制的两种情况不行,不绘制而键盘移动也不行,只能不绘制不拖拽。

而且由于一开始的stitch 分支的拖拽标志位,拖拽不会被键盘打断,而moveflag标准位,键盘不会被拖拽打断。

在数据计算,拖拽是第一位的,在贴图层面,绘制,和键盘移动是第一位的,这样可以实现优先级随指变化而变化。

  1. 游戏地图瓦片

网格实现原理:

bkmesh 是九个 bk 大小, 一个 bk 即background 是 9*9 个瓦片(tile) 一个 瓦片边长 30 个像素

数组里记录这些瓦片的序号,选取 bkmesh 所包含的 区域,通过这些区域的瓦片序号来找到对应瓦片,贴图 bkmesh,然后 bk 在复制bkmesh 的一部分,打印到屏幕上,实现了游戏背景缓冲区,和游戏背景顺畅滚动,并解决了超大地图在屏幕上局部显现的,超大地图就是这样以瓦片和瓦片序号存储,实现大地图压缩,这样节省的内存可以增加更多的游戏内容

性能优化:

用中间变量暂存上一次绘制,如 old 系列,避免重复绘制

用中间变量暂存当前范围,减少比较之前的计算,把计算结果暂存,如边界检查,常数替换计算式

使用两倍范围,一倍移动,把检测点困住在九宫格中心

使用switch case 实现一次分支解决命令。

使用if else if 二分支,节约运算,如边界,或者某个方向的坐标增减。

使用多个标志位和组合坐标当作检测点,实现数据在三个模块的传输。

cpp 复制代码
#undef UNICODE
#undef _UNICODE
#pragma warning(disable : 4996)		// VS2022 对某些函数警告,但是为了方便移植,就无视这些警告 这样devc++ VS2022都能跑 
#include <graphics.h>
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <direct.h>
inline void animation(int chararcterflag,int bkgameleft,int bkgametop,int bkmeshgameleft,int bkmeshgametop,int bkmeshdeskleft,int bkmeshdesktop);
//边界检查 , move 是平移大小,单位:像素,若使得寄存区在九宫格中心,move的大小就是寄存区的边长,相应的size的大小是move的两倍。这样无论长宽都是 3倍。size是大小区域,代表不会触发缓冲区更新的范围,单位:像素
inline void checkboundary(int* bkgameleft,int* bkgametop,int gamelimitright,int gamelimitbuttom,int* bkmeshleft,int* bkmeshtop,int* meshlimit,int*meshlimitbutton,int move,int size);
// 保存游戏地图贴图数据
void savegamemap(int** map,int gamemapi,int gamemapj,IMAGE* pentable,int pixnum,int imagenum);
// 导入游戏地图贴图数据
void loadgamemap(int** map,int *gamemapi,int *gamemapj,IMAGE* pentable,int *pixnum,int *imagenum);
// 使用关键字 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 = -1;										// 暂存每一次循环的映射代号
	IMAGE pen = NULL;										// 所找到的纹理
	int left = 0;											// 这是每次循环所找到的纹理对应映射地址
	int top = 0;
	SetWorkingImage(bkmesh);								// 设置绘图目标为游戏背景采样区,刷新采样区,刷新寄存区
	cleardevice();
	for (int i = bkmeshmapi; i < bkmeshmapi + tilenum; i++)
	{
		left = 0;
		for (int j = bkmeshmapj; j < bkmeshmapj + tilenum; j++)
		{
			pennumber = map[i][j];							// 读取游戏大地图数组序号
			if(pennumber==-1)
			{
				rectangle(left,top,left+pixnum,top+pixnum);
			}
			else
			{
				pen = pentable[pennumber];						// 根据序号查找对应贴图
				putimage(left, top, &pen);						// 把贴图画到采样区
			}
			left += pixnum;										// 往右移动,准备下一次绘制位置, 此处贴图就会覆盖白色边框。为保证坐标变换和网格对应,算上网格宽度,也在贴图矩形内
		}
		top += pixnum;											// 往下移动,准备下一次绘制位置,此处就会覆盖白色边框,方便定位
	}
	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 的参数,参数 1 默认黑色
		}
	}
	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;								// 由于 bkgame 控制 mesh 坐标移动,(0,0)则游戏背景完全在当前采样区移动
	bkgametop = 0;
	bkmeshdeskleft = 700;						// 游戏背景缓冲区左上角将会在屏幕的(700,0)处
	bkmeshdesktop = 0;
	bkmeshgameleft = 0;
	bkmeshgametop = 0;
	int meshlimitright;							// 使用变量暂存边界,不用每次来回计算边界
	int meshlimitbuttom;
	meshlimitright=270;							// 和寄存区一样大,把寄存区限制在一个九宫格中心,如果越出九宫格,九宫格整体平移
	meshlimitbuttom=270;
	bkmeshmapi = bkmeshgametop / pixnum;
	bkmeshmapj = bkmeshgameleft / pixnum;
	int mapi;										// 读取 DLC 文件,需要变量记录循环次数,单位 瓦片贴图个数
	int mapj;
	int safemapi;									// 一个安全的贴图范围,避免阅读超过 10000 导致的越界闪退问题。
	int safemapj;
	int gamelimitright;								// 用于减少比较时的计算,存储边界范围
	int gamelimitbuttom;
	safemapi=70;									// 注意导出的是左上角开始的 100*100 部分,如果绘制出界,保存不了,需要改这一行和下一行的数字
	safemapj=70;
	gamelimitright=pixnum*safemapi;
	gamelimitbuttom=pixnum*safemapj;
	mapi=300;
	mapj=300;
	map=initmap(mapi,mapj);							// 初始化一个有 10000*10000 个贴图的地图
	int pentableleft;								// 忘了初始化调色盘了,这里设置调色盘左上角在屏幕的坐标
	int imagenum;									// 调色板数量上限,用于文件读取控制循环次数
	int pentabletop;
	IMAGE* pentable;								// 调色板其实就是贴图数组
	pentableleft = 0;								// 调色盘左上角将会在屏幕的(0,0)处
	pentabletop = 0;
	imagenum=10;
	pentable = new IMAGE[imagenum];
	for (int i = 0; i < imagenum; 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];							// 读取游戏大地图数组序号
			if(pennumber==-1)
			{
				rectangle(left,top,left+pixnum,top+pixnum);
			}
			else
			{
				IMAGE pen = pentable[pennumber];				// 根据序号查找对应贴图
				putimage(left, top, &pen);						// 把贴图画到采样区
			}
			left += pixnum;										// 往右移动,准备下一次绘制位置,
		}
		top += pixnum;											// 往下移动,准备下一次绘制位置
	}
	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 = 9;															// 默认白色笔刷,对应黑色背景
	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;
	int i=-1;																// 用于暂存 mesh 网格坐标
	int j=-1;
	int characterflag;														// 用于按键时游戏小人的颜色变化
	characterflag=5;
	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=-1;
					olddrawj=-1;
					drawoldmx=-1;																					// 清除坐标记录,保证下次按键一定绘制
					drawoldmy=-1;
					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)
					mousex=-1;																						// draft 和 draw 共用 mousex mousey
					mousey=-1;
					break;
				case WM_KEYDOWN:
					switch (m.vkcode)					// 键盘移动控制
					{
						case 0x41:						// A
							if(flag_x-speed>-10)		// 限制范围,减少内存读写
								flag_x-=speed;
							characterflag=1;
							break;
						case 0x57:						// W
							if(flag_y-speed>-10)
								flag_y-=speed;
							characterflag=2;
							break;
						case 0x44:						// D
							if(flag_x+speed<10)
								flag_x+=speed;
							characterflag=3;
							break;
						case 0x53:						// S
							moveflag=1;
							if(flag_y+speed<10)
								flag_y+=speed;
							characterflag=4;
							break;
						case VK_F1:
							savegamemap(map,mapi,mapj,pentable,pixnum,imagenum);
							break;
						case VK_F2:
							loadgamemap(map,&mapi,&mapj,pentable,&pixnum,&imagenum);
							gamelimitright=pixnum*safemapi;
							gamelimitbuttom=pixnum*safemapj;
							for (int i = 0; i < imagenum; i++)					// 刷新绘图板颜色
							{
								pentable[i] = IMAGE(30, 30);
								SetWorkingImage(&pentable[i]);					// 给调色板绘制颜色
								setfillcolor(RGB(i * 20, i * 20, i * 20));		// 这里初始化调色盘的颜色
								fillrectangle(-1, -1, pixnum, pixnum);			// 在调色板上绘制颜色(纹理) 要从 -1,-1 开始绘制,把边框画到外部,不保留边框。
							}
							SetWorkingImage();
							freshmesh(bkmesh, map, pentable, bkmeshmapi, bkmeshmapj, 27, pixnum);			// 刷新,重新映射,其实就是开头初始化的代码,这里是给了一个封装示例,但未进行性能优化
							freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, 9, pixnum);
							showbk(bk, bkdeskleft, bkdesktop);
							showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);
							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;
					}
					if(flag_x==0&&flag_y==0)
						characterflag=5;
					break;
				case WM_MOUSEMOVE:
					if(mousex!=m.x||mousey!=m.y)
					{
						mousex=m.x;
						mousey=m.y;
					}
					break;
			}
		}
//		开始根据指令运行坐标变化
		if (draftflag == 1)
		{
			if(flag_x!=0)																		// 实现拖拽时键盘也能控制移动
				draftoldgamex+=flag_x;
			if(flag_y!=0)
				draftoldgamey+=flag_y;
			bkgameleft = draftoldgamex - (mousex - draftoldmx);									// bkgameleft-draftoldgamex=-(mousex-draftoldmx)
			bkgametop = draftoldgamey - (mousey - draftoldmy);									// bkgametop-draftoldgamey=-(mousey - draftoldmy)
			if(drawflag == 1 && mousex > bkdeskleft && mousey > bkdesktop && mousex < bkdeskleft + 300 && mousey < bkdesktop + 300)		// 实现边拖拽边移动边绘制
			{
				drawx = bkgameleft + (mousex- bkdeskleft);										// drawx-bkgameleft=m.x-bkdeskleft	横坐标方向移动距离相同
				drawy = bkgametop + (mousey - bkdesktop);										// drawy-bkgametop=m.y-bkdesktop	纵坐标方向移动距离相同
				i=drawy/pixnum;
				j=drawx/pixnum;
				if(olddrawi!=i||olddrawj!=j)
				{
					if(i>safemapi)																// map 数组越界检测
						i=safemapi;
					else if(i<0)
						i=0;
					if(j>safemapj)
						j=safemapj;
					else if(j<0)
						j=0;
					drawsmallflag=1;
					map[i][j] = pentake;											// 注意 map[y][x],而不是 map[x][y],因为判断第几行,是通过 y 来控制上下移动的,判断第几列,是通过 x 左右移动的。
					olddrawi=i;
					olddrawj=j;
				}
				else
				{
					drawsmallflag=0;												// 检测到是上一次绘制的瓦片,则不再刷新贴图与缓冲区。
				}
			}
		}
		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	纵坐标方向移动距离相同
			i=drawy/pixnum;
			j=drawx/pixnum;
			if(olddrawi!=i||olddrawj!=j)
			{
				drawsmallflag=1;
				map[i][j] = pentake;														// 注意 map[y][x],而不是 map[x][y],因为判断第几行,是通过 y 来控制上下移动的,判断第几列,是通过 x 左右移动的。
				olddrawi=i;
				olddrawj=j;
			}
			else
			{
				drawsmallflag=0;															// 检测到是上一次绘制的瓦片,则不再刷新贴图与缓冲区。
			}
		}	//	对绘制进行分类计算数据,剥离特殊情况的重复绘制,仅仅是 flag_x,或者flag_y不为零时取消重复绘制判断。
		else if(drawflag == 1 && mousex > bkdeskleft && mousey > bkdesktop && mousex < bkdeskleft + 300 && mousey < bkdesktop + 300)
		{
			moveflag=1;
			bkgameleft+=flag_x;																// 更新游戏背景寄存区左上角坐标
			bkgametop+=flag_y;
			drawx = bkgameleft + (mousex- bkdeskleft);										// drawx-bkgameleft=m.x-bkdeskleft	横坐标方向移动距离相同
			drawy = bkgametop + (mousey - bkdesktop);										// drawy-bkgametop=m.y-bkdesktop	纵坐标方向移动距离相同
			i=drawy/pixnum;
			j=drawx/pixnum;
			if(olddrawi!=i||olddrawj!=j)
			{
				drawsmallflag=1;
				map[i][j] = pentake;											// 注意 map[y][x],而不是 map[x][y],因为判断第几行,是通过 y 来控制上下移动的,判断第几列,是通过 x 左右移动的。
				olddrawi=i;
				olddrawj=j;
			}
			else
			{
				drawsmallflag=0;												// 检测到是上一次绘制的瓦片,则不再刷新贴图与缓冲区。
			}
		}
		else if(drawflag==0&&flag_x!=0||flag_y!=0)
		{
			moveflag=1;
			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)
		{
			checkboundary(&bkgameleft,&bkgametop,gamelimitright,gamelimitbuttom,&bkmeshgameleft,&bkmeshgametop,&meshlimitright,&meshlimitbuttom,270,540);
			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 时,再选择性刷新缓冲区
		{
			checkboundary(&bkgameleft,&bkgametop,gamelimitright,gamelimitbuttom,&bkmeshgameleft,&bkmeshgametop,&meshlimitright,&meshlimitbuttom,270,540);
			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)																// 分类渲染-只拖拽
		{
			checkboundary(&bkgameleft,&bkgametop,gamelimitright,gamelimitbuttom,&bkmeshgameleft,&bkmeshgametop,&meshlimitright,&meshlimitbuttom,270,540);
			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);
		}
		animation(characterflag,bkgameleft,bkgametop,bkmeshgameleft,bkmeshgametop,bkmeshdeskleft,bkmeshdesktop);
		Sleep(2);																		// 休眠 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,int imagenum)
{
	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",imagenum);								// 瓦片个数
	char imageindix[400]= {'\0'};
	for(i=0; i<imagenum; i++)
	{
		sprintf(imageindix,"tile_%d.png",i);
		fprintf(fp,"%s\n",imageindix);
	}
	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<imagenum; i++)
	{
		sprintf(imagename,"tile_%d.png",i);								// 数字转字符串
		strcpy(imagepath,dirpath);										// 函数直接从头开始粘贴,自动清除开头的字符
		strcat(imagepath,next);
		strcat(imagepath,imagename);
		saveimage(imagepath,&pentable[i]);								// 批量导出贴图
	}
}
// 读取 DLC 继续开发
void loadgamemap(int** map,int *gamemapi,int *gamemapj,IMAGE* pentable,int *pixnum,int* imagenum)
{
	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)==0) 										// 检查 DLC 是否存在,存在为 0
		{
			break;
		}
	}
	if(i==100)															// 100 次查找失败,则返回,不再读取
		return;
	char filename[400]="gamemap.txt";
	const char* next="./";
	strcat(filepath,dirpath);
	strcat(filepath,next);
	strcat(filepath,filename);
	fp=fopen(filepath,"r");												// 读取 .txt 文件
	fscanf(fp,"注意此导出的游戏文件为按键 F1 后自动生成 修改汉语字符或者英文字符 或导致该 .txt 文件不可读取\n");
	fscanf(fp,"warning this saved gamefile is automatically create after F1 pressed change Chinesse character or English character lead to read failed");
	fscanf(fp,"pixnum %d\n",pixnum);									// 正方形瓦片贴图边长信息:单位:像素
	fscanf(fp,"imagenum %d\n",imagenum);								// 瓦片个数
	char imageindix[400]= {'\0'};
	char imagename[400]= {'\0'};
	char imagepath[400]= {'\0'};
	for(i=0; i<*imagenum; i++)
	{
		fscanf(fp,"%s\n",imageindix);
		strcpy(imagepath,dirpath);
		strcat(imagepath,next);
		strcat(imagepath,imageindix);
		loadimage(&pentable[i],imagepath,*pixnum,*pixnum,false);		// 批量导入贴图
	}
	fscanf(fp,"gamemapi %d gamemapj %d\n",gamemapi,gamemapj);
	for(i=0; i<*gamemapi; i++)
	{
		fscanf(fp,"%d",&map[i][j]);
		for(j=0; j<*gamemapj; j++)
		{
			fscanf(fp," %d",&map[i][j]);
		}
		fscanf(fp,"\n");
	}
	fclose(fp);
}
// 检查边界
inline void checkboundary(int* bkgameleft,int* bkgametop,int gamelimitright,int gamelimitbuttom,int* bkmeshgameleft,int* bkmeshgametop,int* meshlimitright,int*meshlimitbuttom,int move,int size)
{
	if(*bkgameleft<0)													// 网格越界检测并调整
		*bkgameleft=0;
	else if(*bkgameleft>gamelimitright)
		*bkgameleft=gamelimitright;
	if(*bkgametop<0)
		*bkgametop=0;
	else if(*bkgametop>gamelimitbuttom)
		*bkgametop=gamelimitbuttom;
	if(*bkgameleft < *bkmeshgameleft)									// 更新游戏采样区坐标,一些简单换算,由于频繁调用函数在这里产生了明显的卡顿影响,所以这里就不再封装成函数
	{
		*bkmeshgameleft -=move;
		*meshlimitright=*bkmeshgameleft+size;
	}
	else if(*bkgameleft>*meshlimitright)
	{
		*meshlimitright += move;
		*bkmeshgameleft=*meshlimitright-size;
	}
	if(*bkgametop <*bkmeshgametop )
	{
		*bkmeshgametop -= move;
		*meshlimitbuttom=*bkmeshgametop+size;
	}
	else if(*bkgametop>*meshlimitbuttom)
	{
		*meshlimitbuttom += move;
		*bkmeshgametop=*meshlimitbuttom-size;
	}
}
// 性能:CPU 2% 占有率,峰值 1.20GHz,核显 GPU 10% 使用率
inline void animation(int characterflag,int bkgameleft,int bkgametop,int bkmeshgameleft,int bkmeshgametop,int bkmeshdeskleft,int bkmeshdesktop)
{
	static ExMessage m;
	static int i=1;
	static int x=10;
	static int y=10;
	if(i%3==0)
	{
		x=bkmeshdeskleft+bkgameleft-bkmeshgameleft;
		y=bkmeshdesktop+bkgametop-bkmeshgametop;
		i%=125;															// i 的上限 * 放大倍数 不超过 255
		switch(characterflag)											// 选取颜色
		{
			case 1:
				setfillcolor(RGB(i*2,i*2,0));
				break;
			case 2:
				setfillcolor(RGB(i*2,0,i*2));
				break;
			case 3:
				setfillcolor(RGB(i*2,0,0));
				break;
			case 4:
				setfillcolor(RGB(0,i*2,0));
				break;
			case 5:														// 如果没有按键,则使用默认颜色
				setfillcolor(RGB(0,0,i*2));
				break;
		}
		fillrectangle(x,y,x+30,y+30);
	}
	i++;
}
相关推荐
三品吉他手会点灯10 小时前
STM32 VSCode 开发-C语言程序运行后,终端中文乱码
c语言·ide·笔记·vscode·stm32·学习·编辑器
李李李勃谦11 小时前
Vue3 + Electron + OpenHarmony 跨平台实战:从架构设计到 Markdown 编辑器完整实现
javascript·华为·electron·编辑器·harmonyos
想拿大厂offer11 小时前
【Linux】编辑器、IDE 与操作系统:Linux 开发工具链的哲学与实践
linux·ide·编辑器
其实防守也摸鱼11 小时前
MarkText:开源免费的 Markdown 编辑器新星
笔记·pdf·编辑器·免费·工具·调试·可下载
念一不念二12 小时前
vscode中添加claude code插件
ide·vscode·编辑器
HealthScience12 小时前
vscode推荐使用的插件
ide·vscode·编辑器
蓉妹妹1 天前
vscode的各种使用场景
ide·vscode·编辑器
巴德鸟1 天前
DaVinci 常用技巧 关键帧 自动字幕 追踪 音频 冻结帧 快捷键 多轨道字幕 扩充边缘
android·编辑器·音视频·视频·davinci·davin
magrich1 天前
VSCode-reinstall-remote-extension备份重装vscodeextension
ide·vscode·编辑器
L-影2 天前
vscode安装SQLAlchemy步骤
ide·vscode·编辑器