《游戏编程模式》学习笔记(五)原型模式 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
相关文章:
《游戏编程模式》学习笔记(五)原型模式 Prototype Pattern
原型的定义 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 举个例子 假设我现在要做一款游戏,这个游戏里有许多不同种类的怪物,鬼魂,恶魔和巫师。这些怪物通过“生产者”进入这片区域,每种敌人…...
ansible案列之LNMP分布式剧本
LNMP分布式剧本 一:环境设置二:编写Nginx剧本准备nginx下载源准备配置文件并开放PHP的访问路径准备php测试页面编写nginx剧本 三:编写Mysql剧本编写密码获取脚本准备Mysql的yum源编写mysql剧本 四:准备PHP剧本准备两个配置文件编写…...
React2023电商项目实战 - 1.项目搭建
古人学问无遗力,少壮工夫老始成。 纸上得来终觉浅,绝知此事要躬行。 —— 陆游《《冬夜读书示子聿》》 系列文章目录 项目搭建App登录及网关App文章自媒体平台(博主后台)内容审核(自动) 文章目录 系列文章目录一、项目介绍1.页面…...
数据库连接池(c3p0和德鲁伊)
目录 连接池介绍 c3p0连接池 传统方法引入jar包 配置文件 德鲁伊连接池 德鲁伊工具类 传统jdbc数据库使用DriverManger来获取,每次向数据库建立连接需要将Connection加载到内存中,频繁的操作会造成占用很多系统资源,造成服务器崩溃&…...
ARM--day6(实现字符、字符串收发的代码和现象,分析RCC、GPIO、UART章节)
uart4.h #ifndef __UART4_H__ #define __UART4_H__#include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_uart.h"//RCC/GPIO/UART4章节初始化 void hal_uart4_init();//发送一个字符函数 void hal_put_char(const c…...
2023牛客暑期多校训练营9 B.Semi-Puzzle: Brain Storm
文章目录 题目大意题解求解回溯 参考代码 题目大意 给定两个数 a , m a,m a,m ,求满足 a u ≡ u ( m o d m ) a^u \equiv u (mod\ \ m) au≡u(mod m) 的一个解。 ( 1 ≤ a , m ≤ 1 0 9 , 0 ≤ u ≤ 1 0 18 ) (1\leq a,m \leq10^9 ,0\leq u\leq 10^{18}) (1≤a…...
mysql中的窗口函数
MySQL中的窗口函数(Window Functions)是一种用于在查询结果集内执行计算的功能。窗口函数可以在查询中进行分析和聚合操作,而无需将查询结果分组。它们可以用于计算排名、行号、累积值等各种分析操作。窗口函数通常与OVER子句一起使用&#x…...
【双指针】经典数组双指针题LeetCode
文章目录 27. 移除元素 简单283. 移动零 简单🔥167. 两数之和 II - 输入有序数组 中等11. 盛最多水的容器 中等🔥15. 三数之和 中等(N数之和)中等🔥42. 接雨水 困难 🔥26. 删除有序数组中的重复项 简单5. 最…...
极智嘉x吉利汽车 x京东物流,引领汽车行业智慧物流新变革!
近日,中国领先的汽车制造商吉利汽车携手中国领先的技术驱动的供应链解决方案及物流服务商京东物流、全球仓储机器人引领者极智嘉(Geek),在西安吉利汽车制造基地RDC仓库率先落地SkyPick上存下拣解决方案,实现了全物流链精益化、智能化、一体化…...
RK3588平台开发系列讲解(AI 篇)RKNN C API 详细说明
文章目录 一、API 硬件平台支持说明二、API 函数介绍2.1、rknn_init2.2、rknn_destroy2.3、rknn_query2.4、rknn_inputs_set2.5、rknn_run2.6、rknn_outputs_get2.7、rknn_outputs_release沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解 RKNN C API 详细…...
【基础】Android Handler
一、博客参考 Handler机制详解【重点】:https://www.jianshu.com/p/b4d745c7ff7a Handler Thread工作线程操作UI范例【重点】:https://www.cnblogs.com/net168/p/4075126.html 二、内存泄漏的解决:静态内部类弱引用 关于 Handler…...
c语言实现MD5算法
MD5加密 文章目录 MD5加密MD5介绍应用场景代码分析 (基于qt5.14.2)测试记录 MD5介绍 1。 一种单向加密算法,即对明文加密,而不能通过密文得到明文。对原数据的任何改动,哪怕是1字节,得到的MD5值都有很大的区…...
Apache Doris 2.0.0 特性分析
1、存算分离 所谓存算分离是指查询外表时,使用一种专门做计算的BE节点,但对于存储在BE上的内部表,目前还不能做到存储分离。 doris可以查询外部表,包括: Hive、Iceberg、Hudi、Elasticsearch、JDBC、Paimon 早期版本中…...
如何做H5性能测试?
提起H5性能测试,可能许多同学有所耳闻,但是不知道该如何对H5做性能测试,或者不知道H5应该关注哪些性能指标。今天我们就来看下,希望阅读本文后,能够有所了解。 常用指标 1、H5性能相关参数介绍 白屏时间:…...
【Docker】Docker Desktop配置资源:cpu、内存等(windows环境下)
Docker Desktop配置资源:cpu、内存等(windows环境下) 一、WSL2 以及 hyper-v区别,二者安装docker desktop1.WSL2和hyper-v区别2.安装Docker Desktop 二、docker desktop限额配置,资源配置方法 Docker 是指容器化技术&a…...
8.2.tensorRT高级(3)封装系列-内存管理的封装,内存的复用
目录 前言1. 内存管理封装2. 补充知识总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。 本次课程学习 tensorRT 高级-内存管理的封装&…...
Keepalived入门指南:实现故障转移和负载均衡
文章目录 一、简介1. Keepalived概述2. 高可用性和负载均衡的重要性 二、故障转移1. 什么是故障转移2. Keepalived的故障转移原理a) VRRP协议b) 虚拟路由器ID和优先级 3. 配置Keepalived实现故障转移a) 主备服务器的设置b) 监控网络接口c) 虚拟IP的配置d) 备份服务器接管流程 三…...
cuOSD(CUDA On-Screen Display Library)库的学习
目录 前言1. cuOSD1.1 Description1.2 Getting started1.3 For Python Interface1.4 Demo1.5 Performance Table 2. cuOSD案例2.1 环境配置2.2 simple案例2.3 segment案例2.4 segment2案例2.5 polyline案例2.6 comp案例2.7 perf案例 3. cuOSD浅析3.1 simple_draw函数 4. 补充知…...
c++函数指针基本用法
将函数像变量一样传递,实际上拿到的是函数的地址,由于函数类型的多样,可以使用auto关键字,可以使用 void(*function2)() ,不过它太繁琐,因此使用typedef 起个名字 typedef void(*HelloWorldFunction)(); 叫…...
Java创建对象的几种方式
在Java中,对象是程序中的一种基本元素,它通过类定义和创建。本篇教程旨在介绍Java中创建对象的几种方式,包括使用new关键字、反射、clone、反序列化等方式。 使用new关键字创建对象 在Java中,最常用的创建对象方式是使用new关键…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果