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/pixnumint 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;															// 设置长按 flagint 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;															// 设置不绘制时贴图代号为 -1pentake = 9;															// 默认白色笔刷,对应黑色背景int draftoldmx;															// 记录刚刚拖拽时的鼠标的位置,用于坐标变换计算位移int draftoldmy;int draftoldgamex;														// 记录刚刚拖拽时的游戏地图位置,用于坐标变换计算新的游戏坐标int draftoldgamey;int draftflag;															// 设置拖拽 flagdraftoldmx = 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 mouseymousey=-1;break;case WM_KEYDOWN:switch (m.vkcode)					// 键盘移动控制{case 0x41:						// Aif(flag_x-speed>-10)		// 限制范围,减少内存读写flag_x-=speed;characterflag=1;break;case 0x57:						// Wif(flag_y-speed>-10)flag_y-=speed;characterflag=2;break;case 0x44:						// Dif(flag_x+speed<10)flag_x+=speed;characterflag=3;break;case 0x53:						// Smoveflag=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:						// Aflag_x=0;break;case 0x57:						// Wflag_y=0;break;case 0x44:						// Dflag_x=0;break;case 0x53:						// Sflag_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 的上限 * 放大倍数 不超过 255switch(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++;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS加密解密之JS广告漂浮代码分析

前言 之前有个客户要求帮忙复刻一份广告漂浮代码&#xff0c;我看了下&#xff0c;目标站的广告代码是通过了JS加密后的&#xff0c;经过我解密还原后分析了一下该代码的作用如下。 ;var _0xodDddd,_0xodD_[_0xodD],_0x1d02[_0xodD,\x73\x54\x69\x6d\x65,\x6c\x6f\x61\x64\x5…

计算机专业必看的几部电影

计算机专业必看的几部电影 计算机专业必看的几部电影&#xff0c;就像一场精彩的编程盛宴&#xff01;《黑客帝国》让你穿越虚拟世界&#xff0c;感受高科技的魅力&#xff1b;《社交网络》揭示了互联网巨头的创业之路&#xff0c;《源代码》带你穿越时间解救世界&#xff0c;…

PAM | 账户安全 | 管理

PAM PAM&#xff08;Pluggable Authentication Modules&#xff0c;可插入式身份验证模块&#xff09;是一个灵活的身份验证系统&#xff0c;允许我们通过配置和组合各种模块来实现不同的身份验证策略。 在 Linux 或类 Unix 系统中&#xff0c;常见的 PAM 模块包括以下几种类…

Ansible file文件模块 设置文件的属性,比如创建文件、创建链接文件、删除文件

目录 语法创建目录创建链接文件删除文件 每个值的属性 语法 创建目录 ansible slave -m file -a path/data/app statedirectory path/data/app # 定义创建路径 statedirectory # 如果目录不存在就创建目录这就是创建目录成功之后的回显 可以看到&#xff0c;已经打印出目录a…

MySQL的基础架构

文章目录 前言MySQL的基础架构总结 前言 你使用 MySQL 开发&#xff0c;你知道 MySQL 的基础架构吗&#xff1f;本文带你来入门MySQL 的基础架构 MySQL的基础架构 MySQL 是我们经常使用到的数据库。它的基础架构分为 server 层与存储引擎层。 server 层&#xff1a;用于存储…

推荐彩虹知识付费商城免授权7.0源码

彩虹知识付费商城免授权7.0源码&#xff0c;最低配置环境 PHP7.2 1、上传源码到网站根目录&#xff0c;导入数据库文件&#xff1a;xydai.sql 2、修改数据库配置文件&#xff1a;/config.php 3、后台&#xff1a;/admin 账号&#xff1a;admin 密码&#xff1a;123456 4、前…

【Jvm】类加载机制(Class Loading Mechanism)原理及应用场景

文章目录 Jvm基本组成一.什么是JVM类的加载二.类的生命周期阶段1&#xff1a;加载阶段2&#xff1a;验证阶段3&#xff1a;准备阶段4&#xff1a;解析阶段5&#xff1a;初始化 三.类初始化时机四.类加载器1.引导类加载器&#xff08;Bootstrap Class Loader&#xff09;2.拓展类…

第九篇:node静态文件服务(中间件)

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! &#x1f4d8; 引言&#xff1a; 当今互联网时代&am…

[AIGC] 利用 ChatGpt 深入理解 Java 虚拟机(JVM)的内存分布

深入理解 Java 虚拟机&#xff08;JVM&#xff09;的内存分布 Java 虚拟机&#xff08;JVM&#xff09;是 Java 编程语言的核心运行环境&#xff0c;它负责解释和执行 Java 字节码。在 JVM 中&#xff0c;内存被划分为几个不同的区域&#xff0c;每个区域都有特定的用途。了解…

SG3225EEN晶体振荡器规格书

SG3225EEN 晶振是EPSON/爱普生的一款额定频率25 MHz至500 MHz的石英晶体振荡器&#xff0c;6脚贴片&#xff0c;LV-PECL输出&#xff0c;3225封装常规有源晶振&#xff0c;具有小尺寸&#xff0c;轻薄型&#xff0c;高稳定性&#xff0c;低相位抖动&#xff0c;低电源电压&…

anomalib1.0学习纪实-续2:三个文件夹

为了读懂程序&#xff0c;有三个最重要的文件夹&#xff0c;如下图&#xff1a; 正好对应四个类&#xff0c;如下图&#xff1a; 四个类的来源如下图所示&#xff1a; 注意&#xff0c;MVTec是个大类&#xff0c;里面用到了这里的第四个类MVTecDataset&#xff0c;代码如下。…

java以及android类加载机制

类加载机制 一、Java类加载机制 java中&#xff0c;每一个类或者接口&#xff0c;在编译后&#xff0c;都会生成一个.class文件。 类加载机制指的是将这些.class文件中的二进制数据读入到内存中并对数据进行校验&#xff0c;解析和初始化。最终&#xff0c;每一个类都会在方…

深入实战:ElasticSearch的Rest API与迭代器模式在高效查询中的应用

在我们公司&#xff0c;大多数Java开发工程师在项目中都有使用Elasticsearch的经验。通常&#xff0c;他们会通过引入第三方工具包或使用Elasticsearch Client等方式来进行数据查询。然而&#xff0c;当涉及到基于Elasticsearch Rest API的/_sql?formatjson接口时&#xff0c;…

CentOS上如何配置手动和定时任务自动进行时间同步

场景 Linux(Centos)上使用crontab实现定时任务(定时执行脚本)&#xff1a; Linux(Centos)上使用crontab实现定时任务(定时执行脚本)_centos 定时任务-CSDN博客 Winserver上如何配置和开启NTP客户端进行时间同步&#xff1a; Winserver上如何配置和开启NTP客户端进行时间同步…

motplotlib图例案例1:通过多个legend完全控制图例显示顺序(指定按行排序 or 按列排序)

这个方法的核心&#xff0c;是手动的获得图中的handlers和labels&#xff0c;然后对它们进行切分和提取&#xff0c;最后分为几个legend进行显示。代码如下&#xff1a; 后来对下面的代码进行修改&#xff0c;通过handlers, labels get_legend_handles_labels(axs[axis])自动的…

Unity所有关于旋转的方法详解

前言&#xff1a;欧拉角和四元数的简单描述 我们在Inspector面板上看到的rotation其实是欧拉角&#xff0c; 我们将Inspector面板设置成Debug模式&#xff0c;此时看到的local Rotation才是四元数。 Unity中的欧拉旋转是按照Z-X-Y顺规执行的旋转&#xff0c;一组欧拉旋转过程中…

Transformer面试十问

1 Scaled Dot-Product Attention中为什么要除以 d k \sqrt{d_k} dk​ ​? 1. 从纯数学上考虑&#xff1a;对于输入均值为0,方差为1的分布&#xff0c;点乘后结果其方差为dk&#xff0c;所以需要缩放一下。下图为原论文注释。 2. 从神经网络上考虑&#xff1a;防止在计算点积…

杨氏矩阵和杨辉三角

杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于O(N); 分析 若要满足要求时间复杂度小于O(N)&#xff0c;就不能每一行一个个…

树和二叉树的基本知识

一、树的概念及结构 1.树的概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 。 有一个 特殊…

阿里云服务器“镜像”全方面解析

阿里云服务器镜像怎么选择&#xff1f;云服务器操作系统镜像分为Linux和Windows两大类&#xff0c;Linux可以选择Alibaba Cloud Linux&#xff0c;Windows可以选择Windows Server 2022数据中心版64位中文版&#xff0c;阿里云服务器网aliyunfuwuqi.com来详细说下阿里云服务器操…