当前位置: 首页 > news >正文

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

在上一篇笔记之中我们还留有许多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 ImgPlayerLeft[PLAYER_ANIM_NUM];
IMAGE ImgPlayerRight[PLAYER_ANIM_NUM];
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;const int PLAYER_ANIM_NUM = 6;
IMAGE ImgPlayerLeft[PLAYER_ANIM_NUM];
IMAGE ImgPlayerRight[PLAYER_ANIM_NUM];
IMAGE ImgShadow;
int	 IdxCurrentAnim = 0;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();

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

Player类

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • CCF编程能力等级认证GESP—C++7级—20240629
  • C#复习之封装_构造函数,析构函数,垃圾回收
  • 技术周总结 08.05-08.11周日(scala git回滚)
  • Android Basis - 密钥和ID认证
  • 代理IP如何助力社交媒体数据挖掘
  • leetcode26_删除有序数组中的重复项
  • 时序数据库TDengine和QuestDB对比
  • 微服务-实现nacos的集群和Gateway网关的实现、认证校验、解决跨域
  • 驱动开发系列10 - Linux Graphics 图形栈介绍
  • 非负数(0和正数) 限制最大值且保留两位小数,在elementpuls表单中正则自定义验证传更多参数
  • Linux驱动开发基础(Hello驱动)
  • Python 深度学习调试问题
  • 移动端上拉分页加载更多(h5,小程序)
  • Github 2024-08-10 Rust开源项目日报Top10
  • 《嵌入式驱动面试常见问题攻略》
  • Angular 响应式表单之下拉框
  • Docker入门(二) - Dockerfile
  • export和import的用法总结
  • golang 发送GET和POST示例
  • JavaScript DOM 10 - 滚动
  • Java反射-动态类加载和重新加载
  • JS字符串转数字方法总结
  • LeetCode29.两数相除 JavaScript
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • PermissionScope Swift4 兼容问题
  • SpiderData 2019年2月13日 DApp数据排行榜
  • 对JS继承的一点思考
  • 构建二叉树进行数值数组的去重及优化
  • 关于Java中分层中遇到的一些问题
  • 记录一下第一次使用npm
  • 简单基于spring的redis配置(单机和集群模式)
  • 三分钟教你同步 Visual Studio Code 设置
  • 我的业余项目总结
  • 携程小程序初体验
  • 在Unity中实现一个简单的消息管理器
  • ‌‌雅诗兰黛、‌‌兰蔻等美妆大品牌的营销策略是什么?
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (C#)一个最简单的链表类
  • (Java入门)抽象类,接口,内部类
  • (佳作)两轮平衡小车(原理图、PCB、程序源码、BOM等)
  • (三)终结任务
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)大型网站的系统架构
  • (转)大型网站架构演变和知识体系
  • ***检测工具之RKHunter AIDE
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .bat批处理(七):PC端从手机内复制文件到本地
  • .Family_物联网
  • .net core使用RPC方式进行高效的HTTP服务访问
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法
  • .Net Web项目创建比较不错的参考文章
  • .NET 中什么样的类是可使用 await 异步等待的?
  • @开发者,一文搞懂什么是 C# 计时器!
  • [AMQP Connection 127.0.0.1:5672] An unexpected connection driver error occured