重生之我在球球星球,做理解呈续员

序言

在这个热潮的时代,我貌似重生了,刚刚好,游戏貌似成了我们不可缺的一部分,而我的脑子里面,走马观灯的浏览了这重生者内的一生,貌似在这个互联网的时代,一些项目的利用,游戏的开发,是我们不可避免的东西,比如端游的*CF,LOL,刀塔,地下城勇士,卡丁车等,简单的端游,植物大战僵尸,7K7K小游戏,6399小游戏,手游的切水果,俄罗斯方块,神庙逃亡,天天酷跑,到现在的王者荣耀,LOL手游,和平精英,球球大作战。而我这个重生者的体内就是一个热爱球球大作战这款游戏的热爱者。

我们今天就共同来看一下球球大作战这款游戏,球球大作战是一款单人游戏,玩家需要控制一个小球在地图上移动,吞噬其他小球来增大自己的体积。本游戏使用C语言和[easyx]图形库编写。请各位大佬,新手,哥哥姐姐们仔细阅读,谢谢,顺便一键三连哦。

功能

基本规则和功能:

  1. 游戏界面:游戏界面是一个矩形区域,玩家可以在这个区域内控制球的移动。
  2. 玩家小球:玩家控制的小球可以在游戏界面内自由移动,按下空格键可以加速。
  3. 其他小球:其他小球又分为食物小球,和ai控制的小球
  4. 体积:玩家每吃一个其他小球体积就会增大一点,ai小球也可以通过吃食物和玩家小球增大体积。
  5. 玩家被吃:当玩家小球被ai小球吃了,玩家小球就会回到初始点,体积也会变成初始大小。
  6. ai小球的追击和逃跑:当玩家小球与ai小球靠近时,ai小球会根据自身体积的大小选择追击玩家小球或者逃跑。

接下来,我们将通过以下几个步骤来实现这个游戏:

  1. 初始化游戏界面和小球的信息。

  2. 处理键盘输入,实现玩家小球的移动和加速。

  3. 生成足够数量的食物小球。

  4. 生成ai小球,并控制其移动。

  5. 检测小球之间的吞噬关系,增加相应的体积。

  6. 通过学习这个游戏的开发过程,初学者将能够掌握C语言编程和easyx[图形库]的基本技巧。

1. 小球的要素

在此游戏中一个小球的要素无非就是小球所在的位置(坐标)、小球的半径、以及小球的颜色,这里我们可以用一个结构体数组来存放这些要素,以方便初始化小球的信息。 ``

ini 复制代码
struct Ball
{
	int x;
	int y;
	float r;
	DWORD color;
};

2. 初始化小球的信息

将玩家小球的初始位置设置在窗口的中间,半径大小为10。食物小球和ai小球的位置则通过rand函数、srand函数和time函数生成的随机数,随机分布在地图的各个位置,食物小球半径为1-5,ai小球的半径10.其中在这里使用了RGB随机生成一个颜色,使每个食物小球的颜色都不尽相同。 RGB色彩模式是一种工业标准,它通过红(R)、绿(G)、蓝(B)三个颜色通道的组合来表示不同的颜色。每个通道通常分配一个0到255之间的数值,其中0表示该颜色通道没有亮度,255表示该颜色通道的最大亮度。

通过调整这三个通道的值,可以生成几乎所有人类视觉系统能够感知的颜色。例如:
  • 橙色可以通过RGB值(255, 128, 0)来表示。

  • 黄色的RGB值为(255, 255, 0)。

  • 绿色的RGB值是(0, 255, 0)。

  • 蓝色的RGB值为(0, 0, 255)。

  • 紫色可以通过RGB值(170, 0, 255)来表示。

  • 黑色的RGB值为(0, 0, 0)。

  • 白色的RGB值为(255, 255, 255)。

  • 灰色的RGB值可以是(128, 128, 128),其中三个值相等即可,值越接近255,颜色就越接近白色,反之亦然。

void 复制代码
{
    //玩家小球
	srand((unsigned)time(NULL));
	player.x = Wide / 2;
	player.y = Hight / 2;
	player.r = 10;
    //食物小球
	for (int i = 0; i < Food_num; i++)
	{
		Food[i].x = rand() % Wide_map;
		Food[i].y = rand() % Hight_map;
		Food[i].r = rand() % 5 + 1;
		Food[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
	}
    //ai小球
	for (int i = 0; i < Enemy_num; i++)
	{
		Enemy[i].x = rand() % Wide_map;
		Enemy[i].y = rand() % Hight_map;
		Enemy[i].r = 10;
		Enemy[i].color = RGB(0,255,0);
	}
 
}

3. 玩家操作小球

在这里需要用到GetAsyncKeyState(vk virtual key)函数获取异步按键状态,其中vk virtual key是虚拟键值,如果接受到这个虚拟键值,它会返回真。VK_UP、VK_DOWN、VK_LEFT、VK_RIGHT、0x20分别是上箭头键、下箭头键、左箭头键、右箭头键、空格键的虚拟键值。

ini 复制代码
void Player_move()
{
	if (GetAsyncKeyState(VK_UP))
	{
		if (player.y > 0)
		{
			player.y -= player_sleep;
		}
	}
	if (GetAsyncKeyState(VK_DOWN))
	{
		if (player.y < Hight_map)
		player.y += player_sleep;
	}
	if (GetAsyncKeyState(VK_LEFT))
	{
		if (player.x > 0)
		player.x -= player_sleep;
	}
	if (GetAsyncKeyState(VK_RIGHT))
	{
		if (player.x < Wide_map)
		player.x += player_sleep;
	}
	if (GetAsyncKeyState(0x20))
	{
		player_sleep = l + 5;
	}
	else
	{
		player_sleep = l;
	}
}

4. 显示出小球

在此游戏中,小球是一个实心圆,在easyx图形库中提供了一个画实心圆的函数---solidcircle函数,通过它我们就可以在窗口中显示小球了,但显示出的小球默认颜色为白色,为了区分不同的小球,我们还需使用setfillcolor函数来改变小球的颜色。因为显示的函数、玩家操作小球的函数等函数都是放在同一个while循环重复执行的,这样就会重复显示小球,所以我们还需要用到一个清屏函数cleardevice。为了使窗口更好看可以使用setbkcolor函数修改窗口的背景颜色,以及使用line函数在窗口中画一些线,函数setlinecolor可以改变线的颜色。

void 复制代码
{
	
	//设置地图
	SetWorkingImage(&map);
	//清屏
	cleardevice();
	//背景颜色
	setbkcolor(WHITE);
	//划线颜色
	setlinecolor(RGB(230,231,239));
	//划线
	for (int i = 0; i < Wide_map; i += 10)
	{
		line(i, 0, i, Hight_map);
	}
	for (int i = 0; i < Hight_map; i += 10)
	{
		line(0, i, Wide_map, i);
	}
	//食物
	for (int i = 0; i < Food_num; i++)
	{
		setfillcolor(Food[i].color);
		solidcircle(Food[i].x, Food[i].y, Food[i].r);
	}
	//敌人
	for (int i = 0; i < Enemy_num; i++)
	{
		setfillcolor(Enemy[i].color);
		solidcircle(Enemy[i].x, Enemy[i].y, Enemy[i].r);
	}
	//玩家
	setfillcolor(RED);
	solidcircle(player.x, player.y, player.r);
	SetWorkingImage();
 
	int x = player.x - (Wide / 2);
	int y = player.y - (Hight / 2);
	//防止窗口越界
	if (x < 0)
	{
		x = 0;
	}
	if (y < 0)
	{
		y = 0;
	}
	if (x > Wide_map - Wide)
	{
		x = Wide_map - Wide;
	}
	if (y > Hight_map - Hight)
	{
		y = Hight_map - Hight;
	}
	//把map输出到窗口上
	putimage(0, 0, Wide, Hight, &map, x, y);
}

5. 生成地图

可以使用IMAGE map(Wide_map, Hight_map) 创建一个图像映射,其中 Wide_map 代表图像的宽度,而 Hight_map 代表图像的高度。然后使用SetWorkingImage(&map),将map的地址作为参数传递给SetWorkingImage函数。这个函数的作用是将map作为当前操作的对象,以便在后续的图像处理过程中使用。最后使用putimage(0, 0, Wide, Hight, &map, x, y) 将地址绘制到窗口上,其中要让玩家小球始终出现在窗口的中央位置,那么其中的x=player.x - (Wide / 2);y=player.y - (Hight / 2);但是单单这样写就会出现窗口越界的情况,所以我们还需要限制x和y的范围:

//防止窗口越界 复制代码
if (x < 0)
{
	x = 0;
}
if (y < 0)
{
	y = 0;
}
if (x > Wide_map - Wide)
{
	x = Wide_map - Wide;
}
if (y > Hight_map - Hight)
{
	y = Hight_map - Hight;
}

putimage(0, 0, Wide, Hight, &map, x, y) 这个函数调用是用于在屏幕上的特定位置绘制或显示一个图像。下面是对这个函数调用中各个参数的具体解释:

  1. (0, 0):这是图像要绘制的目标位置的左上角坐标,即x=0和y=0,通常表示屏幕的左上角。

  2. Wide:这个参数指定了要绘制的图像的宽度。

  3. Hight:这个参数指定了要绘制的图像的高度。

  4. &map:这是一个指向图像内存地址的指针,该图像将被绘制到屏幕上。在这个上下文中,map可能是一个包含了图像数据的数据结构或数组。

  5. x:这个参数通常指定了要开始绘制图像的起始点的x坐标(在图像数据中)。

  6. y:这个参数通常指定了要开始绘制图像的起始点的y坐标(在图像数据中)。

6. ai小球的移动

随机生成0-3的数字分别代表ai小球上下左右的移动,这样小球就会自由移动了。当玩家小球与ai小球靠近时,ai小球会追击或者逃跑,这里我们需要先计算小球的之间的距离,小球之间的距离就是两小球的圆心坐标的x相减的平方加上y相减的平方再开根号。开根号的函数为sqrt,它的头文件是<math.h>。

//距离 复制代码
int Distance(int x, int y, int x1, int y1)
{
	return sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
}

然后判断两小球的距离是否小于50个像素点,即判断是否小于玩家小球的半径 + ai小球的半径 + 50。判断完后比较两小球的半径大小,如果ai小球的半径大于玩家小球的半径,那么ai小球就要追击玩家小球,即ai小球的坐标需要靠近玩家小球的坐标,就是如果ai小球的x大于玩家小球的x那么ai小球的x就减小,同理可得剩下的操作。

void 复制代码
{
	srand((unsigned)time(NULL));
	for (int i = 0; i < Enemy_num; i++)
	{
		int direction = rand() % 4;
 
		if (Distance(player.x, player.y, Enemy[i].x, Enemy[i].y) > player.r + Enemy[i].r + 50)
		{
			if (direction == 0)
			{
				if (Enemy[i].y > 0)//防止小球越界的判断语句
				{
					Enemy[i].y -= enemy_sleep;
				}
			}
			if (direction == 1)
			{
				if (Enemy[i].y < Hight_map)
					Enemy[i].y += enemy_sleep;
			}
			if (direction == 2)
			{
				if (Enemy[i].x > 0)
					Enemy[i].x -= enemy_sleep;
			}
			if (direction == 3)
			{
				if (Enemy[i].x < Wide_map)
					Enemy[i].x += enemy_sleep;
			}
		}
		//敌人追击玩家
		if (Distance(player.x, player.y, Enemy[i].x, Enemy[i].y) < player.r + Enemy[i].r + 50)
		{
			if (Enemy[i].r > player.r)
			{
				if (Enemy[i].x < player.x)
				{
					if (Enemy[i].x < Wide_map)
					{
						Enemy[i].x += enemy_sleep;
					}
				}
				if (Enemy[i].x > player.x)
				{
					if (Enemy[i].x > 0)
					{
						Enemy[i].x -= enemy_sleep;
					}
				}
				if (Enemy[i].y < player.y)
				{
					if (Enemy[i].y < Hight_map)
					{
						Enemy[i].y += enemy_sleep;
					}
				}
				if (Enemy[i].y > player.y)
				{
					if (Enemy[i].y > 0)
					{
						Enemy[i].y -= enemy_sleep;
					}
				}
			}
			//敌人逃跑
			else
			{
				if (Enemy[i].x < player.x)
				{
					if (Enemy[i].x > 0)
					{
						Enemy[i].x -= enemy_sleep;
					}
				}
				if (Enemy[i].x > player.x)
				{
					if (Enemy[i].x < Wide_map)
					{
						Enemy[i].x += enemy_sleep;
					}
				}
				if (Enemy[i].y < player.y)
				{
					if (Enemy[i].y > 0)
					{
						Enemy[i].y -= enemy_sleep;
					}
				}
				if (Enemy[i].y > player.y)
				{
					if (Enemy[i].y < Hight_map)
					{
						Enemy[i].y += enemy_sleep;
					}
				}
			}
		}
	}
}

7. 小球之间的吞噬关系

玩家小球与ai小球、ai小球与ai小球:

当两小球的圆心小于最大的那个圆的半径时,小的那个球就会被吃掉。吃点就是那个小球重新找个位置生成。吃完后,吃的那个小球半径增加被吃小球半径的十分之一。

ai小球与食物小球、玩家小球与食物小球:同上。

void 复制代码
{
	for (int i = 0; i < Food_num; i++)
	{
		//玩家吃食物
		if (Distance(player.x, player.y, Food[i].x, Food[i].y) < player.r)
		{
			player.r += Food[i].r / 100;
			Food[i].x = rand() % Wide_map;
			Food[i].y = rand() % Hight_map;
			Food[i].r = rand() % 5 + 1;
			Food[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
		}
		
	}
	for (int j = 0; j < Enemy_num; j++)
	{
		//敌人吃食物
		for (int i = 0; i < Food_num; i++)
		{
			if (Distance(Enemy[j].x, Enemy[j].y, Food[i].x, Food[i].y) < Enemy[j].r)
			{
				Enemy[j].r += Food[i].r / 50;
				Food[i].x = rand() % Wide_map;
				Food[i].y = rand() % Hight_map;
				Food[i].r = rand() % 5 + 1;
				Food[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
			}
		}
		//敌人吃敌人
		for (int i = 0; i < Enemy_num; i++)
		{
			if (Distance(Enemy[j].x, Enemy[j].y, Enemy[i].x, Enemy[i].y) < Enemy[j].r&& Enemy[j].r > Enemy[i].r)
			{
				Enemy[j].r += Enemy[i].r / 10;
				Enemy[i].x = rand() % Wide_map;
				Enemy[i].y = rand() % Hight_map;
				Enemy[i].r = 10;
					Enemy[i].color = RGB(0, 255, 0);
			}
			if (Distance(Enemy[j].x, Enemy[j].y, Enemy[i].x, Enemy[i].y) < Enemy[i].r && Enemy[j].r < Enemy[i].r)
			{
				Enemy[i].r += Enemy[j].r / 10;
				Enemy[j].x = rand() % Wide_map;
				Enemy[j].y = rand() % Hight_map;
				Enemy[j].r = 10;
				Enemy[j].color = RGB(0, 255, 0);
			}
		}
		if (Distance(player.x, player.y, Enemy[j].x, Enemy[j].y) < player.r)
		{
			//玩家吃敌人
			if (player.r > Enemy[j].r)
			{
				player.r += Enemy[j].r / 10;
				Enemy[j].x = rand() % Wide_map;
				Enemy[j].y = rand() % Hight_map;
				Enemy[j].r = 10;
				Enemy[j].color = RGB(0, 255, 0);
			}
			//敌人吃玩家
			else
			{
				if (invincibility == 0)
				{
					Enemy[j].r += player.r / 10;
					player.x = Wide / 2;
					player.y = Hight / 2;
					player.r = 10;
					invincibility = 10;
				}
			}
		}
	}
}

8. 最终的效果图

就像上面的的这张图片,画住的红球就是我们的主体,那么那些画住的一些绿色的大球就是别人的母体,我们在没有他们大的情况下,只能去吃那些画住的小一点的求,这样使我们我们的体积就会越来越大,如果分不清楚是否比他大,可以在我们自己的单机内,添加上体积这个故名词,这样就好了。

感谢您的收看,本次我们的东西只有这么多了,可以进一步的去了解[easyx]图形库,只要了解完或者学习完你就可以算是入门了,再或者比入门都高一个等级。下期想看什么欢迎在评论区留言。

相关推荐
David爱编程几秒前
Java 守护线程 vs 用户线程:一文彻底讲透区别与应用
java·后端
小奏技术17 分钟前
国内APP的隐私进步,从一个“营销授权”弹窗说起
后端·产品
小研说技术36 分钟前
Spring AI存储向量数据
后端
苏三的开发日记36 分钟前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台处于同一台服务器)
后端
苏三的开发日记37 分钟前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台不在同一服务器)
后端
陈三一42 分钟前
MyBatis OGNL 表达式避坑指南
后端·mybatis
whitepure43 分钟前
万字详解JVM
java·jvm·后端
我崽不熬夜1 小时前
Java的条件语句与循环语句:如何高效编写你的程序逻辑?
java·后端·java ee
我崽不熬夜1 小时前
Java中的String、StringBuilder、StringBuffer:究竟该选哪个?
java·后端·java ee
我崽不熬夜2 小时前
Java中的基本数据类型和包装类:你了解它们的区别吗?
java·后端·java ee