2013-06-24 7 views
1

Я пытаюсь реализовать базовое обнаружение столкновений для voxel-based 3D Java-игры, над которой я работаю. Я пытаюсь реализовать алгоритм с этого сайта: https://sites.google.com/site/letsmakeavoxelengine/home/collision-detection, , но я не могу понять, как это правильно. Мой вопрос есть, что он подразумевает под «преобразованием позиции игрока в voxel space '. Должен ли я округлить координаты игрока до ближайшего блока, так что вокселем игрока является , где находится центр игрока? В моей игре игрок в настоящее время является кубе размером с вокселом.Основная проблема обнаружения столкновений 3D

Парень на этом веб-сайте пишет, что ему нужно только проверить один вокзал для столкновений . Но если я проверю только воксел, то в центре находится игрока, тогда центр игрока должен пересечь материал, прежде чем он столкнется с ними. Это не предполагается , чтобы быть таким? Если центр игрока находится в неактивном вокселе, но часть куба игрока пересекает активным вокселом, который воксел я должен проверить?

Я понимаю, что этот текст довольно запутанный, но я надеюсь, что вы можете понять мой вопрос. Вот мой класс CollisionHandler, если вам хочется посмотреть на какой-то код: (Он действительно не следует алгоритму с сайта выше, но из-за проблем, которые у меня были. Это только заботит о столкновениях по оси х как сейчас)

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

Его ваше дизайнерское решение, как обрабатывать столкновения, идти с тем, что вы чувствуете, наиболее естественным (разработка следует):

Единственный воксел, ближайший к позиции игроков, равен t он основывается на том, что вы можете легко получить более сложные методы, просто сделав обнаружение столкновения, чтобы проверить воксел. Затем вы можете легко расширить его, чтобы проверить несколько соседних вокселов, чтобы дать игроку размер, который вы ему намереваетесь.

Например, вы можете рассматривать игрока как цилиндр и проверять все воксели под кругом, покрывающим цилиндры. Если вы обнаружите (например) один воксел лавы под кругом, вы можете применить повреждение лавы (независимо от свойств земли, с которыми работает ваша игра).

Еще одна проблема, с которой вам нужно поэкспериментировать, - это возвышение. Вы берете наивысший, самый низкий или какой-то средний покрытый вокселей, чтобы определить, где находится игрок в настоящий момент (или при полете, с какой высоты он сталкивается с землей)?

Существует не один рецепт, чтобы заставить его «чувствовать себя хорошо». Вам нужно будет немного поэкспериментировать, чтобы найти то, что, по вашему мнению, является «естественным» для предполагаемой модели физики вашей игры.

Если ваша физика позволяет быстро перемещаться, вам может потребоваться продлить проверку столкновения, чтобы проверить всю фигуру, покрытую объектом между двумя игровыми шагами, чтобы избежать странных явлений, таких как пули, проходящие через препятствия. Потому что технически они могли путешествовать так быстро, что у них никогда не будет позиции в пределах препятствие, хотя их вектор движения четко пересекает препятствие.

Таким образом, «преобразование координат игрока в пространство вокселя» может означать что угодно, но не определяет метод в деталях. Для первоначального тестирования ваш «округление до ближайшего блока», вероятно, достаточно хорош, для финальной игры вам, вероятно, необходимо применить некоторые из изложенных выше концепций, чтобы физика «чувствовала себя правильно».