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标准位,键盘不会被拖拽打断。
在数据计算,拖拽是第一位的,在贴图层面,绘制,和键盘移动是第一位的,这样可以实现优先级随指变化而变化。


2. 游戏地图瓦片

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

性能优化:
用中间变量暂存上一次绘制,如 old 系列,避免重复绘制
用中间变量暂存当前范围,减少比较之前的计算,把计算结果暂存,如边界检查,常数替换计算式
使用两倍范围,一倍移动,把检测点困住在九宫格中心
使用switch case 实现一次分支解决命令。
使用if else if 二分支,节约运算,如边界,或者某个方向的坐标增减。
使用多个标志位和组合坐标当作检测点,实现数据在三个模块的传输。

#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++;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/387705.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习3----决策树

这是前期准备 import numpy as np import pandas as pd import matplotlib.pyplot as plt #ID3算法 #每个特征的信息熵 # target : 账号是否真实&#xff0c;共2种情况 # yes 7个 p0.7 # no 3个 p0.3 info_D-(0.7*np.log2(0.7)0.3*np.log2(0.3)) info_D #日志密度…

华为23年9月笔试原题,巨详细题解,附有LeetCode测试链接

文章目录 前言思路主要思路关于f函数的剖析Code就到这&#xff0c;铁子们下期见&#xff01;&#xff01;&#xff01;&#xff01; 前言 铁子们好啊&#xff01;今天阿辉又给大家来更新新一道好题&#xff0c;下面链接是23年9月27的华为笔试原题&#xff0c;LeetCode上面的ha…

- 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》

本文属于专栏《构建工业级QPS百万级服务》​​​​​ 1、前置知识 c的内存管理&#xff0c;主要说的是堆内存管理。现代计算机系统中&#xff0c;用户进程的堆内存&#xff0c;由内核映射。 堆内存的来源 主要是通过mmap()函数&#xff0c;在进程的虚拟地址空…

【Linux技术宝典】深入理解Linux基本指令:命令行新手指南

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构冒险记 ✅Linux技术宝典 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 一、Linux下基本指令1. ls 指令2. pwd指令3. clear指令4. cd指令什么是家目录&#xf…

【AI视野·今日Robot 机器人论文速览 第七十八期】Wed, 17 Jan 2024

AI视野今日CS.Robotics 机器人学论文速览 Wed, 17 Jan 2024 Totally 49 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Safe Mission-Level Path Planning for Exploration of Lunar Shadowed Regions by a Solar-Powered Rover Authors Olivier L…

【Pandas 统计函数和自定义函数的使用】

文章目录 前言一、统计函数1. 描述性统计2. 直方图 二、自定义函数1. 自定义函数示例 总结 前言 Pandas 是基于 NumPy 的数据分析工具&#xff0c;它提供了各种数据结构&#xff0c;如 Series 和 DataFrame&#xff0c;以及各种功能强大的函数&#xff0c;用于数据的统计、清洗…

随机过程及应用学习笔记(四) 马尔可夫过程

马尔可夫过程是理论上和实际应用中都十分重要的一类随机过程。 目录 前言 一、马尔可夫过程的概念 二、离散参数马氏链 1 定义 2 齐次马尔可夫链 3 齐次马尔可夫链的性质 三、齐次马尔可夫链状态的分类 四、有限马尔可夫链 五、状态的周期性 六、极限定理 七、生灭过…

Android adb使用超级大全

Android adb使用超级大全 ADB&#xff0c;即Android Debug Bridge&#xff0c;是一款强大的工具&#xff0c;对于Android开发/测试人员来说是不可或缺的&#xff0c;同时也是Android设备玩家的好玩具。本文将详细介绍ADB的使用方法。 ADB的基本用法如下&#xff1a; 命令语法…

chatglm3-6b使用

源码地址 GitHub - THUDM/ChatGLM3: ChatGLM3 series: Open Bilingual Chat LLMs | 开源双语对话语言模型 创建环境 conda create -n chatglm36 python3.11.7 修改源码中依赖&#xff0c;使得使用cuda&#xff0c;否则太慢了 pip3 install torch2.1.2 torchvision0.16.2 to…

SpringBoot3 + Vue3 由浅入深的交互 基础交互教学

说明&#xff1a;这篇文章是适用于已经学过SpringBoot3和Vue3理论知识&#xff0c;但不会具体如何实操的过程的朋友&#xff0c;那么我将手把手从教大家从后端与前端交互的过程教学。 目录 一、创建一个SpringBoot3项目的和Vue3项目并进行配置 1.1后端配置: 1.1.1applicatio…

C语言第二十四弹---指针(八)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 指针 1、数组和指针笔试题解析 1.1、字符数组 1.1.1、代码1&#xff1a; 1.1.2、代码2&#xff1a; 1.1.3、代码3&#xff1a; 1.1.4、代码4&#xff1a; 1…

StringJoiner

JDK8开始有&#xff0c;用来操作字符串&#xff0c;不仅可以提高字符串的操作效率&#xff0c;而且在某些场景使用它操作字符串&#xff0c;代码会更加简洁。 import java.util.StringJoiner;public class Test {public static void main(String[] args) {//StringJoiner的应用…

ChatGPT高效提问—prompt实践(漏洞风险分析-重构建议-识别内存泄漏)

ChatGPT高效提问—prompt实践&#xff08;漏洞风险分析-重构建议-识别内存泄漏&#xff09; 1.1 漏洞和风险分析 ChatGPT还可以帮助开发人员预测代码的潜在风险&#xff0c;识别其中的安全漏洞&#xff0c;而不必先运行它&#xff0c;这可以让开发人员及早发现错误&#xff0…

探索设计模式的魅力:创建型设计模式的比较与决策

设计模式专栏&#xff1a;http://t.csdnimg.cn/U54zu 目录 一、设计模式概览 1.1 创建型模式 二、比较创建型设计模式 1.1 适用场景典型用例 1.2 关键要素与差异对比 1.3 结构图 三、模式选择指南 3.1 场景分析 3.2 决策流程图 四、结语 4.1 优势 4.2 考量因素 一、…

【漏洞扫描】网络空间安全工具—Goby 快速入门使用指南

下载地址 Goby&#xff08;含1322个POC&#xff09; v2.8.9 社区版 介绍 Goby是一款基于网络空间测绘技术的新一代网络安全工具&#xff0c;它通过给目标网络建立完整的资产知识库&#xff0c;进行网络安全事件应急与漏洞应急。 Goby可提供最全面的资产识别&#xff0c;目前…

串行通信的艺术:深入解析UART与奇偶校验

发送数据位是电流传输吗&#xff1f; 在UART&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;通信中&#xff0c;发送数据位不直接以电流的形式传输。而是通过改变电压水平或者光信号&#xff08;在光纤通信中&#xff09;来表示不同的数据位&#xff08…

C#利用接口实现选择不同的语种

目录 一、涉及到的知识点 1.接口定义 2.接口具有的特征 3.接口通过类继承来实现 4.有效使用接口进行组件编程 5.Encoding.GetBytes(String)方法 &#xff08;1&#xff09;检查给定字符串中是否包含中文字符 &#xff08;2&#xff09;编码和还原前后 6.Encoding.GetS…

[计算机网络]---网络编程套接字

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、基础知识…

数仓建模—数据网格

数据网格 随着数字化时代的到来,近几年数据领域的新技术概念不断涌现,无论是数据湖、湖仓一体、流批一体、存算一体、数据编织抑或数据网格,很多还爬上了Gartner曲线,其中数据网格备受关注,数据网格从字面意思来看挺抽象的,会劝退很多人,但当你深入去理解这个概念时,才…

数据存储以及内存

数据在内存中的存储是因不同的类型而不同的。 但首先我们需要知道的是&#xff0c;在C语言中&#xff0c;数据在内存中的存储是以变量的形式存储的。每个变量都有一个地址&#xff0c;指向内存中的特定位置。变量的值存储在这个地址对应的内存单元中。不同类型的变量在内存中占…