Я программировал систему обнаружения и реагирования на столкновения для своей воксельной игры, и после нескольких переписываний я нашел довольно стабильное и надежное решение.Проблема, с которой я столкнулся сейчас, заключается в том, что мне нужно различать, падает ли игрок через пол или идет в стену.На данный момент у меня есть только код, если скорость у отрицательна, затем исправьте вверх, но это заставляет игрока поднимать стены при ходьбе против одной, падая.
Моим первым решением было вычислить направлениеигрок возвращается, вычитая предыдущую позицию и блок, с которым он в данный момент сталкивается.Затем я «смотрю» в этом направлении на блоки у ног игроков, если я не нахожу их, полагаю, что игрок идет к стене, и поэтому вместо этого корректирую по горизонтали.Если я нахожу блоки, я полагаю, что игрок застрял в земле и исправил вверх.
Это текущий код столкновения:
private void checkCollision() {
if (this.world != null) {
if (this.collision) {
float length = (float) Math.sqrt(this.velocityX * this.velocityX + this.velocityY * this.velocityY + this.velocityZ * this.velocityZ);
if (length == 0.0) {
this.predictedX = this.posX;
this.predictedY = this.posY;
this.predictedZ = this.posZ;
} else {
float nVelocityX = this.velocityX / length;
float nVelocityY = this.velocityY / length;
float nVelocityZ = this.velocityZ / length;
float advancement = Math.min(1.0f, length);
float lengthLeft = Math.max(length - 1.0f, 0.0f);
boolean collided;
this.boundingBox.setCenterXY(this.posX, this.posY, this.posZ);
loop:
while (advancement <= length) {
this.predictedX = this.posX + nVelocityX * advancement;
this.predictedY = this.posY + nVelocityY * advancement;
this.predictedZ = this.posZ + nVelocityZ * advancement;
// Update bb
this.prevBoundingBox.set(this.boundingBox);
this.boundingBox.setCenterXY(this.predictedX, this.predictedY, this.predictedZ);
// Loop through all blocks the bounding box could collide with from top to bottom
for (int y = (int) Math.floor(this.boundingBox.getMaxY()); y >= Math.floor(this.boundingBox.getMinY()); y--) {
for (int x = (int) Math.floor(this.boundingBox.getMinX()); x < this.boundingBox.getMaxX(); x++) {
for (int z = (int) Math.floor(this.boundingBox.getMinZ()); z < this.boundingBox.getMaxZ(); z++) {
if (y >= 0 && y < IChunk.BLOCK_COUNT) {
collided = false;
IChunk chunk = this.world.getChunk(x >> ISection.BLOCK_TO_CHUNK, z >> ISection.BLOCK_TO_CHUNK, false);
Block block;
if (chunk != null) {
block = BlockRegistry.getBlock(chunk.getBlock(x, y, z));
} else {
block = BlockRegistry.getBlock(1);
}
if (block.isSolid()) {
RESULT.facing = null;
if (nVelocityY < 0.0f) {
int minX = x;
int maxX = x;
int minZ = z;
int maxZ = z;
float distX = this.prevBoundingBox.getMinX() - x;
float distZ = this.prevBoundingBox.getMinZ() - z;
if (distX < 0.0f) {
minX -= (int) Math.floor(this.width);
maxX--;
} else if (distX > 0.0f) {
minX++;
maxX += (int) Math.floor(this.width);
}
if (distZ < 0.0f) {
minZ -= (int) Math.floor(this.depth);
maxZ--;
} else if (distZ > 0.0f) {
minZ++;
maxZ += (int) Math.floor(this.depth);
}
int blockY = (int) Math.floor(this.prevBoundingBox.getMinY());
correction:
for (int blockX = minX; blockX <= maxX; blockX++) {
for (int blockZ = minZ; blockZ <= maxZ; blockZ++) {
if (blockX == x && blockZ == z) continue;
int blockId = this.world.getBlock(blockX, blockY, blockZ);
if (blockId != -1) {
block = BlockRegistry.getBlock(blockId);
if (block.isSolid()) {
RESULT.facing = Facing.TOP;
break correction;
}
}
}
}
} else if (nVelocityY > 0.0f && y > Math.floor(this.posY) + this.height && (chunk != null && !BlockRegistry.getBlock(chunk.getBlock(x, y - 1, z)).isSolid())) {
// entity is jumping against ceiling, correction downwards has priority
RESULT.facing = Facing.BOTTOM;
}
if (RESULT.facing == null) {
if (Math.abs(nVelocityX) > Math.abs(nVelocityZ)) {
// velocity on x axis is greater, correcting on x axis has priority
if (nVelocityX < 0.0f)
RESULT.facing = Facing.EAST;
else
RESULT.facing = Facing.WEST;
} else {
// velocity on z axis is greater, correcting on z axis has priority
if (nVelocityZ < 0.0f)
RESULT.facing = Facing.SOUTH;
else
RESULT.facing = Facing.NORTH;
}
}
System.out.println("Correction Facing: " + RESULT.facing.name());
OTHER.setPosition(x, y, z);
OTHER.setSize(1.0f, block.getHeight(), 1.0f);
collided = this.boundingBox.intersects(OTHER, RESULT);
}
if (collided) {
if (y == Math.floor(this.boundingBox.getMinY()) && RESULT.facing != Facing.TOP) {
// there is only collision at the feet -> stepup
this.predictedY = Math.floor(this.predictedY) + block.getHeight();
} else {
this.applyCorrection();
}
break loop;
}
}
}
}
}
if (advancement >= length) {
break;
}
lengthLeft = Math.max(lengthLeft - 1.0f, 0.0f);
advancement = length - lengthLeft;
}
}
} else {
this.predictedX = this.posX + this.velocityX;
this.predictedY = this.posY + this.velocityY;
this.predictedZ = this.posZ + this.velocityZ;
}
}
}
Чтобы игрок не попадал сквозь стены при быстром переходеЯ разделил движение на единицы вдоль оси скорости, пока вся скорость не будет обработана.Первое положительное обнаружение столкновения вызовет исправление и отменит любые дальнейшие проверки.
Если найден блок, который является сплошным, статические AABB устанавливаются в координатах блока, и выполняется проверка столкновения по нему.На основе ранее определенных условий поправка для оси рассчитывается и сохраняется в переменной RESULT.Само пересечение и коррекция работают, как и ожидалось.
Я не уверен, почему именно, но из-за этого игрок приземляется на землю на один тик при приземлении после ходьбы в стену, как показано в этом видео: https://streamable.com/ogtjk
Есть ли другой способ определить, иду ли я к стене или падаю в землю?