制作一个网站流程,做网站用的产品展示横幅,网站建设计算机人员招聘,广州建站模板搭建仅仅修改了showbk#xff08;#xff09; 函数部分#xff0c;增加bkv4 直接采样#xff0c;然后粘贴到左上角#xff0c;实现多余部分裁剪#xff0c;形成视口内放大缩小 // 程序#xff1a;2D RPG 地图编辑器与摄像机追随
// 作者#xff1a;bilibili 民用级脑的研发… 仅仅修改了showbk 函数部分增加bkv4 直接采样然后粘贴到左上角实现多余部分裁剪形成视口内放大缩小 // 程序2D RPG 地图编辑器与摄像机追随
// 作者bilibili 民用级脑的研发记录
// 邮箱1309602336qq.com
// 编译环境小熊猫C2.25.1/Devc/VC 2010/Visual Studio 2022EasyX_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 // 移植到小熊猫 C2.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纹理集bkmeshmapibkmeshmapj映射起始点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 重整增加pennumberdefaultnum,只对空的瓦片不绘制因为之后有的瓦片绘制一次会有残留直线边界所以需要重绘解决} 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];elsepen 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); // 获取主窗口的 DCSetStretchBltMode(mainDC, STRETCH_HALFTONE); // 设置拉伸贴图模式为抗锯齿性能差效果高
// SetStretchBltMode(mainDC, COLORONCOLOR); // 设置拉伸贴图模式为不抗锯齿贴图模式为临近颜色效果差性能高// 将源图像贴图到目标位置StretchBlt( mainDC, // 目标 DCbkdeskx bk-getwidth() / 2 - bk-getwidth() / 2 * camerasize,bkdesky bk-getheight() / 2 - bk-getheight() / 2 * camerasize, // 目标位置的坐标bk-getwidth()*camerasize, bk-getheight()*camerasize, // 目标位置的长宽imgDC, // 源 DC0, 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-getwidth300,bkv4-getwidth250,计算新的图片左上角坐标在窗口的位置
// bkdeskx bk-getwidth()/2 是图片中心在窗口的坐标无论怎样放缩图片中心在窗口的坐标不变
// bkdeskx bk-getwidth()/2 -bkv4-getwidth() 是找到距离图片中心坐标半个图片宽度就是左上角坐标的 xbkdesky bk-getheight() / 2 - bkv4-getheight() / 2,
// bkdesky bk-getheight() / 2 是图片中心在窗口的坐标无论怎样放缩图片中心在窗口的坐标不变
// bkdesky bk-getheight()/2 -bkv4-getheight()/2 是找到距离图片中心坐标半个图片宽度就是左上角坐标的 ybkv4-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 816 记录本行注释
// 硬写函数 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; // 高的上限界限是 九宫格其中一格高的三倍像素
// mapMaxheightbkmeshMaxheight*100; // 游戏地图高度的像素上限是九宫格的高度的300倍
// mapMaxwidthbkmeshMaxwidth*100; // 游戏地图宽度的像素上限是九宫格的宽度的300倍
// mapMaxheightbkmeshMaxheight*1; // 测试发现边界闪退越界数组刷新发现。游戏地图高度的像素上限是九宫格的高度的1倍
// mapMaxwidthbkmeshMaxwidth*1; // 游戏地图宽度的像素上限是九宫格的宽度的1倍mapMaxheight bkmeshMaxheight * 2; // 测试发现边界闪退越界数组刷新发现。游戏地图高度的像素上限是九宫格的高度的2倍mapMaxwidth bkmeshMaxwidth * 2;// 实际自适应于瓦片的大小用于保证不同的 background bk 都能在九宫格的每个宫格边缘切换地图时不错位。int bkheight; // 2024.4.21修改发现需要适配屏幕高度增大面积新增变量替换常数 270 ,先原样替换验证替换正常再继续改数据int bkwidth;
// bkheight370;
// bkwidth370;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; // 游戏右边界像素坐标左边界坐标默认是0int safemapbottom; // 游戏下边界像素坐标上边界坐标默认是0int 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-1safemapj mapj - 1; // 绘制的时候确定右网格边界数据-1是因为数组的第 0 个对应左边第一个瓦片于是等同于数组下标是总数-1 初始化n个数组对应序号 0~n-1safemapbottom 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; // 背景图片寄存区左上角坐标是在游戏里的像素坐标。00可以理解为游戏大地图的左上角顶点。int bkgametop;int bkmeshgameleft; // 背景图片采样区左上角坐标是在游戏里的像素坐标。int bkmeshgametop;int bkmeshmapi; // 背景图片采样区左上角所对应的 map 数组序号。从 map[0][0]开始按照 map[i][j]其中 bkmeshmapibkmeshtop/pixnumint bkmeshmapj;bkgameleft 0; // 由于 bkgame 控制 mesh 坐标移动00则游戏背景完全在当前采样区移动bkgametop 0;bkmeshgameleft 0;bkmeshgametop 0;bkmeshmapi bkmeshgametop / pixnum; // 缓存区左上角所在的游戏网格的哪一行bkmeshmapj bkmeshgameleft / pixnum; // 缓存区左上角所在的游戏网格的哪一列int bkdeskleft; // 规定在屏幕上显示游戏背景寄存区此处记录其左上角在屏幕上的像素坐标int bkdesktop;int bkmeshdeskleft; // 规定在屏幕上显示游戏背景采样区此处记录其左上角在屏幕上的像素坐标int bkmeshdesktop;bkdeskleft 200; // 游戏背景左上角将会在屏幕的200200) 处bkdesktop 200;bkmeshdeskleft 700; // 游戏背景缓冲区左上角将会在屏幕的7000处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; // 调色盘左上角将会在屏幕的00处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; // 初始化绘制采样区所需的坐标相对于采样区00就是采样区左上角顶点坐标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 替换 30putimage(bkdeskleft, bkdesktop, bk); // 绘制游戏背景putimage(bkmeshdeskleft, bkmeshdesktop, bkmesh); // 显示游戏背景缓冲区// 此时绘制完成以上 刷贴图采样粘贴就是实现 RPG 游戏大地图的压缩// 开始检测鼠标键盘功能int drawflag; // 设置长按 flagint drawoldmx; // 记录上一次绘制时的鼠标坐标用于检测是否重复点击相同像素来减少重复绘制int drawoldmy;int drawx; // 画笔在游戏里的位置单位像素int drawy;int olddrawi; // 记录上一次绘制的瓦片判断是否需要重新绘制int olddrawj;int drawsmallflag; // 在 drawflag1 时检测是否刷新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 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-correctlyswitch (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 mouseymousey -1;break;case WM_KEYDOWN:switch (m.vkcode) { // 键盘移动控制case 0x41: // Aflag_xv2 -1;characterflag 1;break;case 0x57: // Wflag_yv2 -1;characterflag 2;break;case 0x44: // Dflag_xv2 1;characterflag 3;break;case 0x53: // Sflag_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.21freshbk(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: // Aflag_xv2 0;break;case 0x57: // Wflag_yv2 0;break;case 0x44: // Dflag_xv2 0;break;case 0x53: // Sflag_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;// 根据moveflag1处的代码复制粘贴来当时没有和玩家移动排列组合就是当时没穷举到玩家移动边界检测于是就没有放一块发现摄像机边界检测和摄像机追随玩家在边界检测上是相同的// 检测采样区是否超过缓冲区界限是的话就刷新checkboundary(bkgameleft, bkgametop, gamelimitright, gamelimitbottom, bkmeshgameleft, bkmeshgametop, meshlimitright, meshlimitbottom, bkmeshcopyheight, bkmeshcopywidth); // move pixnum * 9, size 2*9* pinxumbkmeshmapi 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.21oldbkmeshgamex 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-bkgameleftm.x-bkdeskleft 横坐标方向移动距离相同
// drawy bkgametop (mousey - bkdesktop); // drawy-bkgametopm.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-bkgametopmousey-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-bkgameleftmousex-bkdeskleft 横坐标方向移动距离相同drawx bkgameleft (mousex - (bkdeskleft bkwidth / 2 - bkwidth / 2 * camerasize)) / camerasize; // drawx-bkgameleftmousex-bkdeskleft 横坐标方向移动距离相同
// bkdeskleft bkwidth / 2 是视口中心
// - bkwidth / 2 * camerasize 是往左缩小的图片长度
// bkdeskleft bkwidth / 2 - bkwidth / 2 * camerasize 是新的图片贴到的左上角坐标因为要保持中心不变所以计算中心然后计算新的边角
// drawy bkgametop (mousey - bkdesktop); // drawy-bkgametopmousey-bkdesktop 纵坐标方向移动距离相同drawy bkgametop (mousey - (bkdesktop bkheight / 2 - bkheight / 2 * camerasize)) / camerasize; // drawy-bkgametopmousey-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-bkgameleftm.x-bkdeskleft 横坐标方向移动距离相同
// drawy bkgametop (mousey - bkdesktop); // drawy-bkgametopm.y-bkdesktop 纵坐标方向移动距离相同drawx bkgameleft (mousex - (bkdeskleft bkwidth / 2 - bkwidth / 2 * camerasize)) / camerasize; // drawx-bkgameleftmousex-bkdeskleft 横坐标方向移动距离相同
// bkdeskleft bkwidth / 2 是视口中心
// - bkwidth / 2 * camerasize 是往左缩小的图片长度
// bkdeskleft bkwidth / 2 - bkwidth / 2 * camerasize 是新的图片贴到的左上角坐标因为要保持中心不变所以计算中心然后计算新的边角
// drawy bkgametop (mousey - bkdesktop); // drawy-bkgametopmousey-bkdesktop 纵坐标方向移动距离相同drawy bkgametop (mousey - (bkdesktop bkheight / 2 - bkheight / 2 * camerasize)) / camerasize; // drawy-bkgametopmousey-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.21freshbk(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 替换 bkmeshcopyheightbkmeshmapi bkmeshgametop / pixnum;bkmeshmapj bkmeshgameleft / pixnum;freshmesh(oldmesh, bkmesh, map, pentable, pentablev2, bkmeshmapi, bkmeshmapj, bkmeshfreshi, bkmeshfreshj, pixnum, defaultnum, defaultcolor); // 刷新重新映射其实就是开头初始化的代码,这里是给了一个封装示例用了一个数组过滤重复位置进行性能优化-2024.4.21freshbk(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) { // 分类渲染, drawflag0 时再选择性刷新缓冲区checkboundary(bkgameleft, bkgametop, gamelimitright, gamelimitbottom, bkmeshgameleft, bkmeshgametop, meshlimitright, meshlimitbottom, bkmeshcopyheight, bkmeshcopywidth); // move pixnum * 9, size 2*9* pinxumbkmeshmapi 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.21oldbkmeshgamex 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 是否存在不存在为 -1break;}}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 是否存在存在为 0break;}}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 2048 改写替换fscanf,因为在小熊猫C里fscanf不能读取带空格的字符串。而VisualStudio2022,DevC5.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 的上限 * 放大倍数 不超过 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;
}
// 从默认文件夹中读取自定义图片
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/73716980long long fHandle; // win 10 win 11 fHandle 要从long 改成long long 否则闪退
// long fHandle;// 文件数记录器char dirpath[100];strcpy(dirpath, dirname);if (access(dirpath, 0) -1) { // 检查文件夹是否存在不存在为 -1mkdir(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) { // 检查文件夹是否存在不存在为 -1mkdir(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 内的指定文件夹是否存在不存在为 -1mkdir(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);}}
}