从零到有的游戏开发(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库的配置(将下述代码复制进去)

cpp 复制代码
#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 time
    for (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();    // 静态变量,记录上一次 tick
    oldclock += ms * CLOCKS_PER_SEC / 1000;  // 更新 tick
    if (clock() > oldclock)          // 如果已经超时,无需延时
        oldclock = clock();
    else
        while (clock() < oldclock)      // 延时
            Sleep(1);            // 释放 CPU 控制权,降低 CPU 占用率,精度 10~16ms
    //      Sleep(0);            // 更高精度、更高 CPU 占用率,精度 1ms
}

复制好后,点击上方绿色空三角运行。(运行效果如下)

以上便完成了全部的环境配置,开启开发游戏之旅

基本游戏逻辑

首先需要包含头文件 #include<easyx.h>来调用图形函数

想要将代码中的效果展现出来,需要一个图形化窗口,并非是黑框框。

所以,第一步初始化一个图形化窗口。

cpp 复制代码
initgraph(800,800);

该函数运行后,除了命令提示符的黑窗口之外,还会产生一个新的窗口,此时窗口内是空的。

如果我们想把外部图片贴上去,需要一个容器储存外部图片

cpp 复制代码
IMAGE img;//声明一个可以存储外部图片的容器

然后储存外部图片进入容器操作,&img是获取容器地址,"photo.png"是需要引入图片的路径

(路径可分为相对路径和绝对路径,我推荐将图片和源程序放到同一个根目录中,既方便引用,又方便后续对于游戏的封装)

cpp 复制代码
loadimage(&img, "photo.png");加载图片进容器

那储存好的图片如何显示在屏幕上,我们需要函数将图片贴到屏幕上。

图中,x,y,前两个函数是指贴入图片的坐标(图片左上角顶点的坐标),

&img参数指贴入的图片容器,确定具体贴入哪个图片。

cpp 复制代码
putimage(x,y, &img);

现在基本的图片显示便有了。

如果我们想让这个图片动起来,很好理解,我们只需要逐渐改变putimage函数的坐标参数就可以。

需要一个循环来刷新新的图像(改变坐标之后的贴图),(还需要刷新屏幕,或者使用背景覆盖法)

1,刷新屏幕:FlushBatchDraw ();(不需要参数) 清除掉上一个贴图,执行目前的贴图。

cpp 复制代码
FlushBatchDraw ();

2,背景覆盖法:可以每次循环(先贴背景(覆盖掉上个位置的贴图)再贴改变坐标后的贴图)

关于图像的移动

cpp 复制代码
#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++(向右移动)

cpp 复制代码
if(GetAsyncKeyState('D'))
x++;

所以整体移动函数模块就是(其中设置了范围,防止目标移动出边界),每次增加或减少的值不是1,而是一个预先定义好的值,可以自由控制移动速度(#define SPEED 10)

cpp 复制代码
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是非阻塞性函数,也就是说,即使没有按键按下,主循环依然循环着,游戏持续运行着,只是目标贴图未移动。

cpp 复制代码
int main()
{
....省略
while(1)
{
control_move();
putimage(0,0,&back);
putimage(i,i,&img);
}
return 0;}

关于目标发射物(开发目标远程攻击)

cpp 复制代码
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运行一次函数体,且不阻塞主循环

cpp 复制代码
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,头文件

cpp 复制代码
#include<stdio.h>
#include<easyx.h>
#include<conio.h>
#include<time.h>
#include<windows.h>
#include<stdlib.h>

2,设定图形变量存储图片

cpp 复制代码
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**,预定义需要使用参数值 设定 结构体(飞机和敌机)**

cpp 复制代码
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则判定死亡 )

cpp 复制代码
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;
}

子弹和敌机的创建

cpp 复制代码
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;
} 

详细解释一下该部分(使用两张互补的色差图像可以实现透明贴图,后续有优化版本)

cpp 复制代码
putimage(BULLET[i].x, BULLET[i].y, &BULLET_2, NOTSRCERASE);
			putimage(BULLET[i].x, BULLET[i].y, &BULLET_1, SRCINVERT);

子弹和敌机的移动,以及碰撞检测(检测可以放到里面,也可以独立出一个函数)非

cpp 复制代码
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);

		}


	}
}

需要采用双缓冲绘图法,可以去除游戏循环的卡顿,

cpp 复制代码
BeginBatchDraw();	开始批量绘图。写在循环外
EndBatchDraw();	结束批量绘制,并执行未完成的绘制任务。循坏外,程序结束前
FlushBatchDraw();	执行未完成的绘制任务。写在循环内,构图后,延迟前

飞机大战代码汇总

cpp 复制代码
#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角色扮演游戏框架

先展示优化的游戏函数

设定好的全局变量和常量宏

cpp 复制代码
#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];//龙的随机追击底码
//=============================================================================全局变量

设定好的结构体

cpp 复制代码
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]; //敌龙  同时最多存在五只




//==============================================================================结构体

加载图片

cpp 复制代码
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);
	}

}
//加载图片素材

时间戳

cpp 复制代码
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;
}
/*时间戳*/

获取屏幕参数(全屏的关键)

cpp 复制代码
/*获取当前屏幕的参数*/

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的透明背景,然后插入该函数可自动剔除掉背景

cpp 复制代码
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透明贴图工具*/

原创函数,用于可控范围的切换图片目标,实现特定范围的人物移动行走效果,和特效

cpp 复制代码
void random_nums()//一帧内生成十个的随机数,前五个赋值给龙的判断移动变量,后五个给龙的追击判断变量
{
	int num = 10;
	int used[100] = { 0 };  // 标记数组,初始化为 0
	int 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类型指针变为false

void draw_effect_ADD_blood()
{
	if (add_blood.live)
		transparentimage3(NULL, hero.x - 100, hero.y - 150, &TX_ADD_HP[TX_ADD_HP_num]);
}

控制移动(通过检测上次的移动方向,可以知道某时刻角色的面朝向,从而决定贴图朝向)

cpp 复制代码
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;
}
/*控制角色移动*/

发射物光刃一体化程序

cpp 复制代码
//创造光刃

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;
			else
				light_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);
			else
				transparentimage3(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;
	}
}
//基本光刃命中判定以及反馈

游戏特效

cpp 复制代码
//创造加血特效 (内含按键 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);//分配速度

		}
}

用算法赋予目标自动寻敌并且追击的效果

cpp 复制代码
//根据敌我位移分配速度和状态

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;
		else
			dragon[i].direction = true;
	}
}

敌对目标的创建

cpp 复制代码
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;
		else
			dragon[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]);
			else
				transparentimage3(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=2s
				continue;
			//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函数主运行块

cpp 复制代码
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;
}

该游戏总代码

cpp 复制代码
#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 };  // 标记数组,初始化为 0
	int 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;
				else
					light_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;
			else
				light_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);
			else
				transparentimage3(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 {//没有按下D
		if (GetAsyncKeyState('A'))//是否按下A
		{
			transparentimage3(NULL, hero.x, hero.y, &run_imgL[run_num]);
		}
		else {//没有按下A
			if (GetAsyncKeyState('W') || GetAsyncKeyState('S'))
			{//是否按下W或S
				if (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类型指针变为false

void 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;
		else
			dragon[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]);
			else
				transparentimage3(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=2s
				continue;
			//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游戏框架,未添加太多功能,感兴趣的小伙伴可以按照喜好尝试添加)

本文结束....感谢观看。

相关推荐
励志要当大牛的小白菜2 小时前
ART配对软件使用
开发语言·c++·qt·算法
qq_513970442 小时前
力扣 hot100 Day56
算法·leetcode
PAK向日葵3 小时前
【算法导论】如何攻克一道Hard难度的LeetCode题?以「寻找两个正序数组的中位数」为例
c++·算法·面试
爱装代码的小瓶子4 小时前
数据结构之队列(C语言)
c语言·开发语言·数据结构
爱喝矿泉水的猛男5 小时前
非定长滑动窗口(持续更新)
算法·leetcode·职场和发展
YuTaoShao5 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
YouQian7726 小时前
Traffic Lights set的使用
算法
快乐飒男6 小时前
哈希表(c语言)
c语言·哈希算法·散列表
go54631584657 小时前
基于深度学习的食管癌右喉返神经旁淋巴结预测系统研究
图像处理·人工智能·深度学习·神经网络·算法
aramae7 小时前
大话数据结构之<队列>
c语言·开发语言·数据结构·算法