2

我正在尝试为我正在开发的基于体素的 3D Java 游戏实现基本的碰撞检测。我正在尝试从该站点实现算法: https ://sites.google.com/site/letsmakeavoxelengine/home/collision-detection ,但我似乎无法正确处理。我的问题是,他所说的“将玩家位置转换为体素空间”是什么意思。我是否应该将玩家的坐标四舍五入到最近的块,以便玩家所在的体素是玩家中心所在的体素?在我的游戏中,玩家目前是一个体素大小的立方体。

该网站上的那个人写道,他只需要检查一个体素是否有碰撞。但是如果我只检查玩家中心所在的体素,那么玩家的中心需要在与物体碰撞之前与它们相交。不应该是这样的吧?如果玩家的中心位于非活动体素中,但玩家立方体的一部分与活动体素相交,那么我应该检查哪个体素?

我意识到这段文字很混乱,但我希望你能理解我的问题。如果您想查看一些代码,这是我的 CollisionHandler 类:(由于我遇到的问题,它并没有真正遵循上面网站的算法。它只关心沿 x 轴的碰撞截至目前)

public class CollisionHandler {
private static final float COLLISION_TOLERANCE = 0.4f;
private boolean xCol, yCol, zCol = false;

public void handleCollisions(ChunkManager chunkManager,
        FPCameraController player, float delta) {

    Vector3D playerPos = player.getPosition();
    Vector3D collision = findCollisionVector(player, chunkManager);

    if (collidesWithWorld()) {
        if (!(player.isFalling() && isGrounded(playerPos, chunkManager))) {
            player.setCollisionVector(collision);
            player.translateX(-playerPos.subtract(playerPos.round()).getX());
        }else{
            //123456
        }
    } else {
        if (player.isFalling()) {
            if (isGrounded(playerPos, chunkManager)) {
                float overlap = getYOverlap(player, chunkManager);
                player.translateY(overlap);
                player.setYSpeed(0);
                player.setIsFalling(false);
            } else {
                player.applyGravity(delta);
            }
        } else {
            if (!isGrounded(playerPos, chunkManager)) {
                player.setIsFalling(true);
                player.applyGravity(delta);
            }
        }

    }
}

private boolean collidesWithWorld() {
    return xCol || yCol || zCol;
}

/*
 * Returns a collision vector. Dot with velocity and then subtract it from
 * the player velocity.
 */
private Vector3D findCollisionVector(FPCameraController player,
        ChunkManager chunkManager) {

    Vector3D playerPos = player.getPosition();
    Vector3D distance = playerPos.subtract(playerPos.floor()).abs();

    Vector3D collisions = new Vector3D(1, 1, 1);
    float xDirection = (getCollisionDirection(distance.getX()));
    // float yDirection = (getCollisionDirection(distance.getY()));
    // float zDirection = (getCollisionDirection(distance.getZ()));

    try {
        Vector3D collision = getCollisionNormal(chunkManager, playerPos,
                xDirection, 'x');

        if (collision != null) {
            collisions = collision;
            xCol = true;
        } else {
            xCol = false;
        }

        // collision = getCollisionNormal(chunkManager, playerPos,
        // yDirection,
        // 'y');
        // if (collision != null) {
        // collisions.cross(collision);
        // yCol = true;
        // } else {
        // yCol = false;
        // }
        //
        // collision = getCollisionNormal(chunkManager, playerPos,
        // zDirection,
        // 'z');
        // if (collision != null) {
        // collisions.cross(collision);
        // zCol = true;
        // } else {
        // zCol = false;
        // }
    } catch (OutsideOfWorldException e) {
        e.printStackTrace();
    }

    return collisions;
}

/*
 * Returns the normal of the colliding block, given the axis and
 * direction.
 */
private static Vector3D getCollisionNormal(ChunkManager chunkManager,
        Vector3D playerPos, float direction, char axis)
        throws OutsideOfWorldException {
    Block b;
    Vector3D blockPos;
    if (direction != 0) {
        Vector3D dirVector;
        if (axis == 'x') {
            dirVector = new Vector3D(direction, 0, 0);
        } else if (axis == 'y') {
            dirVector = new Vector3D(0, direction, 0);
        } else if (axis == 'z') {
            dirVector = new Vector3D(0, 0, direction);
        } else {
            return null;
        }
        blockPos = playerPos.add(dirVector);

        b = chunkManager.getBlock(blockPos);

        if (b.isActive()) {

            return Plane3D.getBlockNormal(blockPos, direction, axis);
        }
    }
    return null;
}

private static float getCollisionDirection(float distance) {
    if (distance > COLLISION_TOLERANCE) {
        return 1;
    } else if (distance < COLLISION_TOLERANCE) {
        return -1;
    }
    return 0;
}

private static boolean isGrounded(Vector3D playerPosition,
        ChunkManager chunkManager) {
    try {
        return chunkManager.getBlock(
                playerPosition.add(new Vector3D(0, -1, 0))).isActive();
    } catch (OutsideOfWorldException e) {
        e.printStackTrace();
    }
    return true;
}

private static float getYOverlap(FPCameraController player,
        ChunkManager chunkManager) {
    Vector3D playerPosition = player.getPosition();
    Vector3D blockPosition = player.getLowestBlockPos();
    Block collisionBlock = null;

    try {
        collisionBlock = chunkManager.getBlock(blockPosition);

        // +" "+blockPosition);

        if (collisionBlock.isActive()) {
            float distance = playerPosition.subtract(blockPosition).getY();

            distance += player.getHeight();

            return -distance;

        }
    } catch (OutsideOfWorldException e) {
        e.printStackTrace();
    }

    return 0;
}
}

这是另一种相关的方法:

    public static Vector3D getBlockNormal(Vector3D blockPos, float direction,
        char axis) {
    float offset = Block.BLOCK_RENDER_SIZE / 2f;

    Vector3D pointA = null;
    Vector3D pointB = null;
    Vector3D pointC = null;

    Vector3D a = blockPos.round();

    a = a.addScalar(Block.BLOCK_RENDER_SIZE / 2f);
    float factor = -direction;
    if (axis == 'x') {
        pointA = a.add(new Vector3D(factor * offset, -offset, -offset));
        pointB = a.add(new Vector3D(factor * offset, offset, -offset));
        pointC = a.add(new Vector3D(factor * offset, -offset, offset));
    } else if (axis == 'y') {
        pointA = a.add(new Vector3D(-offset, factor * offset, offset));
        pointB = a.add(new Vector3D(offset, factor * offset, offset));
        pointC = a.add(new Vector3D(offset, factor * offset, -offset));
    } else if (axis == 'z') {
        pointA = a.add(new Vector3D(-offset, -offset, factor * offset));
        pointB = a.add(new Vector3D(offset, -offset, factor * offset));
        pointC = a.add(new Vector3D(offset, offset, factor * offset));
    } else {
        return null;
    }

    Vector3D v = new Vector3D(pointB.getX() - pointA.getX(), pointB.getY()
            - pointA.getY(), pointB.getZ() - pointA.getZ()).normalize();
    Vector3D w = new Vector3D(pointC.getX() - pointA.getX(), pointC.getY()
            - pointA.getY(), pointC.getZ() - pointA.getZ()).normalize();
    Vector3D normal = v.cross(w).scale(-1);

    return normal.scale(factor);

}
4

1 回答 1

1

如何处理碰撞由您的设计决定,使用您认为最自然的方式(详细说明如下):

最接近玩家位置的单个体素是基础,您可以通过使碰撞检测对体素进行检查,轻松地从中推导出更复杂的方法。然后,您可以轻松地扩展它以检查多个相邻的体素,从而为玩家提供您希望他拥有的大小。

例如,您可以将玩家视为圆柱体,并检查圆柱体覆盖的圆圈下方的所有体素。如果您检测到(例如)圆圈下方的单个熔岩体素,您可能会施加熔岩伤害(无论您的游戏使用什么地面属性)。

您需要尝试的另一个问题是海拔。您是否采用最高、最低或某种平均覆盖的体素来确定玩家当前所在的位置(或飞行时他与地面碰撞的高度)?

没有单一的配方可以让它“感觉良好”。您将需要进行一些试验,以找到您认为对于您的游戏预期物理模型而言“自然”的东西。

如果您的物理允许快速移动,您可能需要扩展碰撞检查以检查两个游戏步骤之间对象覆盖的整个形状,以避免出现子弹穿过障碍物等奇怪现象。因为从技术上讲,它们可以移动得如此之快,以至于它们永远不会障碍物中占据一席之地,尽管它们的运动矢量明显与障碍物相交。

所以“将玩家坐标转换到体素空间”可以意味着任何东西,它没有详细定义方法。对于初始测试,您的“四舍五入到最近的块”可能已经足够好,对于最终游戏,您可能需要应用上面概述的一些概念以使其物理“感觉正确”。

于 2013-06-24T18:21:21.070 回答