从零到有的游戏开发(visual studio 2022 + easyx.h)
引言
本文章适用于C语言初学者掌握基本的游戏开发,
我将用详细的步骤引领大家如何开发属于自己的游戏。
作者温馨提示:不要认为开发游戏很难,一些基本的游戏逻辑其实很简单,
关于游戏的开发环境也不用担心,我会详细说明如何配置开发环境,下载链接我也会列出。
文章前半部分教你掌握开发游戏的基本逻辑(各种游戏逻辑)。
文章后半部分我会提供一个基本的2D角色扮演的游戏框架,(开发功能取决于玩家)。
游戏开发环境的配置
首先我们需要一个能安装easyx.h图形界面库的C语言编译器,这里我推荐vsual studio 2022
该编译器功能也是很强大,可以兼容各种编程语言的项目开发,这里我们只使用C语言即可。
visual studio 2022 的 安装
下载链接:Visual Studio 2022 IDE - 适用于软件开发人员的编程工具

选择图中的 community 2022 社区版本(社区版免费),
然后等待安装资源包的下载。
下载好后,弹出来的窗口,点击继续→
稍稍等待一小会儿.....

从左上角可以看到(工作负荷,单个组件,语言包,安装位置)四个头目录。
首先是(工作负荷):我们只需要勾选 “使用C++的桌面开发”。

然后(单个组件):只需要检查一下图中是否勾选了上述选项,一般不用更改(确定好win系统)
(语言包)默认勾选“简体中文”即可
最后(安装位置)要注意的是分成三个不同的子文件夹,你可以在同一个文件夹中新建三个子文件夹,然后将上述三个位置分别选中子文件夹即可,(如果第三个路径不可选,说明你之前下载过该编译器。)关于安装路径在哪个盘都随意。
第三个路径不可选的解决办法也很简单
第一步 :win + R 打开运行
第二步:输入 regedit 打开注册表
第三步:找到该位置
第四步:删除图中除(默认)以外的配置即可

然后点击安装,等待下载完成即可(需要一段时间,内存不小)

下载好后,运行打开,点击图中创建新项目。

选择空项目点击下一步

输入项目名称和路径

右键点击

新建项

定义名

然后就可以写代码了(你可以用helloworld试试)

现在编译器便安装好了,然后还需要安装图形界面库(很快)
easyx库的配置
下载链接:EasyX Graphics Library for C++

点击右侧红色 “下载EasyX”
下载好后,弹出窗口点击下一步。
然后会自动检测你的编译器版本,找到刚下载的Visual C++2022点击安装,
,显示安装成功就可以了,重启visual studio 2022,即可。
(最上面的EasyX文档也可以安装,里面包含easyx图形界面库的全部函数用法)
测试easyx库的配置(将下述代码复制进去)
#include<graphics.h> //需安装easyx图形库插件
#include<conio.h>
#include<time.h>
#include<math.h>
#include<sys/timeb.h>struct MyLove
{int NUMS; // 编号double m;double n;double size;bool Is_show;int x;int y;
};MyLove mylove[400];
int CenterX = 320;
int CenterY = 180;
double Size = 60;
void initdata(); // 初始化数据
void updata(); // 更新
void movedata(); // 平移
void showdata(); // 显示
int* GetRand(int* buf, int count, int range); // 随机数的生成
void heart(int x0, int y0, int size, COLORREF C);
void HpSleep(int ms);int main()
{initgraph(640, 480);initdata();BeginBatchDraw();while (true){updata();showdata();HpSleep(30); // 改为精确延时FlushBatchDraw();cleardevice();}EndBatchDraw();_getch();return 0;
}void updata()
{int* buf = (int*)malloc(sizeof(int) * 20);buf = GetRand(buf, 20, (int)(2 * Size / 0.01));movedata();for (int i = 0; i < 20; i++){mylove[i].m = buf[i] * 0.01;mylove[i].n = (((sin(buf[(int)i] * 0.01) * sqrt(fabs(cos(buf[(int)i] * 0.01)))) / (sin(buf[(int)i] * 0.01) + 1.4142)) - 2 * sin(buf[(int)i] * 0.01) + 2);mylove[i].size = Size;mylove[i].NUMS = i / 20;mylove[i].Is_show = true;mylove[i].x = (int)(-Size * mylove[i].n * cos(mylove[i].m) + CenterX);mylove[i].y = (int)(-Size * mylove[i].n * sin(mylove[i].m) + CenterY - mylove[i].size);}for (int i = 20; i < 400; i++){mylove[i].size = mylove[i].size + 1;if (mylove[i].size > 80){mylove[i].size = 80;}mylove[i].NUMS = i / 20;mylove[i].x = (int)(-mylove[i].size * mylove[i].n * cos(mylove[i].m) + CenterX);mylove[i].y = (int)(-mylove[i].size * mylove[i].n * sin(mylove[i].m) + CenterY - mylove[i].size);}
}void movedata()
{for (int i = 399; i > 19; i--){mylove[i] = mylove[i - 20];}
}void showdata()
{settextcolor(RED);wchar_t c = 0x59; // 0x28 是电话机在 Wingdings 字体中的对应编码for (int i = 0; i < 400; i++){settextstyle(mylove[i].NUMS + 10, 0, _T("Webdings"));setbkmode(TRANSPARENT);outtextxy(mylove[i].x + 20, mylove[i].y + 20, c);}
}int* GetRand(int* buf, int count, int range)
{struct timeb timeSeed;ftime(&timeSeed);srand(timeSeed.time * 1000 + timeSeed.millitm); // milli timefor (int i = 0; i < count; i++){int randTmp = rand() % range;for (int j = 0; j < i; j++){if (buf[j] == randTmp){break;//检查重复。}}buf[i] = randTmp;}return buf;
}void initdata()
{for (int i = 0; i < 400; i++){mylove[i].NUMS = 0;mylove[i].m = 0;mylove[i].n = 0;mylove[i].size = 0;mylove[i].Is_show = false;mylove[i].x = 0;mylove[i].y = 0;}
}// 精确延时函数(可以精确到 1ms,精度 ±1ms)
// by yangw80<yw80@qq.com>, 2011-5-4
void HpSleep(int ms)
{static clock_t oldclock = clock(); // 静态变量,记录上一次 tickoldclock += ms * CLOCKS_PER_SEC / 1000; // 更新 tickif (clock() > oldclock) // 如果已经超时,无需延时oldclock = clock();elsewhile (clock() < oldclock) // 延时Sleep(1); // 释放 CPU 控制权,降低 CPU 占用率,精度 10~16ms// Sleep(0); // 更高精度、更高 CPU 占用率,精度 1ms
}
复制好后,点击上方绿色空三角运行。(运行效果如下)

以上便完成了全部的环境配置,开启开发游戏之旅
基本游戏逻辑
首先需要包含头文件 #include<easyx.h>来调用图形函数
想要将代码中的效果展现出来,需要一个图形化窗口,并非是黑框框。
所以,第一步初始化一个图形化窗口。
initgraph(800,800);
该函数运行后,除了命令提示符的黑窗口之外,还会产生一个新的窗口,此时窗口内是空的。
如果我们想把外部图片贴上去,需要一个容器储存外部图片。
IMAGE img;//声明一个可以存储外部图片的容器
然后储存外部图片进入容器操作,&img是获取容器地址,“photo.png”是需要引入图片的路径
(路径可分为相对路径和绝对路径,我推荐将图片和源程序放到同一个根目录中,既方便引用,又方便后续对于游戏的封装)
loadimage(&img, "photo.png");加载图片进容器
那储存好的图片如何显示在屏幕上,我们需要函数将图片贴到屏幕上。
图中,x,y,前两个函数是指贴入图片的坐标(图片左上角顶点的坐标),
&img参数指贴入的图片容器,确定具体贴入哪个图片。
putimage(x,y, &img);
现在基本的图片显示便有了。
如果我们想让这个图片动起来,很好理解,我们只需要逐渐改变putimage函数的坐标参数就可以。
需要一个循环来刷新新的图像(改变坐标之后的贴图),(还需要刷新屏幕,或者使用背景覆盖法)
1,刷新屏幕:FlushBatchDraw ();(不需要参数) 清除掉上一个贴图,执行目前的贴图。
FlushBatchDraw ();
2,背景覆盖法:可以每次循环(先贴背景(覆盖掉上个位置的贴图)再贴改变坐标后的贴图)
关于图像的移动
#include<stdio.h>
#include<easyx.h>
#include<windows.h>IMAGE back;
IMAGE img;
int main()
{loadimg (&back,"选中背景图片的路径");
loadimg (&img,"选中目标图片的路径");
for(int i=1;i<=500;i++)
{
putimage(0,0,&back);
putimage(i,i,&img);
Sleep(100);
}return 0;
}
其中,&back 是获取背景(IMAGE back 容器存储着与窗口大小一致的背景图片),
所以每次贴图的坐标是0,0,
&img存取的则是需要移动的目标贴图,
每次循环,会在不同坐标贴上目标图片,
由于每次循环都会贴一次背景图,所以会覆盖掉上次的目标贴图,再贴下次的目标贴图,
这样,窗口中就始终只能看到一个目标贴图,且位置在不停发生改变,产生目标图片移动的效果。
(Sleep(100)是没隔100ms也就是每0.1秒刷新一次位置,不然上述循环会在一瞬间结束,无法观察,该函数在Windows.h库内)
上述代码就会产生一个从(1,1)移动到(500,500)的图像。
自主控制实时移动
既然贴图函数的坐标参数决定了目标图像的位置,那么我们如果按下相应的按键改变坐标参数,便可实现用按键控制移动,
我们可以调用一个Windows.h函数 GetAsyncKeyState('D') ,括号内参数是被检测的按键,
如果D( 不分大小写)按键被按下,则返回非零值,否则返回零,
所以,该代码便可检测按键的实时状态,如果按下D则x++(向右移动)
if(GetAsyncKeyState('D'))
x++;
所以整体移动函数模块就是(其中设置了范围,防止目标移动出边界),每次增加或减少的值不是1,而是一个预先定义好的值,可以自由控制移动速度(#define SPEED 10)
void control_move()//控制人物移动
{if (GetAsyncKeyState('D') && hero.x < width)//角色右移{hero.x += SPEED;}if (GetAsyncKeyState('A') && hero.x > 0)//角色左移{hero.x -= SPEED;}if (GetAsyncKeyState('W') && hero.y > 0)//角色上移hero.y -= SPEED;if (GetAsyncKeyState('S') && hero.y < high)//角色下移hero.y += SPEED;
}
然后把这个函数放入主循环内,因为游戏是一致运行的,所以全部需要改变的行为都要放到一个主循环内,由于GetAsyncKeyState是非阻塞性函数,也就是说,即使没有按键按下,主循环依然循环着,游戏持续运行着,只是目标贴图未移动。
int main()
{
....省略
while(1)
{
control_move();
putimage(0,0,&back);
putimage(i,i,&img);
}
return 0;}
关于目标发射物(开发目标远程攻击)
struct bang{
int x;//坐标
int y;
bool live = false;//是否存活
}fire;if(GetAsyncKeyState('j'))
fire.live = true;if(fire.live)
{
fire.x+=SPEED;
putimage(x,y,&img);
}
需要设定发射物的结构体,如果检测到J按键,则让发射物存活,并且自定义逻辑发射出去。
图中假设只有一个发射物,并且横向发射移动,如果需要发射多个,则只需要将结构体变量改成结构体变量数组,然后每次判断存活和移动的操作加一个外层数组遍历,同时同步所有状态。
现在基本的移动和发射逻辑都已说明
我们还需要一些辅助函数代码块,比如时间戳,每间隔多少ms运行一次函数体,且不阻塞主循环
bool timer(int ms, int id)//时间戳
{static DWORD t[500];// 将 clock() 的返回值转换为 DWORD 类型if (static_cast<DWORD>(clock()) - t[id] > static_cast<DWORD>(ms)){t[id] = static_cast<DWORD>(clock());return true;}return false;
}
/*时间戳*/
飞机大战测试
1,头文件
#include<stdio.h>
#include<easyx.h>
#include<conio.h>
#include<time.h>
#include<windows.h>
#include<stdlib.h>
2,设定图形变量存储图片
IMAGE BACK_DROP;
IMAGE PLANE_1;//飞机1
IMAGE PLANE_2;//飞机2
IMAGE DG_1;//敌机1
IMAGE DG_2;//敌机2
IMAGE BULLET_1;//子弹1
IMAGE BULLET_2;//子弹2
3,预定义需要使用参数值 设定 结构体(飞机和敌机)
enum My {WIDTH = 600,HEIGHT = 864,BULLET_NUM = 300,SHIP_SPEED = 2,BULLET_SPEED = 30,ENEMY_NUM = 5,ENEMY_SPEED = 1,
};struct ZT//状态结构体
{int x;int y; //坐标int hp = 100;//血量bool live = false;//是否存活int width;int height;
};ZT myplane;//飞机ZT BULLET[BULLET_NUM];//子弹ZT ENEMY[ENEMY_NUM];//敌机
基本互动(如果子弹和敌机图像有交叉,则判定击中,减血,血量<=0则判定死亡 )
int play()
{for (int i = 0;i <= ENEMY_NUM;i++){if (!ENEMY[i].live){continue;}for (int j = 0;j < BULLET_NUM;j++){if (!BULLET[i].live){continue;}//检测击中if (BULLET[j].x > ENEMY[i].x && BULLET[j].x<ENEMY[i].x + ENEMY[i].width&& BULLET[j].y>ENEMY[i].y && BULLET[j].y < ENEMY[i].y + ENEMY[i].height){BULLET[i].live = false;ENEMY[i].hp--;}//掉血就去死,ok?if (ENEMY[i].hp == 0){ENEMY[i].live = false;}}}return 0;
}
子弹和敌机的创建
int PLANE_MY()//构建飞机和子弹和敌机
{//绘制飞机putimage(myplane.x, myplane.y, &PLANE_1,NOTSRCERASE);putimage(myplane.x, myplane.y, &PLANE_2, SRCINVERT);//绘制子弹for (int i = 0;i <= BULLET_NUM;i++){if (BULLET[i].live){putimage(BULLET[i].x, BULLET[i].y, &BULLET_2, NOTSRCERASE);putimage(BULLET[i].x, BULLET[i].y, &BULLET_1, SRCINVERT);}}//绘制敌机for (int i = 0;i <= ENEMY_NUM;i++){if (ENEMY[i].live){putimage(ENEMY[i].x, ENEMY[i].y, &DG_2, NOTSRCERASE);putimage(ENEMY[i].x, ENEMY[i].y, &DG_1, SRCINVERT);}}return 0;
}
int createbullet()//子弹创建
{for (int i = 0;i <= BULLET_NUM;i++){if (!BULLET[i].live){BULLET[i].x = myplane.x + 49;BULLET[i].y = myplane.y;BULLET[i].live = true;break;}}return 0;
}
详细解释一下该部分(使用两张互补的色差图像可以实现透明贴图,后续有优化版本)
putimage(BULLET[i].x, BULLET[i].y, &BULLET_2, NOTSRCERASE);putimage(BULLET[i].x, BULLET[i].y, &BULLET_1, SRCINVERT);
子弹和敌机的移动,以及碰撞检测(检测可以放到里面,也可以独立出一个函数)非
int bulletmove()//子弹移动
{for (int i = 0;i <= BULLET_NUM;i++){if (BULLET[i].live){BULLET[i].y -= BULLET_SPEED;}if (BULLET[i].y < 0){BULLET[i].live = false;}}return 0;
}
int createenemy()
{for (int i = 0;i <= ENEMY_NUM;i++){if (!ENEMY[i].live){ENEMY[i].x = rand() % (WIDTH - 60);ENEMY[i].y = 0;ENEMY[i].live = true;break;}enemyhp(i);}return 0;
}int enemymove()//敌机的移动
{for (int i = 0;i <= ENEMY_NUM;i++){if (ENEMY[i].live){ENEMY[i].y += ENEMY_SPEED;}if (ENEMY[i].y > HEIGHT){ENEMY[i].live = false;}
}return 0;
}
int penzhuang()//碰撞检测
{for (int i = 0;i <= ENEMY_NUM;i++){if (myplane.y <= ENEMY[i].y && myplane.y >= ENEMY[i].y + ENEMY[i].height&& myplane.x >= ENEMY[i].x && myplane.x <= ENEMY[i].x + ENEMY[i].width){myplane.live = false;exit(0);}}
}
需要采用双缓冲绘图法,可以去除游戏循环的卡顿,
BeginBatchDraw(); 开始批量绘图。写在循环外
EndBatchDraw(); 结束批量绘制,并执行未完成的绘制任务。循坏外,程序结束前
FlushBatchDraw(); 执行未完成的绘制任务。写在循环内,构图后,延迟前
飞机大战代码汇总
#include<stdio.h>
#include<easyx.h>
#include<conio.h>
#include<time.h>
#include<windows.h>
#include<stdlib.h>
//牢笼
IMAGE BACK_DROP;
IMAGE PLANE_1;
IMAGE PLANE_2;
IMAGE DG_1;
IMAGE DG_2;
IMAGE BULLET_1;
IMAGE BULLET_2;enum My {WIDTH = 600,HEIGHT = 864,BULLET_NUM = 300,SHIP_SPEED = 2,BULLET_SPEED = 30,ENEMY_NUM = 5,ENEMY_SPEED = 1,
};
const int MAX = 10;struct ZT//状态结构体
{int x;int y; //坐标int hp = 100;//血量bool live = false;//是否存活int width;int height;
};ZT myplane;//飞机ZT BULLET[BULLET_NUM];//子弹ZT ENEMY[ENEMY_NUM];//敌机int DRAW_BACKDROP()//构造背景图
{putimage(0, 0, &BACK_DROP);return 0;
}
int enemyhp(int i)
{ENEMY[i].hp = 1;ENEMY[i].width = 90; ENEMY[i].height = 100;return 0;
}int play()
{for (int i = 0;i <= ENEMY_NUM;i++){if (!ENEMY[i].live){continue;}for (int j = 0;j < BULLET_NUM;j++){if (!BULLET[i].live){continue;}//检测击中if (BULLET[j].x > ENEMY[i].x && BULLET[j].x<ENEMY[i].x + ENEMY[i].width&& BULLET[j].y>ENEMY[i].y && BULLET[j].y < ENEMY[i].y + ENEMY[i].height){BULLET[i].live = false;ENEMY[i].hp--;}//掉血就去死,ok?if (ENEMY[i].hp == 0){ENEMY[i].live = false;}}}return 0;
}int PLANE_MY()//构建飞机和子弹和敌机
{//绘制飞机putimage(myplane.x, myplane.y, &PLANE_1,NOTSRCERASE);putimage(myplane.x, myplane.y, &PLANE_2, SRCINVERT);//绘制子弹for (int i = 0;i <= BULLET_NUM;i++){if (BULLET[i].live){putimage(BULLET[i].x, BULLET[i].y, &BULLET_2, NOTSRCERASE);putimage(BULLET[i].x, BULLET[i].y, &BULLET_1, SRCINVERT);}}//绘制敌机for (int i = 0;i <= ENEMY_NUM;i++){if (ENEMY[i].live){putimage(ENEMY[i].x, ENEMY[i].y, &DG_2, NOTSRCERASE);putimage(ENEMY[i].x, ENEMY[i].y, &DG_1, SRCINVERT);}}return 0;
}
int createbullet()//子弹创建
{for (int i = 0;i <= BULLET_NUM;i++){if (!BULLET[i].live){BULLET[i].x = myplane.x + 49;BULLET[i].y = myplane.y;BULLET[i].live = true;break;}}return 0;
}
bool timer(int ms, int id)//制造随机性
{static DWORD t[MAX];if (clock() - t[id] > ms){t[id] = clock();return true;}return false;}int bulletmove()//子弹移动
{for (int i = 0;i <= BULLET_NUM;i++){if (BULLET[i].live){BULLET[i].y -= BULLET_SPEED;}if (BULLET[i].y < 0){BULLET[i].live = false;}}return 0;
}
int createenemy()
{for (int i = 0;i <= ENEMY_NUM;i++){if (!ENEMY[i].live){ENEMY[i].x = rand() % (WIDTH - 60);ENEMY[i].y = 0;ENEMY[i].live = true;break;}enemyhp(i);}return 0;
}int enemymove()//敌机的移动
{for (int i = 0;i <= ENEMY_NUM;i++){if (ENEMY[i].live){ENEMY[i].y += ENEMY_SPEED;}if (ENEMY[i].y > HEIGHT){ENEMY[i].live = false;}
}return 0;
}
int penzhuang()//碰撞检测
{for (int i = 0;i <= ENEMY_NUM;i++){if (myplane.y <= ENEMY[i].y && myplane.y >= ENEMY[i].y + ENEMY[i].height&& myplane.x >= ENEMY[i].x && myplane.x <= ENEMY[i].x + ENEMY[i].width){myplane.live = false;exit(0);}}
}int main()
{initgraph(600, 1000);loadimage(&BACK_DROP, "back.jpg");loadimage(&PLANE_1,"plane1.png");loadimage(&PLANE_2, "plane2.png");loadimage(&DG_1, "D1.png");loadimage(&DG_2, "D2.png");loadimage(&BULLET_1, "zd1.png");loadimage(&BULLET_2, "zd2.png");myplane.x = 200;myplane.y = 500;myplane.live = true;for (int i = 0;i <= BULLET_NUM;i++){BULLET[i].x = 0;BULLET[i].y = 0;BULLET[i].live = false;}while (1){if (_kbhit())//检测案件发生{char c = _getch();//获取键盘信息switch (c)//控制移动{case 'w'://上if (myplane.y >= 10)myplane.y -= 20;break;case 's'://下if (myplane.y <= 885)myplane.y += 20;break;case 'a'://左if (myplane.x >= 20)myplane.x -= 20;break;case 'd'://右if (myplane.x <= 465)myplane.x += 20;break;case 'j':createbullet();break;}}else {Sleep(100);//基本刷新频率}DRAW_BACKDROP();//构建背景图//FlushBatchDraw();PLANE_MY();//基本原件生成bulletmove();//子弹移动if (timer(500, 0))//控制敌机的出现频率{createenemy();}if (timer(30, 2)) { enemymove();}play();//打penzhuang();//碰撞检测}//主循环return 0;
}//八个小时,老弟。
需要链接图片才可以运行哦,(上述说过,需要将目标图片放入指定容器)
上述可能会不太好理解,纯干货,可以参照b站课程
原创优化游戏逻辑的2D角色扮演游戏框架
先展示优化的游戏函数
设定好的全局变量和常量宏
#include <graphics.h>//图形算法库
#include <conio.h>//控制台交流库
#include<windows.h>//系统函数库
#include<stdio.h>//标准输入输出库
#include<time.h>//时间定义库
#include<easyx.h>//图形界面库
#include<math.h>//数学函数库#pragma comment( lib, "MSIMG32.LIB")//图形链接库
//============================================================================预处理
#define M_PI 3.1415926 //圆周率#define HERO_SPEED 1 //hero.移动速度#define HERO_JUMP_SPEED 10 //hero.跳跃帧高度#define HERO_JUMP_NUM 5 //hero.跳跃帧数#define LIGHT_SWORD_SPEED 3 //light_sword.光刃飞行速度#define DRAGON_NUM_MAX 2 //龙同时存在最大数量#define DRAGON_SPEED 2 //龙的移动速度
//============================================================================常量宏
int HEIGHT = 1000;//当前屏幕设备的高度(单位毫米)int WIDTH = 1700;//当前屏幕设备的宽度(单位毫米)IMAGE back;//背景IMAGE stop_imgR[13];//静止 右 待机动作IMAGE stop_imgL[13];//静止 左 待机动作IMAGE run_imgR[5];//奔跑 右 动作IMAGE run_imgL[5];//奔跑 左 动作IMAGE raise_sword;//举剑的动作IMAGE light_sword_imgR;//右光刃
IMAGE light_sword_imgL;//左光刃IMAGE HP_img;//血量显示IMAGE MP_img;//蓝量显示IMAGE TX_ADD_HP[16]; //加血特效图IMAGE dragon_imgR[7]; //右 龙图片
IMAGE dragon_imgL[7]; //左 龙图片IMAGE light_effect[31]; //受击光效图片int run_num = 1;//移动动作循环底码int stop_num = 1;//待机动作循环底码int TX_ADD_HP_num = 1;//特效图像循环底码int dragon_img_num = 1;//龙图运动循环底码int Affected_img_num = 1;//基础光刃受击特效图循环底码bool Previous_direction = true;//前一时刻方向判定量int dragon_rand_move_num[DRAGON_NUM_MAX + 1];//龙的随机运动底码
int dragon_rand_pursuit_num[DRAGON_NUM_MAX + 1];//龙的随机追击底码
//=============================================================================全局变量
设定好的结构体
struct role {int x = 200; //hero.x坐标int y = 100; //hero.y坐标int blood = 100; //hero.血量int blue = 100; //hero.蓝量bool live = true; //hero.存活bool ground = true; //hero.触地
}hero;
/*人物状态结构体*/struct sword {int x = 0;//光刃x坐标int y = 0;//光刃y坐标bool live = false;//光刃存活bool direction = true;//光刃方向
};
/*基本远程攻击结构体*/struct sword light_sword[11];//光刃struct Special_effects {int x = 1; //特效.x坐标int y = 1; //特效.y坐标bool live = false; //是否激活
};/*基本特效结构体*/
struct Special_effects add_blood; //加血特效
struct Special_effects Affected_effect[11];//基础光刃受击效果struct move {//基本移动体坐标int x = 800;int y = 500;//坐标int HP = 100;//血量int speed_x = 10;int speed_y = 10;//速度bool live = false;//是否存活bool if_move = true; //是否能移动bool direction = true;//向左向右bool pursuit = true;//是否追击int die_num_zhen = 0;//死亡后的帧数
};
//基本敌对目标结构体
struct move dragon[DRAGON_NUM_MAX + 1]; //敌龙 同时最多存在五只//==============================================================================结构体
加载图片
void load()//加载图片素材
{loadimage(&back, "back.png", 1700, 1000);//背景图的加载loadimage(&HP_img, "HP.png", 100, 50);//血条HP图片加载loadimage(&MP_img, "MP.png", 100, 50);//蓝条MP图片加载//loadimage(&raise_sword, "attack.png", 400, 400);//攻击举剑动作图片加载loadimage(&light_sword_imgR, "光刃.png", 400, 400);//右光刃攻击特效图片加载loadimage(&light_sword_imgL, "光刃f.png", 400, 400);//左光刃攻击特效图片加载for (int i = 1;i <= 9;i++)//01.png 02.png 03.png 04........{char str[50];sprintf_s(str, "0%d.png", i);loadimage(&stop_imgR[i], str, 200, 200);//加载待机动作}for (int x = 10;x <= 12;x++){char str2[50];sprintf_s(str2, "%d.png", x);loadimage(&stop_imgR[x], str2, 200, 200);//加载 右 待机动作}for (int y = 1;y <= 4;y++){char str3[50];char str4[50];sprintf_s(str3, "run%d.png", y);loadimage(&run_imgR[y], str3, 180, 180);//加载 右 奔跑动作sprintf_s(str4, "frun%d.png", y);loadimage(&run_imgL[y], str4, 180, 180);//加载 左 奔跑动作}for (int a = 1; a <= 12; a++){char str5[50];sprintf_s(str5, "fs%d.png", a);loadimage(&stop_imgL[a], str5, 200, 200);//加载 左 待机动作}for (int i = 1;i <= 15;i++)//加载加血特效{char str6[50];sprintf_s(str6, "tx%d.png", i);loadimage(&TX_ADD_HP[i], str6, 400, 400);}for (int i = 1;i <= 6;i++)//加载龙的素材图{char str7[50];sprintf_s(str7, "dg%d.png", i);loadimage(&dragon_imgR[i], str7, 200, 200);char str8[50];sprintf_s(str8, "dgf%d.png", i);loadimage(&dragon_imgL[i], str8, 200, 200);}for (int i = 1;i <= 30;i++)//加载受击光效{char str9[50];sprintf_s(str9, "gx%d.png", i);loadimage(&light_effect[i], str9, 200, 200);}}
//加载图片素材
时间戳
bool timer(int ms, int id)//时间戳
{static DWORD t[500];// 将 clock() 的返回值转换为 DWORD 类型if (static_cast<DWORD>(clock()) - t[id] > static_cast<DWORD>(ms)){t[id] = static_cast<DWORD>(clock());return true;}return false;
}
/*时间戳*/
获取屏幕参数(全屏的关键)
/*获取当前屏幕的参数*/void transparentimage3(IMAGE* dstimg, int x, int y, IMAGE* srcimg) //png_windows透明贴图
{HDC dstDC = GetImageHDC(dstimg);HDC srcDC = GetImageHDC(srcimg);int w = srcimg->getwidth();int h = srcimg->getheight();BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };AlphaBlend(dstDC, x, y, w, h, srcDC, 0, 0, w, h, bf);
}
前面我们每个目标都要采用两张叠加的图片才能实现透明贴图,而该函数只需要使用wps工具将单个图片背景设置成win的透明背景,然后插入该函数可自动剔除掉背景
void transparentimage3(IMAGE* dstimg, int x, int y, IMAGE* srcimg) //png_windows透明贴图
{HDC dstDC = GetImageHDC(dstimg);HDC srcDC = GetImageHDC(srcimg);int w = srcimg->getwidth();int h = srcimg->getheight();BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };AlphaBlend(dstDC, x, y, w, h, srcDC, 0, 0, w, h, bf);
}
/*windows.h 的png透明贴图工具*/
原创函数,用于可控范围的切换图片目标,实现特定范围的人物移动行走效果,和特效
void random_nums()//一帧内生成十个的随机数,前五个赋值给龙的判断移动变量,后五个给龙的追击判断变量
{int num = 10;int used[100] = { 0 }; // 标记数组,初始化为 0int numbers[10];srand((unsigned int)time(NULL)); // 初始化随机数种子for (int i = 0; i < num; i++) {int num;do {num = rand() % 100; // 生成 0 到 RANGE - 1 之间的随机数} while (used[num]); // 如果该数字已被使用,则重新生成numbers[i] = num;used[num] = 1; // 标记该数字已被使用}// 输出生成的随机数for (int i = 1; i <= num / 2; i++) {dragon_rand_move_num[i] = numbers[i];}for (int i = num / 2 + 1;i <= num;i++){dragon_rand_pursuit_num[i - num / 2] = numbers[i];}
}
//一帧内生成特定数量的随机数int cycle_count(int min, int max, int type)//调用返回值从min~max之间的单向循环
{static int count[10];while (count[type] < min - 1)count[type]++;count[type]++;if (count[type] > max)count[type] = min;return count[type];
}//不同type参数分配不同的静态变量count
/*可控范围的底码循环,用于运动图片的切换*/
控制特效的单次便利图像运行,单次便利结束后,将传入的bool类型指针变为falsevoid draw_effect_ADD_blood()
{if (add_blood.live)transparentimage3(NULL, hero.x - 100, hero.y - 150, &TX_ADD_HP[TX_ADD_HP_num]);
}
控制移动(通过检测上次的移动方向,可以知道某时刻角色的面朝向,从而决定贴图朝向)
void control_hero()//控制人物移动
{if (GetAsyncKeyState('D') && hero.x < 1550)//角色右移{hero.x += HERO_SPEED;Previous_direction = true;}if (GetAsyncKeyState('A') && hero.x > -5)//角色左移{hero.x -= HERO_SPEED;Previous_direction = false;}if (GetAsyncKeyState('W') && hero.y > -5)//角色上移hero.y -= HERO_SPEED;if (GetAsyncKeyState('S') && hero.y < 850)//角色下移hero.y += HERO_SPEED;
}
/*控制角色移动*/
发射物光刃一体化程序
//创造光刃void move_sword()
{for (int i = 1;i <= 10;i++){if (light_sword[i].live){if (light_sword[i].direction)//是否朝右light_sword[i].x += LIGHT_SWORD_SPEED;elselight_sword[i].x -= LIGHT_SWORD_SPEED;}}
}
//移动光刃void draw_sword()
{for (int i = 1;i <= 10;i++)if (light_sword[i].live){if (light_sword[i].direction)transparentimage3(NULL, light_sword[i].x, light_sword[i].y, &light_sword_imgR);elsetransparentimage3(NULL, light_sword[i].x, light_sword[i].y, &light_sword_imgL);}
}
//绘画光刃void draw_HPMP()
{transparentimage3(NULL, 10, 10, &HP_img);transparentimage3(NULL, 10, 70, &MP_img);
}//对基本光刃受击特效的绘画void Attack_detection()
{for (int i = 1;i <= 10;i++){int ctr = 1;for (int a = 1;a <= DRAGON_NUM_MAX;a++){if (light_sword[i].x - dragon[a].x<200 && light_sword[i].x - dragon[a].x>-200 && light_sword[i].live)if (dragon[a].live)if (light_sword[i].y - dragon[a].y<0 && light_sword[i].y - dragon[a].y>-200){dragon[a].HP -= 20;Affected_effect[i].x = dragon[a].x + 50;Affected_effect[i].y = dragon[a].y + 30;Affected_effect[i].live = true;light_sword[i].live = false;ctr = 0;break;}}if (ctr == 0)break;}
}
//基本光刃命中判定以及反馈
游戏特效
//创造加血特效 (内含按键 U )int control_effect_count(int min, int max, bool* live, int type)//控制特效的单次循环运行
{static int count[10] = { min - 1 };count[type]++;if (count[type] >= max + 1){*live = false;count[type] = min - 1;return count[type] + 1;}return count[type];
}//加血特效的绘画void select_dragon_speed() //根据距离分配速度
{for (int i = 1;i <= DRAGON_NUM_MAX;i++)if (dragon[i].pursuit && dragon[i].live){//同时满足追击和移动和存活条件后,赋值追击速度double cx = (double)(dragon[i].x - hero.x); //敌我x坐标差double cy = (double)(dragon[i].y - hero.y); //敌我y坐标差double cz = sqrt(cx * cx + cy * cy); //绝对距离if (cx == 0 && cy == 0)//防止敌我目标重合带来的除0bug{cz = 1;}double cxz = cx / cz;double cyz = cy / cz;//移动方向参数dragon[i].speed_x = (int)(-DRAGON_SPEED * cxz);dragon[i].speed_y = (int)(-DRAGON_SPEED * cyz);//分配速度}
}
用算法赋予目标自动寻敌并且追击的效果
//根据敌我位移分配速度和状态void dragon_move()
{for (int i = 1;i <= DRAGON_NUM_MAX;i++){if (dragon[i].live && dragon[i].pursuit){//基本移动dragon[i].x += dragon[i].speed_x;dragon[i].y += dragon[i].speed_y;}if (dragon[i].speed_x > 0)dragon[i].direction = false;elsedragon[i].direction = true;}
}
敌对目标的创建
void dragon_move()
{for (int i = 1;i <= DRAGON_NUM_MAX;i++){if (dragon[i].live && dragon[i].pursuit){//基本移动dragon[i].x += dragon[i].speed_x;dragon[i].y += dragon[i].speed_y;}if (dragon[i].speed_x > 0)dragon[i].direction = false;elsedragon[i].direction = true;}
}
//龙的移动void draw_dragon()
{for (int i = 1;i <= DRAGON_NUM_MAX;i++)if (dragon[i].live){if (dragon[i].direction)transparentimage3(NULL, dragon[i].x, dragon[i].y, &dragon_imgR[dragon_img_num]);elsetransparentimage3(NULL, dragon[i].x, dragon[i].y, &dragon_imgL[dragon_img_num]);}
}
//龙的绘画void Stop_the_Dragon_Crossing_Realm()//阻止龙的越界
{for (int i = 1;i <= DRAGON_NUM_MAX;i++){if (dragon[i].x <= 20)// 注意30-20要 > speed_x,防止瞬间越界{dragon[i].x = 30;dragon[i].speed_x = -dragon[i].speed_x;}if (dragon[i].x >= 1680)// 注意980-970要 > speed_x,防止瞬间越界{dragon[i].x = 1670;dragon[i].speed_x = -dragon[i].speed_x;}if (dragon[i].y <= 20)// 注意30-20要 > speed_y,防止瞬间越界{dragon[i].y = 30;dragon[i].speed_y = -dragon[i].speed_y;}if (dragon[i].y >= 980)// 注意1680-1670要 > speed_y,防止瞬间越界{dragon[i].y = 970;dragon[i].speed_y = -dragon[i].speed_y;}}
}
//阻止龙越界void creat_dragon()
{for (int i = 1;i <= DRAGON_NUM_MAX;i++){if (dragon[i].HP <= 0 && dragon[i].live){dragon[i].die_num_zhen = 0;dragon[i].live = false;//dragon[i].deathTime = clock(); // 更新死亡时间}if (!dragon[i].live){if (dragon[i].die_num_zhen <= 4)//4*0.5=2scontinue;//if (clock() - dragon[i].deathTime < 2000) continue; // 5 秒内不重新生成dragon[i].x = 800;dragon[i].y = 500;dragon[i].live = true;dragon[i].HP = 100; // 重新生成时恢复血量break;}}
}
//创造龙,附带空地才创造void dragon_x_dragon()//两条龙之间保持距离,避免重叠
{for (int i = 1;i <= DRAGON_NUM_MAX;i++){for (int a = 1;a <= i;a++){if (dragon[i].x - dragon[a].x <= 200 && dragon[i].x - dragon[a].x > 0){// dragon[i]在左 <- -> dragon[i+1]在右 if (dragon[a].speed_x > 0)dragon[a].speed_x = 0;//如果左边的在右移则水平停止if (dragon[i].speed_x < 0)dragon[i].speed_x = 0;}if (dragon[a].x - dragon[i].x <= 200 && dragon[a].x - dragon[i].x > 0){// dragon[i+1]在左 <- -> dragon[i]在右if (dragon[i].speed_x > 0)dragon[i].speed_x = 0;if (dragon[a].speed_x < 0)dragon[a].speed_x = 0;}}}
}
//两条龙之间保持距离,避免重叠,该函数需要放到获取所有速度之后void draw_light_effect()
{for (int i = 1;i <= 10;i++)if (Affected_effect[i].live)transparentimage3(NULL, Affected_effect[i].x, Affected_effect[i].y, &light_effect[Affected_img_num]);
}
组合的mian函数主运行块
int main()
{Get_Height_And_Width(&HEIGHT, &WIDTH);//获取屏幕参数,构建全屏窗口initgraph(WIDTH, HEIGHT);//初始化图形界面窗口load();//加载图片putback();//张贴背景BeginBatchDraw();//开启双缓冲绘图srand(time(0));//设定随机种子while (true){putback();//背景绘画control_hero();//控制角色移动 (控制按键:W,A,S,D )Select_texture();//控制选择人物状态并绘图出人物timer_thing();//需要时间延迟的事件集合(内含控制按键J)select_dragon_speed();//赋予龙追击的能力Attack_detection();//受击检测dragon_x_dragon();//防止龙的重叠Stop_the_Dragon_Crossing_Realm();//绘画{draw_sword();//光刃的绘画draw_HPMP();//状态条的绘画draw_effect_ADD_blood();//加血特效的绘画draw_dragon();//绘画龙draw_light_effect();}//移动{move_sword();//光刃的移动}{creat_add_HP();//创造加血特效 (内含按键 U )}beyond_sword_boundary();//超出边界的光刃判断消失FlushBatchDraw();//刷新缓冲绘图//cleardevice();}EndBatchDraw();//结束缓冲绘图exit(0);//退出程序return 0;
}
该游戏总代码
#include <graphics.h>//图形算法库
#include <conio.h>//控制台交流库
#include<windows.h>//系统函数库
#include<stdio.h>//标准输入输出库
#include<time.h>//时间定义库
#include<easyx.h>//图形界面库
#include<math.h>//数学函数库#pragma comment( lib, "MSIMG32.LIB")//图形链接库
//============================================================================预处理
#define M_PI 3.1415926 //圆周率#define HERO_SPEED 1 //hero.移动速度#define HERO_JUMP_SPEED 10 //hero.跳跃帧高度#define HERO_JUMP_NUM 5 //hero.跳跃帧数#define LIGHT_SWORD_SPEED 3 //light_sword.光刃飞行速度#define DRAGON_NUM_MAX 2 //龙同时存在最大数量#define DRAGON_SPEED 2 //龙的移动速度
//============================================================================常量宏
int HEIGHT = 1000;//当前屏幕设备的高度(单位毫米)int WIDTH = 1700;//当前屏幕设备的宽度(单位毫米)IMAGE back;//背景IMAGE stop_imgR[13];//静止 右 待机动作IMAGE stop_imgL[13];//静止 左 待机动作IMAGE run_imgR[5];//奔跑 右 动作IMAGE run_imgL[5];//奔跑 左 动作IMAGE raise_sword;//举剑的动作IMAGE light_sword_imgR;//右光刃
IMAGE light_sword_imgL;//左光刃IMAGE HP_img;//血量显示IMAGE MP_img;//蓝量显示IMAGE TX_ADD_HP[16]; //加血特效图IMAGE dragon_imgR[7]; //右 龙图片
IMAGE dragon_imgL[7]; //左 龙图片IMAGE light_effect[31]; //受击光效图片int run_num = 1;//移动动作循环底码int stop_num = 1;//待机动作循环底码int TX_ADD_HP_num = 1;//特效图像循环底码int dragon_img_num = 1;//龙图运动循环底码int Affected_img_num = 1;//基础光刃受击特效图循环底码bool Previous_direction = true;//前一时刻方向判定量int dragon_rand_move_num[DRAGON_NUM_MAX + 1];//龙的随机运动底码
int dragon_rand_pursuit_num[DRAGON_NUM_MAX + 1];//龙的随机追击底码
//=============================================================================全局变量struct role {int x = 200; //hero.x坐标int y = 100; //hero.y坐标int blood = 100; //hero.血量int blue = 100; //hero.蓝量bool live = true; //hero.存活bool ground = true; //hero.触地
}hero;
/*人物状态结构体*/struct sword {int x = 0;//光刃x坐标int y = 0;//光刃y坐标bool live = false;//光刃存活bool direction = true;//光刃方向
};
/*基本远程攻击结构体*/struct sword light_sword[11];//光刃struct Special_effects {int x = 1; //特效.x坐标int y = 1; //特效.y坐标bool live = false; //是否激活
};/*基本特效结构体*/
struct Special_effects add_blood; //加血特效
struct Special_effects Affected_effect[11];//基础光刃受击效果struct move {//基本移动体坐标int x = 800;int y = 500;//坐标int HP = 100;//血量int speed_x = 10;int speed_y = 10;//速度bool live = false;//是否存活bool if_move = true; //是否能移动bool direction = true;//向左向右bool pursuit = true;//是否追击int die_num_zhen = 0;//死亡后的帧数
};
//基本敌对目标结构体
struct move dragon[DRAGON_NUM_MAX + 1]; //敌龙 同时最多存在五只//==============================================================================结构体
void load()//加载图片素材
{loadimage(&back, "back.png", 1700, 1000);//背景图的加载loadimage(&HP_img, "HP.png", 100, 50);//血条HP图片加载loadimage(&MP_img, "MP.png", 100, 50);//蓝条MP图片加载//loadimage(&raise_sword, "attack.png", 400, 400);//攻击举剑动作图片加载loadimage(&light_sword_imgR, "光刃.png", 400, 400);//右光刃攻击特效图片加载loadimage(&light_sword_imgL, "光刃f.png", 400, 400);//左光刃攻击特效图片加载for (int i = 1;i <= 9;i++)//01.png 02.png 03.png 04........{char str[50];sprintf_s(str, "0%d.png", i);loadimage(&stop_imgR[i], str, 200, 200);//加载待机动作}for (int x = 10;x <= 12;x++){char str2[50];sprintf_s(str2, "%d.png", x);loadimage(&stop_imgR[x], str2, 200, 200);//加载 右 待机动作}for (int y = 1;y <= 4;y++){char str3[50];char str4[50];sprintf_s(str3, "run%d.png", y);loadimage(&run_imgR[y], str3, 180, 180);//加载 右 奔跑动作sprintf_s(str4, "frun%d.png", y);loadimage(&run_imgL[y], str4, 180, 180);//加载 左 奔跑动作}for (int a = 1; a <= 12; a++){char str5[50];sprintf_s(str5, "fs%d.png", a);loadimage(&stop_imgL[a], str5, 200, 200);//加载 左 待机动作}for (int i = 1;i <= 15;i++)//加载加血特效{char str6[50];sprintf_s(str6, "tx%d.png", i);loadimage(&TX_ADD_HP[i], str6, 400, 400);}for (int i = 1;i <= 6;i++)//加载龙的素材图{char str7[50];sprintf_s(str7, "dg%d.png", i);loadimage(&dragon_imgR[i], str7, 200, 200);char str8[50];sprintf_s(str8, "dgf%d.png", i);loadimage(&dragon_imgL[i], str8, 200, 200);}for (int i = 1;i <= 30;i++)//加载受击光效{char str9[50];sprintf_s(str9, "gx%d.png", i);loadimage(&light_effect[i], str9, 200, 200);}}
//加载图片素材bool timer(int ms, int id)//时间戳
{static DWORD t[500];// 将 clock() 的返回值转换为 DWORD 类型if (static_cast<DWORD>(clock()) - t[id] > static_cast<DWORD>(ms)){t[id] = static_cast<DWORD>(clock());return true;}return false;
}
/*时间戳*/void Get_Height_And_Width(int* H, int* W)//获取当前屏幕的参数
{int screenWidth = *W = GetSystemMetrics(SM_CXSCREEN);int screenHeight = *H = GetSystemMetrics(SM_CYSCREEN);
}
/*获取当前屏幕的参数*/void transparentimage3(IMAGE* dstimg, int x, int y, IMAGE* srcimg) //png_windows透明贴图
{HDC dstDC = GetImageHDC(dstimg);HDC srcDC = GetImageHDC(srcimg);int w = srcimg->getwidth();int h = srcimg->getheight();BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };AlphaBlend(dstDC, x, y, w, h, srcDC, 0, 0, w, h, bf);
}
/*windows.h 的png透明贴图工具*/void random_nums()//一帧内生成十个的随机数,前五个赋值给龙的判断移动变量,后五个给龙的追击判断变量
{int num = 10;int used[100] = { 0 }; // 标记数组,初始化为 0int numbers[10];srand((unsigned int)time(NULL)); // 初始化随机数种子for (int i = 0; i < num; i++) {int num;do {num = rand() % 100; // 生成 0 到 RANGE - 1 之间的随机数} while (used[num]); // 如果该数字已被使用,则重新生成numbers[i] = num;used[num] = 1; // 标记该数字已被使用}// 输出生成的随机数for (int i = 1; i <= num / 2; i++) {dragon_rand_move_num[i] = numbers[i];}for (int i = num / 2 + 1;i <= num;i++){dragon_rand_pursuit_num[i - num / 2] = numbers[i];}
}
//一帧内生成特定数量的随机数int cycle_count(int min, int max, int type)//调用返回值从min~max之间的单向循环
{static int count[10];while (count[type] < min - 1)count[type]++;count[type]++;if (count[type] > max)count[type] = min;return count[type];
}//不同type参数分配不同的静态变量count
/*可控范围的底码循环,用于运动图片的切换*/void control_hero()//控制人物移动
{if (GetAsyncKeyState('D') && hero.x < 1550)//角色右移{hero.x += HERO_SPEED;Previous_direction = true;}if (GetAsyncKeyState('A') && hero.x > -5)//角色左移{hero.x -= HERO_SPEED;Previous_direction = false;}if (GetAsyncKeyState('W') && hero.y > -5)//角色上移hero.y -= HERO_SPEED;if (GetAsyncKeyState('S') && hero.y < 850)//角色下移hero.y += HERO_SPEED;
}
/*控制角色移动*/void creat_sword()
{if (GetAsyncKeyState('J')){for (int i = 1;i <= 10;i++){if (!light_sword[i].live){light_sword[i].live = true;light_sword[i].x = hero.x - 100;//光刃继承人物前坐标释放light_sword[i].y = hero.y - 100;if (Previous_direction)//是否朝右light_sword[i].direction = true;elselight_sword[i].direction = false;break;}}}
}
//创造光刃void move_sword()
{for (int i = 1;i <= 10;i++){if (light_sword[i].live){if (light_sword[i].direction)//是否朝右light_sword[i].x += LIGHT_SWORD_SPEED;elselight_sword[i].x -= LIGHT_SWORD_SPEED;}}
}
//移动光刃void draw_sword()
{for (int i = 1;i <= 10;i++)if (light_sword[i].live){if (light_sword[i].direction)transparentimage3(NULL, light_sword[i].x, light_sword[i].y, &light_sword_imgR);elsetransparentimage3(NULL, light_sword[i].x, light_sword[i].y, &light_sword_imgL);}
}
//绘画光刃void draw_HPMP()
{transparentimage3(NULL, 10, 10, &HP_img);transparentimage3(NULL, 10, 70, &MP_img);
}
//状态栏的构建void Select_texture()//选择任务状态并且绘图
{if (GetAsyncKeyState('D'))//是否按下D{transparentimage3(NULL, hero.x, hero.y, &run_imgR[run_num]);}else {//没有按下Dif (GetAsyncKeyState('A'))//是否按下A{transparentimage3(NULL, hero.x, hero.y, &run_imgL[run_num]);}else {//没有按下Aif (GetAsyncKeyState('W') || GetAsyncKeyState('S')){//是否按下W或Sif (Previous_direction)//是否右朝向transparentimage3(NULL, hero.x, hero.y, &run_imgR[run_num]);//右朝向上下移动else//左朝向transparentimage3(NULL, hero.x, hero.y, &run_imgL[run_num]);//左朝向上下移动}else {//待机动作if (Previous_direction)//是否右朝向transparentimage3(NULL, hero.x, hero.y, &stop_imgR[stop_num]);//待机右朝向else//左朝向transparentimage3(NULL, hero.x, hero.y, &stop_imgL[stop_num]);//待机左朝向}}}
}
//人物动作状态的选择判断绘图void putback()
{putimage(0, 0, &back);
}
//背景图的绘画void beyond_sword_boundary()
{for (int i = 1;i <= 10;i++)if (light_sword[i].x<0 || light_sword[i].x>WIDTH)light_sword[i].live = false;}
//超出边界的光刃判定消失void creat_add_HP()//创造加血特效 (内含按键 U )
{//触发条件,检验按键“U" 并且 特效不存活 并且 特效已完成if (GetAsyncKeyState('U') && !add_blood.live)add_blood.live = true;
}
//创造加血特效 (内含按键 U )int control_effect_count(int min, int max, bool* live, int type)//控制特效的单次循环运行
{static int count[10] = { min - 1 };count[type]++;if (count[type] >= max + 1){*live = false;count[type] = min - 1;return count[type] + 1;}return count[type];
}
控制特效的单次便利图像运行,单次便利结束后,将传入的bool类型指针变为falsevoid draw_effect_ADD_blood()
{if (add_blood.live)transparentimage3(NULL, hero.x - 100, hero.y - 150, &TX_ADD_HP[TX_ADD_HP_num]);
}
//加血特效的绘画void select_dragon_speed() //根据距离分配速度
{for (int i = 1;i <= DRAGON_NUM_MAX;i++)if (dragon[i].pursuit && dragon[i].live){//同时满足追击和移动和存活条件后,赋值追击速度double cx = (double)(dragon[i].x - hero.x); //敌我x坐标差double cy = (double)(dragon[i].y - hero.y); //敌我y坐标差double cz = sqrt(cx * cx + cy * cy); //绝对距离if (cx == 0 && cy == 0)//防止敌我目标重合带来的除0bug{cz = 1;}double cxz = cx / cz;double cyz = cy / cz;//移动方向参数dragon[i].speed_x = (int)(-DRAGON_SPEED * cxz);dragon[i].speed_y = (int)(-DRAGON_SPEED * cyz);//分配速度}
}
//根据敌我位移分配速度和状态void dragon_move()
{for (int i = 1;i <= DRAGON_NUM_MAX;i++){if (dragon[i].live && dragon[i].pursuit){//基本移动dragon[i].x += dragon[i].speed_x;dragon[i].y += dragon[i].speed_y;}if (dragon[i].speed_x > 0)dragon[i].direction = false;elsedragon[i].direction = true;}
}
//龙的移动void draw_dragon()
{for (int i = 1;i <= DRAGON_NUM_MAX;i++)if (dragon[i].live){if (dragon[i].direction)transparentimage3(NULL, dragon[i].x, dragon[i].y, &dragon_imgR[dragon_img_num]);elsetransparentimage3(NULL, dragon[i].x, dragon[i].y, &dragon_imgL[dragon_img_num]);}
}
//龙的绘画void Stop_the_Dragon_Crossing_Realm()//阻止龙的越界
{for (int i = 1;i <= DRAGON_NUM_MAX;i++){if (dragon[i].x <= 20)// 注意30-20要 > speed_x,防止瞬间越界{dragon[i].x = 30;dragon[i].speed_x = -dragon[i].speed_x;}if (dragon[i].x >= 1680)// 注意980-970要 > speed_x,防止瞬间越界{dragon[i].x = 1670;dragon[i].speed_x = -dragon[i].speed_x;}if (dragon[i].y <= 20)// 注意30-20要 > speed_y,防止瞬间越界{dragon[i].y = 30;dragon[i].speed_y = -dragon[i].speed_y;}if (dragon[i].y >= 980)// 注意1680-1670要 > speed_y,防止瞬间越界{dragon[i].y = 970;dragon[i].speed_y = -dragon[i].speed_y;}}
}
//阻止龙越界void creat_dragon()
{for (int i = 1;i <= DRAGON_NUM_MAX;i++){if (dragon[i].HP <= 0 && dragon[i].live){dragon[i].die_num_zhen = 0;dragon[i].live = false;//dragon[i].deathTime = clock(); // 更新死亡时间}if (!dragon[i].live){if (dragon[i].die_num_zhen <= 4)//4*0.5=2scontinue;//if (clock() - dragon[i].deathTime < 2000) continue; // 5 秒内不重新生成dragon[i].x = 800;dragon[i].y = 500;dragon[i].live = true;dragon[i].HP = 100; // 重新生成时恢复血量break;}}
}
//创造龙,附带空地才创造void dragon_x_dragon()//两条龙之间保持距离,避免重叠
{for (int i = 1;i <= DRAGON_NUM_MAX;i++){for (int a = 1;a <= i;a++){if (dragon[i].x - dragon[a].x <= 200 && dragon[i].x - dragon[a].x > 0){// dragon[i]在左 <- -> dragon[i+1]在右 if (dragon[a].speed_x > 0)dragon[a].speed_x = 0;//如果左边的在右移则水平停止if (dragon[i].speed_x < 0)dragon[i].speed_x = 0;}if (dragon[a].x - dragon[i].x <= 200 && dragon[a].x - dragon[i].x > 0){// dragon[i+1]在左 <- -> dragon[i]在右if (dragon[i].speed_x > 0)dragon[i].speed_x = 0;if (dragon[a].speed_x < 0)dragon[a].speed_x = 0;}}}
}
//两条龙之间保持距离,避免重叠,该函数需要放到获取所有速度之后void draw_light_effect()
{for (int i = 1;i <= 10;i++)if (Affected_effect[i].live)transparentimage3(NULL, Affected_effect[i].x, Affected_effect[i].y, &light_effect[Affected_img_num]);
}
//对基本光刃受击特效的绘画void Attack_detection()
{for (int i = 1;i <= 10;i++){int ctr = 1;for (int a = 1;a <= DRAGON_NUM_MAX;a++){if (light_sword[i].x - dragon[a].x<200 && light_sword[i].x - dragon[a].x>-200 && light_sword[i].live)if (dragon[a].live)if (light_sword[i].y - dragon[a].y<0 && light_sword[i].y - dragon[a].y>-200){dragon[a].HP -= 20;Affected_effect[i].x = dragon[a].x + 50;Affected_effect[i].y = dragon[a].y + 30;Affected_effect[i].live = true;light_sword[i].live = false;ctr = 0;break;}}if (ctr == 0)break;}
}
//基本光刃命中判定以及反馈//=========================================================================功能函数的构建void timer_thing()//需要时间延迟的事件集合
{if (timer(100, 1)){//角色待机动作速率stop_num = cycle_count(1, 12, 1);}if (timer(60, 2)){//角色奔跑动作速率run_num = cycle_count(1, 4, 2);}if (timer(50, 3)) //防止一瞬间释放过多的光刃{creat_sword();//控制光刃释放(控制按键:J )}if (timer(50, 4) && add_blood.live)//控制加血特效图片运行的延迟{TX_ADD_HP_num = control_effect_count(1, 15, &add_blood.live, 1);}if (timer(100, 5)) //控制龙的动作图片{dragon_img_num = cycle_count(1, 6, 3);}if (timer(2000, 7)){creat_dragon();//创造龙}if (timer(10, 8)){dragon_move();//龙的移动}if (timer(10, 9)){//基础光刃攻击受击特效速度控制for (int i = 1;i <= 10;i++)Affected_img_num = control_effect_count(1, 30, &Affected_effect[i].live, 2);}if (timer(500, 10)){for (int i = 1;i <= DRAGON_NUM_MAX;i++){if (!dragon[i].live)dragon[i].die_num_zhen++;}}}
//需要时间延迟的事件集合,内含J按键int main()
{Get_Height_And_Width(&HEIGHT, &WIDTH);//获取屏幕参数,构建全屏窗口initgraph(WIDTH, HEIGHT);//初始化图形界面窗口load();//加载图片putback();//张贴背景BeginBatchDraw();//开启双缓冲绘图srand(time(0));//设定随机种子while (true){putback();//背景绘画control_hero();//控制角色移动 (控制按键:W,A,S,D )Select_texture();//控制选择人物状态并绘图出人物timer_thing();//需要时间延迟的事件集合(内含控制按键J)select_dragon_speed();//赋予龙追击的能力Attack_detection();//受击检测dragon_x_dragon();//防止龙的重叠Stop_the_Dragon_Crossing_Realm();//绘画{draw_sword();//光刃的绘画draw_HPMP();//状态条的绘画draw_effect_ADD_blood();//加血特效的绘画draw_dragon();//绘画龙draw_light_effect();}//移动{move_sword();//光刃的移动}{creat_add_HP();//创造加血特效 (内含按键 U )}beyond_sword_boundary();//超出边界的光刃判断消失FlushBatchDraw();//刷新缓冲绘图//cleardevice();}EndBatchDraw();//结束缓冲绘图exit(0);//退出程序return 0;
}
游戏效果












该游戏资源
游戏已经被我封装好,分享到了网盘上,感情兴趣的可以尝试一下。
通过网盘分享的文件:封装游戏测试.zip链接:
https://pan.baidu.com/s/1indM1boxj6QvrpsaIH_85Q?pwd=LONG
提取码: LONG
提取后使用方法:
使用文件资源管理器打开,点击Debug

点击该运行文件,

点击全部解压缩


同样再找到该运行文件并运行

一步步操作




上述您选择安装的指定位置(一般和解压后的文件一个位置)
就会出现一个软件,点击运行,就可以玩了
(注意:同时打开的有一个黑框框,最小化即可,不要关掉,他会获取用户的按键操作)
(W A S D 移动 U 特效 J 攻击)(只是一个基础2D游戏框架,未添加太多功能,感兴趣的小伙伴可以按照喜好尝试添加)

本文结束....感谢观看。
相关文章:
从零到有的游戏开发(visual studio 2022 + easyx.h)
引言 本文章适用于C语言初学者掌握基本的游戏开发, 我将用详细的步骤引领大家如何开发属于自己的游戏。 作者温馨提示:不要认为开发游戏很难,一些基本的游戏逻辑其实很简单, 关于游戏的开发环境也不用担心,我会详细…...
Open3d无法使用plt.get_cmap(“viridis“)着色pcd格式点云问题
在使用Open3D进行点云处理和可视化时,我们经常会遇到一个问题:直接加载PCD文件时,点云的颜色无法正确显示,但将其转换为PLY格式后再加载,颜色就能正常显示。本文将探讨这一问题的原因,并提供解决方案。 1.…...
网络故障排查实战指南:从准备到定位的全流程拆解
目录 第一章:排查前的准备工作 —— 别急着动手,先把底摸清 搞清楚故障现象:别被表象骗了 收集关键信息:把线索攒齐 做好心理准备:复杂问题不慌 第二章:排查工具箱 —— 你的 “武器” 得趁手 Wireshark:抓包界的 “显微镜” Ping:最基础但超实用的 “敲门员” …...
MCU的USB接口作为 USB CDC串口输出
前言: 如下内容是和Chatgpt的问答对话。询问了Chatgpt 关于 MCU微控制器内部的USB端口作为串口输出是怎么工作的,是否需要在上位机上安装串口驱动程序等,Chatgpt解答的很好。 正文: STM32 使用USB作为串行设备端口,需…...
【C++初阶】--- vector容器功能模拟实现
1.什么是vector? 在 C 里,std::vector 是标准模板库(STL)提供的一个非常实用的容器类,它可以看作是动态数组 2.成员变量 iterator _start;:指向 vector 中第一个元素的指针。 iterator _finish;&#x…...
函数式编程在 Java:Function、BiFunction、UnaryOperator 你真的会用?
大家好,我是你们的Java技术博主!今天我们要深入探讨Java函数式编程中的几个核心接口:Function、BiFunction和UnaryOperator。很多同学虽然知道它们的存在,但真正用起来却总是不得要领。这篇文章将带你彻底掌握它们!&am…...
Elasticsearch 学习规划
Elasticsearch 学习规划 明确学习目标与动机 场景化需求分析 - **S**:掌握Elasticsearch架构体系,熟练使用Elasticsearch 进行数据分析,Elasticsearch结合java 项目落地案例 - **M**:搜索和Elasticsearch相关GitHub项目 - **A**:每…...
【AI提示词】Emoji风格排版艺术与设计哲学
提示说明 Emoji风格排版艺术与设计哲学。 提示词 请使用 Emoji 风格编辑以下段落,该风格以引人入胜的标题、每个段落中包含表情符号和在末尾添加相关标签为特点。请确保保持原文的意思。使用案例(春日穿搭) 🌸 2025春季穿搭灵…...
LVM 扩容详解
目录 一、LVM扩容 1. 查看磁盘分区情况: 2. 查看pv、vg、lv 情况 3. 将新硬盘分区初始化 4. 将初始化后的分区添加到VG中 5. 查看逻辑卷的设备路径 6. VG分配给lv 二、扩展文件系统 1.确认文件系统类型 三、检验 一、LVM扩容 1. 查看磁盘分区情况: …...
STM32 低功耗模式下 RTC唤醒 和 PA0唤醒 的配合使用
STM32 低功耗模式不同唤醒源的配合使用 by 矜辰所致前言 关于 STM32 如何实现低功耗模式,我之前写过一篇文章: STM32 使用 STM32CubeMX HAL库实现低功耗模式 各种休眠模式如何实现文中已经讲得很清楚了,但是作为教学文章,文…...
QML 弹窗控件:Popup的基本用法与样式
目录 引言相关阅读Popup基本属性工程结构示例实现Main.qml - 主界面SimplePopup.qml - 简单弹窗ModalPopup.qml - 模态弹窗CustomPopup.qml - 自定义样式弹窗AnimatedPopup.qml - 带动画的弹窗 总结工程下载 引言 在现代图形用户界面(GUI)开发中,弹窗(Popup)是一种…...
MCP基础学习三:MCP客户端开发与工具集成
MCP客户端开发与工具集成 文章目录 MCP客户端开发与工具集成一, 学习目标二, 学习内容1. MCP客户端与服务端的通信方式1.1 通信原理1.2 通信实现分析 2. 如何开发MCP工具并集成到客户端2.1 工具开发流程2.2 工具实现示例2.3 客户端集成 3. 如何集成外部API到MCP客户端3.1 集成流…...
NSS#Round30 Web
小桃的PHP挑战 <?php include jeer.php; highlight_file(__FILE__); error_reporting(0); $A 0; $B 0; $C 0;//第一关 if (isset($_GET[one])){$str $_GET[str] ?? 0;$add substr($str, 0, 1); $add;if (strlen($add) > 1 ) {$A 1;} else {echo $one; } } else…...
POSIX线程(pthread)库:线程的终止与管理
在POSIX线程(pthread)库中,线程的终止和管理涉及多个关键函数。以下是关于线程终止的pthread系列函数的详细介绍: 1. pthread_exit:线程主动退出 ✨ 功能: 允许线程主动终止自身,并返回一个退出…...
解决 IntelliJ IDEA 中 Maven 项目左侧项目视图未显示顶层目录问题的详细步骤说明
以下是解决 IntelliJ IDEA 中 Maven 项目左侧项目视图未显示顶层目录问题的详细步骤说明: 1. 切换项目视图模式 默认情况下,IDEA 的项目视图可能处于 Packages 模式,仅显示代码包结构,而非物理目录。 操作步骤: 点击…...
408 计算机网络 知识点记忆(6)
前言 本文基于王道考研课程与湖科大计算机网络课程教学内容,系统梳理核心知识记忆点和框架,既为个人复习沉淀思考,亦希望能与同行者互助共进。(PS:后续将持续迭代优化细节) 往期内容 408 计算机网络 知识…...
Multisim 仿真 DC Sweep 双源嵌套扫描嵌套
Multisim仿真工具箱里头有DC Sweep分析方法,分析中可以对两个源参数扫描分析 类似于编程的循环嵌套: for( Source 2 : start value; Increment; Source 2 : stop value;) {for( Source 1 : start value; Increment; Source 2 : stop value;){... //…...
Python | 绘制黑底的水平空间分布图
写在前面 记录一下之前为了做PPT汇报画的一张图,虽然最后也没怎么用上。为了方面以后再需要,这里把代码和数据整理放到GitHub上。有兴趣的也可以玩玩 需要的数据 风场数据可以从ERA5的官网下载 https://cds.climate.copernicus.eu/datasets/reanalys…...
京东与喜茶关系破裂:切断所有合作 禁止进入办公场所
快科技4月10日消息,据报道,京东集团近日被曝出内部下发全员禁令,全面封杀喜茶产品进入办公区域。 据知情人士透露,京东人力行政部门发布的通知明确规定:全国各职场禁止与喜茶品牌开展任何形式的合作;员工不…...
LangChain-记忆系统 (Memory)
记忆系统是LangChain的核心组件之一,允许应用程序记住和使用过去的交互信息。本文档详细介绍了LangChain中的记忆组件类型、工作原理和使用场景。 概述 在构建对话式AI应用时,能够记住上下文和之前的交互至关重要。LangChain的记忆组件负责:…...
stm32开发(一)之创建工程与第一个程序
ps: 开发模式 1.基于库函数(标准库) 推荐 2.基于HAL库 图形化 3.基于寄存器 最直接 一、创建工程 1、打开keil5 new Project->路径->命名->保存 2、选择型号:stm32f103c8 初始创建工程我们不使用快捷项目建设 …...
【电商】基于LangChain框架将多模态大模型连接数据库实现精准识别
1. LangChain框架 LangChain是一个用于构建基于大语言模型的应用框架,通过模块化设计简化了LLM与外部工具,数据源和复杂逻辑的集成。 连接能力 将多个LLM调用,工具调用或者数据处理步骤串联成工作流 数据感知 外部数据集成 支持连接数据…...
鸿蒙HarmonyOS埋点SDK,ClkLog适配鸿蒙埋点分析
ClkLog埋点分析系统,是一种全新的、开源的洞察方案,它能够帮助您捕捉每一个关键数据点,确保您的决策基于最准确的用户行为分析。技术人员可快速搭建私有的分析系统。 ClkLog鸿蒙埋点SDK通过手动埋点的方式实现HarmonyOS 原生应用的前端数据采…...
详解 kotlin 相对 Java 特有的关键字及使用
文章目录 1. val 和 var2. fun3. when4. is 和 !is5. lateinit6. by7. reified8. companion 本文首发地址:https://h89.cn/archives/366.html 最新更新地址:https://gitee.com/chenjim/chenjimblog Kotlin 在兼容Java的基础上,引入了许多特有…...
湘西的未来交响曲
故事摘要 在中国湖南湘西的未来,苗族文化与高科技完美融合,构建出一个既传统又现代的世界。晨曦中的沱江,悬浮的吊脚楼面带着品位独特的织锦纹样,展示了令人惊叹的未来建筑美学。独特的工坊技术使得每件首饰都能感知佩戴者的情感&…...
STM32_HAL库提高中断执行效率
目录 中断流程分析我的解决办法优缺点 大家都在说STM32 HAL 库中断效率低下。具体哪里不行?如何优化? 我手里的项目要用到多个定时器TIM6、TIM7、TIM9、TIM10、TIM11、TIM12、TIM13,在处理这些定时器中断的时候,也发现了这个问题。…...
软件系统安全设计方案,信息化安全建设方案(Word原件)
1.1 总体设计 1.1.1 设计原则 1.2 物理层安全 1.2.1 机房建设安全 1.2.2 电气安全特性 1.2.3 设备安全 1.2.4 介质安全措施 1.3 网络层安全 1.3.1 网络结构安全 1.3.2 划分子网络 1.3.3 异常流量管理 1.3.4 网络安全审计 1.3.5 网络访问控制 1.3.6 完…...
什么是微前端?有什么好处?有哪一些方案?
微前端(Micro Frontends) 微前端是一种架构理念,借鉴了微服务的思想,将一个大型的前端应用拆分为多个独立、自治的子应用,每个子应用可以由不同团队、使用不同技术栈独立开发和部署,最终聚合为一个整体产品…...
电机 断路器选型
一、断路器额定电流计算基础 电机额定电流估算 三相380V电机额定电流可按经验公式快速计算: I电机≈2P(P为功率/kW)I电机≈2P(P为功率/kW) 例如:7.5kW电机额定电流约15A。 断路器倍数选择范围 通用标准:1.2~2.5倍电机额定电…...
Web前端之Vue+Element实现表格动态不同列合并多行、localeCompare、forEach、table、push、sort、Map
MENU 效果图公共数据数据未排序时(需要合并的行数据未处于相邻位置)固定合并行(写死)动态合并行方法(函数)执行 效果图 公共数据 Html <el-table :data"tableData" :span-method"chang…...
