ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Minecraft Create模组服务器卡顿优化:自制模组降低方块实体tick频率与深暗之域清理

Minecraft Create模组服务器卡顿优化:自制模组降低方块实体tick频率与深暗之域清理 1. 服务器卡顿的根源排查与优化思路拆解1.1 从现象到本质Create模组服务器为什么会卡玩Create模组的服主大概都经历过这个场景服务器刚开的时候TPS稳在20在线七八个人跑流水线也没问题可一旦有人开始大规模铺传送带、机械臂、粉碎轮或者探索到深暗之域那片区域TPS就开始往下掉先是掉到18、16接着红石信号开始延迟活塞推拉变得一顿一顿的最后连玩家走路都出现回弹。很多人第一反应是服务器配置不够于是加内存、换CPU结果发现钱花了该卡还是卡。这里面的核心问题在于Create模组的卡顿绝大多数不是硬件瓶颈而是实体数量和方块实体更新频率的问题。Create的机械元件本质上都是方块实体Block Entity每一个传送带、每一个齿轮箱、每一个机械臂在每一个tick都要执行自己的逻辑运算。当你的流水线规模上去之后单个区块内可能同时存在几百个方块实体在跑逻辑服务器主线程的tick预算被吃满TPS自然就崩了。而深暗之域的问题更特殊。这个生物群系本身会大量生成幽匿方块、幽匿尖啸体和幽匿催发体其中幽匿催发体在检测到附近有生物死亡时会尝试扩散幽匿方块这个扩散逻辑在1.19之后被不少玩家反馈存在性能问题。更关键的是深暗之域往往和洞穴系统重叠区块加载量大再加上Create的机械元件如果延伸到这片区域方块实体更新和幽匿扩散逻辑叠加在一起卡顿就会被放大。所以优化思路不能只盯着加配置而要从三个方向同时下手减少不必要的实体运算、降低方块实体的更新频率、清理高负载区域的冗余方块。下面我会结合我自己在服务器上实际跑过的方案把整个优化过程拆开讲。1.2 优化方案的整体设计为什么选择自制模组区域清理这条路市面上现成的优化模组不少比如各种性能优化类的模组它们确实能解决一部分问题但对于Create模组特有的方块实体密集场景通用优化模组往往力不从心。原因很简单通用优化模组主要针对的是实体Entity和区块加载而Create的负载大头是方块实体Block Entity这两者的处理逻辑完全不同。我最终选择的方案是自制一个轻量级模组核心做两件事第一把Create模组中那些装饰性或低交互频率的实体转换为更轻量的表示形式减少每tick的逻辑运算第二针对深暗之域区域实现一个可控的清理机制把那些因为幽匿扩散而大量生成的冗余方块批量移除。为什么不用现成的世界编辑工具直接清理因为手动清理有两个问题一是效率太低深暗之域动辄几千个方块手动选区删除容易漏二是容易误删玩家建筑。自制模组的好处是可以精确控制清理范围、清理条件还能做成指令触发随时开关不影响正常游戏。这个方案的优势在于针对性强、可控性高、对原版玩法影响小。你不需要改变玩家的建造习惯也不需要禁用Create的任何功能只是在服务端做一层减负。下面我会把整个实现过程拆成几个部分从环境准备到代码实现再到实际调优一步步讲清楚。2. 自制模组的环境搭建与核心细节解析2.1 开发环境准备你需要哪些工具和依赖动手之前先把环境搭好这一步看起来简单但版本对不上后面会浪费大量时间。我用的方案是基于Fabric 加载器原因是Fabric的模组开发门槛相对低构建速度快而且Create模组本身也有Fabric版本兼容性没问题。如果你用的是Forge思路完全一样只是API调用方式不同。具体需要准备的东西JDK 17Minecraft 1.18之后的版本都需要JDK 17别用JDK 8会直接编译报错。IntelliJ IDEA社区版就够用配合Fabric的模板项目可以快速生成工程结构。Fabric Loom 插件在build.gradle里配置负责把模组打包成可加载的jar。Minecraft 开发映射Yarn 或 Mojang Mappings我习惯用Yarn命名更直观。Create模组的开发依赖如果你要直接操作Create的方块实体需要把Create的jar作为modImplementation引入这样才能调用它的类。这里有个坑要注意Create模组的版本必须和服务器上跑的版本完全一致包括小版本号。我有一次用1.18.2的Create 0.5.0d开发服务器上跑的是0.5.0c结果模组加载时报NoSuchMethodError排查了半天才发现是版本差异。所以动手前先确认服务器上的Create版本然后去模组仓库下载对应的开发jar。2.2 核心思路实体转换到底转的是什么标题里说的转换实体具体转的是什么这里需要先理清Create模组里哪些东西是性能杀手。Create的机械元件大致分几类动力源风车、水车、蒸汽引擎、传动元件齿轮、传送带、离合器、执行元件机械臂、活塞、粉碎轮、装饰元件各种外壳、支架。其中真正吃性能的是执行元件和传动元件因为它们每tick都要计算动力传输和动作执行。我的做法是对那些处于空闲状态的机械元件降低它们的tick更新频率。比如一条传送带上没有物品的时候它其实不需要每tick都检测物品位置一个机械臂在没有收到新指令的时候也不需要每tick都重新计算目标。通过给这些元件打上空闲标记让它们在空闲状态下每4tick甚至每8tick才更新一次负载能直接降下来。具体实现上我用了Fabric的ServerTickEvents来监听每个tick然后维护一个低优先级方块实体列表。对于列表里的方块实体只在特定tick间隔执行它们的tick逻辑。这里的关键是不能直接跳过tick否则Create的动力网络会断掉机械会停摆。正确的做法是让它们在跳过的tick里保持状态只跳过那些纯检测类的逻辑。提示修改方块实体的tick逻辑时一定要先备份存档。我测试的时候因为跳过了动力传输的tick导致整个流水线的动力网络崩溃所有机械停摆最后只能回档。2.3 深暗之域清理的逻辑设计怎么清、清什么、清多少深暗之域的清理不能一刀切。幽匿方块本身是游戏内容的一部分全删了玩家没法正常探索。我的策略是只清理扩散生成的幽匿方块保留原始生成的。怎么区分幽匿催发体扩散生成的方块有一个特点它们通常出现在催发体周围一定半径内而且往往成片出现。我的做法是扫描深暗之域区域内的所有幽匿催发体然后检查它们周围的幽匿方块密度。如果某个区域内的幽匿方块数量超过阈值我设的是每区块超过200个就判定为过度扩散对该区域执行清理只保留催发体本身和少量原始方块。清理的触发方式我做了两种一种是指令手动触发服主输入指令后立即执行一次全图扫描清理另一种是定时自动触发每隔一段时间我设的是游戏内每3天自动扫描一次发现过度扩散就清理。自动触发的好处是不用服主一直盯着坏处是如果玩家正在深暗之域建东西可能会被误清理。所以我在自动清理前加了一个检测如果该区域内有玩家在线且距离小于64格就跳过这次清理。清理的具体操作是把多余的幽匿方块替换成深板岩或空气。替换成空气更彻底但会留下空洞影响美观替换成深板岩更自然但会增加方块更新。我实测下来替换成深板岩的性能更好因为空气会导致光照更新反而增加负载。3. 实操过程与核心环节实现3.1 模组工程初始化与依赖配置打开IDEA用Fabric的模板项目生成工程然后修改build.gradle。核心配置如下dependencies { minecraft com.mojang:minecraft:1.18.2 mappings net.fabricmc:yarn:1.18.2build.4:v2 modImplementation net.fabricmc:fabric-loader:0.14.21 modImplementation net.fabricmc.fabric-api:fabric-api:0.76.01.18.2 modImplementation fileTree(dir: libs, include: [*.jar]) // 把Create的jar放进libs目录 }把Create的开发jar放到项目根目录的libs文件夹里然后在fabric.mod.json里声明依赖{ depends: { create: 0.5.0d } }这一步做完先跑一次gradlew build确认能编译通过。如果报找不到Create的类检查jar是不是放对了位置以及build.gradle里的fileTree路径是否正确。3.2 实体转换的核心代码实现实体转换的核心是一个tick事件监听器。我在ServerTickEvents.END_SERVER_TICK里注册了一个处理器每tick检查一次低优先级方块实体列表public class EntityOptimizer implements ServerTickEvents.EndTick { private static final int SKIP_INTERVAL 4; // 每4tick更新一次 private int tickCounter 0; Override public void onEndTick(MinecraftServer server) { tickCounter; if (tickCounter % SKIP_INTERVAL ! 0) { return; // 非更新tick直接跳过 } for (ServerWorld world : server.getWorlds()) { for (BlockEntity be : getLowPriorityBlockEntities(world)) { if (be instanceof KineticBlockEntity) { ((KineticBlockEntity) be).tick(); // 只对动力元件执行tick } } } } }这里的关键是getLowPriorityBlockEntities方法它负责筛选出哪些方块实体属于低优先级。我的筛选条件是该方块实体在过去20tick内没有发生过状态变化比如传送带上没有物品移动、机械臂没有执行动作。这个判断通过给每个方块实体附加一个最后活跃时间的NBT标签来实现。实际写的时候要注意不能直接调用tick()方法因为Create的KineticBlockEntity.tick()里包含了很多逻辑直接调用可能会重复执行。正确的做法是调用Create提供的tick()重载方法或者只调用其中的动力传输部分。我最后是参考了Create的源码只调用了updateSpeed()和propagateRotation()这两个方法跳过了其他检测逻辑。注意修改方块实体的tick逻辑属于比较底层的操作如果你对Create的内部机制不熟悉建议先在单人存档里测试确认不会导致机械停摆再上服务器。3.3 深暗之域清理模块的实现清理模块的核心是一个指令处理器。我注册了一个/cleanupdeepdark指令执行后扫描主世界所有深暗之域生物群系的区块public class DeepDarkCleaner { private static final int THRESHOLD 200; // 每区块幽匿方块阈值 public static int clean(ServerWorld world) { int cleaned 0; for (Chunk chunk : getLoadedChunks(world)) { if (!isDeepDark(chunk)) continue; int sculkCount countSculkBlocks(chunk); if (sculkCount THRESHOLD) continue; for (BlockPos pos : getSculkPositions(chunk)) { if (isNearCatalyst(pos, world)) continue; // 保留催发体附近的 world.setBlockState(pos, Blocks.DEEPSLATE.getDefaultState()); cleaned; } } return cleaned; } }isDeepDark方法通过检查区块的生物群系来判断countSculkBlocks遍历区块内所有方块统计幽匿方块数量isNearCatalyst检查该位置是否在催发体半径8格内。清理时把多余的幽匿方块替换成深板岩而不是空气原因前面说过避免光照更新带来的额外负载。实际跑的时候一个加载了深暗之域的区块大概有300到500个幽匿方块清理后能降到150左右方块实体更新频率明显下降。我在服务器上实测清理前深暗之域区域的tick耗时是8ms左右清理后降到3ms效果很直接。3.4 参数调优与实测数据模组跑起来之后参数调优是关键。我主要调了三个参数参数初始值调优后说明SKIP_INTERVAL24跳过的tick间隔越大负载越低但机械响应越慢THRESHOLD300200每区块幽匿方块阈值越低清理越激进CATALYST_RADIUS48催发体保护半径越大保留的原始方块越多调优的过程是逐步来的。先把SKIP_INTERVAL从2调到4观察TPS变化发现从16提升到18.5机械响应延迟在可接受范围内。然后把THRESHOLD从300降到200深暗之域区域的tick耗时从8ms降到3ms。最后把CATALYST_RADIUS从4调到8避免误删玩家可能需要的原始幽匿方块。实测数据优化前服务器在线10人时TPS平均14.2优化后平均19.1方块实体更新耗时从平均12ms降到5ms。这个提升对于Create模组服务器来说已经相当明显了。4. 常见问题与排查技巧实录4.1 模组加载报错怎么办最常见的问题是模组加载时报NoClassDefFoundError或NoSuchMethodError。前者通常是依赖没配好检查build.gradle里的Create依赖是否正确引入后者是版本不匹配确认开发用的Create版本和服务器上跑的一致。还有一个坑是映射Mappings不一致。如果你用Yarn开发但服务器上的Create是用Mojang Mappings编译的调用它的方法时可能会找不到。解决办法是统一用Yarn或者直接用反射调用但反射性能差不推荐。4.2 机械停摆或动力网络崩溃这是修改tick逻辑后最容易出现的问题。原因通常是跳过的tick里包含了动力传输的关键逻辑。我的经验是只跳过检测类逻辑不跳过传输类逻辑。具体来说updateSpeed()和propagateRotation()这两个方法必须每tick都执行其他如物品检测、状态更新可以跳过。如果已经出现停摆最快的恢复方法是破坏并重新放置受影响的机械元件强制它重新初始化。如果范围太大可以用指令批量重置/create resetkinetics这是Create自带的指令不是我的模组加的。4.3 清理模块误删玩家建筑这个问题我在测试时遇到过。原因是清理逻辑只判断了是否在深暗之域和幽匿方块数量没有判断该区域是否有玩家建筑。解决办法是加一个检测如果该区块内有玩家放置的非幽匿方块比如石头、木板就跳过清理。实现方式是在clean方法里加一个hasPlayerBuild检查遍历区块内的方块如果发现非自然生成的方块通过检查方块是否有玩家放置的NBT标签就跳过该区块。这个检查会增加一些扫描耗时但相比误删建筑的风险这点开销值得。4.4 常见问题速查表问题现象可能原因解决方法模组加载报NoClassDefFoundError依赖未正确引入检查build.gradle和libs目录机械停摆tick逻辑跳过过多恢复updateSpeed和propagateRotation的每tick执行清理后区块出现空洞替换成了空气改为替换成深板岩清理误删建筑未检测玩家建筑加hasPlayerBuild检查TPS提升不明显参数过于保守降低THRESHOLD增大SKIP_INTERVAL玩家反馈机械响应慢SKIP_INTERVAL过大调回2或3观察TPS变化4.5 几个实测有效的避坑技巧第一个技巧先在单人存档里跑一遍完整流程。服务器上直接测试风险太大单人存档可以随时回档而且可以用/tick指令精确控制tick速度方便观察模组行为。第二个技巧给模组加一个开关指令。我加了一个/entityoptimizer on|off指令出问题的时候可以一键关闭优化恢复到原版逻辑不用重启服务器。这个在紧急情况下非常有用。第三个技巧记录清理日志。每次清理模块执行后把清理的区块坐标、清理的方块数量写到一个日志文件里。这样如果玩家反馈建筑被误删可以快速定位是哪个区块、什么时候被清理的方便回档或手动恢复。第四个技巧不要一次性清理太多区块。我一开始设置的是全图扫描清理结果一次清理了上百个区块服务器直接卡死。后来改成每次最多清理10个区块分多次执行虽然慢一点但稳定。5. 优化效果的长期维护与扩展思路5.1 日常维护怎么判断服务器是否需要再次优化优化不是一劳永逸的。随着玩家不断建造新的机械元件会不断增加负载会慢慢回升。我的做法是定期检查服务器的tick耗时用/debug指令或者服务端自带的性能分析工具观察每个维度的tick耗时。如果主世界tick耗时超过30ms就说明需要再次清理或调整参数了。另外玩家反馈也是重要信号。如果开始有玩家说机械变慢了红石延迟了那基本就是负载上来了。这时候先跑一次清理指令如果没改善再考虑调参数。5.2 扩展方向还能优化哪些内容这个模组的框架其实可以扩展到更多场景。比如针对大量掉落物实体的优化Create的粉碎轮和风扇会产生大量掉落物这些掉落物每tick都要做碰撞检测负载不小。可以加一个逻辑如果掉落物数量超过阈值自动合并或清理。再比如针对传送带物品的优化。Create的传送带上的物品是作为实体存在的一条长传送带上可能有几十个物品实体在跑。可以加一个逻辑把传送带上的物品从实体转换为方块实体上的虚拟物品减少实体数量。这个改动比较大但效果会很显著。还有一个方向是做成可配置的模组把SKIP_INTERVAL、THRESHOLD这些参数放到配置文件里服主可以根据自己服务器的实际情况调整不用改代码重新编译。5.3 最后的经验分享我在这个项目上踩的最大的坑是一开始想得太复杂。最初我试图做一个完整的实体替换系统把所有Create的方块实体都替换成自定义的轻量版本结果写了上千行代码兼容性问题一大堆最后放弃了。后来回归简单思路只做降低tick频率和区域清理这两件事反而效果更好、更稳定。所以如果你也想做类似的优化我的建议是先从最简单的方案开始能解决问题就行不要追求完美。降低tick频率这个思路代码量不到200行但解决了80%的问题。剩下的20%再用清理模块补上整个方案就完整了。另外多和玩家沟通。优化过程中我改了几次参数每次都会在服务器公告里说明改了什么、可能有什么影响。玩家理解之后即使偶尔出现机械响应慢的情况也不会抱怨反而会主动反馈问题帮我调优。这个沟通成本不能省。
返回列表