Table of Contents

像素视线追踪

假如你想知道你的屏幕的某个像素对应哪个方块或者实体,可以使用像素视线追踪。

这些都是客户端层面的。

有两种情况:中心像素(准星)和任意像素。

特例:中心像素

可以这样完成:

  1. MinecraftClient client = MinecraftClient.getInstance();
  2. HitResult hit = client.crosshairTarget;
  3.  
  4. switch(hit.getType()) {
  5. case Type.MISS:
  6. //nothing near enough
  7. break;
  8. case Type.BLOCK:
  9. BlockHitResult blockHit = (BlockHitResult) hit;
  10. BlockPos blockPos = blockHit.getBlockPos();
  11. BlockState blockState = client.world.getBlockState(blockPos);
  12. Block block = blockState.getBlock();
  13. break;
  14. case Type.ENTITY:
  15. EntityHitResult entityHit = (EntityHitResult) hit;
  16. Entity entity = entityHit.getEntity();
  17. break;
  18. }

对于任意触及距离

上述代码允许正常的触及距离,生存模式3个方块,创造模式4.5个方块。如果你需要视线追踪更远的距离,可以使用下面的一般案例。

一般案例:任意像素

示例在此处

对于这个,我们需要先计算出一些数值:

  1. MinecraftClient client = MinecraftClient.getInstance();
  2. int width = client.getWindow().getScaledWidth();
  3. int height = client.getWindow().getScaledHeight();
  4. Vec3d cameraDirection = client.cameraEntity.getRotationVec(tickDelta);
  5. double fov = client.options.fov;
  6. double angleSize = fov/height;
  7. Vector3f verticalRotationAxis = new Vector3f(cameraDirection);
  8. verticalRotationAxis.cross(Vector3f.POSITIVE_Y);
  9. if(!verticalRotationAxis.normalize()) {
  10. return;//相机直接指向顶部或者底部,你需要修复这个
  11. }
  12.  
  13. Vector3f horizontalRotationAxis = new Vector3f(cameraDirection);
  14. horizontalRotationAxis.cross(verticalRotationAxis);
  15. horizontalRotationAxis.normalize();
  16.  
  17. verticalRotationAxis = new Vector3f(cameraDirection);
  18. verticalRotationAxis.cross(horizontalRotationAxis);

有了这个之后,可以使用这个函数来计算对应某个像素的方向向量:

  1. private static Vec3d map(float anglePerPixel, Vec3d center, Vector3f horizontalRotationAxis,
  2. Vector3f verticalRotationAxis, int x, int y, int width, int height) {
  3. float horizontalRotation = (x - width/2f) * anglePerPixel;
  4. float verticalRotation = (y - height/2f) * anglePerPixel;
  5.  
  6. final Vector3f temp2 = new Vector3f(center);
  7. temp2.rotate(verticalRotationAxis.getDegreesQuaternion(verticalRotation));
  8. temp2.rotate(horizontalRotationAxis.getDegreesQuaternion(horizontalRotation));
  9. return new Vec3d(temp2);
  10. }

然后你需要有 GameRenderer#updateTargetedEntity 处代码的重新实现:

  1. private static HitResult raycastInDirection(MinecraftClient client, float tickDelta, Vec3d direction) {
  2. Entity entity = client.getCameraEntity();
  3. if (entity == null || client.world == null) {
  4. return null;
  5. }
  6.  
  7. double reachDistance = client.interactionManager.getReachDistance();//Change this to extend the reach
  8. HitResult target = raycast(entity, reachDistance, tickDelta, false, direction);
  9. boolean tooFar = false;
  10. double extendedReach = reachDistance;
  11. if (client.interactionManager.hasExtendedReach()) {
  12. extendedReach = 6.0D;//改变此值以扩展触及距离
  13. reachDistance = extendedReach;
  14. } else {
  15. if (reachDistance > 3.0D) {
  16. tooFar = true;
  17. }
  18. }
  19.  
  20. Vec3d cameraPos = entity.getCameraPosVec(tickDelta);
  21.  
  22. extendedReach = extendedReach * extendedReach;
  23. if (target != null) {
  24. extendedReach = target.getPos().squaredDistanceTo(cameraPos);
  25. }
  26.  
  27. Vec3d vec3d3 = cameraPos.add(direction.multiply(reachDistance));
  28. Box box = entity
  29. .getBoundingBox()
  30. .stretch(entity.getRotationVec(1.0F).multiply(reachDistance))
  31. .expand(1.0D, 1.0D, 1.0D);
  32. EntityHitResult entityHitResult = ProjectileUtil.raycast(
  33. entity,
  34. cameraPos,
  35. vec3d3,
  36. box,
  37. (entityx) -> !entityx.isSpectator() && entityx.collides(),
  38. extendedReach
  39. );
  40.  
  41. if (entityHitResult == null) {
  42. return target;
  43. }
  44.  
  45. Entity entity2 = entityHitResult.getEntity();
  46. Vec3d vec3d4 = entityHitResult.getPos();
  47. double g = cameraPos.squaredDistanceTo(vec3d4);
  48. if (tooFar && g > 9.0D) {
  49. return null;
  50. } else if (g < extendedReach || target == null) {
  51. target = entityHitResult;
  52. if (entity2 instanceof LivingEntity || entity2 instanceof ItemFrameEntity) {
  53. client.targetedEntity = entity2;
  54. }
  55. }
  56.  
  57. return target;
  58. }
  59.  
  60. private static HitResult raycast(
  61. Entity entity,
  62. double maxDistance,
  63. float tickDelta,
  64. boolean includeFluids,
  65. Vec3d direction
  66. ) {
  67. Vec3d end = entity.getCameraPosVec(tickDelta).add(direction.multiply(maxDistance));
  68. return entity.world.raycast(new RaycastContext(
  69. entity.getCameraPosVec(tickDelta),
  70. end,
  71. RaycastContext.ShapeType.OUTLINE,
  72. includeFluids ? RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE,
  73. entity
  74. ));
  75. }

有了这个方向以及视线追踪之后,可以把这些都放到一起:

  1. Vec3d direction = map(
  2. (float) angleSize,
  3. cameraDirection,
  4. horizontalRotationAxis,
  5. verticalRotationAxis,
  6. x,
  7. y,
  8. width,
  9. height
  10. );
  11. HitResult hit = raycastInDirection(client, tickDelta, direction);
  12.  
  13. switch(hit.getType()) {
  14. case Type.MISS:
  15. //nothing near enough
  16. break;
  17. case Type.BLOCK:
  18. BlockHitResult blockHit = (BlockHitResult) hit;
  19. BlockPos blockPos = blockHit.getBlockPos();
  20. BlockState blockState = client.world.getBlockState(blockPos);
  21. Block block = blockState.getBlock();
  22. break;
  23. case Type.ENTITY:
  24. EntityHitResult entityHit = (EntityHitResult) hit;
  25. Entity entity = entityHit.getEntity();
  26. break;
  27. }

其中 x 和 y 是你的像素坐标。

性能考虑

函数开销较大,如果多次计算,会导致游戏变慢。如果仅需要对于更长的触及距离,如果需要每帧做多次视线追踪,可以参考此链接