《游戏编程模式》学习笔记(五)原型模式 Prototype Pattern

原型的定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

举个例子

假设我现在要做一款游戏,这个游戏里有许多不同种类的怪物,鬼魂,恶魔和巫师。这些怪物通过“生产者”进入这片区域,每种敌人有不同的生产者。

假设每种怪物都有不同的类,同时他们都继承怪兽这个基类,那么我们的代码就会是这样

class Monster
{// 代码……
};class Ghost : public Monster {};
class Demon : public Monster {};
class Sorcerer : public Monster {};

为了能够产生这些怪物,我们需要不同的生产者类,这些类都继承spawner这个基类,那么我们就得写如下的代码:

class Spawner
{
public:virtual ~Spawner() {}virtual Monster* spawnMonster() = 0;
};class GhostSpawner : public Spawner
{
public:virtual Monster* spawnMonster(){return new Ghost();}
};class DemonSpawner : public Spawner
{
public:virtual Monster* spawnMonster(){return new Demon();}
};// 你知道思路了……

那么这么一来,我们的架构就类似于这样:
在这里插入图片描述
每个怪物类都有生产者类,得到平行的类结构
已经闻到臭味了……你的代码里有一堆特定的spawner,将来要是有一个新怪物,你就得多些一堆代码。 众多类,众多引用,众多冗余,众多副本,众多重复自我……

那么这个时候,原型模式就可以派上用场了! 我们来看看实现
原型模式提供了一个解决方案。 关键思路是一个对象可以产出与它自己相近的对象。 如果你有一个恶灵,你可以制造更多恶灵。 如果你有一个恶魔,你可以制造其他恶魔。 任何怪物都可以被视为原型怪物,产出其他版本的自己。

我们给基类Monster增加一个Clone()的方法

class Monster
{
public:virtual ~Monster() {}virtual Monster* clone() = 0;// 其他代码……
};
每个怪兽子类提供一个特定实现,返回与它自己的类和状态都完全一样的新对象。就像这样
class Ghost : public Monster {
public:Ghost(int health, int speed): health_(health),speed_(speed){}virtual Monster* clone(){return new Ghost(health_, speed_);}private:int health_;int speed_;
};

然后我们就不需要那么多特定的spawner了,我们只需要一个spawner()如下:

class Spawner
{
public:Spawner(Monster* prototype): prototype_(prototype){}Monster* spawnMonster(){return prototype_->clone();}private:Monster* prototype_;
};

可以看到这个Spawner内部有一个Monster*类型的prototype,这就是原型,一个隐藏的怪物, 它唯一的任务就是被生产者当做模板,去产生更多一样的怪物, 有点像一个从来不离开巢穴的蜂后。
在这里插入图片描述
当你要使用的时候,你只需要这么做:

Monster* ghostPrototype = new Ghost(15, 3);
Spawner* ghostSpawner = new Spawner(ghostPrototype);

这个模式的灵巧之处在于它不但拷贝原型的类,也拷贝它的状态。 这就意味着我们可以创建一个生产者,生产快速鬼魂,虚弱鬼魂,慢速鬼魂,而只需创建一个合适的原型鬼魂。

如果你还是觉得麻烦,懒得写Clone()方法的话,这里还有一种思路,使用生产函数来代替生产者类,我们这么写一个生产函数:

Monster* spawnGhost()
{return new Ghost();
}

这比构建怪兽生产者类更简洁。生产者类只需简单地存储一个函数指针:

typedef Monster* (*SpawnCallback)();class Spawner
{
public:Spawner(SpawnCallback spawn): spawn_(spawn){}Monster* spawnMonster(){return spawn_();}private:SpawnCallback spawn_;
};

而你调用的时候,就这样写就行:

Spawner* ghostSpawner = new Spawner(spawnGhost);

原型还可以做什么?

原型模式不仅仅可以应用在代码之中,我们还可以将其用在一些别的地方,比如数据存储中。
再举个例子,我们在游戏中经常使用Json来存储一些数据,比如怪物的各种属性。
所以游戏中的哥布林也许被定义为像这样的东西:

{"name": "goblin grunt","minHealth": 20,"maxHealth": 30,"resists": ["cold", "poison"],"weaknesses": ["fire", "light"]
}

接下来,如果策划和你说,我还要别的哥布林,例如哥布林巫师,哥布林弓箭手,即使他们的很多属性都是一样的,我们还是不得不这么写:

{"name": "goblin wizard","minHealth": 20,"maxHealth": 30,"resists": ["cold", "poison"],"weaknesses": ["fire", "light"],"spells": ["fire ball", "lightning bolt"]
}{"name": "goblin archer","minHealth": 20,"maxHealth": 30,"resists": ["cold", "poison"],"weaknesses": ["fire", "light"],"attacks": ["short bow"]
}

太重复了,我们讨厌重复,太多重复的数据意味着我们要话更多的时间去维护和管理,这是我们都不想看到的。
如果这是代码,我们会为“哥布林”构建抽象,并在三个哥布林类型中重用。 但是无能的JSON没法这么做。所以让我们把它做得更加巧妙些。
我们可以为对象添加"prototype"字段,记录委托对象的名字。 如果在此对象内没找到一个字段,那就去委托对象中查找。
这样,我们可以简化我们的哥布林JSON内容:

{"name": "goblin grunt","minHealth": 20,"maxHealth": 30,"resists": ["cold", "poison"],"weaknesses": ["fire", "light"]
}{"name": "goblin wizard","prototype": "goblin grunt","spells": ["fire ball", "lightning bolt"]
}{"name": "goblin archer","prototype": "goblin grunt","attacks": ["short bow"]
}

这样不就好多了?只需在游戏引擎上多花点时间,你就能让设计者更加方便地添加不同的武器和怪物,而增加的这些丰富度能够取悦玩家。

这一节的内容有好多关于原型模式的思想方面的介绍,作者还介绍了原型模式在编程语言方面的应用,我认为这些都是暂时对游戏编程帮助不大,所以没有记录下来,有兴趣的同学请翻阅原文。

原文链接:https://gpp.tkchu.me/prototype.html

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

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

相关文章

TiDB v7.1.0 跨业务系统多租户解决方案

本文介绍了 TiDB 数据库的资源管控技术,并通过业务测试验证了效果。资源管控技术旨在解决多业务共用一个集群时的资源隔离和负载问题,通过资源组概念,可以限制不同业务的计算和 I/O 资源,实现资源隔离和优先级调度,提高…

第5章:神经网络

神经元模型 上述定义的简单单元即为神经元模型。 多层网络 误差逆传播算法 标准BP算法:参数更新非常频繁,可能出现抵消现象。积累BP算法:下降到一定程度上,进行下一步会非常缓慢。 过拟合 早停:划分训练集和验证集…

spring的核心技术---bean的生命周期加案例分析详细易懂

目录 一.spring管理JavaBean的初始化过程(生命周期) Spring Bean的生命周期: 二.spring的JavaBean管理中单例模式及原型(多例)模式 2.1 . 默认为单例,但是可以配置多例 2.2.举例论证 2.2.1 默认单例 2.2…

【AIGC】 快速体验Stable Diffusion

快速体验Stable Diffusion 引言一、安装二、简单使用2.1 一句话文生图2.2 详细文生图 三、进阶使用 引言 stable Diffusion是一款高性能的AI绘画生成工具,相比之前的AI绘画工具,它生成的图像质量更高、运行速度更快,是AI图像生成领域的里程碑…

【PACS源码】认识PACS的架构和工作流程

(一)PACS系统的组成及架构 PACS系统的基本组成部分包括:数字影像采集、通讯和网络、医学影像存储、医学影像管理、各类工作站五个部分。 而目前PACS系统的软件架构选型上看,主要有C/S和B/S两种形式。 C/S架构,即Client…

接口测试,负载测试,并发测试,压力测试区别

接口测试 1.定义:接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换,传递和控制管理过程,以及系统间的相互逻辑依赖关系等。 2.目的&#xf…

预训练GNN:GPT-GNN Generative Pre-Training of Graph Neural Networks

一.文章概述 本文提出了一种自监督属性图生成任务来预训练GNN,使得其能捕图的结构和语义属性。作者将图的生成分为两个部分:属性生成和边生成,即给定观测到的边,生成节点属性;给定观测到的边和生成的节点属性&#xf…

CSS基础 知识点总结

一.CSS简介 1.1 CSS简介 ① CSS指的是层叠样式表,用来控制网页外观的一门技术 ② CSS发展至今,经历过CSS1.0 CSS2.0 CSS2.1 CSS3.0这几个版本,CSS3.0是CSS最新版本 1.2 CSS引入方式 ① 在一个页面引入CSS,共有三种方式 外部…

TPS_C++版本及功能支持备注

TPS_C版本及功能支持备注 相关参考链接C23:https://zh.cppreference.com/w/cpp/23 相关参考链接C20:https://zh.cppreference.com/w/cpp/20 相关参考链接C17:https://zh.cppreference.com/w/cpp/17 相关参考链接C14:https://zh.cp…

深入了解Maven(一)

目录 一.Maven介绍与功能 二.依赖管理 1.依赖的配置 2.依赖的传递性 3.排除依赖 4.依赖的作用范围 5.依赖的生命周期 一.Maven介绍与功能 maven是一个项目管理和构建工具,是基于对象模型POM实现。 Maven的作用: 便捷的依赖管理:使用…

springboot异步文件上传获取输入流提示找不到文件java.io.FileNotFoundException

springboot上传文件,使用异步操作处理上传的文件数据,出现异常如下: 这个是在异步之后使用传过来的MultipartFile对象尝试调用getInputStream方法发生的异常。 java.io.FileNotFoundException: C:\Users\Administrator\AppData\Local\Temp\to…

【C语言】指针的进阶

目录 一、字符指针 二、指针数组 三、数组指针 1.数组指针的定义 2.&数组名和数组名区别 3.数组指针的使用 四、数组参数与指针参数 1.一维数组传参 2.二维数组传参 3.一级指针传参 4.二级指针传参 五、函数指针 六、函数指针数组 七、指向函数指针数组的指针…

问题:【IntelliJ IDEA】解决idea自动声明变量加finall修饰符问题

问题:【IntelliJ IDEA】解决idea自动声明变量加finall修饰符问题 场景复现 1 new String() 2 快捷方式生成变量 final修饰的 final String s new String();步骤一:确保settings配置信息 settings-----》Editor------》Code Style--------》java下的这两个选项不…

模糊测试面面观 | 模糊测试工具知多少

自1988年威斯康星大学的Barton Miller首次提出模糊测试这一概念以来,模糊测试领域经历了持续长久发展。模糊测试作为一种软件测试方法,旨在通过向程序输入模糊、随机、异常的数据,探测和发现潜在的漏洞和错误。这种方法备受安全研究人员的青睐…

YOLOv5算法改进(2)— 添加SE注意力机制

前言:Hello大家好,我是小哥谈。注意力机制是近年来深度学习领域内的研究热点,可以帮助模型更好地关注重要的特征,从而提高模型的性能。注意力机制可被应用于模型的不同层级,以便更好地捕捉图像中的细节和特征&#xff…

Linux 网络发包流程

哈喽大家好,我是咸鱼 之前咸鱼在《Linux 网络收包流程》一文中介绍了 Linux 是如何实现网络接收数据包的 简单回顾一下: 数据到达网卡之后,网卡通过 DMA 将数据放到内存分配好的一块 ring buffer 中,然后触发硬中断CPU 收到硬中…

跟左神刷算法02 基础

题目1 先根据难度拍个序,难度一样的按收入大的排序,因为他每份都有招无数份人。难度相同的就留一个最大的。难度增加了,而薪水变小了,直接丢掉。难度和薪水保持一个单调性就可以了。 用有序表取做。 key是排序的。 迅速查到小于…

gcc语法解析如何解决dangling else的问题?

GCC上古版本(3.4)还有yacc,学习GCC如何实现if else 嵌套的问题。即: 问题 else后面的if到底是else if语义 if (xxx)a1 else if (xxx) a2还是 else (语法块中的if else)。 if (xxx)a1 else if (xxx) a 2 else a2;PostgreSQL的…

文心一言 VS 讯飞星火 VS chatgpt (76)-- 算法导论7.3 1题

一、如果用go语言,为什么我们分析随机化算法的期望运行时间,而不是其最坏运行时间呢? 文心一言: 在分析算法的性能时,期望运行时间是一个重要的指标,它描述了算法在平均情况下的表现。期望运行时间考虑了所有可能的…

C#如何打包EXE程序生成setup安装文件

项目结束之后,有需要将winForm程序打包成.exe文件提供给用户。 这里记录一下打包过程。 1:首先获取打包插件,如果你的VS已经安装,忽略此步骤。 点击 工具->扩展和更新,选择联机,搜索installer&#x…