RimWorld模组开发实战:地质学框架核心原理与自定义资源生成 大家好我是专注于技术实战分享的博主。今天我们来深入探讨一个在《环世界》RimWorld模组开发中非常核心且有趣的主题——地质学框架。如果你正在为你的模组添加复杂的地形、资源生成或地质事件系统或者你只是对游戏底层的地质模拟机制感到好奇那么这篇文章正是为你准备的。我们将从一个基础的演示项目出发逐步拆解地质学框架的核心概念、实现原理、代码结构并最终构建一个可运行的示例。虽然标题提到“非最终品质”但这恰恰是我们学习和迭代的起点。通过本文你将掌握如何利用框架构建自己的地质系统理解其扩展点并学会规避开发中的常见陷阱。1. 地质学框架概念、作用与在模组开发中的位置在深入代码之前我们首先要厘清几个关键概念什么是《环世界》的地质学框架它解决了什么问题以及它在整个模组生态中扮演什么角色1.1 什么是地质学框架简单来说地质学框架是《环世界》游戏引擎提供的一套用于定义和管理游戏世界中地质特征的标准化接口和基类系统。它不是一个独立的、可见的模组而是一系列Def定义、GenStep生成步骤和WorldObject世界对象的集合为模组开发者提供了创建自定义岩石、矿脉、特殊地形如地热喷口、陨石坑的“脚手架”。你可以把它想象成乐高积木的基础板。游戏本体已经提供了一些基础的地质“积木块”如花岗岩、片岩、铀矿脉。地质学框架则定义了这些积木块的形状接口和如何将它们拼接到基础板世界生成流程上的规则。模组开发者要做的就是利用这套规则创造出新的、更复杂的积木块和拼接图案。1.2 核心解决的问题标准化生成确保不同模组添加的地质元素能够和谐地集成到同一个世界中不会互相冲突或覆盖。框架规定了资源生成的优先级、密度计算和分布算法。性能与内存管理通过抽象层和缓存机制高效地存储和读取庞大的地质数据每个地图格子的岩石类型、资源含量等。可扩展性允许开发者以声明式通过XML定义和程序式通过C#代码两种方式添加新内容而无需修改游戏核心代码。与游戏系统交互将地质资源与开采、建筑、研究等游戏玩法系统无缝连接起来。例如定义一种新矿石后游戏会自动处理它的开采工作量、冶炼产出和交易价值。1.3 常见应用场景添加新矿石与资源这是最普遍的应用例如添加“钛合金矿”、“水晶矿”等。创建特殊地质构造如放射性废料场、古代机械废墟层、魔法水晶簇等它们不仅提供资源还可能影响环境辐射、美观度或触发事件。修改整个生物群系的地质构成让某个森林永远不生成大理石或者让冰原拥有丰富的煤炭层。实现动态地质事件模拟地震形成新矿脉、火山喷发覆盖特定岩石等。理解这些你就明白了我们不是在创造一个孤立的“地质模组”而是在扩展游戏世界的基础规则。接下来我们开始搭建开发环境。2. 环境准备与项目结构工欲善其事必先利其器。开发《环世界》模组尤其是涉及框架扩展需要一个清晰、规范的项目环境。2.1 所需工具与版本游戏本体《环世界》最新稳定版本文基于1.4版本原理相通。确保你拥有正版游戏因为模组开发需要引用游戏程序集。集成开发环境Visual Studio 2022或JetBrains Rider。它们对C#和.NET的支持最为完善。社区版即可。.NET FrameworkRimWorld 1.4 基于.NET Framework 4.7.2。请确保你的开发环境已安装相应版本的开发包。模组开发基础你需要对C#有中级了解并熟悉《环世界》基本的模组结构About文件夹、Defs文件夹等。如果还不熟悉建议先完成一个简单的物品添加模组。2.2 创建模组解决方案新建类库项目在IDE中创建一个新的“类库(.NET Framework)”项目命名为MyGeologyFrameworkDemo目标框架选择.NET Framework 4.7.2。引用游戏程序集在项目的“引用”中添加以下来自游戏安装目录的程序集通常在RimWorld\RimWorldWin64_Data\Managed下Assembly-CSharp.dll(核心游戏逻辑)UnityEngine.CoreModule.dllUnityEngine.IMGUIModule.dll可能还需要Verse.dll(RimWorld的底层框架)。请根据你的游戏版本确认。设置生成后事件可选但推荐为了自动将编译好的DLL复制到模组文件夹可以配置项目的生成后事件命令行例如xcopy /Y $(TargetPath) D:\Games\RimWorld\Mods\MyGeologyFrameworkDemo\Assemblies\将路径替换为你自己的模组目录。2.3 模组文件夹结构在你的RimWorld Mods目录下创建如下结构的文件夹MyGeologyFrameworkDemo/ ├── About/ │ ├── About.xml │ └── Preview.png ├── Assemblies/ │ └── (这里将存放你编译的 MyGeologyFrameworkDemo.dll) ├── Defs/ │ ├── ThingDefs/ │ │ └── (我们的地质相关定义文件) │ └── (其他Def文件夹) ├── Patches/ │ └── (可选用于Harmony补丁) ├── Textures/ │ └── (岩石和矿石的纹理) └── Languages/ └── (本地化文件)这个结构是标准模组格式框架相关的代码C#将编译成DLL放入Assemblies而数据定义XML则放在Defs下。两者通过特定的命名规则和标签关联。3. 核心原理与框架类拆解地质学框架的核心逻辑主要由几个关键的类和接口驱动。理解它们的关系是进行高级定制的基础。3.1 核心类图概念性以下为文字描述帮助理解层级关系ThingDef万物定义的基础。岩石、矿石、 chunks石块都是“物”。地质框架扩展了ThingDef通过category类别和comps组件来标识其地质属性。BuildableDef可建造/可放置物的定义。许多地质资源继承于此。GenStep_Scatterer及其子类世界/地图生成步骤。GenStep_ScatterLumpsMineable是负责散布“可开采矿脉”的关键生成步骤。它决定了矿脉在地图中的分布、大小和形状。WorldObject与WorldGenStep对于全球性的地质特征如大陆板块、巨型矿脉可能需要用到世界层面的对象和生成步骤。CompProperties_Mineable这是一个ThingComp事物组件的属性定义。任何具有此组件的ThingDef都会被游戏视为“可开采”的矿脉。它定义了开采后掉落什么、掉落数量、开采工作量等。RockNoises与GenStep_RocksFromGrid负责根据噪声图生成基础岩石层。这是地图地质的“画布”自定义岩石需要在这里注册。3.2 数据驱动XML Defs 的作用《环世界》高度依赖XML进行数据定义。对于地质内容以下几个Def文件至关重要ThingDefs岩石/矿石定义定义一种新岩石或矿石的所有静态属性。TerrainDefs地形定义虽然不完全是地质但紧密相关例如“粗糙岩石”地形。BiomeDefs生物群系定义在这里指定该群系允许生成的岩石类型列表(allowedRockTypes)和基础岩石(baseRock)。GenStepDefs生成步骤定义可以自定义新的生成步骤或修改现有步骤的参数。框架的魅力在于大部分基础功能如生成一个随机大小的矿脉只需通过精心配置XML即可实现无需编写C#代码。只有当你需要非常特殊的生成逻辑例如矿脉必须沿着河流分布时才需要动用C#。4. 完整实战创建一个“辉晶矿”系统现在让我们动手创建一个完整的“辉晶矿”系统。它将包括一种新的岩石、一种在该岩石中生成的稀有矿石、以及对应的开采产物。4.1 第一步定义新岩石 - “辉长岩”首先我们需要一种新的基础岩石来承载我们的矿石。在Defs/ThingDefs/下创建文件Stones_MyGeology.xml。?xml version1.0 encodingutf-8? Defs !-- 定义一种新的岩石类型 -- ThingDef NameRock_Gabbro ParentNameRockBase defNameRockGabbro/defName label辉长岩/label description一种深色的火成岩质地坚硬是辉晶矿的主要母岩。/description graphicData texPathThings/Terrain/Rock/Gabbro/texPath graphicClassGraphic_Single/graphicClass /graphicData statBases Beauty-5/Beauty Flammability0/Flammability WorkToBuild800/WorkToBuild WorkToMake200/WorkToMake /statBases pathCost14/pathCost terrainAffordanceNeededHeavy/terrainAffordanceNeeded selectabletrue/selectable constructionSkillPrerequisite4/constructionSkillPrerequisite resourceReadoutPriorityRock/resourceReadoutPriority !-- 关键将其添加到岩石类别使其能被地质系统识别 -- categoryRock/category thingCategories liStoneBlocks/li /thingCategories designationCategoryStructure/designationCategory comps !-- 使其成为可开采的岩石开采后掉落石块 -- li ClassCompProperties_Mineable mineableItems li thingRockGabbroChunk/thing count18/count /li /mineableItems workToMine8000/workToMine yield0.8/yield soundOnHitPawn_Melee_Punch_Miss/soundOnHit /li /comps /ThingDef !-- 定义开采辉长岩后获得的石块 -- ThingDef NameRockGabbroChunk ParentNameStoneChunkBase defNameRockGabbroChunk/defName label辉长岩石块/label graphicData texPathThings/Item/Resource/StoneChunk/Gabbro/texPath graphicClassGraphic_Single/graphicClass /graphicData statBases MarketValue1.2/MarketValue Mass12/Mass /statBases /ThingDef /Defs代码解释ParentNameRockBase继承游戏内所有岩石的通用属性避免重复定义。categoryRock/category这是关键告诉游戏这是一种地质岩石。CompProperties_Mineable定义开采行为。开采RockGabbro会掉落18个RockGabbroChunk需要8000工作量的开采工作。4.2 第二步定义稀有矿石 - “辉晶矿脉”现在定义一种只能在辉长岩中生成的稀有矿石。在同一文件或新文件中添加!-- 定义辉晶矿脉一种可开采的资源 -- ThingDef NameOre_LuminousCrystal ParentNameMineableThingBase defNameLuminousCrystalOre/defName label辉晶矿脉/label description一种蕴含微弱能量的发光晶体嵌在坚硬的辉长岩中。是高级电子元件的关键材料。/description graphicData texPathThings/Building/Resource/LuminousCrystalOre/texPath graphicClassGraphic_Single/graphicClass drawSize(1.5, 1.5)/drawSize !-- 可以比普通岩石大一点 -- /graphicData statBases Beauty-2/Beauty Flammability0/Flammability WorkToBuild1200/WorkToBuild MaxHitPoints280/MaxHitPoints MarketValue85/MarketValue /statBases pathCost20/pathCost !-- 更难通行 -- terrainAffordanceNeededHeavy/terrainAffordanceNeeded selectabletrue/selectable resourceReadoutPriorityOre/resourceReadoutPriority categoryItem/category thingCategories liOres/li !-- 归入矿石类别 -- /thingCategories designationCategoryStructure/designationCategory comps !-- 定义开采此矿脉的产出 -- li ClassCompProperties_Mineable mineableItems li thingRawLuminousCrystal/thing !-- 开采后获得粗晶体 -- count25/count !-- 每次开采掉落数量 -- /li /mineableItems workToMine12000/workToMine !-- 开采需要更多工作量 -- yield1.0/yield soundOnHitPawn_Melee_Punch_Miss/soundOnHit /li !-- 可选添加一个发光组件使其在黑暗中可见 -- li ClassCompProperties_Glower glowColor(0.2, 0.8, 1.0)/glowColor !-- 淡蓝色光 -- glowRadius4.5/glowRadius overlightRadius3.5/overlightRadius /li /comps /ThingDef !-- 定义开采后获得的粗晶体资源 -- ThingDef NameResource_RawLuminousCrystal ParentNameResourceBase defNameRawLuminousCrystal/defName label粗辉晶体/label description从矿脉中开采出的原始晶体需要进一步精炼才能使用。/description graphicData texPathThings/Item/Resource/RawLuminousCrystal/texPath graphicClassGraphic_Single/graphicClass /graphicData statBases MarketValue12/MarketValue Mass0.8/Mass Flammability0.3/Flammability /statBases stackLimit75/stackLimit thingCategories liResourcesRaw/li /thingCategories /ThingDef /Defs关键点ParentNameMineableThingBase继承了可开采物的基础属性。thingCategories中的liOres/li将其归类为矿石使其出现在矿石资源栏并可能被特定的生成步骤处理。CompProperties_Glower这是一个非地质框架的组件但展示了如何轻松地为地质物体添加额外功能使其更具特色。4.3 第三步将新岩石添加到生物群系光定义了岩石还不够我们需要告诉游戏在哪些地方生成它。这通过修改或创建BiomeDef来实现。通常我们使用补丁Patch来修改原版群系而不是直接覆盖原文件。在Defs/下创建Patches/文件夹如果还没有然后创建BiomePatches.xml?xml version1.0 encodingutf-8? Defs !-- 为温带森林生物群系添加辉长岩作为可生成岩石 -- BiomeDef defNameTemperateForest/defName allowedRockTypes !-- 列出所有允许的岩石包括我们新增的 -- liGranite/li liLimestone/li liMarble/li liSandstone/li liSlate/li liRockGabbro/li !-- 这是我们新增的 -- /allowedRockTypes /BiomeDef !-- 也可以为其他群系添加例如寒带森林 -- BiomeDef defNameBorealForest/defName allowedRockTypes liGranite/li liLimestone/li liMarble/li liRockGabbro/li !-- 在这里也添加 -- /allowedRockTypes !-- 甚至可以将其设为基础岩石 -- baseRockRockGabbro/baseRock /BiomeDef /Defs注意使用BiomeDef并指定defName会直接修改游戏中已存在的同名生物群系定义。这是一种安全的打补丁方式。4.4 第四步控制矿脉生成XML方式我们希望“辉晶矿脉”只生成在“辉长岩”中且生成密度较低。这可以通过修改原版的GenStepDef或创建自己的GenStepDef来实现。更简单的方式是利用原版已有的ScatterLumpsMineable生成步骤并通过ThingDef的属性来控制。实际上原版矿脉的生成主要由GenStep_ScatterLumpsMineable控制它会扫描所有category为Item且具有CompProperties_Mineable组件的ThingDef。生成频率和位置受到BiomeDef中的mineableResources列表和岩石类型的双重影响。一个更直接的方法是在我们的矿石定义中通过一个自定义的CompProperties来限制其生成母岩。但这需要C#代码。作为演示我们先采用一个纯XML的近似方案通过控制其marketValue、workToMine和pathCost使其变得“稀有”并依赖游戏默认的、基于价值的分布算法。然而要实现“仅生于辉长岩”我们必须进入C#编码环节。4.5 第五步C#编码 - 自定义矿脉生成器为了精确控制我们需要创建一个自定义的GenStep。在Visual Studio项目中新建一个C#类文件GenStep_ScatterLuminousCrystal.cs。// 文件路径MyGeologyFrameworkDemo/GenStep_ScatterLuminousCrystal.cs using System.Collections.Generic; using RimWorld; using Verse; namespace MyGeologyFrameworkDemo { // 继承自 GenStep_Scatterer这是所有散布类生成步骤的基类 public class GenStep_ScatterLuminousCrystal : GenStep_Scatterer { // 矿脉的Def public ThingDef oreDef ThingDef.Named(LuminousCrystalOre); // 允许生成的岩石Def public ThingDef allowedRockDef ThingDef.Named(RockGabbro); // 每单位面积万格尝试生成的数量。调整这个值控制稀有度。 public override int SeedPart 123456789; // 一个固定的种子部分确保生成可重复 // 控制生成频率 public override void Generate(Map map, GenStepParams parms) { // 计算要生成多少个矿脉簇。这里示例地图每10万格尝试生成1簇。 int num MapSizeToClusterCount(map); for (int i 0; i num; i) { // 尝试找到一个有效位置 if (TryFindScatterCell(map, out IntVec3 cell)) { // 在找到的位置生成矿脉 ScatterAt(cell, map); } } } // 计算集群数量 private int MapSizeToClusterCount(Map map) { int tileSize map.Size.x * map.Size.z; // 例如每10万格地图尝试生成1个集群 return tileSize / 100000; } // 重写位置验证逻辑必须位于指定的岩石上 protected override bool CanScatterAt(IntVec3 c, Map map) { // 1. 基础检查必须在可行走、未覆盖的区域 if (!base.CanScatterAt(c, map)) { return false; } // 2. 核心检查该格子的底层地形必须是我们的“辉长岩” // 注意地图格子的“东西”和“地形”是分开的。这里检查的是作为“建筑”或“物品”的岩石。 // 更准确的做法是检查该格子的“自然”岩石建筑。 Building edifice c.GetEdifice(map); if (edifice null || edifice.def ! allowedRockDef) { return false; } // 3. 可选检查周围一定范围内不能有同类矿脉避免扎堆 CellRect rect CellRect.CenteredOn(c, 10); foreach (IntVec3 cell in rect) { if (cell.InBounds(map)) { Building building cell.GetEdifice(map); if (building ! null building.def oreDef) { return false; } } } return true; } // 重写生成逻辑创建矿脉建筑 protected override void ScatterAt(IntVec3 c, Map map, int stackCount 1) { // 在目标位置生成我们的矿脉 GenSpawn.Spawn(oreDef, c, map); // 可以在这里添加更多逻辑例如围绕中心生成一个小矿脉群 int clusterRadius Rand.RangeInclusive(0, 2); // 随机集群半径0-2 if (clusterRadius 0) { foreach (IntVec3 cell in GenRadial.RadialCellsAround(c, clusterRadius, true)) { if (cell.InBounds(map) Rand.Value 0.6f CanScatterAt(cell, map)) // 60%几率在周围格生成 { GenSpawn.Spawn(oreDef, cell, map); } } } } } }4.6 第六步注册自定义生成步骤我们需要让游戏在生成地图时执行我们的GenStep。这通常通过一个GenStepDef定义来完成。在Defs/GenStepDefs/下创建GenSteps_MyGeology.xml。?xml version1.0 encodingutf-8? Defs GenStepDef defNameScatterLuminousCrystalOre/defName genStep ClassMyGeologyFrameworkDemo.GenStep_ScatterLuminousCrystal/ order200/order !-- 生成顺序在主要岩石和普通矿脉生成之后 -- /GenStepDef /Defs然后我们需要将这个GenStep注入到具体的地图生成器中。例如修改温带森林的地图生成器。创建补丁文件Defs/MapGeneratorDefs_Patch.xml?xml version1.0 encodingutf-8? Defs MapGeneratorDef defNameBase_Player_Faction/defName !-- 这是玩家派系起始地图的生成器 -- genSteps !-- 在岩石生成之后插入我们的辉晶矿生成步骤 -- li defNameScatterLuminousCrystalOre/defName /li /genSteps /MapGeneratorDef /Defs4.7 第七步编译、加载与测试编译项目在Visual Studio中生成解决方案。如果配置了生成后事件DLL会自动复制到模组文件夹。准备纹理将制作好的Gabbro.png、LuminousCrystalOre.png等纹理文件放入Textures/下对应的子路径与XML中texPath一致。激活模组启动RimWorld在模组列表中激活MyGeologyFrameworkDemo。创建新世界并定居选择一个我们修改过的生物群系如温带森林。验证结果使用开发模式 (Ctrl F12打开需在选项中启用)查看地图图块信息确认有RockGabbro辉长岩。在地图上寻找发着淡蓝色光的LuminousCrystalOre辉晶矿脉。尝试开采它们检查是否掉落RawLuminousCrystal粗辉晶体。如果一切顺利你就成功通过地质学框架添加了一个全新的、带有自定义生成逻辑的地质系统5. 常见问题、调试与排查思路开发过程中难免遇到问题。以下是一些常见坑点及其解决方案。问题现象可能原因排查思路与解决方案模组加载失败红字错误1. DLL编译错误或版本不匹配。2. XML语法错误标签未闭合属性错误。3. 引用了不存在的Def如ParentName错误。1. 检查游戏日志 (Player.log文件)。错误信息通常会指向具体文件和行号。2. 使用XML验证工具或仔细核对标签。3. 确保所有自定义的defName在引用时拼写完全一致区分大小写。新岩石/矿石在游戏中不显示1. 纹理路径错误或纹理文件缺失。2.ThingDef的category或thingCategories设置错误导致不被系统识别。3. 生物群系allowedRockTypes未添加新岩石。1. 确认纹理文件格式为.png且路径与texPath完全匹配。2. 岩石必须设category为Rock。矿石通常设为category为Item并加入Ores类别。3. 使用开发模式“查看调试信息”功能检查地图图块的Terrain和Thing。矿脉不在指定岩石上生成1. 自定义GenStep的CanScatterAt逻辑有误。2. 生成步骤的顺序 (order) 不对可能在岩石生成之前就执行了。3. 地图上根本没有生成你指定的岩石。1. 在CanScatterAt方法中添加Log.Message输出调试信息查看验证失败的原因。2. 确保自定义GenStep的order大于岩石生成步骤如RocksFromGrid的 order 约为 100。3. 检查生物群系定义确保allowedRockTypes列表正确包含你的岩石。开采后不掉落物品或掉落错误1.CompProperties_Mineable中的thing字段指向错误的defName。2. 掉落物的ThingDef本身有错误无法加载。1. 仔细检查mineableItems下的thing标签值。2. 确保掉落物的ThingDef已正确定义且无错误。游戏性能下降卡顿1. 自定义GenStep的算法效率低下尤其是在大地图或循环中。2. 矿脉生成数量过多。3. 添加了复杂的图形或计算组件。1. 优化CanScatterAt和ScatterAt中的循环和检查避免全图扫描。2. 调整MapSizeToClusterCount中的除数减少生成数量。3. 审查CompProperties移除不必要的实时计算。调试技巧善用开发模式Ctrl F12打开开发工具使用“调试操作”菜单中的“生成物品”、“地形工具”等来手动放置你的物品进行测试。查看日志Player.log文件位于C:\Users\[用户名]\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios是排查错误的第一手资料。分步测试先确保岩石能生成再测试矿石生成最后测试开采逻辑。不要一次性完成所有功能。6. 进阶最佳实践与工程建议当你掌握了基础以下建议能帮助你打造更专业、更稳定、更易与其他模组兼容的地质内容。6.1 设计原则保持风格一致新岩石/矿石的纹理风格、命名规则defName使用驼峰式label使用中文、属性值市场价值、重量、开采工作量应尽量贴近原版避免破坏游戏平衡和沉浸感。平衡性考量稀有资源的获取难度生成概率、开采工作量应与其效用制作物品的强度相匹配。过度强大的资源会破坏游戏进程。提供多种获取途径除了地图生成考虑通过交易、任务奖励、事件陨石坠落等方式让玩家获得稀有地质资源增加游戏动态性。6.2 代码与架构使用Harmony进行非侵入式修改如果你需要修改原版GenStep的行为例如让所有矿脉都有小概率生成在你的新矿石强烈建议使用HarmonyLib打补丁而不是直接复制并修改原版代码。这能极大提高模组兼容性。模块化设计将岩石定义、矿石定义、生成逻辑、补丁配置分别放在不同的XML文件和C#类中。例如Defs/ThingDefs/Stones_MyMod.xmlDefs/ThingDefs/Ores_MyMod.xmlDefs/GenStepDefs/MyMod_GenSteps.xmlDefs/BiomeDefs/Patches/MyMod_BiomePatches.xmlSource/GenSteps/GenStep_MyCustomOre.cs提供配置选项考虑使用ModSettings为玩家提供选项例如调整生成频率、开关某些特性等。这能增加模组的友好度。6.3 兼容性与扩展性使用ModExtension对于复杂的自定义数据不要滥用comps。可以创建自定义的DefModExtension来存储和处理与你的地质系统相关的独特数据这样更干净且不影响原版组件系统。考虑其他模组你的新岩石可能会被其他建筑模组用作材料。确保其statBases如强度、美观度设置合理。可以使用补丁动态为其他模组的配方添加你的材料作为可选选项。清晰的文档与注释在XML和C#代码中添加详细的注释说明每个关键参数的作用和修改影响。这对于你未来维护或他人学习你的模组至关重要。6.4 性能优化避免昂贵的每帧计算不要在Thing或Comp的Tick()方法中进行复杂运算除非绝对必要。地质物体通常是静态的。缓存计算结果例如如果你的自定义GenStep需要频繁计算某个值考虑在Generate开始时计算并缓存它。纹理优化确保纹理尺寸是2的幂次方如256x256并使用合适的压缩格式以节省内存和GPU资源。从定义一个简单的岩石到实现具有特定生成逻辑的稀有矿脉我们完成了一个完整的地质学框架应用演示。这个过程涵盖了从数据定义、生成步骤定制、C#逻辑编码到最终测试的完整闭环。记住框架的意义在于提供规范和扩展点而真正的创造力在于你如何利用它们去构建独特而有趣的世界元素。在动手开发自己的复杂地质系统前建议多分析原版和其他优秀模组的实现方式理解其设计模式。