zh_cn:tutorial:pixel_raycast
像素视线追踪
假如你想知道你的屏幕的某个像素对应哪个方块或者实体,可以使用像素视线追踪。
这些都是客户端层面的。
有两种情况:中心像素(准星)和任意像素。
特例:中心像素
可以这样完成:
MinecraftClient client = MinecraftClient.getInstance(); HitResult hit = client.crosshairTarget; switch(hit.getType()) { case Type.MISS: //nothing near enough break; case Type.BLOCK: BlockHitResult blockHit = (BlockHitResult) hit; BlockPos blockPos = blockHit.getBlockPos(); BlockState blockState = client.world.getBlockState(blockPos); Block block = blockState.getBlock(); break; case Type.ENTITY: EntityHitResult entityHit = (EntityHitResult) hit; break; }
对于任意触及距离
上述代码允许正常的触及距离,生存模式3个方块,创造模式4.5个方块。如果你需要视线追踪更远的距离,可以使用下面的一般案例。
一般案例:任意像素
示例在此处。
对于这个,我们需要先计算出一些数值:
MinecraftClient client = MinecraftClient.getInstance(); int width = client.getWindow().getScaledWidth(); int height = client.getWindow().getScaledHeight(); Vec3d cameraDirection = client.cameraEntity.getRotationVec(tickDelta); double fov = client.options.fov; double angleSize = fov/height; Vector3f verticalRotationAxis = new Vector3f(cameraDirection); verticalRotationAxis.cross(Vector3f.POSITIVE_Y); if(!verticalRotationAxis.normalize()) { return;//相机直接指向顶部或者底部,你需要修复这个 } Vector3f horizontalRotationAxis = new Vector3f(cameraDirection); horizontalRotationAxis.cross(verticalRotationAxis); horizontalRotationAxis.normalize(); verticalRotationAxis = new Vector3f(cameraDirection); verticalRotationAxis.cross(horizontalRotationAxis);
有了这个之后,可以使用这个函数来计算对应某个像素的方向向量:
private static Vec3d map(float anglePerPixel, Vec3d center, Vector3f horizontalRotationAxis, Vector3f verticalRotationAxis, int x, int y, int width, int height) { float horizontalRotation = (x - width/2f) * anglePerPixel; float verticalRotation = (y - height/2f) * anglePerPixel; final Vector3f temp2 = new Vector3f(center); temp2.rotate(verticalRotationAxis.getDegreesQuaternion(verticalRotation)); temp2.rotate(horizontalRotationAxis.getDegreesQuaternion(horizontalRotation)); return new Vec3d(temp2); }
然后你需要有 GameRenderer#updateTargetedEntity
处代码的重新实现:
private static HitResult raycastInDirection(MinecraftClient client, float tickDelta, Vec3d direction) { if (entity == null || client.world == null) { return null; } double reachDistance = client.interactionManager.getReachDistance();//Change this to extend the reach HitResult target = raycast(entity, reachDistance, tickDelta, false, direction); boolean tooFar = false; double extendedReach = reachDistance; if (client.interactionManager.hasExtendedReach()) { extendedReach = 6.0D;//改变此值以扩展触及距离 reachDistance = extendedReach; } else { if (reachDistance > 3.0D) { tooFar = true; } } Vec3d cameraPos = entity.getCameraPosVec(tickDelta); extendedReach = extendedReach * extendedReach; if (target != null) { extendedReach = target.getPos().squaredDistanceTo(cameraPos); } Vec3d vec3d3 = cameraPos.add(direction.multiply(reachDistance)); Box box = entity .getBoundingBox() .stretch(entity.getRotationVec(1.0F).multiply(reachDistance)) .expand(1.0D, 1.0D, 1.0D); EntityHitResult entityHitResult = ProjectileUtil.raycast( entity, cameraPos, vec3d3, box, (entityx) -> !entityx.isSpectator() && entityx.collides(), extendedReach ); if (entityHitResult == null) { return target; } Vec3d vec3d4 = entityHitResult.getPos(); double g = cameraPos.squaredDistanceTo(vec3d4); if (tooFar && g > 9.0D) { return null; } else if (g < extendedReach || target == null) { target = entityHitResult; if (entity2 instanceof LivingEntity || entity2 instanceof ItemFrameEntity) { client.targetedEntity = entity2; } } return target; } private static HitResult raycast( Entity entity, double maxDistance, float tickDelta, boolean includeFluids, Vec3d direction ) { Vec3d end = entity.getCameraPosVec(tickDelta).add(direction.multiply(maxDistance)); return entity.world.raycast(new RaycastContext( entity.getCameraPosVec(tickDelta), end, RaycastContext.ShapeType.OUTLINE, includeFluids ? RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE, entity )); }
有了这个方向以及视线追踪之后,可以把这些都放到一起:
Vec3d direction = map( (float) angleSize, cameraDirection, horizontalRotationAxis, verticalRotationAxis, x, y, width, height ); HitResult hit = raycastInDirection(client, tickDelta, direction); switch(hit.getType()) { case Type.MISS: //nothing near enough break; case Type.BLOCK: BlockHitResult blockHit = (BlockHitResult) hit; BlockPos blockPos = blockHit.getBlockPos(); BlockState blockState = client.world.getBlockState(blockPos); Block block = blockState.getBlock(); break; case Type.ENTITY: EntityHitResult entityHit = (EntityHitResult) hit; break; }
其中 x 和 y 是你的像素坐标。
性能考虑
函数开销较大,如果多次计算,会导致游戏变慢。如果仅需要对于更长的触及距离,如果需要每帧做多次视线追踪,可以参考此链接。
zh_cn/tutorial/pixel_raycast.txt · Last modified: 2021/10/09 01:36 by solidblock