EasyX自学笔记3(割草游戏1)

割草游戏,有玩家(上下左右控制移动)周围围绕子弹,敌人(随机刷新)向玩家靠近,子弹打死敌人,玩家与敌人触碰游戏结束。

分析需求

1.有玩家、敌人、子弹三种对象

2.玩家上下左右控制、左右移动式玩家朝向不同

3.敌人从边框随机出现、向玩家移动

4.子弹与敌人有碰撞检测、敌人与玩家有碰撞检测

图片素材加载

在之前的笔记中使用的是绘画的线条函数进行的简单游戏,这次我们来制作由图片构成的游戏。关键在于两个函数(EasyX怎么安装看之前笔记

IMAGE ImgBackground;

loadimage(&ImgBackground, _T("img/background.png"));
putimage(0, 0, &ImgBackground);

在#include <graphics.h>文件下有IMAG类让我们可以操作图片素材。

loadimage第一个参数为指针名,第二个参数为文件实际存放目录,其将文件名变为指针变量可供我们使用。

putimage函数将这个图片的左上角放在界面的(0,0)位置。

图片素材的目录存放在如图位置,img文件存放了游戏所需要的所有游戏素材,点进img即可看见图片素材

我们有背景素材、 野猪向左和向右的6帧动画、主角的向左向右帧动画等等。提前对素材特点命名方便代码使用。

至此你已经会了加载图片素材。

#include <graphics.h>
bool GameOver = false;
int main()
{
	initgraph(1280, 720);
	ExMessage msg;
	IMAGE ImgBackground;
	loadimage(&ImgBackground, _T("img/background.png"));
	
	while (!GameOver)
	{

		while (peekmessage(&msg))
		{

		}
		putimage(0, 0, &ImgBackground);
	}
}

创建1280x720画布,image创建图片类、exmessage创建消息类,loadimage将文件目录与指针对应。

第一个while循环就是游戏进程,跳出第一个循环即为游戏结束我们常创建GameOver标志作为标记,当在游戏过程中达到某种胜利条件时将GameOver置为true游戏即退出。 

第二个while循环代表事件循环,一直扫描键鼠消息。

putimage函数就是将指针代表的图片左上角放置在(0,0)位置上。

 此时加载图片还是有些卡顿,我们加入以下代码

#include <graphics.h>
bool GameOver = false;
int main()
{
	initgraph(1280, 720);
	ExMessage msg;
	IMAGE ImgBackground;
	loadimage(&ImgBackground, _T("img/background.png"));

	BeginBatchDraw();
	while (!GameOver)
	{

		while (peekmessage(&msg))
		{

		}
		putimage(0, 0, &ImgBackground);
		FlushBatchDraw();
	}
	EndBatchDraw();
}

BeginBatchDraw函数用于开始批量绘图。执行后,任何绘图操作都将暂时不输出到绘图窗口上,直到执行 FlushBatchDraw 或 EndBatchDraw 才将之前的绘图输出。

玩家加载

至此背景你已经会实现了,接下来我们来加载玩家

在图片素材文件下我们看到派蒙的帧动画命名很有规律。两类一类向左一类向右序号从0到5,我们只需要改变数字即可。

让玩家动起来就是轮播这6帧动画

IMAGE ImgBackground;

loadimage(&ImgBackground, _T("img/background.png"));
putimage(0, 0, &ImgBackground);

在背景图中我们使用了loadimage函数,其第二个参数表示图片位置我们修改即可

_T表示字符需要用unicode字符集表示

 打开项目的属性在字符集处勾选unicode字符集

IMAGE ImgPlayerLeft;

loadimage(&ImgPlayerLeft, _T("img/player_left_0.png"));
putimage(0, 0, &ImgPlayerLeft);

 但是这仅仅加载了派蒙的0号帧,怎么在目录字符串中修改012345呢?字符串拼接

将img/player_left_0.png拆分为“img/player_left_”      +   i       +“.png”三个部分放在循环内修改i的值将其拼凑起传入loadimage函数。

此时我们用到了<string>文件中函数

#include <graphics.h>
#include <string>
bool GameOver = false;

const int PLAYER_ANIM_NUM = 6;
IMAGE ImgPlayerLeft[PLAYER_ANIM_NUM];
IMAGE ImgPlayerRight[PLAYER_ANIM_NUM];

void LoadAnimation()
{
	for (int i = 0; i < PLAYER_ANIM_NUM; i++)
	{
		std::wstring path = L"img/player_left_" + std::to_wstring(i) + L".png";
		loadimage(&ImgPlayerLeft[i], path.c_str());
	}
	for (int i = 0; i < PLAYER_ANIM_NUM; i++)
	{
		std::wstring path = L"img/player_right_" + std::to_wstring(i) + L".png";
		loadimage(&ImgPlayerRight[i],path.c_str());
	}
}

我们定义path为“img/player_left_”      +   i       +“.png”

第一,字符串前加L,将ASCII转为Unicode码,就是说“abc”占三个字节L“abc”占六个字节。

第二,to_wstring函数是将int型转为宽字符字符串

第三,+是文件中已经有对“+”的重载,所以可以进行字符串运算

第四,c_str函数将const string* 类型 转化为 const char* 类型,将其转化为字符串数组

#include <graphics.h>
#include <string>
bool GameOver = false;

const int PLAYER_ANIM_NUM = 6;
IMAGE ImgPlayerLeft[PLAYER_ANIM_NUM];
IMAGE ImgPlayerRight[PLAYER_ANIM_NUM];

int	 IdxCurrentAnim = 0;
void LoadAnimation()
{
	for (int i = 0; i < PLAYER_ANIM_NUM; i++)
	{
		std::wstring path = L"img/player_left_" + std::to_wstring(i) + L".png";
		loadimage(&ImgPlayerLeft[i], path.c_str());
	}
	for (int i = 0; i < PLAYER_ANIM_NUM; i++)
	{
		std::wstring path = L"img/player_right_" + std::to_wstring(i) + L".png";
		loadimage(&ImgPlayerRight[i],path.c_str());
	}
}

int main()
{
	initgraph(1280, 720);
	ExMessage msg;
	IMAGE ImgBackground;

	loadimage(&ImgBackground, _T("img/background.png"));
	LoadAnimation();

	BeginBatchDraw();
	while (!GameOver)
	{

		while (peekmessage(&msg))
		{

		}
		static int counter = 0;
		if (++counter%5==0)
		{
			IdxCurrentAnim++;
		}
		IdxCurrentAnim = IdxCurrentAnim % PLAYER_ANIM_NUM;
		cleardevice();
		putimage(0, 0, &ImgBackground);
		putimage(500, 500, &ImgPlayerLeft[IdxCurrentAnim]);



		FlushBatchDraw();
	}
	EndBatchDraw();
}

 在main函数下我们使用counter计数器,我们用%5或者其他数字也可以,控制IdxCurrentAnim的增长速度,就是控制帧动画的播放速率。

此时派蒙在这里抽搐,我们将%5换为%100就舒服多了。

还有一个问题,黑框怎么办?

第一种方法自己在ps等修图工具间裁剪出派蒙的边界,显然工作量略大。

第二种我们利用rgb配色修改纯黑位置

#pragma comment(lib,"MSIMG32.LIB")
inline void  putimageA(int x, int y, IMAGE* img)
{
	int w = img->getwidth();
	int h = img->getheight();
	AlphaBlend(GetImageHDC(NULL), x, y, w, h,
	GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });
}

 我们编写一个函数putimageA函数,其功能就是去掉黑色背景,inline关键字就是为了在while重复调用中减少栈的开销,类似“宏”的操作。将putimageA放入main函数中。

#include <graphics.h>
#include <string>
bool GameOver = false;

const int PLAYER_ANIM_NUM = 6;
IMAGE ImgPlayerLeft[PLAYER_ANIM_NUM];
IMAGE ImgPlayerRight[PLAYER_ANIM_NUM];

int	 IdxCurrentAnim = 0;

#pragma comment(lib,"MSIMG32.LIB")
inline void  putimageA(int x, int y, IMAGE* img)
{
	int w = img->getwidth();
	int h = img->getheight();
	AlphaBlend(GetImageHDC(NULL), x, y, w, h,
		GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });
}
void LoadAnimation()
{
	for (int i = 0; i < PLAYER_ANIM_NUM; i++)
	{
		std::wstring path = L"img/player_left_" + std::to_wstring(i) + L".png";
		loadimage(&ImgPlayerLeft[i], path.c_str());
	}
	for (int i = 0; i < PLAYER_ANIM_NUM; i++)
	{
		std::wstring path = L"img/player_right_" + std::to_wstring(i) + L".png";
		loadimage(&ImgPlayerRight[i],path.c_str());
	}
}

int main()
{
	initgraph(1280, 720);
	ExMessage msg;
	IMAGE ImgBackground;

	loadimage(&ImgBackground, _T("img/background.png"));
	LoadAnimation();

	BeginBatchDraw();
	while (!GameOver)
	{

		while (peekmessage(&msg))
		{

		}
		static int counter = 0;
		if (++counter%100==0)
		{
			IdxCurrentAnim++;
		}
		IdxCurrentAnim = IdxCurrentAnim % PLAYER_ANIM_NUM;
		cleardevice();
		putimage(0, 0, &ImgBackground);
		putimageA(500, 500, &ImgPlayerLeft[IdxCurrentAnim]);



		FlushBatchDraw();
	}
	EndBatchDraw();
}

此时代码运行效果为



玩家移动

键盘上下左右控制,我们要习惯设置标志位,按下弹起两个状态

POINT player_pos = { 500,500 };
const int PLAYER_SPEED = 10;
bool is_move_up = 0;
bool is_move_down = 0;
bool is_move_left = 0;
bool is_move_right = 0;

设置图片初始位置500,500(自带的位置结构体POINT)、设置速度、方向标志位置零。

接下来写事件函数,扫描键盘按键信息,根据情况改变标志位并退出switch语句。 

while (peekmessage(&msg))
		{
			if (msg.message == WM_KEYDOWN)
			{
				switch (msg.vkcode)
				{
				case VK_UP:
					is_move_up = true;
					break;
				case VK_DOWN:
					is_move_down = true;
					break;
				case VK_LEFT:
					 is_move_left = true;
					break;
				case VK_RIGHT:
					 is_move_right = true;
					break;
				}
			}
			else if (msg.message == WM_KEYUP)
			{
				switch (msg.vkcode)
				{
				case VK_UP:
					 is_move_up = false;
					break;
				case VK_DOWN:
					 is_move_down = false;
					break;
				case VK_LEFT:
					 is_move_left = false;
					break;
				case VK_RIGHT:
					 is_move_right = false;
					break;
				}
			}
		}
		if(is_move_up)player_pos.y -= PLAYER_SPEED;
		if (is_move_down)player_pos.y += PLAYER_SPEED;
		if (is_move_left)player_pos.x -= PLAYER_SPEED;
		if (is_move_right)player_pos.x += PLAYER_SPEED;

如果标志位为真,则对玩家坐标进行计算,对于坐标不太熟悉同学可以看井字棋补课。总的说画布左上角(0,0)右下角(1280,720),从左到右x变大、从上到下y变大。

记得将putimageA函数坐标改变

putimageA(player_pos.x, player_pos.y, &ImgPlayerLeft[IdxCurrentAnim]);

此时玩家的坐标就不再是(500,500)定点了,可以随着计算出的数值移动。此时你会发现派蒙窜的飞快。原因是按下就走走走走走走走一直不停的累加,就显得飞快,只要在之间加入sleep延时即可按下走、走、走。

那么如何把握sleep的时间长度呢?我们知道sleep(1000)表示延时1s,sleep(1000/144)表示的就是1000/144为1帧,在一帧内派蒙存在时间加上延时时间为一帧即可。

实现方法就是

	while (!GameOver)
	{
		DWORD StartTime = GetTickCount();
		while (peekmessage(&msg))

在事件函数循环前记录时间

		DWORD EndTime = GetTickCount();
		DWORD DeleteTime = EndTime - StartTime;
		if (DeleteTime <1000/144)
		{
			Sleep(1000 / 144 - DeleteTime);
		}
		FlushBatchDraw();

 在结尾时记录结束时间,并根据时间差补全一帧内应延时的时间。

DWORD是EasyX带有的无符号int的别名,在这里是为了更容易分辨这两个位置是实现同功能的代码。

至此角色移动你已经学会了。

#include <graphics.h>
#include <string>
bool GameOver = false;

const int PLAYER_ANIM_NUM = 6;
IMAGE ImgPlayerLeft[PLAYER_ANIM_NUM];
IMAGE ImgPlayerRight[PLAYER_ANIM_NUM];

int	 IdxCurrentAnim = 0;

POINT player_pos = { 500,500 };
const int PLAYER_SPEED = 10;
bool is_move_up = 0;
bool is_move_down = 0;
bool is_move_left = 0;
bool is_move_right = 0;

#pragma comment(lib,"MSIMG32.LIB")
inline void  putimageA(int x, int y, IMAGE* img)
{
	int w = img->getwidth();
	int h = img->getheight();
	AlphaBlend(GetImageHDC(NULL), x, y, w, h,
		GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });
}
void LoadAnimation()
{
	for (int i = 0; i < PLAYER_ANIM_NUM; i++)
	{
		std::wstring path = L"img/player_left_" + std::to_wstring(i) + L".png";
		loadimage(&ImgPlayerLeft[i], path.c_str());
	}
	for (int i = 0; i < PLAYER_ANIM_NUM; i++)
	{
		std::wstring path = L"img/player_right_" + std::to_wstring(i) + L".png";
		loadimage(&ImgPlayerRight[i],path.c_str());
	}
}

int main()
{
	initgraph(1280, 720);
	ExMessage msg;
	IMAGE ImgBackground;

	loadimage(&ImgBackground, _T("img/background.png"));
	LoadAnimation();

	BeginBatchDraw();
	while (!GameOver)
	{
		DWORD StartTime = GetTickCount();
		while (peekmessage(&msg))
		{
			if (msg.message == WM_KEYDOWN)
			{
				switch (msg.vkcode)
				{
				case VK_UP:
					is_move_up = true;
					break;
				case VK_DOWN:
					is_move_down = true;
					break;
				case VK_LEFT:
					 is_move_left = true;
					break;
				case VK_RIGHT:
					 is_move_right = true;
					break;
				}
			}
			else if (msg.message == WM_KEYUP)
			{
				switch (msg.vkcode)
				{
				case VK_UP:
					 is_move_up = false;
					break;
				case VK_DOWN:
					 is_move_down = false;
					break;
				case VK_LEFT:
					 is_move_left = false;
					break;
				case VK_RIGHT:
					 is_move_right = false;
					break;
				}
			}
		}
		if(is_move_up)player_pos.y -= PLAYER_SPEED;
		if (is_move_down)player_pos.y += PLAYER_SPEED;
		if (is_move_left)player_pos.x -= PLAYER_SPEED;
		if (is_move_right)player_pos.x += PLAYER_SPEED;

		static int counter = 0;
		if (++counter%100==0)
		{
			IdxCurrentAnim++;
		}
		IdxCurrentAnim = IdxCurrentAnim % PLAYER_ANIM_NUM;
		cleardevice();

		putimage(0, 0, &ImgBackground);
		putimageA(player_pos.x, player_pos.y,&ImgPlayerLeft[IdxCurrentAnim]);

		DWORD EndTime = GetTickCount();
		DWORD DeleteTime = EndTime - StartTime;
		if (DeleteTime <1000/144)
		{
			Sleep(1000 / 144 - DeleteTime);
		}


		FlushBatchDraw();
	}
	EndBatchDraw();
}

至此完成成了一半的任务了,下一步将Player封装为类将与之相关的函数与变量都丢进去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少杰是小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值