基本的3D碰撞检测问题

时间:2013-06-24 17:54:12

标签: java collision-detection game-engine game-physics voxel

我正在尝试为a实现基本的碰撞检测 我正在研究基于体素的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);

}

1 个答案:

答案 0 :(得分:1)

你的设计决定如何处理碰撞,与你认为最自然的一致(详见下文):

最靠近玩家位置的单个体素是您可以通过仅使碰撞检测进行体素检查来轻松获得更复杂方法的基础。然后,您可以轻松扩展它以检查多个相邻的体素,以便为玩家提供您打算拥有的尺寸。

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

您需要尝试的另一个问题是提升。您是否采用覆盖体素的最高,最低或某种平均值来确定玩家当前所处的安排(或者当他在与地面碰撞的高度飞行时)?。

没有任何一种方法可以让它“感觉正确”。您将需要进行一些实验,以找到您认为对于您的游戏预期物理模型而言“自然”的内容。

如果您的物理场允许快速移动,您可能需要延长碰撞检查以检查两个游戏步骤之间物体所覆盖的整个形状,以避免像子弹穿过障碍物这样的奇怪现象。因为从技术上来说,它们可以行进得如此之快,以至于它们在障碍物中永远不会有位置,尽管它们的运动矢量明显与障碍物相交。

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