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

news2024/11/15 8:05:15

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

分析需求

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封装为类将与之相关的函数与变量都丢进去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2038781.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring MVC数据绑定和响应学习笔记

学习视频:12001 数据绑定_哔哩哔哩_bilibili 目录 1.数据绑定 简单数据绑定 默认类型数据绑定 简单数据类型绑定的概念 参数别名的设置 PathVariable注解的两个常用属性 POJO绑定 自定义类型转换器 xml方式 注解方式 数组绑定 集合绑定 复杂POJO绑定 属性为对象类…

力扣面试经典算法150题:最长公共前缀

最长公共前缀 今天的题目是力扣面试经典150题中的数组的简单题: 最长公共前缀 题目链接&#xff1a;https://leetcode.cn/problems/longest-common-prefix/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 编写一个函数来查找字符串数组中的最长公…

修改OpenSSH服务版本号

前言 这几年信息安全要求很高&#xff0c;奈何口号响亮掩盖不了我们技术基础依然很低的事实&#xff0c;加上风口烧钱和政绩工程等因素&#xff0c;于是就诞生了一些乱象&#xff0c;其中一个就是安全扫描胡乱标记&#xff0c;这里面的典型就是OpenSSH的漏洞扫描报告。 比如&…

人工智能小车——智能车臂控制平台

随着机器人技术的不断发展 &#xff0c;各行业对机器人应用人才的需求也随之增加&#xff0c;培养符合行业发展需求的机器人技术专业人才成为了高校的重要任务。 基本介绍 智能车臂控制平台&#xff08;ZI-AutoRB&#xff09;是一套用以机器人控制技术学习和研究的软硬件系统。…

米联客-FPGA程序设计Verilog语法入门篇连载-10 Verilog语法_一般设计规范

软件版本&#xff1a;无 操作系统&#xff1a;WIN10 64bit 硬件平台&#xff1a;适用所有系列FPGA 板卡获取平台&#xff1a;https://milianke.tmall.com/ 登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑&#xff01; 1概述 本小节讲解Verilog语法的一般…

照片回收利器:最新数据恢复软件推荐

照片回收利器&#xff1a;最新数据恢复软件推荐 在今天的数字化时代&#xff0c;我们将大量珍贵的照片和个人数据存储在电脑、手机和其他设备中。然而&#xff0c;由于各种原因&#xff0c;这些数据可能会意外删除或丢失&#xff0c;这对我们来说是一个巨大的损失。因此&#…

【Redis】List类型

目录 List列表 命令 LPUSH LPUSHX RPUSH RPUSHX LRANGE LPOP RPOP LINDEX LINSERT LLEN lrem ltrim lset 阻塞版本命令 BLPOP BRPOP 内部编码 使用场景 消息队列 分频道的消息队列 作为栈或者队列 List列表 列表类型是⽤来存储多个有序的字符串&…

5 大场景上手通义灵码企业知识库 RAG

大家好&#xff0c;我是通义灵码&#xff0c;你的智能编程助手&#xff01;最近我又升级啦&#xff0c;智能问答功能全面升级至 Qwen2&#xff0c;新版本在各个方面的性能和准确性都得到了显著提升。此外&#xff0c;行间代码补全效果也全面优化&#xff0c;多种编程语言生成性…

python-小理和他的猫(赛氪OJ)

[题目描述] 今天小理又要为他的猫小咪准备好吃的猫粮了&#xff0c;你愿意帮助一下他们么&#xff1f; 小理现在拥有的金钱数为 N &#xff0c;有 M 种小咪喜欢的猫粮从左到右排列&#xff0c;已知每种猫粮的价格 ai​ &#xff0c;他的购买规则如下&#xff1a; 1.必须按照从左…

数据结构与算法--图的存储与遍历

文章目录 回顾提要图的定义和表示图的表示完全图和子图顶点的度路径与回路连通图 邻接矩阵权和网 邻接表示例 深度优先遍历 (DFS)广度优先遍历 (BFS)广度优先遍历过程总结邻接矩阵存储结构邻接表存储结构 回顾 线索化二叉树&#xff1a;在某种次序遍历过程中创建线索&#xff…

简单数学运算(c语言)

1.描述 //牛牛最近学会了一些简单的数学运算&#xff0c;例如 //∑i1 ∑i 1 //请你帮他模拟一下这个运算。 &#xff08;即 1 2 3.... n - 1 n) //输入描述&#xff1a; //输入仅一个正整数 n //输出描述&#xff1a; //请你计算 //∑i1n 2.就是递归函数 方法一&#xf…

40.【C语言】指针(重难点)(E)

目录 13.指针的使用和传址调用 14.数组名的理解 *数组名就是数组首元素的地址 *两个例外 *使用指针访问数组 *一维数组的传参本质 往期推荐 承接上篇39.【C语言】指针&#xff08;重难点&#xff09;&#xff08;D&#xff09; 13. 指针的使用和传址调用 见29.【C语言】函数系…

Linux系统编程(9)

一、wait函数 1.wait函数 #include <sys/wait.h> pid_t wait(int *status);wait函数有两个作用&#xff1a; 1.获取子进程 的退出状态 当父进程要获取子进程的退出状态时&#xff0c;子进程里需要使用exit函数&#xff08;exit&#xff08;退出状态值&#xff09;退出…

10:【stm32】USART与串口通信一:USART(上)

USART&#xff08;上&#xff09; 1、串口通信1.1、简介1.2、数据帧1.2.1、简介1.2.2、校验规则1.2.3、停止位的长度 1.3、异步通信的波特率1.3.1、同步通信1.3.2、异步通信1.3.3、硬件流控 2、USART2.1、简介2.2、工作的原理2.3、相关寄存器 3、标准库编程3.1、编程接口USART_…

day16-测试自动化之selenium的PO模式

一、PO模式介绍 PO&#xff08;Page Object&#xff09;模式是一种在自动化测试中常用的设计模式&#xff0c;将页面的每个元素封装成一个对象&#xff0c;通过操作对象来进行页面的交互。 一般分为六个版本&#xff0c;现在大部分企业都用的V4版本&#xff0c;三层结构…

redis面试(十六)公平锁释放和排队加锁

锁释放 RedissonFairLock.unlockInnerAsync()方法 这和加锁的逻辑没有太大区别 也就是说在客户端A他释放锁的时候&#xff0c;也会走while true的脚本逻辑&#xff0c;看一下有序集合中的元素的timeout时间如果小于了当前时间&#xff0c;就认为他的那个排队就过期了&#xf…

Spring自动注册-<bean>标签和属性解析

xml文件中最常见也最核心的就是<bean>,<Import>,<beans>,<alias>标签,关于它们的解析主要是BeanDefinitionParserDelegate类中.<bean>标签的解析最为复杂和重要. <bean>标签 processBeanDefinition(ele, delegate)方法中,主要是是对…

数据库管理-Redis

数据库管理-Redis 一、关系型数据库和非关系型数据库1、关系型数据库&#xff08;Relational Database Management System, RDBMS&#xff09;&#xff1a;2、非关系型数据库&#xff08;NoSQL Database Management System&#xff09;&#xff1a; 二、redis简述 redis是把数据…

苦WPS云盘已久矣

主要因为软件更新后&#xff0c;设置位置都会跑到其他地方 打开wps客户端后&#xff0c;点击电脑底部任务栏的云朵图标。 2. 找到存储位置后&#xff0c;点击“更换位置”。 来自https://www.wps.cn/mlearning/question/detail/id/333165.html

Java | Leetcode Java题解之第328题奇偶链表

题目&#xff1a; 题解&#xff1a; class Solution {public ListNode oddEvenList(ListNode head) {if (head null) {return head;}ListNode evenHead head.next;ListNode odd head, even evenHead;while (even ! null && even.next ! null) {odd.next even.nex…