You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 22:13:10