Fabric 1.20模组中实现Block ESP与Player ESP的更新方案问询
Player/Block ESP 透视渲染实现方案
核心解决思路
- 可靠坐标转换:依赖游戏内置渲染管理器的投影逻辑,避免手动计算导致的无效坐标
- 正确渲染时机:在
postRender阶段执行绘制,确保ESP覆盖层显示在所有游戏元素之上 - 方块透视优化:遍历目标方块类型,转换坐标后绘制半透明绿色填充矩形
- 玩家透视实现:针对每个有效玩家(排除自身/死亡),绘制边框、名称标签及屏幕中心追踪线
示例代码(遵循BasicModule结构)
public class ESPModule extends BasicModule { // 方块透视半透明绿色 private final Color BLOCK_ESP_COLOR = new Color(0, 255, 0, 100); // 玩家透视不透明绿色 private final Color PLAYER_ESP_COLOR = new Color(0, 255, 0, 255); public ESPModule() { super("ESP", "Render player/block ESP overlays"); } @Override public void onPostRender() { if (!isEnabled()) return; renderBlockESP(); renderPlayerESP(); } private void renderBlockESP() { // 遍历玩家周围区块内的方块,可自行过滤目标类型(如矿石) for (BlockPos pos : getNearbyBlocks()) { Block block = mc.world.getBlockState(pos).getBlock(); // 示例:仅透视钻石矿石,可替换为其他方块 if (block != Blocks.DIAMOND_ORE) continue; Vec3d screenPos = worldToScreen(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); if (!isOnScreen(screenPos)) continue; // 计算方块在屏幕上的显示尺寸 float blockWidth = mc.fontRenderer.getStringWidth(block.getLocalizedName()) / 2f; float blockHeight = 10f; // 绘制半透明绿色覆盖层 RenderUtils.drawRect( screenPos.x - blockWidth, screenPos.y - blockHeight, screenPos.x + blockWidth, screenPos.y + blockHeight, BLOCK_ESP_COLOR.getRGB() ); } } private void renderPlayerESP() { for (EntityPlayer player : mc.world.playerEntities) { if (player == mc.player || player.isDead) continue; // 获取玩家 bounding box 中心坐标 Vec3d playerCenter = new Vec3d( player.posX, player.posY + player.getEyeHeight() / 2, player.posZ ); Vec3d screenPos = worldToScreen(playerCenter.x, playerCenter.y, playerCenter.z); if (!isOnScreen(screenPos)) continue; // 根据距离缩放玩家边框尺寸 float scale = 1f / (float) screenPos.z * mc.getRenderPartialTicks(); float playerWidth = player.width * scale * 200f; float playerHeight = player.height * scale * 200f; // 绘制玩家绿色边框 RenderUtils.drawOutlineRect( screenPos.x - playerWidth/2, screenPos.y - playerHeight/2, screenPos.x + playerWidth/2, screenPos.y + playerHeight/2, 2, PLAYER_ESP_COLOR.getRGB() ); // 绘制玩家名称标签 String playerName = player.getName(); float nameWidth = mc.fontRenderer.getStringWidth(playerName); mc.fontRenderer.drawStringWithShadow( playerName, screenPos.x - nameWidth/2, screenPos.y - playerHeight/2 - 12, PLAYER_ESP_COLOR.getRGB() ); // 绘制屏幕中心到玩家的追踪线 RenderUtils.drawLine( mc.displayWidth/2, mc.displayHeight/2, screenPos.x, screenPos.y, 1, PLAYER_ESP_COLOR.getRGB() ); } } // 世界坐标转屏幕坐标,使用游戏内置渲染逻辑避免无效值 private Vec3d worldToScreen(double x, double y, double z) { GL11.glPushMatrix(); GL11.glLoadIdentity(); mc.entityRenderer.setupCameraTransform(mc.getRenderPartialTicks(), 0); Vec3d relativePos = new Vec3d( x - mc.getRenderManager().viewerPosX, y - mc.getRenderManager().viewerPosY, z - mc.getRenderManager().viewerPosZ ); double projX = mc.getRenderManager().getProjectionMatrix().m00 * relativePos.x + mc.getRenderManager().getProjectionMatrix().m01 * relativePos.y + mc.getRenderManager().getProjectionMatrix().m02 * relativePos.z + mc.getRenderManager().getProjectionMatrix().m03; double projY = mc.getRenderManager().getProjectionMatrix().m10 * relativePos.x + mc.getRenderManager().getProjectionMatrix().m11 * relativePos.y + mc.getRenderManager().getProjectionMatrix().m12 * relativePos.z + mc.getRenderManager().getProjectionMatrix().m13; double projZ = mc.getRenderManager().getProjectionMatrix().m20 * relativePos.x + mc.getRenderManager().getProjectionMatrix().m21 * relativePos.y + mc.getRenderManager().getProjectionMatrix().m22 * relativePos.z + mc.getRenderManager().getProjectionMatrix().m23; GL11.glPopMatrix(); // 过滤过近/不可见的坐标 if (projZ < 0.1) return new Vec3d(0, 0, -1); double screenX = mc.displayWidth / 2 * (1 + projX / projZ); double screenY = mc.displayHeight / 2 * (1 - projY / projZ); return new Vec3d(screenX, screenY, projZ); } // 判断坐标是否在屏幕可视范围内 private boolean isOnScreen(Vec3d pos) { return pos.z > 0.1 && pos.x > 0 && pos.x < mc.displayWidth && pos.y > 0 && pos.y < mc.displayHeight; } // 获取玩家周围一定范围内的方块,优化遍历性能 private List<BlockPos> getNearbyBlocks() { List<BlockPos> blocks = new ArrayList<>(); int renderRange = mc.gameSettings.renderDistanceChunks * 16; BlockPos playerPos = mc.player.getPosition(); for (int x = -renderRange; x <= renderRange; x++) { for (int y = -64; y <= 256; y++) { for (int z = -renderRange; z <= renderRange; z++) { blocks.add(playerPos.add(x, y, z)); } } } return blocks; } }
常见问题修复要点
- 无效坐标:使用
worldToScreen内的游戏内置投影矩阵,添加isOnScreen过滤超出屏幕或不可见的坐标 - 渲染层级错误:必须在
onPostRender方法中执行绘制,确保覆盖层在游戏场景渲染完成后显示 - 性能卡顿:优化
getNearbyBlocks方法,可改为按区块遍历,或缩小遍历范围,避免全地图扫描
内容的提问来源于stack exchange,提问作者Insilicon
相关产品推荐
相关产品推荐

