====== 像素视线追踪 ======
假如你想知道你的屏幕的某个像素对应哪个方块或者实体,可以使用像素视线追踪。
这些都是客户端层面的。
有两种情况:中心像素(准星)和任意像素。
===== 特例:中心像素 =====
可以这样完成:
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;
Entity entity = entityHit.getEntity();
break;
}
==== 对于任意触及距离 ====
上述代码允许正常的触及距离,生存模式3个方块,创造模式4.5个方块。如果你需要视线追踪更远的距离,可以使用下面的一般案例。
===== 一般案例:任意像素 =====
示例[[https://github.com/EmmanuelMess/BoundingBoxMinecraftMod|在此处]]。
对于这个,我们需要先计算出一些数值:
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) {
Entity entity = client.getCameraEntity();
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;
}
Entity entity2 = entityHitResult.getEntity();
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;
Entity entity = entityHit.getEntity();
break;
}
其中 x 和 y 是你的像素坐标。
==== 性能考虑 ====
函数开销较大,如果多次计算,会导致游戏变慢。如果仅需要对于更长的触及距离,如果需要每帧做多次视线追踪,可以参考[[https://stackoverflow.com/q/777997/3124150|此链接]]。