我一直在为我的体素游戏编程一个碰撞检测和响应系统,经过几次重写,我得出了一个相当稳定和可靠的解决方案。我现在遇到的问题是,我需要区分玩家是从地板上跌落还是走进墙壁。现在,我只有代码,如果y速度为负,然后向上校正,但这会导致玩家在跌倒时与墙壁相对时会出现毛刺。
我的第一个解决方案是通过减去先前的位置及其当前与之碰撞的方块来计算玩家的方向。然后,我朝这个方向“看”球员脚下的障碍物,如果找不到任何东西,我认为球员正在靠墙行走,因此应水平校正。如果我发现障碍物,则认为玩家卡在了地面并向上修正。
这是当前的碰撞代码:
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
还有其他方法可以检测我是靠墙行走还是掉入地下?