实现左上角的固定视口但是网格以图片中心放大缩小

news2024/12/25 8:51:11

 仅仅修改了showbk() 函数部分,增加bkv4 直接采样,然后粘贴到左上角,实现多余部分裁剪,形成视口内放大缩小

 

 


// 程序:2D RPG 地图编辑器与摄像机追随
// 作者:@bilibili 民用级脑的研发记录
// 邮箱:1309602336@qq.com
// 编译环境:小熊猫C++2.25.1/Devc++/VC 2010/Visual Studio 2022,EasyX_20220901/Easyx_2023 大暑版
// 编写日期:-2024-2-22 2024-4-14至2024-4-22 2024-4-26-2024-4-27
//
//  GCC MinGW-w64  链接选项增加 -leasyx -lgdi32  因为使用了 wingdi.h 的 StrctchBlt() 进行缩放 bk 变量
#undef UNICODE
#undef _UNICODE
#pragma warning(disable : 4996)		// VS2022 对某些函数警告,但是为了方便移植,就无视这些警告 这样 Devc++ VC2010 VS2022 都能跑
//#include <graphics.h>
#include <easyx/graphics.h>			// 移植到小熊猫 C++2.25.1 后,查找 MinGW 的bin 文件下的easyx文件里的 graphics.h
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <direct.h>
float camerasize = 0.5;						// 设置放大倍数
//30.使用时发现像素putimage刷新界面未修改,pentable系列问题如瓦片重叠部分,30的瓦片放在20的网格导致的覆盖
// 2024.4.22 修改——以bkmeshcopywidth,替换边界,以bkhieght替换鼠标按键的if边界检测与视口大小,原始数据是 30像素正方形瓦片边长,2708270 的原始视口与 3*270宽高的缓存区 。如今已自适应,可以多种像素边长
// 从默认文件夹中读取自定义图片
void loadfile(IMAGE*** bentablev2, int pixnum);
// 加载自定义图片
void loadfile_scanf(char* dirname, char* next, char* kind, IMAGE** pentablev2, int pixnum);
// 保存自定义文件
void savepersonalfile(char* DLC, IMAGE*** pentablev2);
// 保存自定义瓦片贴图
void savelocal(char* DLC, char* dirname, char* next, char* kind, char* sign, IMAGE** files);
// 实时渐变色小动画
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 bkmeshcopywidth, int bkmeshcopyheight);
// 保存游戏地图贴图数据
void savegamemap(int** map, int gamemapi, int gamemapj, IMAGE* pentable, IMAGE*** pentablev2, 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:一个映射块的边长,单位:像素。defaultnum,默认未绘制网格的标号,defaultcolor 未绘制网格的颜色
inline void freshmesh(int** oldmesh, IMAGE* bkmesh,  int** map, IMAGE* pentable, IMAGE*** pentablev2, int bkmeshmapi, int bkmeshmapj, int bkmeshfreshi, int bkmeshfreshj, int pixnum, int defaultnum, int defaultcolor) {
	int kind = 0;											// 存储代号第一位:类型
	int number = 0;											// 存储代号第二位:序列号
	int pennumber = -1;										// 暂存每一次循环的映射代号
	IMAGE* pen = NULL;										// 所找到的纹理
	int left = 0;											// 这是每次循环所找到的纹理对应映射地址
	int top = 0;
	SetWorkingImage(bkmesh);								// 设置绘图目标为游戏背景采样区,刷新采样区,刷新寄存区
//	cleardevice();											// 当时没注意导致一直重绘制,并且当时原版cleardevice失效,数组功能不确定。却由此能跑项目,可知当时问题根源不定导致的修改困难
	int sidei = bkmeshmapi + bkmeshfreshi;					// 计算变量,每次循环就不用再次计算了,表达式在for循环中会一直重复计算,用side替换for的表达式
	int sidej = bkmeshmapj + bkmeshfreshj;

	for (int i = bkmeshmapi; i < sidei; i++) {
		left = 0;
		for (int j = bkmeshmapj; j < sidej; j++) {
			// 注意数组越界
			pennumber = map[i][j];							// 读取游戏大地图数组序号
			if (oldmesh[i - bkmeshmapi][j - bkmeshmapj] == pennumber && pennumber == defaultnum) {						// 性能优化,如果相同位置瓦片序号相同,就不用重新绘制了,即缓存区只选择部分像素重新绘制
				// 2024.4.22 重整,增加pennumber==defaultnum,只对空的瓦片不绘制,因为之后有的瓦片绘制一次会有残留直线边界,所以需要重绘解决
			} else {
				oldmesh[i - bkmeshmapi][j - bkmeshmapj] = pennumber;
				if (pennumber == defaultnum) {
					setfillcolor(BGR(defaultcolor));
					fillrectangle(left, top, left + pixnum, top + pixnum);				// 修正,如果是rectangle则会有黑色不完全打印
				} else {
					kind = pennumber / 10 - 6;						// 剥离第一位
					number = pennumber % 10;						// 剥离最后一位
					if (pennumber < 10)
						pen = &pentable[pennumber];
					else
						pen = pentablev2[kind][number];				// 根据序号查找对应贴图
					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 bkwidth, int bkheight, int pixnum) {
	SetWorkingImage(bkmesh);
	getimage(bk, gamex - bkmeshmapj * pixnum, gamey - bkmeshmapi * pixnum, bkwidth, bkheight);		// 计算去除平移多个bkmeshmapj对应的像素距离,从刚刚绘制好的采样区取样,刷新游戏背景寄存区。
	SetWorkingImage();
}
// 在屏幕显示截图
//inline void showbk(IMAGE* bk, int bkdeskx, int bkdesky) {
//	SetWorkingImage();
//	putimage(bkdeskx, bkdesky, bk);
//}
//https://qa.codebus.cn/question/1318
//inline void showbk(IMAGE* bk, int bkdeskx, int bkdesky) {
	SetWorkingImage();
	putimage(bkdeskx, bkdesky, bk);
//	static HDC imgDC = GetImageHDC(bk);					// 获取源图像的 DC
//
 主窗口作为目标位置
//	static HDC mainDC = GetImageHDC(NULL);				// 获取主窗口的 DC
//	SetStretchBltMode(mainDC, STRETCH_HALFTONE);		// 设置拉伸贴图模式为抗锯齿,性能差,效果高
	SetStretchBltMode(mainDC, COLORONCOLOR);		// 设置拉伸贴图模式为不抗锯齿,贴图模式为临近颜色,效果差,性能高
//
//	// 将源图像贴图到目标位置
//	StretchBlt( mainDC,															// 目标 DC
//		bkdeskx + bk->getwidth() / 2 - bk->getwidth() / 2 * camerasize,
//		bkdesky + bk->getheight() / 2 - bk->getheight() / 2 * camerasize,		// 目标位置的坐标
//		bk->getwidth()*camerasize, bk->getheight()*camerasize,					// 目标位置的长宽
//		imgDC,																	// 源 DC
//		0, 0,																	// 源位置的坐标
//		bk->getwidth(), bk->getheight(),										// 源位置的长宽
//		SRCCOPY);																// 贴图的光栅模式
//}
// 版本3,增加粘贴到裁剪区v3然后再采样
inline void showbk(IMAGE* bk, int bkdeskx, int bkdesky) {
//	SetWorkingImage();
//	putimage(bkdeskx, bkdesky, bk);
	static HDC imgDC = GetImageHDC(bk);					// 获取源图像的 DC
//	static IMAGE* bkv2  = new IMAGE(bk->getheight(), bk->getwidth());								// 暂存的游戏压缩地图
//	static IMAGE* bkv3 =new IMAGE (bk->getheight()*camerasize,bk->getwidth()*camerasize);			// 游戏压缩地图部分采样,即采样区
//	static HDC mainDCv2 = GetImageHDC(bkv2);
// 主窗口作为目标位置
	static HDC mainDC = GetImageHDC(NULL);				// 获取主窗口的 DC
	SetStretchBltMode(mainDC, STRETCH_HALFTONE);		// 设置拉伸贴图模式为抗锯齿,性能差,效果高
//	SetStretchBltMode(mainDC, COLORONCOLOR);			// 设置拉伸贴图模式为不抗锯齿,贴图模式为临近颜色,效果差,性能高

	// 将源图像贴图到目标位置
	StretchBlt( mainDC,															// 目标 DC
	            bkdeskx + bk->getwidth() / 2 - bk->getwidth() / 2 * camerasize,
	            bkdesky + bk->getheight() / 2 - bk->getheight() / 2 * camerasize,		// 目标位置的坐标
	            bk->getwidth()*camerasize, bk->getheight()*camerasize,					// 目标位置的长宽
	            imgDC,																	// 源 DC
	            0, 0,																	// 源位置的坐标
	            bk->getwidth(), bk->getheight(),										// 源位置的长宽
	            SRCCOPY);																// 贴图的光栅模式

//	StretchBlt( mainDCv2,																// 目标 DC
//	            0, 0,																	// 目标位置的坐标
//	            bk->getwidth()*camerasize, bk->getheight()*camerasize,					// 目标位置的长宽
//	            imgDC,																	// 源 DC
//	            0, 0,																	// 源位置的坐标
//	            bk->getwidth(), bk->getheight(),										// 源位置的长宽
//	            SRCCOPY);
//
//	SetWorkingImage(bkv2);
//	getimage(bkv3,bk->getheight()/2-bkv3->getheight()/2,bk->getwidth()/2-bkv3->getwidth()/2,bkv3->getheight(),bkv3->getwidth());
//	SetWorkingImage();
//	putimage(bkdeskx,bkdesky,bkv3);

	static IMAGE* bkv4 = new IMAGE(250, 250);

	getimage(bkv4,
	         bkdeskx +  bk->getwidth() / 2 - bkv4->getwidth() / 2,						// bkdeskx =100,bk->getwidth=300,bkv4->getwidth=250,计算新的图片左上角坐标在窗口的位置
//		    bkdeskx + bk->getwidth()/2 是图片中心在窗口的坐标,无论怎样放缩,图片中心在窗口的坐标不变
//		bkdeskx + bk->getwidth()/2 -bkv4->getwidth() 是找到距离图片中心坐标半个图片宽度,就是左上角坐标的 x
	         bkdesky + bk->getheight() / 2 - bkv4->getheight() / 2,
//	         bkdesky + bk->getheight() / 2 是图片中心在窗口的坐标,无论怎样放缩,图片中心在窗口的坐标不变
//		bkdesky + bk->getheight()/2 -bkv4->getheight()/2 是找到距离图片中心坐标半个图片宽度,就是左上角坐标的 y
	         bkv4->getheight(), bkv4->getwidth());

	putimage(0, 0, bkv4);
}
// 在屏幕上显示缓冲区
inline void showbkmesh(IMAGE* bkmesh, int bkmeshdeskx, int bkmeshdesky) {
	SetWorkingImage();
	putimage(bkmeshdeskx, bkmeshdesky, bkmesh);
}
// 初始化游戏地图
int** initmap(int wide, int high, int defaultnum) {
	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] = defaultnum;					// 初始化游戏大地图 map 的参数,参数 1 默认黑色
		}
	}
	return map;
}
// 高频调用函数统一为 inline 后就地展开,避免频繁调用函数需要新建地址等等导致的时间消耗大,程序运行慢,内存占用高
// 以下函数是摄像机追随玩家系统,bkmesh部分引用之前的checkboundary函数,也就是说,仅仅是摄像机的移动数据来源变化,摄像机在缓冲区的移动检测还是原来的代码 2024-4-27 8:16 记录本行注释
// 硬写函数 checkboundaryv2, bkfollowplayer ,bklocalplayer,发现可以归类成——1.玩家在摄像机外,2.玩家在采样区里,3.玩家在采样区里移动,摄像机追随玩家,4.玩家在采样区里静止,相机追随玩家,5.相机和玩家都不移动
// 玩家边界检测用于控制采样区移动,此时可以理解成玩家拖着摄像机移动,参考自键盘控制移动的 flag_x,flag_y ,不过就是之前键盘控制采样区移动,这次玩家控制采样区移动,flag_x,flga_y被其他参数控制
inline void checkboundaryv2(int* crossflag_x, int* crossflag_y, int bkgameleft, int bkgametop,
                            int playerleft, int playertop, int playerright, int playerbottom,
                            int playerlimitleft, int playerlimittop, int playerlimitright, int playerlimitbottom) {

	// 选择 playerleft作为左上角是因为没来得及思考其他的先实现追随一个再说,现在看来playerleft可以增加参数来控制图片相对于playerleft打印的位置,于是就用playerleft作为相机追随点,即相机中心是playerleft左上角顶点
	if (playerleft < bkgameleft + playerlimitleft ) {
		*crossflag_x = -1;												// 玩家距离摄像机边框过窄,允许摄像机的左右移动被控制
	} else if ( playerleft > bkgameleft + playerlimitright) {
		*crossflag_x = 1;												//玩家距离摄像机边框过宽,允许摄像机的左右移动被控制
	} else {
		*crossflag_x = 0;
	}
	if (playertop < bkgametop + playerlimittop ) {
		*crossflag_y = -1;												// 玩家距离摄像机边框过高,允许摄像机的上下移动被控制
	} else if ( playertop > bkgametop + playerlimitbottom) {
		*crossflag_y = 1;												// 玩家距离摄像机边框过低,允许摄像机的上下移动被控制
	} else {
		*crossflag_y = 0;
	}
}
// 发现可以参考 593 行的按键移动对地图的控制,把checkboundary 的增减平衡重新处理,形成 bkfollowplayer
inline void bkfollowplayer(int* bkgameleft, int* bkgametop, int crossflag_x, int crossflag_y, int* speedv4_x, int *speedv4_y) {
	if (crossflag_x > 0) {													// 玩家距离摄像机边框过宽,相机追随右边玩家
		*bkgameleft += *speedv4_x;
	} else if (crossflag_x < 0) {											//玩家距离摄像机边框过窄,相机追随左边玩家
		*bkgameleft -= *speedv4_x;
	}
	if (crossflag_y > 0) {													// 玩家距离摄像机边框过低,相机追随下边玩家
		*bkgametop += *speedv4_y;
	} else if (crossflag_y < 0) {											// 玩家距离摄像机边框过高,相机追随上边玩家
		*bkgametop -= *speedv4_y;
	}
}
// 摄像机重新定位玩家,即追上玩家之后,相机还得移动,让相机中心成为玩家中心
// 速度控制在和其他函数放一起写之后,觉得涉及的参数情况多,就单开一个速度控制函数,形成速度控制中心,于是就从定位玩家部分剥离速度调节
inline void bklocalplayer(int* bkgameleft, int* bkgametop, int bkheight, int bkwidth, int playerleft, int playertop, int crossflag_x, int crossflag_y, int* speedv4_x, int* speedv4_y) {
	static int width = bkwidth / 2;									// 暂存数据,减少除法运算
	static int height = bkheight / 2;
	if (width != bkwidth / 2)	width = bkwidth / 2;				// 如果边界变化就更新,为后续可控摄像机中心代码做准备
	if (height != bkheight / 2)	 height = height / 2;
	if (*bkgameleft + width > playerleft) {							// 相机中心超过玩家,就倒退回去
		(*bkgameleft) -= *speedv4_x;
	} else if (*bkgameleft + width < playerleft) {					// 相机中心没超过玩家,就追过去
		(*bkgameleft) += *speedv4_x;
	}
	if (*bkgametop + height > playertop) {							// 相机中心超过玩家,就倒退回去
		(*bkgametop) -= *speedv4_y;
	} else if (*bkgametop + height < playertop) {					// 相机中心没超过玩家,就追过去
		(*bkgametop) += *speedv4_y;
	}
}
// 速度控制中心,调节速度 2024-4-27更新 从speedv2 无中生有到 speedv4_x speedv4_y,然后对应上面的speedv2 也按图索骥一样去拆分
inline void checkspeedv2(int* speedv4_x, int* speedv4_y, int crossflag_x, int crossflag_y, int bkgameleft, int bkgametop, int bkheight, int bkwidth, int playerleft, int playertop) {
	static int cnt = 0;														// 限制速度过快增加
	static int time = 10;													// 几次循环增加一次速度
	static int width = bkwidth / 2;											// 暂存数据,减少除法运算
	static int height = bkheight / 2;
	if (width != bkwidth / 2)	width = bkwidth / 2;						// 如果边界变化就更新,为后续可控摄像机中心代码做准备
	if (height != bkheight / 2)	 height = height / 2;
	if (cnt % time == 0) {
		if (crossflag_x != 0)
			*speedv4_x += 1;
		else {
			if ((bkgameleft + width - playerleft) >= -10 && (bkgameleft + width - playerleft) <= 10)				// 玩家没超过边界,但是玩家距离屏幕距离大于10像素,相机就加快追赶
				*speedv4_x = 1;
			else if (bkgameleft + width - playerleft == 0)															// 玩家整好在摄像机中心,停止追赶,和平共处
				*speedv4_x = 0;
		}
	}

	if (cnt % time == 0) {															// 减少比较,因为都有这一步,提出来发现不出bug就这样不放回去了
		if (crossflag_y != 0)
			*speedv4_y += 1;
		else {
			if ((bkgametop + bkheight / 2 - playertop) >= -10 && (bkgametop + bkheight / 2 - playertop) <= 10)
				*speedv4_y = 1;
			else if (crossflag_y == 0 && (bkgametop + bkheight / 2 - playertop) == 0)
				*speedv4_y = 0;
		}
	}

	if (cnt >= 999) {																// 防止加爆导致游戏退出
		cnt = 0;
	}
	cnt++;
}
// 在游戏背景中打印玩家
inline void showplayer(IMAGE* bk, IMAGE* player, int bkgameleft, int bkgametop, int playerleft, int playertop) {
	SetWorkingImage(bk);
	putimage(playerleft - bkgameleft, playertop - bkgametop, player);
	SetWorkingImage();
}

int main() {
	// 2024-4-24 新增玩家控制,自复制粘贴的临门一脚直接粘贴距离参数发现能跑,于是另起炉灶,开始掺入玩家数据

	int playerleft;							// 玩家左上角坐标
	int playertop;
	int playerright;						// 玩家右下角坐标
	int playerbottom;
	int playerheight;						// 玩家高度
	int playerwidth;						// 玩家宽度
	int playerlimitleft;					// 玩家距离采样区左上角过窄的距离。用玩家推动采样区移动,攒够距离到达极限地图跟随移动,即原来是直接移动地图,现在是等一些距离再移动
	int playerlimittop;						// 玩家距离采样区左上角过宽的距离
	int playerlimitright;					// 玩家距离采样区左上角过低的距离
	int playerlimitbottom;					// 玩家距离采样区左上角过高的距离
	int crossflag_x;						// 控制层级击穿,1 时代表控制地图左右移动
	int crossflag_y;						// 控制层级击穿,1 时代表控制地图上下移动
	int cameraleft;							// 暂时记录玩家位置,可用于重新设置初始的游戏起点不再是默认左上角
	int cameratop;
	int speedv4_x;								// 出BUG 发现相机移动如果横纵坐标相同就是对角线45°移动,由不同时间或者移动方向切换时出现的不如意,自然而然意识到新的不能受共用影响的速度,于是差分原来的speedv2的控制相机移动功能
	int sppedv4_y;								// speedv2中控制相机移动速度,单独存储,这样speedv2变化,相机移动速度可以不变化,实现相机移动速度不同
	playerleft = 500;
	playertop = 500;
	playerheight = 100;
	playerwidth = 100;
	playerright = playerleft + playerwidth;
	playerbottom = playertop + playerheight;
	playerlimitleft = 60;							// 不要超过采样区的宽度
	playerlimitright = 360;							// 不要超过采样区的宽度
	playerlimittop = 60;
	playerlimitbottom = 360;
	crossflag_x = 0;
	crossflag_y = 0;
	speedv4_x = 0;
	sppedv4_y = 0;
	cameraleft = playerleft;					// 默认一开始玩家坐标就是采样区左上角坐标
	cameratop = playertop;						// 默认一开始玩家坐标就是采样区左上角坐标
	IMAGE* player = new IMAGE(playerwidth, playerheight);
	SetWorkingImage(player);
	setfillcolor(RED);
	fillrectangle(-1, -1, playerwidth, playerheight);
	SetWorkingImage();

	initgraph(1640, 980, 1);
	setbkcolor(GREEN);
	setlinecolor(WHITE);
	cleardevice();
	// 这里是当时重整的起点,复制粘贴这部分以前的变量去改写 ,写一块新的就注释一块旧的
	int defaultnum;								// 地图初始化时,默认空地图的序号
	defaultnum = 9999;
	int defaultcolor = 0x00CCCCCC;				// 00 是完全不透明,FF是完全透明00-FF刚好是0-255。 ARGB 0x 00: alpha + 00: red + 00: green + 00: blue ,但是绘制时 setfillcolor的格式是 BGR,所以使用时需要 BGR()转换。
	int pixnum;
	pixnum = 30;
	// 规定上限参数
	// 进行初始化,规定各位置具体数字
	int bkmeshMaxwidth;							// 缓冲区自适应宽度,用于不同pixnum 下缓存九宫格的边界适应
	int bkmeshMaxheight;						// 缓冲区自适应高度,用于不同pixnum 下缓存九宫格的边界适应
	int mapMaxwidth;							// 游戏地图自适应高度网格数,用于选取最接近上限的网格数,用于不同的 pixnum 下游戏网格数目是九宫格的每一格的整数倍
	int mapMaxheight;
	int bkmeshcopyMaxheight;					// 自适应九宫格其中的一格格子的宽高上限。
	int bkmeshcopyMaxwidth;
	int bkMaxwidth;								// 用于限制窗口大小 ,这是在窗口可以拖住拉伸之后用的变量
	int bkMaxheight;							// 用于限制窗口大小 ,这是在窗口可以拖住拉伸之后用的变量
	// 上限的一种参考
	bkmeshcopyMaxheight = 1000;					// 测试当宫格小于游戏视口时,绘制的边界检测
	bkmeshcopyMaxwidth = 1000;
	bkMaxheight = bkmeshcopyMaxheight;
	bkMaxwidth = bkmeshcopyMaxwidth;
	bkmeshMaxwidth = bkmeshcopyMaxwidth * 3;						// 宽的上限界限是 九宫格其中一格宽的三倍像素
	bkmeshMaxheight = bkmeshcopyMaxheight * 3;						// 高的上限界限是 九宫格其中一格高的三倍像素
//	mapMaxheight=bkmeshMaxheight*100;								// 游戏地图高度的像素上限是九宫格的高度的300倍
//	mapMaxwidth=bkmeshMaxwidth*100;									// 游戏地图宽度的像素上限是九宫格的宽度的300倍
//	mapMaxheight=bkmeshMaxheight*1;									// 测试发现边界闪退,越界数组刷新发现。游戏地图高度的像素上限是九宫格的高度的1倍
//	mapMaxwidth=bkmeshMaxwidth*1;									// 游戏地图宽度的像素上限是九宫格的宽度的1倍
	mapMaxheight = bkmeshMaxheight * 2;								// 测试发现边界闪退,越界数组刷新发现。游戏地图高度的像素上限是九宫格的高度的2倍
	mapMaxwidth = bkmeshMaxwidth * 2;
	// 实际自适应于瓦片的大小,用于保证不同的 background bk 都能在九宫格的每个宫格边缘切换地图时不错位。
	int bkheight;												// 2024.4.21修改,发现需要适配屏幕高度,增大面积,新增变量替换常数 270 ,先原样替换,验证替换正常,再继续改数据
	int bkwidth;
//	bkheight=370;
//	bkwidth=370;
	bkheight = 470;												// 如果小于一个宫格的最大
	bkwidth = 470;
	if (bkMaxheight < bkheight || bkMaxwidth < bkwidth) {
		printf("测试阶段:视口比单个宫格大,会在边界出现黑边情况\n");
	} else {
		printf("正常运行,视口比单个宫格小,不会在边界出现黑边情况\n");
	}

	IMAGE* bk;										// 背景图片寄存区
	bk = new IMAGE(bkwidth, bkheight);
	int bkmeshheight;
	int bkmeshwidth;
	int bkmeshcopywidth;							// 2024.4.21修改,增加copywidth,发现需要适配屏幕高度,增大面积,新增变量替换常数 270 ,先原样替换,验证替换正常,再继续改数据
	int bkmeshcopyheight;							// 九宫格的其中一格高度,采样越过九宫格左上四个块的边界,就进行更新九宫格,从原来的九宫格右边边界变成左边固定边界,实现不同大小的游戏背景 bk 不会因为边界检测距离变化而导致需要重新计算
	int bkmeshcopyi;								// 九宫格上下平移一次的网格个数,就是一格宫格竖着数有几个瓦片
	int bkmeshcopyj;								// 九宫格左右平移一次的网格个数,就是一格宫格横着数有几个瓦片
	int** map;
	int mapi;										// 读取 DLC 文件,需要变量记录循环次数,单位 瓦片贴图个数
	int mapj;
	int safemapi;									// 一个安全的贴图范围,避免阅读超过 10000 导致的越界闪退问题。
	int safemapj;
	int safemapright;								// 游戏右边界像素坐标,左边界坐标默认是0
	int safemapbottom;								// 游戏下边界像素坐标,上边界坐标默认是0
	int gamelimitright;								// 用于减少比较时的计算,存储游戏大地图边界范围
	int gamelimitbottom;
	int meshlimitright;								// 使用变量暂存边界,不用每次来回计算边界
	int meshlimitbottom;
	int bkmeshfreshi;								// 九宫格高有多少个瓦片,用于刷新缓存区时确定游戏网格宽的采样次数
	int bkmeshfreshj; 								// 九宫格宽有多少个瓦片,用于刷新缓存区时确定游戏网格高的采样次数
	// 开始自适应,各个存储区大小以及边界
	bkmeshcopyi = bkmeshcopyMaxheight / pixnum;		// 一格高度能按瓦片高度分,能分成几份,就是一格宫格横着数有几个瓦片
	bkmeshcopyj = bkmeshcopyMaxheight / pixnum;			// 一格宽度能按瓦片宽度分,能分成几份,就是一格宫格竖着数有几个瓦片
	bkmeshcopyheight = bkmeshcopyi * pixnum;			// 整除之后乘像素算出实际一格像素宽度,用于计算九宫格实际宽度
	bkmeshcopywidth = bkmeshcopyj * pixnum;
	bkmeshheight = bkmeshcopyheight * 3;				// 九宫格的像素高度
	bkmeshwidth = bkmeshcopywidth * 3;					// 九宫格的像素宽度
	bkmeshfreshi = bkmeshheight / pixnum;				// 计算九宫格横着一行有几个瓦片
	bkmeshfreshj = bkmeshwidth / pixnum;				// 计算九宫格竖着一列有几个瓦片
	meshlimitright = bkmeshcopywidth * 2;				// 计算 bk 采样到右边界时更新所需的边界,是像素坐标,checkboundary 会更新这个边界
	meshlimitbottom = bkmeshcopyheight * 2;				// 计算 bk 采样到下边界时更新所需的边界,是像素坐标,checkboundary 会更新这个边界
	mapi = mapMaxheight / bkmeshcopyheight * bkmeshcopyi;		// 计算实际游戏地图长有多少个瓦片,就是游戏最大高度整除瓦片高度,算出有几个宫格,然后在乘一个宫格竖着数瓦片个数
	mapj = mapMaxwidth / bkmeshcopywidth * bkmeshcopyj;			// 计算实际游戏地图宽有多少个瓦片,就是游戏最大宽度整除瓦片宽度,算出有几个宫格,然后在乘一个宫格横着数瓦片个数
	safemapi = mapi - 1;										// 绘制的时候确定下网格界限,数据-1是因为数组的第 0 个对应左边第一个瓦片,于是等同于数组下标是总数-1 ,初始化n个,数组对应序号 0~n-1
	safemapj = mapj - 1;										// 绘制的时候确定右网格边界,数据-1是因为数组的第 0 个对应左边第一个瓦片,于是等同于数组下标是总数-1 ,初始化n个,数组对应序号 0~n-1
	safemapbottom = mapi * pixnum - bkmeshcopyheight;			// 计算出采样区距离最后一格的像素距离,因为采样区的移动控制缓存区的移动,这样的设计导致只有采样区停止移动,缓存区才能停止移动,就需要把缓存区停止移动的位置换算成采样区停止移动的距离
	safemapright = mapj * pixnum - bkmeshcopywidth;				// 计算出采样区距离最后一格的像素距离,因为采样区的移动控制缓存区的移动,这样的设计导致只有采样区停止移动,缓存区才能停止移动,就需要把缓存区停止移动的位置换算成采样区停止移动的距离
	gamelimitright = safemapright;					// 换算出对应像素位置,就是玩家视口左上角坐标的极限,同样说明视口的最大不超过宫格,但实际上可以,只不过就是看到黑边但不闪退
	gamelimitbottom = safemapbottom;
	printf("mapj,i=%d,%d\n", mapj, mapi);
	printf("safemapj,safemapi %d,%d\n", safemapj, safemapi);
	printf("gamelimitrx,gamelimitry %d %d", gamelimitright, gamelimitbottom);

	map = initmap(mapj, mapi, defaultnum);			// 初始化游戏网格大地图!			j对应函数第一个参数,i对应第二个参数,否则会闪退
	IMAGE* bkmesh;									// 背景图片采样区
	bkmesh = new IMAGE(bkmeshwidth, bkmeshheight);
	int bkgameleft;								// 背景图片寄存区左上角坐标,是在游戏里的像素坐标。(0,0)可以理解为游戏大地图的左上角顶点。
	int bkgametop;
	int bkmeshgameleft;							// 背景图片采样区左上角坐标,是在游戏里的像素坐标。
	int bkmeshgametop;
	int bkmeshmapi;								// 背景图片采样区左上角所对应的 map 数组序号。从 map[0][0]开始,按照 map[i][j],其中 bkmeshmapi=bkmeshtop/pixnum
	int bkmeshmapj;
	bkgameleft = 0;								// 由于 bkgame 控制 mesh 坐标移动,(0,0)则游戏背景完全在当前采样区移动
	bkgametop = 0;
	bkmeshgameleft = 0;
	bkmeshgametop = 0;
	bkmeshmapi = bkmeshgametop / pixnum;		// 缓存区左上角所在的游戏网格的哪一行
	bkmeshmapj = bkmeshgameleft / pixnum;		// 缓存区左上角所在的游戏网格的哪一列
	int bkdeskleft;								// 规定在屏幕上显示游戏背景寄存区,此处记录其左上角在屏幕上的像素坐标
	int bkdesktop;
	int bkmeshdeskleft;							// 规定在屏幕上显示游戏背景采样区,此处记录其左上角在屏幕上的像素坐标
	int bkmeshdesktop;
	bkdeskleft = 200;							// 游戏背景左上角将会在屏幕的(200,200) 处
	bkdesktop = 200;
	bkmeshdeskleft = 700;						// 游戏背景缓冲区左上角将会在屏幕的(700,0)处
	bkmeshdesktop = 0;
	// 自此为止就是变量自适应结束
	int** oldmesh;								// 提出来的性能优化代码,用于freshmesh时刷新过滤相同的数字的瓦片,这样数字相同,瓦片相同,就不用绘制了
	oldmesh = new int*[bkmeshfreshi];
	for (int i = 0; i < bkmeshfreshi; i++) {
		oldmesh[i] = new int[bkmeshfreshj];
		for (int j = 0; j < bkmeshfreshj; j++) {
			oldmesh[i][j] = 999999;					// 正数
		}
	}

	int pentableleft;								// 忘了初始化调色盘了,这里设置调色盘左上角在屏幕的坐标
	int pentabletop;
	int imagenum;									// 调色板数量上限,用于文件读取控制循环次数
	IMAGE* pentable;								// 调色板其实就是贴图数组
	pentableleft = 0;								// 调色盘左上角将会在屏幕的(0,0)处
	pentabletop = 0;
	imagenum = 10;
	int pentablev2left;								// 自定义贴图插槽
	int pentablev2top;
	int pentablev2high;								// 自定义贴图的容量大小
	int pentablev2wide;
	pentablev2left = 10;
	pentablev2top = 300;
	pentablev2high = 10;
	pentablev2wide = 4;
	IMAGE*** pentablev2;
	pentablev2 = new IMAGE **[pentablev2wide];
	for (int i = 0; i < pentablev2wide; i++)
		pentablev2[i] = new IMAGE * [pentablev2high];
	for (int i = 0; i < pentablev2wide; i++)
		for (int j = 0; j < pentablev2high; j++)
			pentablev2[i][j] = NULL;
	loadfile(pentablev2, pixnum);							// 加载本地图片

// 调色盘加载数据,需要调整,可以加载任意大小的图片,然后切割成多个小图片然后导出。
// 绘制的时候,可以选中多个瓦片,一次绘制多个瓦片,mesh除以选中的网格数。画区分线,每四个画一个网格线。
// 瓦片设计的时候,需要一个再绘图板实现数组刷新。原图放缩小图笔刷
	pentable = new IMAGE[imagenum];
	for (int i = 0; i < imagenum; i++) {
		pentable[i] = IMAGE(pixnum, pixnum);				// 默认调色板也自适应像素高度
		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 + bkmeshfreshi; i++) {
		left = 0;
		for (int j = bkmeshmapj; j < bkmeshmapj + bkmeshfreshj; j++) {
			int pennumber = map[i][j];							// 读取游戏大地图数组序号
			if (pennumber == 9999) {
				rectangle(left, top, left + pixnum, top + pixnum);
			}
			left += pixnum;										// 往右移动,准备下一次绘制位置,
		}
		top += pixnum;											// 往下移动,准备下一次绘制位置
	}
	getimage(bk, bkgameleft, bkgametop, bkwidth, bkheight);		// 从刚刚绘制好的采样区取样,刷新游戏背景寄存区。
	//	开始往屏幕上绘图
	SetWorkingImage();															// 设置电脑屏幕为绘制对象
	for (int j = 0; j < 10; j++) {
		putimage(pentableleft + 10, pentabletop + j * pixnum, &pentable[j]); 	// 绘制绘图板
	}
	for (int i = 0; i < pentablev2wide; i++)									// 绘制自定义贴图,印自定义笔刷
		for (int j = 0; j < pentablev2high; j++)
			if (pentablev2[i][j] != NULL)
				putimage(pentablev2left + i * pixnum, pentablev2top + j * pixnum, pentablev2[i][j]);						// pixnum 替换 30
	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 flag_xv2;															// 记录当前加速,解决之前一个flag_x flag_y 既当标志位,又当速度,导致的速度的非线性变化没有错位控制时间
	int flag_yv2;
	int speed;																// 键盘控制视口的移动速度
	speed = 5;
	flag_x = 0;
	flag_y = 0;
	flag_xv2 = 0;
	flag_yv2 = 0;
	moveflag = 0;
	int mousex;																// 记录鼠标位置
	int mousey;
	mousex = 0;
	mousey = 0;
	int oldbkmeshgamex;														// 判断拖拽时是否需要刷新 bkmesh 网格
	int oldbkmeshgamey;
	oldbkmeshgamex = -1;
	oldbkmeshgamey = -1;
	int i = -1;																// 用于暂存 mesh 网格坐标
	int j = -1;
	int characterflag;														// 用于按键时游戏小人的颜色变化
	characterflag = 5;
	int cnt = 0;															// 同 bklocalplayer的控制速度增加
	int speeduptime = 20;
	ExMessage m;
	BeginBatchDraw();			// 2024-5-7								// 开启双缓冲,解决 strcechBit() 的卡顿问题
	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 + bkwidth && m.y < bkdesktop + bkheight) {	// 如果之前不是长按状态	且按下左键时,鼠标在游戏背景区域内
						drawflag = 1;																				// 记录为正在绘制的状态
						mousex = m.x;																				// 记录坐标用于绘制
						mousey = m.y;
					} else if (drawflag == 0 && draftflag == 0 && m.x > 0 && m.y > 10 && m.x < 40 && m.y < 300) {
						pentake = m.y / pixnum;																		// 选择贴图对应的代号
						printf("pentake: %d\n", pentake);
					} else if (drawflag == 0 && draftflag == 0 && m.x > 10 && m.y > 300 && m.x < 10 + pentablev2wide * 30 && m.y < 300 + pentablev2high * 30) {
						if (pentablev2[(m.x - pentablev2left) / pixnum][(m.y - pentablev2top) / pixnum] != NULL)
							pentake = ((m.x - pentablev2left) / pixnum + 6) * 10 + (m.y - pentablev2top) / pixnum;	// 计算 map 的二位数代号
						printf("pentakev2: %d\n", pentake);
					}
					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 + bkwidth && m.y < bkdesktop + bkheight) {
						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
							flag_xv2 = -1;
							characterflag = 1;
							break;
						case 0x57:						// W
							flag_yv2 = -1;
							characterflag = 2;
							break;
						case 0x44:						// D
							flag_xv2 = 1;
							characterflag = 3;
							break;
						case 0x53:						// S
							flag_yv2 = 1;
							characterflag = 4;
							break;
						case VK_F1:
							savegamemap(map, mapi, mapj, pentable, pentablev2, pixnum, imagenum);
							break;
						case VK_F2:
							loadgamemap(map, &mapi, &mapj, pentable, &pixnum, &imagenum);
							gamelimitright = pixnum * safemapi;
							gamelimitbottom = pixnum * safemapj;
							for (int i = 0; i < imagenum; i++) {				// 刷新绘图板颜色
								pentable[i] = IMAGE(pixnum, pixnum);
								SetWorkingImage(&pentable[i]);					// 给调色板绘制颜色
								setfillcolor(RGB(i * 20, i * 20, i * 20));		// 这里初始化调色盘的颜色
								fillrectangle(-1, -1, pixnum, pixnum);			// 在调色板上绘制颜色(纹理)要从 -1,-1 开始绘制,把边框画到外部,不保留边框。
							}
							SetWorkingImage();
							freshmesh(oldmesh, bkmesh, map, pentable, pentablev2, bkmeshmapi, bkmeshmapj, bkmeshfreshi, bkmeshfreshj, pixnum, defaultnum, defaultcolor);			// 刷新,重新映射,其实就是开头初始化的代码,这里是给了一个封装示例,用了一个数组过滤重复位置进行性能优化-2024.4.21
							freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, bkwidth, bkheight, pixnum);								// 2024.4.21修改
							showbk(bk, bkdeskleft, bkdesktop);
							showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);
							break;
					}
					break;
				case WM_KEYUP:
					switch (m.vkcode) {
						case 0x41:						// A
							flag_xv2 = 0;
							break;
						case 0x57:						// W
							flag_yv2 = 0;
							break;
						case 0x44:						// D
							flag_xv2 = 0;
							break;
						case 0x53:						// S
							flag_yv2 = 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;
				case WM_MOUSEWHEEL:
					camerasize = camerasize + m.wheel / 10000.0;
					printf("camera size= %f\n", camerasize);
					if		(camerasize < 0.001)		camerasize = 0.001;
					else if	(camerasize > 1)	camerasize = 1;

					break;
			}
		}

// 实现了阻尼
//		if (cnt > 999)		// 禁用,恢复按键就移动的控制方式
		cnt = 0;
		if (cnt % speeduptime == 0) {															// 控制速度慢慢增加
			if (flag_xv2 < 0 || (flag_xv2 == 0 && flag_x > 0))									// 减速有两种,一种是速度是负的,再减速,就是负的速度更快,另一种就是整的速度减速,变慢,是用于不按按键时侯自动停下
				flag_x -= 10;
			else if (flag_xv2 > 0 || (flag_xv2 == 0 && flag_x < 0))
				flag_x += 10;
			if (flag_yv2 < 0 || (flag_yv2 == 0 && flag_y > 0))
				flag_y -= 10;
			else if (flag_yv2 > 0 || (flag_yv2 == 0 && flag_y < 0))
				flag_y += 10;
			if (flag_x < -10)															// 限速
				flag_x = -10;
			else if (flag_x > 10)
				flag_x = 10;
			if (flag_y < -10)															// 限速
				flag_y = -10;
			else if (flag_y > 10)
				flag_y = 10;
		}
//		cnt++;

		if (flag_xv2 < 0)																	// 实现拖拽时键盘也能控制移动
			playerleft += flag_x;
		else if (flag_xv2 > 0)
			playerleft += flag_x;
		if (flag_yv2 < 0)
			playertop += flag_y;															// 实现拖拽时键盘也能控制移动
		else if (flag_yv2 > 0)
			playertop += flag_y;

		// 因为键盘控制移动flag_x既控制角色,又控制采样区,导致不出BUG,没有想象中效果明显
		// 2024-4-27 增加禁用
		// 此时清除flag_x,flag_y的速度信息,导致以flag_x flag_y为数据源的代码失效,就是按键不控制摄像机移动,并且原来与摄像机移动的相关检测也失效
		flag_x = 0;
		flag_y = 0;
		// 根据moveflag=1处的代码复制粘贴来,当时没有和玩家移动排列组合,就是当时没穷举到玩家移动边界检测,于是就没有放一块发现摄像机边界检测和摄像机追随玩家在边界检测上是相同的
		// 检测采样区是否超过缓冲区界限,是的话就刷新
		checkboundary(&bkgameleft, &bkgametop, gamelimitright, gamelimitbottom, &bkmeshgameleft, &bkmeshgametop, &meshlimitright, &meshlimitbottom, bkmeshcopyheight, bkmeshcopywidth);			// move =pixnum * 9, size = 2*9* pinxum
		bkmeshmapi = bkmeshgametop / pixnum;
		bkmeshmapj = bkmeshgameleft / pixnum;
		if (oldbkmeshgamex != bkmeshgameleft || oldbkmeshgamey != bkmeshgametop) {				// 判断是否更新采样区
			freshmesh(oldmesh, bkmesh, map, pentable, pentablev2, bkmeshmapi, bkmeshmapj, bkmeshfreshi, bkmeshfreshj, pixnum, defaultnum, defaultcolor);			// 刷新,重新映射,其实就是开头初始化的代码,这里是给了一个封装示例,用了一个数组过滤重复位置进行性能优化-2024.4.21
			oldbkmeshgamex = bkmeshgameleft;
			oldbkmeshgamey = bkmeshgametop;
			SetWorkingImage();																	// 2024-4-27 启用打印,解决不刷新问题,原来是没有粘贴到桌面,粘贴到其他地方去了
			showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);									// 2024-4-27 启用打印,只在刷新缓冲区时打印,其他时间不打印,这样另一个动画小图片轨迹就能记录
		}

		//		开始根据指令运行坐标变化
		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)
			bkgameleft = draftoldgamex - (mousex - draftoldmx) / camerasize;
// 因为图像缩小,地图移动距离都乘camerasize,放到视口,如果要和鼠标一起移动,
//			原来鼠标移动,到图片缩小,是乘以camerasize,即移动10像素,地图里移动 10*camerasize 像素,
//			如果地图移动10像素,同鼠标,则实际移动距离是 10/camerasize,这样在*camerasize 就是地图移动距离 则现在再放大回来									// bkgameleft-draftoldgamex=-(mousex-draftoldmx)
//			bkgametop = draftoldgamey - (mousey - draftoldmy);									// bkgametop-draftoldgamey=-(mousey - draftoldmy)
			bkgametop = draftoldgamey - (mousey - draftoldmy) / camerasize;									// bkgametop-draftoldgamey=-(mousey - draftoldmy)
			if (drawflag == 1 && mousex > bkdeskleft && mousey > bkdesktop && mousex < bkdeskleft + bkwidth && mousey < bkdesktop + bkheight) {	// 实现边拖拽边移动边绘制
//				drawx = bkgameleft + (mousex - bkdeskleft);										// drawx-bkgameleft=m.x-bkdeskleft	横坐标方向移动距离相同
//				drawy = bkgametop + (mousey - bkdesktop);										// drawy-bkgametop=m.y-bkdesktop	纵坐标方向移动距离相同
				drawx = bkgameleft + (mousex - (bkdeskleft + bkwidth / 2 - bkwidth / 2 * camerasize)) / camerasize;
//			bkdeskleft + bkwidth / 2 							是视口中心
//			- bkwidth / 2 * camerasize 							是往左缩小的图片长度
//			bkdeskleft + bkwidth / 2 - bkwidth / 2 * camerasize 是新的图片贴到的左上角坐标,因为要保持中心不变,所以计算中心,然后计算新的边角
//			drawy = bkgametop + (mousey - bkdesktop);										// drawy-bkgametop=mousey-bkdesktop	纵坐标方向移动距离相同
				drawy = bkgametop + (mousey - (bkdesktop + bkheight / 2 - bkheight / 2 * camerasize)) / camerasize;
				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 + bkwidth && mousey < bkdesktop + bkheight) {
			// 注意不要越界,否则 gamex 为负数,导致数组越界闪退。
			// 通过实现坐标变换与赋值达到修改游戏大地图(数组)
			moveflag = 0;
			drawoldmx = mousex;
			drawoldmy = mousey;
//			drawx = bkgameleft + (mousex - bkdeskleft);										// drawx-bkgameleft=mousex-bkdeskleft	横坐标方向移动距离相同
			drawx = bkgameleft + (mousex - (bkdeskleft + bkwidth / 2 - bkwidth / 2 * camerasize)) / camerasize;										// drawx-bkgameleft=mousex-bkdeskleft	横坐标方向移动距离相同
//			bkdeskleft + bkwidth / 2 							是视口中心
//			- bkwidth / 2 * camerasize 							是往左缩小的图片长度
//			bkdeskleft + bkwidth / 2 - bkwidth / 2 * camerasize 是新的图片贴到的左上角坐标,因为要保持中心不变,所以计算中心,然后计算新的边角
//			drawy = bkgametop + (mousey - bkdesktop);										// drawy-bkgametop=mousey-bkdesktop	纵坐标方向移动距离相同
			drawy = bkgametop + (mousey - (bkdesktop + bkheight / 2 - bkheight / 2 * camerasize)) / camerasize;										// drawy-bkgametop=mousey-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;															// 检测到是上一次绘制的瓦片,则不再刷新贴图与缓冲区。
			}
		}	//	对绘制进行分类计算数据,剥离特殊情况的重复绘制,仅仅是 flag_x,或者 flag_y 不为零时取消重复绘制判断
		else if (drawflag == 1 && mousex > bkdeskleft && mousey > bkdesktop && mousex < bkdeskleft + bkwidth && mousey < bkdesktop + bkheight) {
			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	纵坐标方向移动距离相同
			drawx = bkgameleft + (mousex - (bkdeskleft + bkwidth / 2 - bkwidth / 2 * camerasize)) / camerasize;										// drawx-bkgameleft=mousex-bkdeskleft	横坐标方向移动距离相同
//			bkdeskleft + bkwidth / 2 							是视口中心
//			- bkwidth / 2 * camerasize 							是往左缩小的图片长度
//			bkdeskleft + bkwidth / 2 - bkwidth / 2 * camerasize 是新的图片贴到的左上角坐标,因为要保持中心不变,所以计算中心,然后计算新的边角
//			drawy = bkgametop + (mousey - bkdesktop);										// drawy-bkgametop=mousey-bkdesktop	纵坐标方向移动距离相同
			drawy = bkgametop + (mousey - (bkdesktop + bkheight / 2 - bkheight / 2 * camerasize)) / camerasize;										// drawy-bkgametop=mousey-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 == 0 && flag_x != 0 ) || flag_y != 0) {
			moveflag = 1;
			bkgameleft += flag_x;
			bkgametop += flag_y;
		} else {
			// 既不绘制也不移动也不拖拽
		}
		// 速度检测,控制摄像机的速度,此时发现比较坐标大小控制速度,其实就是比较距离来分级控制速度
		checkspeedv2(&speedv4_x, &sppedv4_y, crossflag_x, crossflag_y, bkgameleft, bkgametop, bkheight, bkwidth, playerleft, playertop);
		// 检测玩家是否出了摄像机边界
		checkboundaryv2(&crossflag_x, &crossflag_y, bkgameleft,  bkgametop,
		                playerleft, playertop, playerright, playerbottom,
		                playerlimitleft, playerlimittop, playerlimitright, playerlimitbottom);
		// 摄像机追随玩家
		bkfollowplayer(&bkgameleft, &bkgametop, crossflag_x, crossflag_y, &speedv4_x, &sppedv4_y);
		// 玩家不出界时,摄像机慢慢追随玩家
		bklocalplayer(&bkgameleft, &bkgametop, bkheight, bkwidth, playerleft, playertop, crossflag_x, crossflag_y, &speedv4_x, &sppedv4_y);

		// 根据计算出的坐标数据进行绘制,分多种情况分别绘制,减少函数重复调用与无效调用
		if (drawsmallflag == 1 && moveflag == 0) {
			freshmesh(oldmesh, bkmesh, map, pentable, pentablev2, bkmeshmapi, bkmeshmapj, bkmeshfreshi, bkmeshfreshj, pixnum, defaultnum, defaultcolor);			// 刷新,重新映射,其实就是开头初始化的代码,这里是给了一个封装示例,用了一个数组过滤重复位置进行性能优化-2024.4.21
			freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, bkwidth, bkheight, pixnum);
//			showbk(bk, bkdeskleft, bkdesktop);
			showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);										// 2024-4-26 禁用 2024-4-27 启用,绘制的时候显示缓冲区
		} else if (moveflag == 1 && drawsmallflag == 1) {
			checkboundary(&bkgameleft, &bkgametop, gamelimitright, gamelimitbottom, &bkmeshgameleft, &bkmeshgametop, &meshlimitright, &meshlimitbottom, bkmeshcopyheight, bkmeshcopywidth);			// move =pixnum * 9, size = 2*9* pinxum 2024.4.22 替换 bkmeshcopyheight
			bkmeshmapi = bkmeshgametop / pixnum;
			bkmeshmapj = bkmeshgameleft / pixnum;
			freshmesh(oldmesh, bkmesh, map, pentable, pentablev2, bkmeshmapi, bkmeshmapj, bkmeshfreshi, bkmeshfreshj, pixnum, defaultnum, defaultcolor);			// 刷新,重新映射,其实就是开头初始化的代码,这里是给了一个封装示例,用了一个数组过滤重复位置进行性能优化-2024.4.21
			freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, bkwidth, bkheight, pixnum);
			showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);										// 2024-4-26 禁用 2024-4-27 启用,绘制的时候显示缓冲区
//			showbk(bk, bkdeskleft, bkdesktop);
		} else if (moveflag == 1 || draftflag) {													// 分类渲染, drawflag==0 时,再选择性刷新缓冲区
			checkboundary(&bkgameleft, &bkgametop, gamelimitright, gamelimitbottom, &bkmeshgameleft, &bkmeshgametop, &meshlimitright, &meshlimitbottom, bkmeshcopyheight, bkmeshcopywidth);			// move =pixnum * 9, size = 2*9* pinxum
			bkmeshmapi = bkmeshgametop / pixnum;
			bkmeshmapj = bkmeshgameleft / pixnum;
			if (oldbkmeshgamex != bkmeshgameleft || oldbkmeshgamey != bkmeshgametop) {				// 判断是否更新采样区
				freshmesh(oldmesh, bkmesh, map, pentable, pentablev2, bkmeshmapi, bkmeshmapj, bkmeshfreshi, bkmeshfreshj, pixnum, defaultnum, defaultcolor);			// 刷新,重新映射,其实就是开头初始化的代码,这里是给了一个封装示例,用了一个数组过滤重复位置进行性能优化-2024.4.21
				oldbkmeshgamex = bkmeshgameleft;
				oldbkmeshgamey = bkmeshgametop;
				showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);
			}
			freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, bkwidth, bkheight, pixnum);
//			showbk(bk, bkdeskleft, bkdesktop);
		}

		freshbk(bk, bkmesh, bkgameleft, bkgametop, bkmeshmapi, bkmeshmapj, bkwidth, bkheight, pixnum);
		showplayer(bk, player, bkgameleft, bkgametop, playerleft, playertop);
		showbk(bk, bkdeskleft, bkdesktop);
//		showbkmesh(bkmesh, bkmeshdeskleft, bkmeshdesktop);									// 2024-4-27 禁用,仅在更新时刷新,在最上面 draft 板块启动freh mesh,用于记录轨迹
		FlushBatchDraw();																	// 2024-5-7	开启双缓冲,解决 strcechBit() 的卡顿问题
		animation(characterflag, bkgameleft, bkgametop, bkmeshgameleft, bkmeshgametop, bkmeshdeskleft, bkmeshdesktop);
		Sleep(2);																			// 休眠 2 毫秒,减少 CPU 占用
	}
	return 0;
}
// 保存游戏地图的全部数据
void savegamemap(int** map, int gamemapi, int gamemapj, IMAGE* pentable, IMAGE*** pentablev2, 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]);								// 批量导出贴图
	}
	savepersonalfile(dirpath, pentablev2);								// 创建默认文件夹并保存自定义贴图
}
// 读取 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");
	// 2024-4-27 20:48 改写,替换fscanf,因为在小熊猫C++里,fscanf不能读取带空格的字符串。而VisualStudio2022,DevC++5.11可以,现在网上查询读取一行的代码,终于发现fgets可以实现数据读取。
	char a[500];
	fgets(a, 500, fp);													// 500是最大长度,当遇到回车符号或者读到最大长度,就终止读取
	printf("%s\n", a);
	fgets(a, 500, fp);
	printf("%s\n", a);
	fscanf(fp, "pixnum %d\n", pixnum);									// 正方形瓦片贴图边长信息:单位:像素
	fscanf(fp, "imagenum %d\n", imagenum);								// 瓦片个数
	char imageindix[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 gamelimitbottom, int* bkmeshgameleft, int* bkmeshgametop, int* meshlimitright, int* meshlimitbuttom, int bkmeshcopywidth, int bkmeshcopyheight) {
	if (*bkgameleft < 0)												// 网格越界检测并调整
		*bkgameleft = 0;
	else if (*bkgameleft > gamelimitright)								// 超过九宫格的边界就会刷新,所以要 gamelimiright = mapwidth - bkmeshcopywidth,这样就不超过九宫格边界了。
		*bkgameleft = gamelimitright;
	if (*bkgametop < 0)
		*bkgametop = 0;
	else if (*bkgametop > gamelimitbottom)								// 超过九宫格的边界就会刷新,所以要 gamelimitbottom = mapheight - bkmeshcopyheight,这样就不超过九宫格边界了。
		*bkgametop = gamelimitbottom;
	if (*bkgameleft < *bkmeshgameleft) {								// 更新游戏采样区坐标,一些简单换算,由于频繁调用函数在这里产生了明显的卡顿影响,所以这里就不再封装成函数
		*bkmeshgameleft -= bkmeshcopywidth;								// 追随玩家,九宫格左移一格宫格,则界限也左移一格宫格的像素宽度
		*meshlimitright -= bkmeshcopywidth;								// 缓存区网格左上角的像素坐标在右界限,移动相同距离,这里改完,发现需要在初始化时规定好边界距离整好是四宫格的宽高
	} else if (*bkgameleft > *meshlimitright) {
		*meshlimitright += bkmeshcopywidth;
		*bkmeshgameleft += bkmeshcopywidth ;							// 缓存区网格左上角的像素坐标在右界限,移动相同距离,这里改完,发现需要在初始化时规定好边界距离整好是四宫格的宽高
	}
	if (*bkgametop < *bkmeshgametop) {
		*bkmeshgametop -= bkmeshcopyheight;
		*meshlimitbuttom -= bkmeshcopyheight;							// 缓存区网格左上角的像素坐标在右界限,移动相同距离,这里改完,发现需要在初始化时规定好边界距离整好是四宫格的宽高
	} else if (*bkgametop > *meshlimitbuttom) {
		*meshlimitbuttom += bkmeshcopyheight;
		*bkmeshgametop += bkmeshcopyheight;								// 缓存区网格左上角的像素坐标在右界限,移动相同距离,这里改完,发现需要在初始化时规定好边界距离整好是四宫格的宽高
	}
}
// 性能:CPU 2% 占有率,峰值 1.20GHz,核显 GPU 10% 使用率
inline void animation(int characterflag, int bkgameleft, int bkgametop, int bkmeshgameleft, int bkmeshgametop, int bkmeshdeskleft, int bkmeshdesktop) {
	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++;
}
// 从默认文件夹中读取自定义图片
void loadfile(IMAGE*** pentablev2, int pixnum) {
	char brick[100] = "aa_birck_6_family";
	char ground[100] = "ab_ground_7_family";
	char change[100] = "ac_change_8_family";
	char trick[100] = "ad_trick_9_family";
	char next[100] = "./";
	char png[100] = "*.png";
	loadfile_scanf(brick, next, png, pentablev2[0], pixnum);			// 加载用于当砖头类型的图片
	loadfile_scanf(ground, next, png, pentablev2[1], pixnum);
	loadfile_scanf(change, next, png, pentablev2[2], pixnum);
	loadfile_scanf(trick, next, png, pentablev2[3], pixnum);
}
// 加载自定义图片
void loadfile_scanf(char* dirname, char* next, char* kind, IMAGE** pentablev2, int pixnum) {
	// 文件存储信息结构体
	struct _finddata_t fileinfo;
	// 保存文件句柄			// https://blog.csdn.net/hemmingway/article/details/73716980
	long long fHandle;		// win 10 win 11 fHandle 要从long 改成long long 否则闪退
//	long fHandle;

	// 文件数记录器
	char dirpath[100];
	strcpy(dirpath, dirname);
	if (access(dirpath, 0) == -1) {												// 检查文件夹是否存在,不存在为 -1
		mkdir(dirpath);
		return;
	} else {
		int i = -1;																// 记录文件数量
		char kindpath[100];
		strcpy(kindpath, dirpath);
		strcat(kindpath, next);
		strcat(kindpath, kind);
		if ((fHandle = _findfirst(kindpath, &fileinfo)) == -1L) {				// *是通配符,默认在当前文件夹内查找文件,这里查找 .png 文件
			printf("当前目录: %s 下没有所需文件\n", dirname);
			return;
		} else {
			char path[100];
			do {
				i++;
				printf("在%s 下找到文件:%s,文件大小:%ld bit\n", dirname, fileinfo.name, fileinfo.size);
				strcpy(path, dirname);
				strcat(path, next);
				strcat(path, fileinfo.name);
				while (pentablev2[i] != NULL) {
					i++;
				}
				// 导入新图片这里需要改动,自适应图片,用于各种大小图片加入,配合剪切
				pentablev2[i] = new IMAGE(pixnum, pixnum);
				loadimage(pentablev2[i], path, pixnum, pixnum);					// 根据名称读取文件
			} while (_findnext(fHandle, &fileinfo) == 0);
		}
		// 关闭文件
		_findclose(fHandle);

		printf("文件数量:%d\n", i + 1);
	}
}
// 在目标 DLC 里创建四个文件夹并保存
void savepersonalfile(char* DLC, IMAGE*** pentablev2) {
	char brick[100] = "aa_birck_6_family";
	char ground[100] = "ab_ground_7_family";
	char change[100] = "ac_change_8_family";
	char trick[100] = "ad_trick_9_family";
	char next[100] = "./";
	char png[100] = ".png";
	if (access(DLC, 0) == -1) {													// 检查文件夹是否存在,不存在为 -1
		mkdir(DLC);
	}
	char sign[10] = {};
	strcpy(sign, "6");
	savelocal(DLC, brick, next, png, sign, pentablev2[0]);
	strcpy(sign, "7");
	savelocal(DLC, ground, next, png, sign, pentablev2[1]);
	strcpy(sign, "8");
	savelocal(DLC, change, next, png, sign, pentablev2[2]);
	strcpy(sign, "9");
	savelocal(DLC, trick, next, png, sign, pentablev2[3]);
}
// 保存自定义文件
void savelocal(char* DLC, char* dirname, char* next, char* kind, char* sign, IMAGE** files) {
	char dirpath[100] = "";
	strcpy(dirpath, DLC);															// 字符串拼接出相对路径
	strcat(dirpath, next);
	strcat(dirpath, dirname);
	if (access(dirpath, 0) == -1) {													// 检查 DLC 内的指定文件夹是否存在,不存在为 -1
		mkdir(dirpath);
	}
	char filepath[100];
	char filepa[100];
	char num[10];
	strcpy(filepath, dirpath);														// 字符串拼接出文件名前缀
	strcat(filepath, next);
	strcat(filepath, sign);
	for (int i = 0; i < 10; i++) {
		sprintf(num, "%d", i);
		strcpy(filepa, filepath);													// 从头粘贴,可以覆盖之前的信息,不受上一次保存的影响
		strcat(filepa, num);
		strcat(filepa, kind);
		if (files[i] != NULL) {
			saveimage(filepa, files[i]);
			printf("file save %s\n", filepa);
		}
	}
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nestjs版若依全栈管理后台完全开源!

hello&#xff0c;大家好&#xff0c;我是徐小夕。之前和大家分享了很多可视化&#xff0c;零代码和前端工程化的最佳实践&#xff0c;今天继续和大家分享一下我们小伙伴开源的基于 nestjs 的若依全栈管理系统。 相信前端小伙伴对若依管理系统并不陌生&#xff0c;它的后端采用…

短视频矩阵系统源码saas开发--可视化剪辑、矩阵托管、多功能合一开发

短视频矩阵系统源码saas开发&#xff08;可视化剪辑、矩阵托管、智能私信聚合、线索转化、数据看板、seo关键词、子账号等多个板块开发&#xff09; 短视频矩阵系统是一种集成了多种功能的系统&#xff0c;旨在帮助用户在短视频平台上进行高效的内容创作、管理和发布。根据您提…

Mysql 基础 order by ,as ,limit,case,asc、desc

order by 、as 、limit 、asc&#xff08;ascending 正序 默认&#xff09; desc&#xff08;descending 倒序&#xff09; SELECT 学号, 姓名, 成绩, CASEWHEN 成绩 > 90 THEN 优秀WHEN 成绩 > 80 THEN 良好WHEN 成绩 > 60 THEN 及格ELSE 不及格 END as 成绩级别 FRO…

一些js的基础

五大主流浏览器 内核 IE trident chrome webkit blink safari webkit firefox gecko opera presto ECMA European Computer Manufactures Association 欧洲计算机制造联合会 评估、开发 认可电信、计算机标准 ECMA-262 脚本语言的规范 ECMAScript 编程语言 1、编译型 翻译…

Android bootchart 分析启动性能工具使用

bootchart简介 bootchart 可为整个系统提供所有进程的 CPU 和 I/O 负载细分。该工具不需要重建系统映像&#xff0c;可以用作进入 systrace 之前的快速健全性检查。 1. 板端配置&#xff1a; 在Android 板端启用 bootchart&#xff0c;请运行以下命令&#xff1a; 2. Bootch…

Java 【数据结构】 TreeSetTreeMap(二叉搜索树详解)【神装】

登神长阶 第八神装 TreeSet 第九神装 TreeMap 目录 &#x1f489; 一.二叉搜索树 &#x1fa78;1. 定义 &#x1f48a;2. 基本操作 &#x1fa79;3. 插入操作 &#x1fa7c;4. 查找操作 &#x1fa7a;5. 删除操作* &#x1fa7b;6. 遍历操作 &#x1fa92;7.性能分析 …

使用固定公网地址远程访问开源服务器运维管理面板1Panel管理界面

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

Garden Planner for Mac v3.8.62注册激活版:园林绿化设计软件

Garden Planner for Mac是一款专为苹果Mac OS平台设计的园林景观设计软件。这款软件的主要功能是帮助用户设计梦想中的花园&#xff0c;包括安排植物、树木、建筑物和其他物体。 Garden Planner for Mac提供了一个包含1200多种植物和物体符号的库&#xff0c;这些符号都可以进行…

C语言——文件描述符、系统调用操作文件

文件描述符 在Unix-like操作系统中&#xff0c;文件描述符&#xff08;file descriptor&#xff09;是一个用于标识打开文件或I/O设备的整数值。它是对底层文件系统的抽象&#xff0c;用于在应用程序和操作系统之间传递文件信息。 文件描述符是一个非负整数&#xff0c;通常是…

这个Python库Streamlit,5分钟内搭建可视化WEB应用

在数据科学的世界里&#xff0c;将分析结果快速、直观地呈现给非技术背景的决策者&#xff0c;是一项重要的技能。而Streamlit&#xff0c;这个开源的Python库&#xff0c;正是为此而生。它允许数据科学家和工程师通过少量的代码&#xff0c;快速创建和分享数据应用。今天&…

用HAL库改写江科大的stm32入门例子_9-1 串口发送

设置串口&#xff1a; 选项说明&#xff1a; 写一个串口发送函数&#xff1a; // serial send string function void serial_send_string(char *str) {HAL_UART_Transmit(&huart1, (uint8_t *)str, strlen(str), 1000); } main函数中调用发送信息&#xff1a; uint8_t dat…

AIGC实战——多模态模型DALL.E 2

AIGC实战——多模态模型DALL.E 2 0. 前言1. 模型架构2. 文本编码器3. CLIP4. 先验模型4.1 自回归先验模型4.2 扩散先验模型 5. 解码器5.1 GLIDE5.2 上采样器 6. DALL.E 2 应用6.1 图像变体6.2 先验模型的重要性6.3 DALL.E 2 限制 小结系列链接 0. 前言 DALL.E 2 是 OpenAI 设计…

docker搭建代码审计平台sonarqube

docker搭建代码审计平台sonarqube 一、代码审计关注的质量指标二、静态分析技术分类三、sonarqube流程四、快速搭建sonarqube五、sonarqube scanner的安装和使用 一、代码审计关注的质量指标 代码坏味道 代码规范技术债评估 bug和漏洞代码重复度单测与集成 测试用例数量覆盖率…

WPF 图片显示某一部分区域

效果图&#xff1a; 代码&#xff1a; <Image Width"32"HorizontalAlignment"Right"Height"32"Source"../../Resources/Images/BLUEWOLF.jpg"><Image.Clip><PathGeometry><PathFigure StartPoint"32,32&quo…

区块链 | NFT 相关论文:Preventing Content Cloning in NFT Collections(三)

&#x1f436;原文&#xff1a; Preventing Content Cloning in NFT Collections &#x1f436;写在前面&#xff1a; 这是一篇 2023 年的 CCF-C 类&#xff0c;本博客只记录其中提出的方法。 F C o l l N F T \mathbf{F_{CollNFT}} FCollNFT​ and Blockchains with Native S…

基于springboot+mybatis+vue的项目实战之(后端+前后端联调)

步骤&#xff1a; 1、项目准备&#xff1a;创建数据库&#xff08;之前已经创建则忽略&#xff09;&#xff0c;以及数据库连接 2、建立项目结构文件夹 3、编写pojo文件 4、编写mapper文件&#xff0c;并测试sql语句是否正确 5、编写service文件 6、编写controller文件 …

二、双fifo流水线操作——verilog练习与设计

文章目录 一、案例分析二、fifo_ctrl模块设计2.1 波形设计&#xff1a;2.2 代码实现2.2.1 fifo_ctrl2.2.2 顶层文件top_fifo_ctrl&#xff08;rx和tx模块省略&#xff09;2.2.3 仿真文件tb_fifo_ctrl 2.3波形仿真 一、案例分析 案例要求&#xff1a;写一个 fifo 控制器&#x…

浅谈消息队列和云存储

1970年代末&#xff0c;消息系统用于管理多主机的打印作业&#xff0c;这种削峰解耦的能力逐渐被标准化为“点对点模型”和稍复杂的“发布订阅模型”&#xff0c;实现了数据处理的分布式协同。随着时代的发展&#xff0c;Kafka&#xff0c;Amazon SQS&#xff0c;RocketMQ&…

【EasySpider】EasySpider+mysql执行配置异常

问题 使用易采集工具操作时候&#xff0c;遇到一个执行异常&#xff0c;后来发现没有选择数据类型 Loading stealth.min.js MySQL config file path: ./mysql_config.json 成功连接到数据库。 Successfully connected to the database. Traceback (most recent call last):…

联发科技发布天玑9300+旗舰5G生成式AI芯片 | 最新快讯

5 月 7 日消息&#xff0c;联发科技今天举办了天玑开发者大会 2024。大会上&#xff0c;联发科技开启了“天玑 AI 先锋计划”&#xff0c;联合业界生态企业发布了《生成式 AI 手机产业白皮书》&#xff0c;分享了生成式 AI 端侧部署的解决方案“天玑 AI 开发套件”。同时&#…