Re0:从零开始的C++游戏开发 【下】

Re0:从零开始的C++游戏开发 (下)

这是蒟蒻观看B站upVoidmatrix的课程从零开始的提瓦特幸存者的个人笔记【自用】

前言:采用适用于小白的easyx图形库。

第三集 提瓦特の幸存者(下)

3.1 用户界面实现和设计模式基础

3.1.1 导言

假设这样一个场景:在一个游戏中,出现在你的视野中的树木数以千计,虽然我们会惊叹建模师和贴图美术师们逼真的还原水平,但程序并不在乎。它只关心如何从磁盘中加载这些数据,并将其高效地渲染在游戏窗口。

我们随意挑出一棵树,若这棵树是绘制在3D场景中,构成它的资源可以笼统的分为模型和贴图两类。在许多3A大作的游戏资源包中,模型和贴图相关的资源所占的比例是极高的。它们不仅占据了大量的硬盘空间,也占据了游戏启动时加载的大部分时间。如果我们把一棵树在内存中所占用的资源为10MB计算,场景中1000棵树就需要10000MB,也就是说,只是为了把屏幕上把这些树绘制出来就需要占用电脑9.8GB左右的内存。这对于玩家显然是不合理的,况且想要从磁盘上加载1000个模型,也需要十分恐怖的加载时间。

那么我们可能会问,我只需要加载一棵树的模型,然后再游戏里把他绘制1000次不就好了?确实如次,虽然在现代的游戏技术中,对于树木这种大批量出现的渲染任务已有许多成熟的解决方案,但他们都离不开一个设计模式——“享元模式”

3.1.2 享元模式

享元”即“共享元素”的意思。”享元模式“是设计模式中使用热度极高的模式之一。(设计模式是一套被反复使用 多数人知晓的 经过分类编码的 代码设计经验的总结),他不像C++等编程语言的语法那样白纸黑字,但也是一套自成体系的方法论

若我们把算法比作功夫中的内功,那么设计模式就是外功招式。

就像在引言所讲述的树林场景,我们在设计对应代码结构时,直截了当的思路是:

// 树结构体
struct tree
{Model model; // 树的模型Texture texture; // 树的贴图int x,y,z; // 树的位置
}

而在使用享元模式进行重新设计后:

// 树的资产结构体
struct TreeAsset
{Model model; // 树的模型Texture texture; // 树的贴图
}
// 树结构体
struct Tree
{TreeAsset* asset; // 资产指针int x,y,x; // 树的位置
}

再重新设计的代码中,我们把绘制一棵树所需的数据里面最庞大的部分挑出来。1000个Tree对象中模型和贴图均使用同一个TreeAsset对象中的数据,这样就可以节省大量的内存空间。

回看我们的代码,这时我们可以看到:在Animation的设计中,每一个Animation对象都拥有自己的动画帧列表;而在Enemy类中,每一个敌人,都拥有两个Animation对象,这就意味着我们在游戏中每次随机刷新一个野猪,都会从磁盘中加载两套动画的图片到内存中,虽然我们所使用的图片不如3D模型那般恐怖,并不会导致严重的内存爆满问题,但是从磁盘上读取数据的这个I/O操作本身就是十分耗时的工作,尤其是在一些机械硬盘上磁盘速度较慢的情况时,刷新敌人的时候便会有明显的卡顿感。在主循环中动态的从磁盘中加载数据,这本身也违背了我们之前认识到的:“主循环中应尽量避免耗时过长的任务”这一设计准则。加载数据的工作应该放置到我们游戏框架中初始化的部分去做。毕竟从游戏体验角度,对玩家来说,比起在游戏过程中出现卡帧和掉帧等情况,更愿接受在加载时稍微多等一会儿。

所以这里我们要对Animation类进行重新的拆分和设计。我们思考一下:游戏画面中的野猪们在动画方面可以共享的元素有哪些呢?

那当然是IMAGE对象构成的vector了;而动画当前正在播放第几帧等状态信息就各异了,所以就不能放在共享的数据里面。

因此,我们重新定义Atlas类来表示动画所使用的“图集”,其所需的成员变量,构造和析构函数都是从Animation中“拆分”下来的。而在整个游戏中,我们只需要用四个共用的Atlas对象,也就是玩家和敌人分别向左和向右的动画。我们将它们的指针定义为全局变量,稍后进行初始化。

class Atlas
{
public:Atlas(LPCTSTR path, int num){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);frame_list.push_back(frame);}}~Atlas(){for (size_t i = 0; i < frame_list.size(); i++)delete frame_list[i];}public:std::vector<IMAGE*> frame_list;
};

而将图片序列拆分出去的Animation类,就需要持有ATlas类对象的指针了,在初始化时,将它保存在成员变量中。

这里需要注意:由于AtlasAnimation之间共享的公共资产,所以千万不能在Animation的析构函数中使用deleteAtlas指针释放掉,Atlas的生命周期应由更上一层的代码进行控制。

这样敌人刷新时可能的卡顿就一去不复返了。这里蒟蒻注意到在每个对象中阴影图片的绘制也是都要读取、再加载渲染。可以试着用类似的思想实现一下。

3.1.3 用户界面

众所周知,EasyX作为2D图形库,它与GUI库是有区别的。我们可以十分便利的调用函数绘制点线面各种图形。但是想要在窗口中实现一个带有交互效果的按钮,这就需要我们自己实现了。

Qt作为GUI程序开发框架的定位,决定了它必然会屏蔽太多底层设计。例如我们在目前程序中所使用的“主循环”,这些封装和屏蔽从工程角度讲是再合适不过的,但是我们在探索游戏开发的初期也就是以学习为目的进行实践的过程中,我们更希望有一个功能简单直接容易上手的图形库来让我们选取,而不是直接使用GUI库。当然,在游戏开发中,Qt这些有着明确定位的GUI框架一般也不会直接参与到游戏程序本身的制作中,而是作为游戏开发工具链上的一环。想要在游戏这种即时渲染的框架中渲染更具有通用性的GUI,imGUI等技术是在合适不过的了。那么想要实现GUI组件,在现有程序中该如何编写呢?

这里,有一句GUI设计哲学一个按钮之所以是一个按钮,不是因为它长得像一个按钮,而是因为它能够对交互事件做出响应”。无论是文本还是图片,如果能够对玩家的点击事件进行捕获,并修改对应的数据进行响应,那么它就是一个按钮。

这里我们每个按钮提供了3张图片,分别对应了按钮的ialehoveredpush形态。

现在回到代码,来考虑按钮类该如何设计。

按钮必然需要一个RECT变量来描述自己的位置和大小,这在判断鼠标响应时是必须的。然后是3张IMAGE图片变量。最后我们还应定义按钮当前的状态枚举变量。,这是因为按钮的悬停、按下等状态实在消息处理时进行判断的 ,而在主循环的每一帧画面渲染时,我们都需要根据现有状态选择对应图片进行绘制。

然后就是内部成员函数的编写,绘制函数、事件处理函数。注意在开始编写之前一定要理清代码逻辑。

class Button
{
public:Button(RECT rect, LPCTSTR path_img_idle, LPCTSTR path_img_hovered, LPCTSTR path_img_pushed){region = rect;loadimage(&img_idle, path_img_idle);loadimage(&img_hovered, path_img_hovered);loadimage(&img_pushed, path_img_pushed);}~Button() = default;void ProccessEvent(const ExMessage& msg){switch (msg.message){case WM_MOUSEMOVE:if (status == Status::Idle && CheckCursoHit(msg.x, msg.y))status = Status::Hovered;else if (status == Status::Hovered && !CheckCursoHit(msg.x, msg.y))status = Status::Idle;break;case WM_LBUTTONDOWN:if (CheckCursoHit(msg.x, msg.y))status = Status::Pushed;break;case WM_LBUTTONUP:if (status == Status::Pushed)OnClick();break;default:break;}}void Draw(){switch (status){case Status::Idle:putimage(region.left, region.top, &img_idle);break;case Status::Hovered:putimage(region.left, region.top, &img_hovered);break;case Status::Pushed:putimage(region.left, region.top, &img_pushed);break;}}protected:virtual void OnClick() = 0;private:enum class Status{Idle = 0,Hovered,Pushed};
private:RECT region;IMAGE img_idle;IMAGE img_hovered;IMAGE img_pushed;Status status = Status::Idle;
private:// 检测鼠标点击bool CheckCursoHit(int x, int y){return x >= region.left && x <= region.right && y >= region.top && y <= region.bottom;}};

接下来,便可以此为基类编写特殊按钮类了

class QuitGameButton :public Button
{
public:QuitGameButton(RECT rect,LPCTSTR path_img_idle, LPCTSTR path_img_howered, LPCTSTR path_img_pushed):Button(rect,path_img_idle,path_img_howered,path_img_pushed){}~QuitGameButton() = default;protected:void OnClick() override{running = false;}
};class StartGameButton :public Button
{
public:StartGameButton(RECT rect, LPCTSTR path_img_idle, LPCTSTR path_img_howered, LPCTSTR path_img_pushed):Button(rect, path_img_idle, path_img_howered, path_img_pushed) {}~StartGameButton() = default;protected:void OnClick() override{is_game_started = true;mciSendString(_T("play bgm repeat from 0"), NULL, 0, NULL);}
};

注意我们在这里,将主循环的播放音乐移了过来。

另外还设置了2个全局变量runningis_game_start

再对主函数内代码稍加修改,此次的项目完成了。。。

整个项目的源码在upVoidmatrix那里可以获取。。。

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

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

相关文章

省级交通运输行政执法综合管理平台项目实施方案

背景 党的十八届四中全会提出全面推进依法治国的总目标和重大任务。全会通过的《中共中央关于全面推进依法治国若干重大问题的决定》&#xff0c;开启了中国法治建设的新时代&#xff0c;依法治国已经成为党领导人民治理国家的基本方略。 为了贯彻和落实《交通运输信息化“十三…

资深人士称:AI开发游戏会降低游戏成本和体验,不会降低就业率

易采游戏网6月1日最新消息&#xff1a;本周在TD Cowen会议上&#xff0c;R星的母公司Take-Two的CEO Strauss Zelnick对于人工智能(AI)是否会影响游戏开发行业表达了自己的看法。他坚定地认为&#xff0c;AI绝对会改变游戏的制作方式&#xff0c;但不会降低游戏行业的就业水平。…

Maven打包错误:无效的源发行版:17

1. 报错问题 在用maven进行打包时&#xff08;clean & install&#xff09;&#xff0c;报如下错误&#xff1a; 一开始让我很摸不着头脑&#xff0c;我确定我的pom.xml&#xff0c;还有IDEA中的Project Settings是正确的。 2. 排查 尽管确定&#xff0c;但还是一个个排…

秒杀基本功能开发(显示商品列表和商品详情)

文章目录 1.数据库表设计1.商品表2.秒杀商品表3.修改一下秒杀时间为今天到明天 2.pojo和vo编写1.com/sxs/seckill/pojo/Goods.java2.com/sxs/seckill/pojo/SeckillGoods.java3.com/sxs/seckill/vo/GoodsVo.java 3.Mapper编写1.GoodsMapper.java2.GoodsMapper.xml3.分别编写Seck…

在浏览器里面输入 url,到浏览器显示页面中间发生了什么?

当用户在浏览器中输入URL&#xff08;例如https://www.example.com&#xff09;按下回车键&#xff0c;到浏览器显示页面&#xff0c;这中间浏览器会执行以下步骤&#xff1a; 浏览器解析URL&#xff1a;浏览器解析URL&#xff0c;提取出协议&#xff08;如HTTP或HTTPS&#xf…

每天一个数据分析题(三百四十二)

根据量化对象是业务行为结果还是财务行为结果&#xff0c;可以将指标分为业务指标及财务指标两大类&#xff0c;以下说法正确的是&#xff1f; A. 财务指标是按照财务规则来对财务情况进行量化的指标 B. 业务指标是按照业务规则来对业务情况进行量化的指标 C. 业务指标需要按…

VS(visual studio)搭建QT开发环境插件安装

优先安装QT Qt6 官网QtCreator 下载与安装方法win10_qt6下载-CSDN博客 如果安装vs2019,打开installer,安装c环境 选择c 下载vsix后&#xff0c;双击安装即可。 插件下载&#xff1a; Index of /qtproject/official_releases/vsaddin/ 创建QT项目&#xff1a; 创建完成&…

4K高刷显示器 - 蚂蚁电竞ANT27VU

可以毫不夸张地说&#xff0c;每一局游戏最终能够取得胜利&#xff0c;实际上都与一套极为优秀的电竞 PC 有着紧密的关联&#xff0c;因为其能够提供强大的性能支持与流畅的体验。同样的道理&#xff0c;一套优秀的电竞 PC 若想发挥出最佳的效果&#xff0c;那也都离不开一台能…

leetCode-hot100-数组专题之求和+数学定理+其他

数组专题之求和数学定理其他 求和1.两数之和15.三数之和 数学定理169.多数元素 其他4.寻找两个正序数组的中位数128.最长连续序列 求和 数组求和问题&#xff0c;即计算数组中所有元素的总和&#xff0c;是编程中常见的任务。以下是一些常用的解决方法&#xff1a; 1. 循环遍历…

介绍一下js的节流与防抖

在JavaScript中&#xff0c;节流&#xff08;Throttling&#xff09;和防抖&#xff08;Debouncing&#xff09;是两种常用的优化高频率触发事件的策略。它们主要用于限制函数的执行频率&#xff0c;以避免因频繁触发导致的性能问题。 1. 防抖&#xff08;Debouncing&#xff…

numpy.ndarray是什么类型

numpy.ndarray是什么类型 numpy.ndarray 类型的特点创建 numpy.ndarray示例代码总结 numpy.ndarray是什么类型 numpy.ndarray 是 NumPy 库中的一个核心数据类型&#xff0c;用于表示多维数组。它是 NumPy 中最重要的数据结构之一&#xff0c;提供了高效的数值计算能力。下面是对…

PPP与HDLC的异同

PPP&#xff08;点对点协议&#xff09;与HDLC&#xff08;高级数据链路控制&#xff09;都是数据链路层协议&#xff0c;用于在两点间提供可靠的通信链接&#xff0c;但它们在设计目标、应用场景、功能特性等方面存在一些显著的异同&#xff1a; 相同点&#xff1a; 目的相似…

nn.Embedding使用

nn.Embedding使用 Embedding.weight会从标准正态分布中初始化成大小为&#xff08;num_embeddings, embedding_dim&#xff09;的矩阵 PE矩阵的作用就是替换这个标准正态分布 input中的标号表示从矩阵对应行获取权重来表示单词 # 1.设置embedding结构 max_seq_len 1000 # 句…

配合busco训练Augustus

使用BUSCO的结果来训练Augustus包括以下几个步骤。这些步骤会帮助你利用BUSCO评估的高质量基因来优化Augustus的基因预测模型。 步骤1&#xff1a;运行BUSCO 首先&#xff0c;你需要运行BUSCO来评估你的基因组或转录组。BUSCO会生成一些包含高质量单拷贝直系同源基因的信息文件…

【Java】面向对象的三大特征:封装、继承、多态

封装 什么叫封装&#xff1f; 在我们写代码的时候经常会涉及两种角色&#xff1a; 类的实现者 和 类的调用者。 封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的&#xff0c; 只要知道如何使用类就行了&#xff0c;这样就降低了类使用者的学习和使用成本&a…

游戏主播到底是为游戏宣传还是蹭游戏带来的热度

易采游戏网6月1日最新消息&#xff1a;近日知名游戏主播周淑怡在社交平台上发表了自己对《地下城与勇士》手游(简称DNF手游)的点评。作为一款拥有庞大粉丝基础的端游改编作品&#xff0c;DNF手游自发布以来便受到了广泛关注。而周淑怡的点评不仅聚焦于游戏体验本身&#xff0c;…

Python代码:二十七、append函数

1、题目 牛牛有一个name [Niumei, YOLO, Niu Ke Le, Mona] 记录了他最好的朋友们的名字&#xff0c;请创建一个二维列表friends&#xff0c;使用append函数将name添加到friends的第一行。 假如Niumei最喜欢吃pizza&#xff0c;最喜欢数字3&#xff0c;YOLO最喜欢吃fish&…

Linux实验报告(一)——Linux系统安装与简单配置

目录 一、实验名称&#xff1a; 二、仪器、设备&#xff1a; 三、参考资料&#xff1a; 四、实验目的&#xff1a; 五、实验内容&#xff08;步骤&#xff09;&#xff1a; 六、实验数据&#xff08;程序&#xff09;记录&#xff1a; 七、实验结果分析&#xff1a; 八、…

XXE漏洞简介

目录 漏洞原理 漏洞危害 前置知识 XML简介 DTD简介 DTD的两种声明方式 实体 实体分类 内置实体(Built-inentities) 字符实体&#xff08;Characterentities&#xff09; 通用实体&#xff08;Generalentities&#xff09; 参数实体(Parameterentities) XXE漏洞…

嵌入式Linux shell编程实例

1. 输入两个数&#xff0c;实现两个数的相加 &#xff08;1&#xff09;具体实现代码如下 1 #!/bin/bash2 read a3 read b4 sum$(($a$b))5 echo "$sum"&#xff08;2&#xff09;编辑完内容后按Esc键再输入:wq保存&#xff0c;回车退出&#xff0c;执行结果如下图&a…