前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >RPG设计(物品锻造与Decorator模式)

RPG设计(物品锻造与Decorator模式)

作者头像
张子阳
发布于 2018-09-30 02:13:59
发布于 2018-09-30 02:13:59
2.1K0
举报

RPG设计(物品锻造与Decorator模式)

2007-12-14 作者: 张子阳 分类: 设计与模式

引言

物品锻造是各类奇幻游戏中的常见功能,就拿众所周知的Diablo来说吧。假设角色拥有一把单手剑,可能基础攻击力只有13,但是它有三个装备孔。当给剑镶嵌一颗蓝宝石的时候,它就拥有了额外的冰冻效果并多加2点攻击力;当给剑镶嵌一颗红宝石的时候,它又拥有了额外的火焰伤害并多加3点攻击力;当给剑镶嵌一颗绿宝石的时候,它又拥有了额外的中毒伤害并多加的4点攻击力。当然,也可以三个孔都镶嵌同一色的宝石。本文将说明如何使用Decorator模式来完成这样的设计。

使用继承来扩展

我们首先想到应该有个基类 Weapon,它供所有各式各样的武器继承,比如说Sword、Axe、Bow。Description字段代表武器的说明,比如“One-Hand light Sword”,Damage()方法则用于获取武器的伤害,GetDescription用于获取武器的说明。在不考虑宝石的情况下,我们得到下面的设计:

现在我们考虑如何创建镶嵌有宝石的武器。我们首先考虑到可以用继承来实现这样的设计,结果却发现如果我们需要定义所有嵌宝石的剑(Sword),就需要3+6+7 = 16个类(NOTE:三个物品孔,每个孔都有 蓝、红、绿 三种选择,可以两个或者三个孔同一色),如果我们给镶嵌了两颗蓝一颗红宝石的剑命名为 Blue2RedSword,给三色不同不剑命名为BlueRedGreenSword,其余的类推。那么,我们会得到下面这样庞大的类体系(只绘制了部分):

而这仅仅是开始,如果我们需要再添一种宝石,比如说白色,它可以附加诅咒的效果;或者我们需要给武器再添加一个物品孔,那么我们的类的数目将迅速的由十几个变成几十个。

我们发现使用继承的问题:使用继承时将会创建出大量的类。除此以外,使用继承,也意味我们需要实例化一个特定的子类以获取我们需要的功能(方法),这在编译阶段(compile time)就已经确定,类的客户端不能控制何时(run time)根据需要改变,除非再实例化另一个子类。

使用复合来扩展

我们发现继承会带来两个主要的问题,所以我们考虑换一种方式来思考,我们可以使用复合来完成它。说详细一点,就是我们将蓝宝石(BlueDiamond)、红宝石(RedDiamond)、绿宝石(GreenDiamond) 作为实体变量(instance variable)复合到基类中,然后在基类的Damage()方法中计算出所有宝石额外增加的伤害(此时基类的Damage()方法不再是抽象的)。

代码语言:txt
AI代码解释
复制
public abstract class Weapon{
    public virturl int Damage(){
       int total = 0;
       if(redDiamond!=null)
           total += redDiamond.Damage();       //附加红宝石的伤害
       if(blueDiamond!=null)
           total += blueDiamond.Damage();      //附加蓝宝石的伤害
       if(greenDiamond!=null)
           total += greenDiamond.Damage();     //附加绿宝石的伤害
       return total;
    }
}

而在实体子类中,我们覆盖这个方法,在方法内部先调用基类方法获取宝石的附加伤害,然后再给它加上武器本身的伤害。

代码语言:txt
AI代码解释
复制
public class Sword: Weapon{
    public override ind Damage(){
       return base.Damage() + 15; // 15 是剑本身伤害
    }
}

此时的图应该变成这样:

相对于继承,复合看上去要好得多,它的类的数目要少的多,并且又可以在运行时决定是否给武器镶嵌宝石,但是使用复合仍存在问题:

  • 宝石与剑是紧密耦合在一起的,当我们想要为武器添加一个白宝石,那么我们需要给Weapon基类再添加一个BlueDiamond字段,同时还需要修改基类的Damage()方法。简言之,每次维护我们都要修改以前的代码。
  • 我们遗忘了一种组合,应该记得,我们的剑是可以镶嵌三个同色宝石的,比如说:三个蓝宝石或者 三个红宝石,那么上面的设计显然无法完成。当然,我们可以从三种宝石中抽象出一个Diamond基类来,而在Weapon中添加三个Diamond类型的变量。但是,问题依然存在:如果我们需要多添一个装备孔,那么我们又得再次修改Weapon类。

为对象添加状态和行为

现在假设我们不是一名软件设计者,而是一个游戏玩家,我们要为剑添加一枚红宝石,一枚蓝宝石,那么实际的操作顺序是什么呢?

  1. 我们当然首先要有一把剑。(需要先创建一个Sword对象,它只是把剑,不含任何宝石)。
  2. 我们为剑添加一个红宝石。(我们包装Sword对象,给它添加3点伤害,并给它火焰效果)。
  3. 我们为剑添加一个蓝宝石。(我们包装 包含了一个红宝石的Sword对象,给它添加2点伤害,并给它冰冻效果。)

用代码来体现应该就是这样的:

代码语言:txt
AI代码解释
复制
Weapon sword = new Sword();             // 创建一把剑
sword = new RedDiamond(sword);          // 给剑添加 红宝石
sword = new BlueDiamond(sword);         // 给剑添加 蓝宝石

从给剑添加红宝石那句代码,我们发现第一件事:宝石应该保存一个对剑的引用。然后我们就可以在宝石类的内部来为sword添加行为或状态。

从给剑添加蓝宝石那句代码,我们发现第二件事:添加了红宝石的剑(仅从代码看它属于是宝石),仍然是剑,所以我们得出:宝石应该和武器是同一个类型(Weapon基类)的,不然这里将无法再次传递。

这个过程用图来体现就是这样的:

如果我们将整个过程用UML来表示,就构成了下面这幅图:

从图中我们可以看到,通过宝石的扩展,我们可以为剑提供新的能力:额外的伤害加成,以及额外的武器特效(抱歉我不能显示一个华丽的魔法效果,只能在黑底白字的屏幕上输出一句:Addtional Effect: Fire !)。

在Damage() 和 GetDescription()中,我们先调用基类的相应方法,然后为Damage()添加来自宝石的额外的伤害(状态): iceDamage,以及来自宝石的额外效果(行为):FrozenEffect()。

Decorator 设计模式

上面这幅图,就构成了GOF的Decorator设计模式,我们现在看一下它的官方定义:动态地为对象添加额外的职能。Decorator模式为通过继承来为类扩展功能这种方式提供了另一种灵活的选择。

代码实现与测试

简单起见,我们只实现一种武器:Sword,两种宝石:蓝宝石 和 红宝石。

代码语言:txt
AI代码解释
复制
using System;
using System.Collections.Generic;
using System.Text;

namespace Decorator {

    // 定义Weapon基类
    public abstract class Weapon {

       protected string description;           // 武器的描述(攻击效果)

       public virtual string GetDescription() {
           return description;
       }

       public abstract int Damage();           // 武器的伤害
    }

    // 定义剑
    public class Sword : Weapon {
       public Sword() {
           description = "One-Hand light Sword";
       }
       public override int Damage() {
           return 15;
       }
    }

    // 定义宝石基类
    public abstract class Diamond : Weapon {
       protected Weapon weapon;        // 保存对武器的引用
    }

    // 定义蓝宝石
    public class BlueDiamond : Diamond {
       private int iceDamage = 2;          // 蓝宝石的额外伤害

       public BlueDiamond(Weapon weapon) {
           this.weapon = weapon;        // 保存引用
       }

       public string IceEffect(){
           return "\nAddtional Effect: Frozen !";
       }

       public override int Damage() {
           return weapon.Damage() + iceDamage; // 攻击加成
       }

       public override string GetDescription() {
           return weapon.GetDescription() + IceEffect();  // 加入特殊攻击效果
       }
    }
    
    // 定义红宝石
    public class RedDiamond : Diamond {
       private int fireDamage = 3;         // 蓝宝石的额外伤害

       public RedDiamond(Weapon weapon) {
           this.weapon = weapon;        // 保存引用
       }

       public string FireEffect() {
           return "\nAddtional Effect: Fire !";
       }

       public override int Damage() {
           return weapon.Damage() + fireDamage;    // 攻击加成
       }

       public override string GetDescription() {
           return weapon.GetDescription() + FireEffect(); // 加入特殊攻击效果
       }
    }

    class Program {
       static void Main(string[] args) {
           Weapon sword = new Sword();     // 创建一把新剑
           // 打印其描述(攻击效果) 和 伤害
           Console.WriteLine(sword.GetDescription() + "\nDamage:" + sword.Damage());
           Console.WriteLine();

           sword = new BlueDiamond(sword); // 给剑添加一颗蓝宝石
           Console.WriteLine(sword.GetDescription() + "\nDamage:" + word.Damage());
           Console.WriteLine();

           sword = new RedDiamond(sword);  // 给剑添加一颗红宝石
           Console.WriteLine(sword.GetDescription() + "\nDamage:" + sword.Damage());
           Console.WriteLine();
       }
    }
}

输出为:

代码语言:txt
AI代码解释
复制
One-Hand light Sword
Damage:15

One-Hand light Sword
Addtional Effect: Frozen !
Damage:17

One-Hand light Sword
Addtional Effect: Frozen !
Addtional Effect: Fire !
Damage:20

总结

本文中,我们通过一个常见的给武器(对象)添加宝石(额外的状态和行为)的例子,讨论了Decorator设计模式的实现过程。

我们首先提出了要解决的问题:给武器添加宝石,以使它有额外的攻击(伤害)加成和特殊的攻击效果。然后提出了使用继承会遇到的问题:大量的类以及只能通过实例化子类的方式获取行为。随后我们使用复合(Composition)的方式来解决,又遇到新的问题:程序不易维护,每次添加新的宝石或者添加新的物品孔,都需要修改代码。最后,我们使用Decorator模式巧妙地解决了这个问题。

感谢阅读,希望这篇文章能给你带来帮助!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2007-12-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
1.opengl绘制三角形
下面,你会看到一个图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。
诺谦
2020/09/27
1.3K0
1.opengl绘制三角形
现代OpenGL(一):我的第一个OpenGL程序
OpenGL是一种应用程序编程接口(Application Programming Interface,API)它是一种可以对图形硬件设备特征进行访问的软件库。 在OpenGL 3.0以前的版本或者使用兼容模式的OpenGL环境,OpenGL包含一个固定管线(fixed-function pipeline),它可以在不使用着色器的环境下处理几何与像素数据。我们看到的glBegin()、glRectf()以及glEnd()这些函数都是以前固定管线模式中所使用的API函数。 从3.1版本开始,固定管线从核心模式中去除,因此我们必须使用着色器来完成工作。现代OpenGL渲染管线严重依赖着色器来处理传入的数据,我们一般会使用GLSL(OpenGL Shading Language)编写着色器程序,GLSL语法类似于C语言,GLSL编译以后运行在GPU端。
卡尔曼和玻尔兹曼谁曼
2019/01/22
2.4K0
现代OpenGL(一):我的第一个OpenGL程序
OpenGL现代编程第二课——第一个多边形
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
用户5908113
2020/01/02
7820
GPU的工作原理
在GPU出现以前,显卡和CPU的关系有点像“主仆”,简单地说这时的显卡就是画笔,根据各种有CPU发出的指令和数据进行着色,材质的填充、渲染、输出等。 较早的娱乐用的3D显卡又称“3D加速卡”,由于大部分坐标处理的工作及光影特效需要由CPU亲自处理,占用了CPU太多的运算时间,从而造成整体画面不能非常流畅地表现出来。 例如,渲染一个复杂的三维场景,需要在一秒内处理几千万个三角形顶点和光栅化几十亿的像素。早期的3D游戏,显卡只是为屏幕上显示像素提供一个缓存,所有的图形处理都是由CPU单独完成。图形渲染适合并行处
刘盼
2018/04/08
3.9K2
GPU的工作原理
【WebGL】初探WebGL,我了解到这些
WebGL基于OpenGL ES(嵌入式系统) 一种广泛用于在各种平台上渲染2D和3D图形的标准。它允许开发人员使用JavaScript与用户设备的GPU(图形处理单元)交互,实现硬件加速渲染。
且陶陶
2023/10/16
5150
【WebGL】初探WebGL,我了解到这些
深入GPU硬件架构及运行机制
对于大多数图形渲染开发者,GPU是既熟悉又陌生的部件,熟悉的是每天都需要跟它打交道,陌生的是GPU就如一个黑盒,不知道其内部硬件架构,更无从谈及其运行机制。
数字芯片社区
2021/04/19
5K1
深入GPU硬件架构及运行机制
快速入门 WebGL
WebGL 是 Web 3D 渲染引擎的基础,它作为非常底层的 API,学习上手难度非常大,这是因为 WebGL 要求的背景知识比较多。而网上的教程一般没有过多介绍直接就介绍 API 开始渲染了,容易让人云里雾里,很容易被劝退,就算学到了 API 使用,也是只懂表面知识,没有了解背后的原理,很容易就忘记了。
羽月
2022/11/11
3K0
快速入门 WebGL
一起来玩玩WebGL
上一篇文章说到我从客户端转前端的历程,短短一年的时间就打开了前端世界的大门,简直就是有无穷多的东西可玩,以前酷爱Java的我终于见识到什么都可以写的JavaScript的厉害了,不仅仅可以写Web,客户端,后端,系统应用,还可以在神经网络、物联网,甚至嵌入式都可以,简直就是一个万能的语言,可以说能编程的地方理论上都可以用JS来写!
ConardLi
2020/06/29
1.2K0
一起来玩玩WebGL
【iOS】OpenGL入门资料整理
在应用程序调用任何OpenGL执行之前,首先需要创建一个OpenGL的上下文。这个上下文是一个非常庞大的状态机,保存了OpenGL中的各种状态,这也是OpenGL指令的基础。
MapleYe
2020/03/30
1.6K0
【iOS】OpenGL入门资料整理
Rust 与 GPU 编程的现状与前景探究
话说,程序员三大浪漫,操作系统、编译器和图形处理。Rust 语言已经攻陷了其中两大浪漫,操作系统和编译器,那么图形处理呢?Rust 语言还能“浪”起来吗?
张汉东
2023/11/20
4.1K0
Rust 与 GPU 编程的现状与前景探究
OpenGL & Metal Shader 编程系列来了,要不要上车?
前面发了一些关于 Shader 编程的文章,有读者反馈太碎片化了,希望这里能整理出来一个系列,方便系统的学习一下 Shader 编程。
字节流动
2023/09/04
1.5K0
OpenGL & Metal Shader 编程系列来了,要不要上车?
谷歌正式发布WebGPU!90多位贡献者研发6年,浏览器终于可以利用底层硬件了
整理 | 褚杏娟、核子可乐 经过六年的开发,当地时间 4 月 6 日,谷歌 Chrome 团队正式发布 WebGPU,用于在网络上进行高性能 3D 图形与数据并行计算。WebGPU 现已在 Beta 测试阶段的 Chrome 113 中默认启用。 WebGPU 是一种新型 Web 图形 API,具有显著减少同等图形规模下 JavaScript 工作量、将机器学习模型的推理效率提升 3 倍以上等优势。之所以能实现这样的飞跃,要归功于其令 WebGL 无法实现的灵活 GPU 编程和高级功能访问能力。 据悉,W
深度学习与Python
2023/04/10
1.3K0
谷歌正式发布WebGPU!90多位贡献者研发6年,浏览器终于可以利用底层硬件了
OpenGL自制游戏引擎-HelloTriangle
Pipeline: 开始绘制图形之前,我们必须先给OpenGL输入一些顶点数据,OpenGL不是简单地把所有的3D坐标变换为屏幕上的2D像素;OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。所有在所谓的标准化设备坐标(Normalized Device Coordinates)范围内的坐标才会最终呈现在屏幕上. 定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。 通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。 顶点缓冲对象是我们在[OpenGL]教程中第一个出现的OpenGL对象。就像OpenGL中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:
祝你万事顺利
2019/07/02
1.6K0
一看就懂的 OpenGL 基础概念丨音视频基础
这个公众号会路线图式的遍历分享音视频技术:音视频基础 → 音视频工具 → 音视频工程示例 → 音视频工业实战。关注一下成本不高,错过干货损失不小 ↓↓↓
关键帧
2022/11/29
2.8K0
一看就懂的 OpenGL 基础概念丨音视频基础
【C++】OpenGL:着色器基础与GLFW创建三角形示例
另外,在图形渲染中,要记住2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。
DevFrank
2024/07/24
4310
【C++】OpenGL:着色器基础与GLFW创建三角形示例
【前端可视化】 OpenGL / WebGL 入门和实践
OpenGL 是一套规范,不是接口,学习这套规范,就可以在支持 OpenGL 的机器上正常使用这些规范,在显示器上看到绘制的结果。
ConardLi
2019/10/24
4.8K1
从关键概念开始,万字带你轻松入门 WebGL
只要理解了 WebGL 背后的概念,学习 WebGL 并没有那么难。很多 WebGL 入门文章并没有介绍这些重要的概念,直接使用 WebGL 复杂的 API 开始渲染图形,很轻松就把入坑文变成了劝退文。这篇文章将会着重讲解这些概念,并一步步探究 WebGL 是如何渲染图片到屏幕的,理解这些重要的概念,将会大大降低学习曲线。
羽月
2022/10/08
2.2K0
从关键概念开始,万字带你轻松入门 WebGL
音视频技术基础(四)-- OpenGL
既然是学习音视频技术,那必然少不了渲染这个环节,OpenGL就是进行图形渲染的一个重要角色。
黑眼圈云豆
2020/07/10
2.1K0
OpenGL 系列---基础绘制流程
OpenGL 是一种应用程序编程接口,它是一种可以对图形硬件设备特性进行访问的软件库。
音视频开发进阶
2019/07/26
2K0
跨平台渲染引擎之路:拨云见日
最近在工作中越来越多地接触到一些3D以及相比常见特性更酷炫的效果,因此萌发了想要自己从0开始打造一个渲染引擎的念头,一方面是为了更好地实现公司业务的需求,另一方面则是可以学到整个渲染流水线上的方方面面。
音视频开发进阶
2019/07/30
1.5K0
相关推荐
1.opengl绘制三角形
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档