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

news2024/12/23 6:54:24

在上一篇笔记之中我们还留有许多bug,如派蒙不会转头、派蒙是鬼没影子、斜向速度过快、会跑出界外的问题在此一并处理。

在上一章里我们知道需要玩家类、敌人类、子弹类三种,但是其包含的是他们的运行逻辑和变量。而播放动画帧也有许多函数我们也将其封装为动画类。

动画类Animation

#include<vector>

class Animation
{
public:
	Animation(LPCTSTR path,int num,int interval)
	{
        interval_ms = interval;
	}
	~Animation()
	{

	}
	void Play(int x, int y, int delta)
	{

	}
private:
	int interval_ms = 0;
    std::vector<IMAGE*> FrameList;//动画帧序列
};

调用vector文件,引用vector文件为了使用动态数组,引用长度时直接调用函数即可。

    std::vector<IMAGE*> FrameList;//动画帧序列

    FrameList动画帧序列存放image类指针,相当于进阶版的指针数组。(不用固定数组长度、有封装函数调用长度,数组长度自动增减不用去管理)

构造函数参数(文件路径、帧数量、帧间隔)

img素材文件中,看到派蒙与野猪的动画帧都是6帧,但是考虑到普遍性有多帧动画的移植,我们也将num帧数作为参数。

帧间隔相当于上一章节的%10控制派蒙抽搐频率。私有变量设置为0ms,初始化是对其进行更改。


class Animation
{
public:
	Animation(LPCTSTR path,int num,int interval)
	{
		interval_ms = interval;
		TCHAR path_file[256];
		for (size_t i = 0; i < num; i++)
		{
			_stprintf_s(path_file, path, i);

			IMAGE* frame = new IMAGE();
			loadimage(frame, path_file);
			FrameList.push_back(frame);
		}
	}
	~Animation()
	{
		for (size_t i = 0; i < FrameList.size(); i++)
		{
			delete FrameList[i];
		}
	}
	void Play(int x, int y, int delta)
	{

	}
private:

	int interval_ms = 0;
	std::vector<IMAGE*> FrameList;//动画帧序列
};

 我们先从loadimage这个函数,下方代码块是之前的代码loadimage有两个参数(image的指针、文件路径)

第一个参数指针,没有创建类时其存在是在栈上建立,函数调用后就被释放。但在类中如果在初始化在栈上建立,初始化结束后就失效了。所以我们在堆上建立在析构函数中释放其内存。(堆与栈区别看笔记)new创建image类指针,析构函数中循环delete每一个指针。

第二个参数文件路径,文件路径的一大问题就是字符串与整型数字交替出现。我们采取了先分布再结合的方法,用已有的函数转为宽字节字符串,再用c_str函数转为字符串数组。

还记得Printf函数我们Printf(“shaojie %d NB”,666);就实现了字符串与数字的拼接。如果类似函数再将其转为字符串数组就达到了我们想要的效果。


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());
	}
}

_stprintf_s(path_file, path, i);就解决了

第一个参数字符串数组、第二个文件路径shaojie %d NB、第三个数字666

所以我就不用再引进<string>文件进<iostream>标准库即可。

通过loadimage函数我们获得了frame指针包含Player_left_0.png、Player_left_1.png等等我们不直接将其传递给putimage函数而是压入FrameList作为数组去引用。也就是指针数组(数组里存放一堆指针)。

至此我们已经将图片文件传入指针数组这个接口搭建好了,再就是播放帧动画

	void Play(int x, int y, int delta)
	{
		timer += delta;
		if (timer >= interval_ms)
		{
			idx_frame = (idx_frame + 1) % FrameList.size();
			timer = 0;
		}
		putimageA(x, y, FrameList[idx_frame]);
	}
private:
	int timer = 0;
	int idx_frame = 0;
	int interval_ms = 0;
	std::vector<IMAGE*> FrameList;//动画帧序列

三个参数(播放动画坐标x,y、播放时长)

将播放时长赋值给timer与帧间隔进行判断,如果动画时长将要等于帧间隔了播放下一帧动画。这时就可以用动态数组的size函数直接获取数组长度,取模达到循环效果。将上一节的putimageA函数封装到这里打印无黑背的派蒙。(记得putimageA定义要在Animation类之上,否者使用不了putimageA函数)。

类的实例


class Animation
{
public:
	Animation(LPCTSTR path,int num,int interval)
	{
		interval_ms = interval;
		TCHAR path_file[256];
		for (size_t i = 0; i < num; i++)
		{
			_stprintf_s(path_file, path, i);

			IMAGE* frame = new IMAGE();
			loadimage(frame, path_file);
			FrameList.push_back(frame);
		}
	}
	~Animation()
	{
		for (size_t i = 0; i < FrameList.size(); i++)
		{
			delete FrameList[i];
		}
	}
	void Play(int x, int y, int delta)
	{
		timer += delta;
		if (timer >= interval_ms)
		{
			idx_frame = (idx_frame + 1) % FrameList.size();
			timer = 0;
		}
		putimageA(x, y, FrameList[idx_frame]);
	}
private:
	int timer = 0;
	int idx_frame = 0;
	int interval_ms = 0;
	std::vector<IMAGE*> FrameList;//动画帧序列
};


Animation AnimLeftPlayer(_T("img/player_left_%d.png"), 6, 45);
Animation AnimRightPlayer(_T("img/player_right_%d.png"), 6, 45);

在类下创建实例面向左面向右(文件目录,帧数量,帧间隔)

45ms是如何计算的呢?在代码中数字1000表示1000ms,1000/144表示144Hz刷新频率,其中1帧时间为1000/144ms约等于6.94ms,每帧动画存在6.5帧就有视觉暂留效果6.94x6.5=45.1。

至此动画帧这个类初始化已经完成,外界调用Animation类的paly函数即可绘制函数了。

=====================================================================

绘制人物

绘制的xy我们初始化POINT类{500,500},再根据键盘改变位置player_pos.x与player_pos.y。

只要加入判定条件引用向左数组还是向右数组即可。

void DrawPlayer(int delta, int dir_x)
{

	static bool facing_left = false;
	if (dir_x < 0)facing_left = true;
	else if (dir_x > 0)facing_left = false;
	if (facing_left)
	{
		AnimLeftPlayer.Play(player_pos.x, player_pos.y,delta);
	}
	else 
	{
		AnimRightPlayer.Play(player_pos.x, player_pos.y, delta);
	}
}

如何编写判定条件呢?还记得我们的按键都是有标志位的,上下左右初始化默认都是false就是0,按下左左为1右为0,按下右右为1左为0。所以我们根据标志位的左右作差结果作为判断标志。但是,为了便于维护玩意标志位改变名称会连累到这个函数,所以我们新建变量dir_x。

给派蒙加上影子怎么做?

IMAGE ImgShadow;

const int PlayerWidth = 80;
const int PlayerHeight = 80;
const int ShadowWidth = 32;

首先是确定相对位置

 影子在派蒙脚下,居中对其后影子x与派蒙x相差(PlayerWidth / 2 - ShadowWidth / 2)距离。

影子y与派蒙y我们大概放在PlayerHeight - 8距离差。

void DrawPlayer(int delta, int dir_x)
{
	static bool facing_left = false;
	if (dir_x < 0)facing_left = true;
	else if (dir_x > 0)facing_left = false;
	if (facing_left)
	{
		AnimLeftPlayer.Play(player_pos.x, player_pos.y,delta);
	}
	else 
	{
		AnimRightPlayer.Play(player_pos.x, player_pos.y, delta);
	}

	int PosShadowX = player_pos.x + (PlayerWidth / 2 - ShadowWidth / 2);
	int PosShadowY = player_pos.y + PlayerHeight - 8;
	putimageA(PosShadowX, PosShadowY, &ImgShadow);
}

 至此派蒙可以摇头了,并且不是鬼了。

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

IMAGE ImgShadow;

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

const int PlayerWidth = 80;
const int PlayerHeight = 80;
const int ShadowWidth = 32;

const int WindowWidth = 1280;
const int WindowHeight = 720;

#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 });
}

class Animation
{
public:
	Animation(LPCTSTR path,int num,int interval)
	{
		interval_ms = interval;
		TCHAR path_file[256];
		for (size_t i = 0; i < num; i++)
		{
			_stprintf_s(path_file, path, i);

			IMAGE* frame = new IMAGE();
			loadimage(frame, path_file);
			FrameList.push_back(frame);
		}
	}
	~Animation()
	{
		for (size_t i = 0; i < FrameList.size(); i++)
		{
			delete FrameList[i];
		}
	}
	void Play(int x, int y, int delta)
	{
		timer += delta;
		if (timer >= interval_ms)
		{
			idx_frame = (idx_frame + 1) % FrameList.size();
			timer = 0;
		}
		putimageA(x, y, FrameList[idx_frame]);
	}
private:
	int timer = 0;
	int idx_frame = 0;
	int interval_ms = 0;
	std::vector<IMAGE*> FrameList;//动画帧序列
};

class Player
{
public:
	Player()
	{

	}
	~Player()
	{
	}

private:

};



Animation AnimLeftPlayer(_T("img/player_left_%d.png"), 6, 45);
Animation AnimRightPlayer(_T("img/player_right_%d.png"), 6, 45);

void DrawPlayer(int delta, int dir_x)
{
	static bool facing_left = false;
	if (dir_x < 0)facing_left = true;
	else if (dir_x > 0)facing_left = false;
	if (facing_left)
	{
		AnimLeftPlayer.Play(player_pos.x, player_pos.y,delta);
	}
	else 
	{
		AnimRightPlayer.Play(player_pos.x, player_pos.y, delta);
	}

	int PosShadowX = player_pos.x + (PlayerWidth / 2 - ShadowWidth / 2);
	int PosShadowY = player_pos.y + PlayerHeight - 8;
	putimageA(PosShadowX, PosShadowY, &ImgShadow);

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

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

	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%20==0)
		{
			IdxCurrentAnim++;
		}
		IdxCurrentAnim = IdxCurrentAnim % PLAYER_ANIM_NUM;
		int dir_x = is_move_right - is_move_left;
		int dir_y = is_move_down - is_move_up;
		double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
		if (len_dir != 0)
		{
			double normalized_x = dir_x / len_dir;
			double normalized_y = dir_y / len_dir;
			player_pos.x += (int)(PLAYER_SPEED*normalized_x);
			player_pos.y += (int)(PLAYER_SPEED*normalized_y);
		}
		if (player_pos.x<0)	player_pos.x = 0;
		if (player_pos.y < 0)	player_pos.y = 0;
		if (player_pos.x + PlayerWidth > WindowWidth)player_pos.x = WindowWidth - PlayerWidth;
		if (player_pos.y + PlayerHeight > WindowHeight)player_pos.y = WindowHeight - PlayerHeight;
		cleardevice();

		putimage(0, 0, &ImgBackground);
		/*putimageA(player_pos.x, player_pos.y,&ImgPlayerLeft[IdxCurrentAnim]);*/
		DrawPlayer(1000 / 144, is_move_right - is_move_left);

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

		FlushBatchDraw();
	}
	EndBatchDraw();
}

解决斜向移动过快 

	int dir_x = is_move_right - is_move_left;
	int dir_y = is_move_down - is_move_up;
	double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
	if (len_dir != 0)
	{
		double normalized_x = dir_x / len_dir;
		double normalized_y = dir_y / len_dir;
		player_pos.x += (int)(PLAYER_SPEED*normalized_x);
		player_pos.y += (int)(PLAYER_SPEED*normalized_y);
	}

以上这一段代码是解决斜向移动过快问题。

首先运用小学二年级学习的向量知识解释为什么移动快。

移动过快是因为按下左上,同时向左再向上位移一个单位长度,相当于向左上位移根号2个单位长度,从视觉上看单位时间内的位移更多就代表速度越快。

所以解决问题就是当按下任意两个反向时让其除以根号2。不不不,if语句会增加开销,还记得我们的按键标志位吗?在让派蒙摇头时我们用到标志位作差,所以我们再定义上下作差。至此上下左右四个方向斜对角就可以用(die_x,dir_y)这对儿数字表示了,当在对角线时除以单位长度即可。

 派蒙出边界问题

		if (player_pos.x<0)	player_pos.x = 0;
		if (player_pos.y < 0)	player_pos.y = 0;
		if (player_pos.x + PlayerWidth > WindowWidth)player_pos.x = WindowWidth - PlayerWidth;
		if (player_pos.y + PlayerHeight > WindowHeight)player_pos.y = WindowHeight - PlayerHeight;
		cleardevice();

根据坐标位置超出被拉回,所以当人物不规则时碰撞箱就很重要了,坐标确立不好就容易将摄像机视角卡到墙里。

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

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

相关文章

学习Java的日子 Day68 jQuery操作节点,Bootstrap

jQuery 1.jQuery操作DOM DOM为文档提供了一种结构化表示方法&#xff0c;通过该方法可以改变文档的内容和展示形式 在访问页面时&#xff0c;需要与页面中的元素进行交互式的操作。在操作中&#xff0c;元素的访问是最频繁、最常用的&#xff0c;主要包括对元素属性attr、内容…

《Hadoop大数据技术与实践》+ 数仓版本

基础概念 随着数字化时代的到来&#xff0c;数据量的爆炸性增长使得传统的数据处理和分析方法变得不够高效&#xff0c;因此大数据技术应运而生。 数据分类 结构化数据&#xff1a;固定格式的SQL数据库等半结构化数据&#xff1a;json非结构化数据&#xff1a;图片、音视频 …

探索sqlmap的奥秘:Python中的强大SQL注入检测工具

文章目录 **探索sqlmap的奥秘&#xff1a;Python中的强大SQL注入检测工具**第一部分&#xff1a;背景介绍第二部分&#xff1a;sqlmap是什么&#xff1f;第三部分&#xff1a;如何安装sqlmap&#xff1f;第四部分&#xff1a;简单库函数使用方法第五部分&#xff1a;场景应用第…

Grafana+Influxdb(Prometheus)+Apache Jmeter搭建可视化性能测试监控平台

此性能测试监控平台&#xff0c;架构可以是&#xff1a; GrafanaInfluxdbJmeterGrafanaPrometheusJmeter Influxdb和Prometheus在这里都是时序性数据库 在测试环境中&#xff0c;压测数据对存储和持久化的要求不高&#xff0c;所以这里的组件可以都通过docker-compose.yml文件…

制氧机在造纸工业中的作用

在现代造纸工业中&#xff0c;制氧机扮演着至关重要的角色&#xff0c;为整个生产流程带来了诸多显著的优势和改进。 制氧机能够优化纸浆的漂白过程。传统的漂白方法可能效果不佳&#xff0c;且对环境造成较大压力。而通过制氧机制备的高纯度氧气参与漂白反应&#xff0c;能大大…

Langchain pandas agent - Azure OpenAI account

题意&#xff1a;Langchain pandas代理 - Azure OpenAI账户 问题背景&#xff1a; I am trying to use Langchain for structured data using these steps from the official document. 我正在尝试使用 Langchain 处理结构化数据&#xff0c;按照官方文档中的这些步骤进行操作…

软件测试第2章 黑盒测试和白盒测试对比

目录 一、黑盒测试 二、白盒测试 三、黑盒测试 VS 白盒测试 一、黑盒测试 1、它只检查程序功能是否能按照需求规格说明书的规定正常使用&#xff0c;程序是否能适当地接受输入数据而产生正确的输出信息。 2、黑盒测试也称功能测试&#xff0c;通过测试来检测每个功能是否能…

《熬夜整理》保姆级系列教程-玩转Wireshark抓包神器教程(4)-再识Wireshark

1.简介 按照以前的讲解和分享路数&#xff0c;宏哥今天就应该从外观上来讲解WireShark的界面功能了。 2.软件界面 由上到下依次是标题栏、主菜单栏、主菜单工具栏、显示过滤文本框、打开区、最近捕获并保存的文件、捕获区、捕获过滤文本框、本机所有网络接口、学习区及用户指…

文件上传漏洞(三,靶场详解)

前言&#xff1a; 本文基于github上的upload-labs&#xff0c;PHP study以及bp抓包软件进行操作。 靶场环境搭建及pass-1.pass-2请转到&#xff1a;文件上传漏洞&#xff08;二&#xff0c;靶场搭建及漏洞利用&#xff09; 一&#xff0c;pass-3。 查看源码可知&#xff0c…

OJ-0815

题目 示例1 输入 bb1234aa 输出 10示例2 输入 bb12-34aa 输出 -31示例3 输入 bb0012-0034aa 输出 -31题解 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);long res 0;String s scanner…

在WinCC(TIA Portal)项目中,如何正确的使用面板,模板,弹出窗口和滑入窗口?

WinCC(TIA Portal)中提供了许多设计元素和组态对象,使得组态工作变得简单,文档中简要介绍了这些内容. WinCC(TIA Portal) 中提供了许多设计元素和组态对象&#xff0c;使得组态工作变得简单,文档中简要介绍了这些内容。 在 WinCC(TIA Portal) 中使用以下选项有助于提高组态过程…

git是什么/基本指令

git作用 去中心化&#xff0c; 分布式版本控制器 新增术语&#xff1a;仓库区&#xff0c; 工作区&#xff0c; 暂存区 具体见下板书 常用git命令 git clone 仓库网址 git status 查看仓库状态 git add newfile 临时添加到git仓库 git commit -m 正式添加git仓库 g…

电商SaaS聚水潭上市“求解”

时隔半年之后&#xff0c;电商ERP龙头聚水潭&#xff0c;再次向港交所递交主板上市申请&#xff0c;中金公司、摩根大通成为其联席保荐人。作为辅助电商商家运营的SaaS&#xff0c;其所提供的服务&#xff0c;主要包括库存分配、店铺管理、客户服务等诸多项目。作为国内最大的电…

机械行业数字化生产供应链产品解决方案(七)

在机械行业的数字化生产供应链产品解决方案中&#xff0c;通过全面部署物联网&#xff08;IoT&#xff09;传感器、智能分析平台和自动化控制系统&#xff0c;实现对生产设备的实时监控和数据采集&#xff0c;并结合大数据和人工智能技术进行深度分析&#xff0c;从而优化生产调…

AI绘画Stable Diffusion可以帮我做室内设计啦!把AI出图 应用到工作当中已经是人人必备的技能啦!

哈喽大家好&#xff0c;我是画画的小强 今天给大家带来个有意思教程&#xff0c;就是AI绘画Stable Diffusion 辅助帮我做室内设计&#xff01; 在本篇文章中我们可以把Stable Diffusion 当做一个小的渲染器来用&#xff0c;帮助我们快速出图&#xff0c;以及快速的出概念的创…

【网络】应用层协议-http协议

应用层协议-http协议 文章目录 1.Http协议1.1什么是http协议1.2认识URL1.3urlencode和urldecode1.4HTTP请求协议格式1.5HTTP响应协议格式1.6HTTP常见的Header1.7HTTP常见状态码1.8HTTP的方法1.8根据url调取对应的服务 2.cookie和session2.1cookie2.2session 3.HTTPS协议3.1对称…

【Linux入门】Linux环境搭建

目录 前言 一、发行版本 二、搭建Linux环境 1.Linux环境搭建方式 2.虚拟机安装Ubuntu 22.02.4 1&#xff09;安装VMWare 2&#xff09;下载镜像源 3&#xff09;添加虚拟机 4&#xff09;换源 5&#xff09;安装VM Tools 6)添加快照 总结 前言 Linux是一款自由和开放…

notepad++安装HexEdit插件

notepad安装HexEdit插件 打开notepad&#xff0c;选择插件—>插件管理 在这里找到HexEdit点击安装就可以 点击完&#xff0c;notepad会自动重启&#xff0c;重启完成就安装好了

企业如何通过数据虚拟化,构建逻辑数据编织平台?

逻辑数据编织的理念来自于 Data Fabric 这一创新的架构理念。尤其是在过去五六年间&#xff0c;这个理念在国际领域持续保持其热度与前沿性&#xff0c;成为备受瞩目的技术趋势。 Data Fabric 的核心观点在于正视并接受数据物理集中化的不可行性&#xff0c;进而探索通过技术手…

44 个 React 前端面试问题

1.你知道哪些React hooks&#xff1f; useState&#xff1a;用于管理功能组件中的状态。useEffect&#xff1a;用于在功能组件中执行副作用&#xff0c;例如获取数据或订阅事件。useContext&#xff1a;用于访问功能组件内的 React 上下文的值。useRef&#xff1a;用于创建对跨…