强制加载并渲染特定区块
-
我也不知道有什么实用价值但反正先写着。
思路:
1)强加载区块
2)主动发送区块数据
3)强制渲染区块强加载
首先是强加载区块,我写了一个简单的类实现:
public class ChunkLoader { private static final HashMap<String, ArrayList<ChunkPos>> loaders = new HashMap<>(); public static void add(ServerLevel level, ChunkPos center) { String key = level.dimension().toString(); if (!loaders.containsKey(key)) loaders.put(key, new ArrayList<>()); if (!loaders.get(key).contains(center)) { loaders.get(key).add(center); ForgeChunkManager.forceChunk(level, MODID, center.getMiddleBlockPosition(0), center.x, center.z, true, true); } } public static void removeAll(ServerLevel level) { String key = level.dimension().toString(); if (!loaders.containsKey(key)) return; if (loaders.get(key).isEmpty()) return; Iterator<ChunkPos> iterator = loaders.get(key).iterator(); while (iterator.hasNext()) { ChunkPos center = iterator.next(); iterator.remove(); ForgeChunkManager.forceChunk(level, MODID, center.getMiddleBlockPosition(0), center.x, center.z, false, false); } } }
写得比较草率,主打一个能用就行。
这是在服务端运行的,区块要加载到服务端的区块缓存(ServerChunkCache)中,才能发送对应的区块数据。
如果想安全地强加载区块,请使用Ticket系统,我这里只是分享思路,就简单用forceChunk方法敷衍过去了。发送区块数据
这部分是mixin得到的,把ChunkMap中的playerLoadedChunk方法拿出来用,最终发送的是一个ClientboundLevelChunkWithLightPacket包。
@Mixin(value = ChunkMap.class) @Implements(@Interface(iface = IChunkMap .class, prefix = "lazy$")) public abstract class ChunkMapMixin implements IChunkMap { @Shadow @Nullable protected abstract ChunkHolder getVisibleChunkIfPresent(long p_140328_); @Shadow protected abstract void playerLoadedChunk(ServerPlayer p_183761_, MutableObject<ClientboundLevelChunkWithLightPacket> p_183762_, LevelChunk p_183763_); public void loadLevelChunk(ServerPlayer player, ChunkPos chunkPos) { ChunkHolder chunkholder = this.getVisibleChunkIfPresent(chunkPos.toLong()); if (chunkholder == null) return; LevelChunk levelchunk = chunkholder.getTickingChunk(); if (levelchunk == null) return; this.playerLoadedChunk(player, new MutableObject<>(), levelchunk); } }
默认情况下,区块更新是惰性的,要使用playerLoadedChunk方法,除了单独拎出来用,也可以插入到move方法中:
@Inject(method = "move", at = @At("HEAD")) private void justMove(ServerPlayer player, CallbackInfo ci) { loadLevelChunk(player, ChunkPos.ZERO); }
这个方法是默认情况下玩家更新区块的方法,插入到这里,等同于为玩家更新额外的区块。
强制渲染
这部分是最复杂的,需要进行非常非常多的mixin。
让我们一步步走。
首先,客户端收到ClientboundLevelChunkWithLightPacket包后需要进行处理,将区块数据存进客户端的区块缓存(ClientChunkCache)中,等待帧渲染将它抓出来渲染。
这里出现了第一次渲染判定,读取区块缓存的时候,要检测区块坐标是否在视距范围内。
那么我们将它干掉。@Mixin(targets = "net.minecraft.client.multiplayer.ClientChunkCache$Storage") public class ClientChunkCache$StorageMixin { @Inject(method = "inRange", at = @At("HEAD"), cancellable = true) private void modifyRange(int x, int z, CallbackInfoReturnable<Boolean> cir) { if (new ChunkPos(*****).equals(new ChunkPos(x, z))) { cir.setReturnValue(true); } } }
如果轮到检测的这个区块和你想渲染的区块是同一个,就强制通过检测。
说完了缓存,接下来就是渲染。
最核心的渲染是在LevelRenderer里,这个类超级超级长。
我其实不太想介绍这个部分,因为Embeddium在这个类中用Overwrite重写了非常多的方法,比如我下面要说的setupRender:@Mixin(LevelRenderer.class) public class LevelRendererMixin { @ModifyVariable(method = "setupRender",at = @At("STORE"), ordinal = 0) private double modifyX(double x) { // 改一下x。 return x; } // 把y和z也改了,此处省略。 }
这么做可以强制转移渲染的中心点,转移到你想渲染的地方。
是的,这个方法被重写了,重写后以客户端实例的摄像机位置为渲染中心了。Minecraft.getInstance().gameRenderer.getMainCamera().getPosition();
如果要修改渲染中心就去改摄像机位置吧,反正强制渲染特定区块大概率是伴随着摄像机移动的。
如果要考虑不安装Embeddium的情况,就用MixinPlugin区分一下:public class MixinPlugin implements IMixinConfigPlugin { @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { if (mixinClassName.equals("com.mafuyu404.examplemod.mixin.LevelRendererMixin")) { return !isClassLoaded("me.jellysquid.mods.sodium.client.SodiumClientMod"); // Sodium/Embeddium核心类 } return true; } private static boolean isClassLoaded(String className) { try { Class.forName(className, false, getClassLoader()); return true; } catch (ClassNotFoundException e) { return false; } } // 必要的补全此处省略。 }
不过,真有人会不装Embeddium吗?我测试的时候32视距给我提了快100帧……
在处理完区块渲染,其实还有最后一步,那就是实体渲染。
@Mixin(targets = "net.minecraft.server.level.ChunkMap$TrackedEntity") public class ChunkMap$TrackedEntityMixin { @ModifyVariable(method = "updatePlayer", at = @At(value = "STORE")) private Vec3 wwa(Vec3 direction) { // 改一下direction。 return direction; } }
很多优化模组都有远处实体剔除的功能,实体渲染其实可以看作与区块渲染独立。
上面这个方法叫updatePlayer,其实是指更新本地玩家区块数据中的实体追踪数据,这里面会计算实体与玩家的距离,从而决定实体是否要渲染。
direction其实就是玩家坐标与实体坐标构成的向量,设为零向量即可。结语
内容就这么多了,其实是相当鸡肋的东西,之后想到什么再来补充吧。
-
我也不知道有什么实用价值但反正先写着。
思路:
1)强加载区块
2)主动发送区块数据
3)强制渲染区块强加载
首先是强加载区块,我写了一个简单的类实现:
public class ChunkLoader { private static final HashMap<String, ArrayList<ChunkPos>> loaders = new HashMap<>(); public static void add(ServerLevel level, ChunkPos center) { String key = level.dimension().toString(); if (!loaders.containsKey(key)) loaders.put(key, new ArrayList<>()); if (!loaders.get(key).contains(center)) { loaders.get(key).add(center); ForgeChunkManager.forceChunk(level, MODID, center.getMiddleBlockPosition(0), center.x, center.z, true, true); } } public static void removeAll(ServerLevel level) { String key = level.dimension().toString(); if (!loaders.containsKey(key)) return; if (loaders.get(key).isEmpty()) return; Iterator<ChunkPos> iterator = loaders.get(key).iterator(); while (iterator.hasNext()) { ChunkPos center = iterator.next(); iterator.remove(); ForgeChunkManager.forceChunk(level, MODID, center.getMiddleBlockPosition(0), center.x, center.z, false, false); } } }
写得比较草率,主打一个能用就行。
这是在服务端运行的,区块要加载到服务端的区块缓存(ServerChunkCache)中,才能发送对应的区块数据。
如果想安全地强加载区块,请使用Ticket系统,我这里只是分享思路,就简单用forceChunk方法敷衍过去了。发送区块数据
这部分是mixin得到的,把ChunkMap中的playerLoadedChunk方法拿出来用,最终发送的是一个ClientboundLevelChunkWithLightPacket包。
@Mixin(value = ChunkMap.class) @Implements(@Interface(iface = IChunkMap .class, prefix = "lazy$")) public abstract class ChunkMapMixin implements IChunkMap { @Shadow @Nullable protected abstract ChunkHolder getVisibleChunkIfPresent(long p_140328_); @Shadow protected abstract void playerLoadedChunk(ServerPlayer p_183761_, MutableObject<ClientboundLevelChunkWithLightPacket> p_183762_, LevelChunk p_183763_); public void loadLevelChunk(ServerPlayer player, ChunkPos chunkPos) { ChunkHolder chunkholder = this.getVisibleChunkIfPresent(chunkPos.toLong()); if (chunkholder == null) return; LevelChunk levelchunk = chunkholder.getTickingChunk(); if (levelchunk == null) return; this.playerLoadedChunk(player, new MutableObject<>(), levelchunk); } }
默认情况下,区块更新是惰性的,要使用playerLoadedChunk方法,除了单独拎出来用,也可以插入到move方法中:
@Inject(method = "move", at = @At("HEAD")) private void justMove(ServerPlayer player, CallbackInfo ci) { loadLevelChunk(player, ChunkPos.ZERO); }
这个方法是默认情况下玩家更新区块的方法,插入到这里,等同于为玩家更新额外的区块。
强制渲染
这部分是最复杂的,需要进行非常非常多的mixin。
让我们一步步走。
首先,客户端收到ClientboundLevelChunkWithLightPacket包后需要进行处理,将区块数据存进客户端的区块缓存(ClientChunkCache)中,等待帧渲染将它抓出来渲染。
这里出现了第一次渲染判定,读取区块缓存的时候,要检测区块坐标是否在视距范围内。
那么我们将它干掉。@Mixin(targets = "net.minecraft.client.multiplayer.ClientChunkCache$Storage") public class ClientChunkCache$StorageMixin { @Inject(method = "inRange", at = @At("HEAD"), cancellable = true) private void modifyRange(int x, int z, CallbackInfoReturnable<Boolean> cir) { if (new ChunkPos(*****).equals(new ChunkPos(x, z))) { cir.setReturnValue(true); } } }
如果轮到检测的这个区块和你想渲染的区块是同一个,就强制通过检测。
说完了缓存,接下来就是渲染。
最核心的渲染是在LevelRenderer里,这个类超级超级长。
我其实不太想介绍这个部分,因为Embeddium在这个类中用Overwrite重写了非常多的方法,比如我下面要说的setupRender:@Mixin(LevelRenderer.class) public class LevelRendererMixin { @ModifyVariable(method = "setupRender",at = @At("STORE"), ordinal = 0) private double modifyX(double x) { // 改一下x。 return x; } // 把y和z也改了,此处省略。 }
这么做可以强制转移渲染的中心点,转移到你想渲染的地方。
是的,这个方法被重写了,重写后以客户端实例的摄像机位置为渲染中心了。Minecraft.getInstance().gameRenderer.getMainCamera().getPosition();
如果要修改渲染中心就去改摄像机位置吧,反正强制渲染特定区块大概率是伴随着摄像机移动的。
如果要考虑不安装Embeddium的情况,就用MixinPlugin区分一下:public class MixinPlugin implements IMixinConfigPlugin { @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { if (mixinClassName.equals("com.mafuyu404.examplemod.mixin.LevelRendererMixin")) { return !isClassLoaded("me.jellysquid.mods.sodium.client.SodiumClientMod"); // Sodium/Embeddium核心类 } return true; } private static boolean isClassLoaded(String className) { try { Class.forName(className, false, getClassLoader()); return true; } catch (ClassNotFoundException e) { return false; } } // 必要的补全此处省略。 }
不过,真有人会不装Embeddium吗?我测试的时候32视距给我提了快100帧……
在处理完区块渲染,其实还有最后一步,那就是实体渲染。
@Mixin(targets = "net.minecraft.server.level.ChunkMap$TrackedEntity") public class ChunkMap$TrackedEntityMixin { @ModifyVariable(method = "updatePlayer", at = @At(value = "STORE")) private Vec3 wwa(Vec3 direction) { // 改一下direction。 return direction; } }
很多优化模组都有远处实体剔除的功能,实体渲染其实可以看作与区块渲染独立。
上面这个方法叫updatePlayer,其实是指更新本地玩家区块数据中的实体追踪数据,这里面会计算实体与玩家的距离,从而决定实体是否要渲染。
direction其实就是玩家坐标与实体坐标构成的向量,设为零向量即可。结语
内容就这么多了,其实是相当鸡肋的东西,之后想到什么再来补充吧。