【问题标题】:Basic 3D collision detection issue基本 3D 碰撞检测问题
【发布时间】:2013-06-24 17:54:11
【问题描述】:

我正在尝试为 我正在开发的基于体素的 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);

}

【问题讨论】:

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


    【解决方案1】:

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

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-05-30
      • 1970-01-01
      • 2014-12-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多