Giải Quyết, xem dưới cùng của bài cho thuật toán chính thứcphát hiện va chạm không nên thực hiện đối tượng dịch chuyển lên
Bối cảnh: tôi đang làm việc trên một platformer 2D sử dụng JS và các yếu tố HTML canvas. Bản đồ cấp độ dựa trên gạch, nhưng người chơi không bị kẹp vào các ô xếp. Tôi đang sử dụng thuật toán phát hiện va chạm được nêu trong "Tiny Platformer" on Code inComplete. Nó chủ yếu hoạt động ngoại trừ một trường hợp cạnh (hoặc '' gờ '' trường hợp).
Vấn đề:
Người chơi đang giảm xuống và cũng di chuyển phải, vào tường. Khi nó rơi, nó dịch chuyển đến độ cao của gờ đá. Thay vào đó, người chơi sẽ rơi bình thường mà không dịch chuyển.
Có cách nào để thay đổi thuật toán để ngăn chặn hành vi này không? Nếu không, bạn có thể đề xuất một thuật toán phát hiện va chạm thay thế không? Lý tưởng nhất là bất kỳ sửa chữa nào sẽ không cho rằng người chơi luôn rơi xuống, bởi vì trong game, hướng rơi của người chơi sẽ chuyển đổi giữa lên/xuống/trái/phải.
Thuật toán:
vị trí mới của người chơi được tính toán giả sử không có va chạm. (Không được hiển thị trong mã bên dưới)
Chức năng được gọi là
getBorderTiles
lấy một đối tượng (trình phát) và trả về các ô chạm vào mỗi góc của 4 người chơi. Vì người chơi không lớn hơn một viên gạch, những viên gạch biên giới đó nhất thiết là gạch duy nhất mà người chơi đang chạm vào. Lưu ý rằng một số các ô này có thể giống nhau. Ví dụ: nếu trình phát chỉ chiếm một cột, các ô trên cùng bên trái/phải trên cùng sẽ giống nhau, cũng như các ô dưới cùng bên trái/dưới cùng bên phải. Nếu điều này xảy ra,getBorderTiles
vẫn trả về tất cả bốn ô, nhưng một số sẽ giống nhau.Nó sẽ kiểm tra các ô gạch biên giới này trong bản đồ cấp (một mảng 2D) để xem chúng có phải là khối rắn hay không. Nếu một lát là vật rắn, đối tượng sẽ va chạm với ô đó.
Nó kiểm tra lên/xuống/trái/phải va chạm. Nếu người chơi đang di chuyển xuống và va chạm với một ô xếp xuống nhưng không va chạm với ô xếp kề tương ứng, người chơi sẽ va chạm. Nếu người chơi đang di chuyển sang trái và va chạm với một ô bên trái nhưng không va chạm với ô bên phải tương ứng, nó sẽ va chạm trái. Vv Kiểm tra lên/xuống được thực hiện trước khi kiểm tra trái/phải. Các biến lưu trữ các ô biên giới được điều chỉnh nếu có một va chạm lên/xuống trước khi thực hiện kiểm tra trái/phải. Ví dụ nếu người chơi va chạm xuống, nó sẽ được đẩy vào các ô xếp lên, do đó, các ô BL/BR giờ giống với các ô TL/TR.
Tốc độ x, y và tốc độ của người chơi được điều chỉnh dựa trên các hướng mà nó đang va chạm.
Tại sao thuật toán thất bại:
Các gạch dưới bên phải là rắn nhưng phía trên bên phải không phải là, như vậy (bước 4) các cầu thủ va chạm xuống và (bước 5) nó bị đẩy lên. Ngoài ra, nó va chạm với gạch BR chứ không phải BL, vì vậy nó va chạm phải và bị đẩy sang trái. Cuối cùng, người chơi được kết xuất ngay phía trên và bên trái của gờ đá. Trong thực tế nó được dịch chuyển lên.
Cố gắng giải pháp: Tôi đã cố sửa lỗi này nhưng nó chỉ tạo ra một vấn đề khác. Tôi đã thêm một kiểm tra để người chơi chỉ va chạm với một ô nếu đó là khoảng cách nào đó trong ô đó (nói 3px). Nếu người chơi chỉ vừa mới trong ô BR, thuật toán sẽ không đăng ký một vụ va chạm xuống, do đó người chơi sẽ không dịch chuyển lên. Tuy nhiên, nếu người chơi rơi xuống mặt đất trong một kịch bản khác, nó sẽ không thừa nhận vụ va chạm cho đến khi người chơi nhận được khá xa vào mặt đất. Người chơi jittered khi nó rơi xuống đất, bị đẩy trở lại trên cùng của mặt đất, giảm một lần nữa, vv
Cảm ơn bạn đã đọc này đến nay. Tôi thực sự đánh giá cao phản hồi của bạn.
hiện tại đang thuật toán:
var borderTiles = getBorderTiles(object), //returns 0 (a falsy value) for a tile if it does not fall within the level
tileTL = borderTiles.topLeft,
tileTR = borderTiles.topRight,
tileBL = borderTiles.bottomLeft,
tileBR = borderTiles.bottomRight,
coordsBR = getTopLeftXYCoordinateOfTile(tileBR), //(x, y) coordinates refer to top left corner of tile
xRight = coordsBR.x, //x of the right tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles)
yBottom = coordsBR.y, //y of the bottom tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles)
typeTL = tileTL ? level.map[tileTL.row][tileTL.col] : -1, //if tileTL is in the level, gets its type, otherwise -1
typeTR = tileTR ? level.map[tileTR.row][tileTR.col] : -1,
typeBL = tileBL ? level.map[tileBL.row][tileBL.col] : -1,
typeBR = tileBR ? level.map[tileBR.row][tileBR.col] : -1,
collidesTL = typeTL == TILETYPE.SOLID, //true if the tile is solid
collidesTR = typeTR == TILETYPE.SOLID,
collidesBL = typeBL == TILETYPE.SOLID,
collidesBR = typeBR == TILETYPE.SOLID,
collidesUp = false,
collidesDown = false,
collidesLeft = false,
collidesRight = false;
//down and up
if (object.vy < 0 && ((collidesTL && !collidesBL) || (collidesTR && !collidesBR))) {
collidesUp = true;
/*The object is pushed out of the bottom row, so the bottom row is now the top row. Change the collides__
variables as this affects collision testing, but is it not necessary to change the tile__ variables. */
collidesTL = collidesBL;
collidesTR = collidesBR;
} else if (object.vy > 0 && ((collidesBL && !collidesTL) || (collidesBR && !collidesTR))) {
collidesDown = true;
/*The object is pushed out of the bottom row, so the bottom row is now the top row. Change the collides__
variables as this affects collision testing, but is it not necessary to change the tile__ variables. */
collidesBL = collidesTL;
collidesBR = collidesTR;
}
//left and right
if (object.vx < 0 && ((collidesTL && !collidesTR) || (collidesBL && !collidesBR))) {
collidesLeft = true;
} else if (object.vx > 0 && ((collidesTR && !collidesTL) || (collidesBR && !collidesBL))) {
collidesRight = true;
}
if (collidesUp) {
object.vy = 0;
object.y = yBottom;
}
if (collidesDown) {
object.vy = 0;
object.y = yBottom - object.height;
}
if (collidesLeft) {
object.vx = 0;
object.x = xRight;
}
if (collidesRight) {
object.vx = 0;
object.x = xRight - object.width;
}
UPDATE: giải quyết với giải pháp maraca của. Thuật toán dưới đây. Về cơ bản nó kiểm tra (x sau đó y) và giải quyết va chạm, và sau đó nó kiểm tra (y rồi x) và giải quyết xung đột theo cách đó. Bất kỳ kết quả thử nghiệm nào trong trình phát di chuyển một khoảng cách ngắn hơn là kết quả sẽ được sử dụng.
Điều thú vị là yêu cầu một trường hợp đặc biệt khi người chơi va chạm theo cả hướng trên cùng và bên trái. Có lẽ điều này liên quan đến thực tế là tọa độ (x, y) của người chơi nằm ở góc trên cùng bên trái của nó. Trong trường hợp này, các thử nghiệm mà kết quả trong các cầu thủ di chuyển một khoảng cách dài hơn nên được sử dụng. Rõ ràng trong gif này:
Người chơi là hộp đen và hộp màu vàng tượng trưng cho nơi người chơi sẽ được nếu nó đã sử dụng thử nghiệm khác (kiểm tra dẫn đến các cầu thủ di chuyển một còn khoảng cách). Lý tưởng nhất là người chơi không nên di chuyển vào tường, và thay vào đó nó sẽ là nơi hộp màu vàng. Vì vậy, trong kịch bản này, thử nghiệm khoảng cách dài hơn nên được sử dụng.
Đây là triển khai nhanh chóng và dơ bẩn. Nó không phải là ở tất cả tối ưu hóa, nhưng hy vọng nó cho thấy các bước của thuật toán khá rõ ràng.
function handleCollision(object) {
var borderTiles = getBorderTiles(object), //returns 0 (a falsy value) for a tile if it does not fall within the level
tileTL = borderTiles.topLeft,
tileTR = borderTiles.topRight,
tileBL = borderTiles.bottomLeft,
tileBR = borderTiles.bottomRight,
coordsBR = getTopLeftXYCoordinateOfTile(tileBR), //(x, y) coordinates refer to top left corner of tile
xRight = coordsBR.x, //x of the right tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles)
yBottom = coordsBR.y, //y of the bottom tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles)
typeTL = tileTL ? level.map[tileTL.row][tileTL.col] : -1, //if tileTL is in the level, gets its type, otherwise -1
typeTR = tileTR ? level.map[tileTR.row][tileTR.col] : -1,
typeBL = tileBL ? level.map[tileBL.row][tileBL.col] : -1,
typeBR = tileBR ? level.map[tileBR.row][tileBR.col] : -1,
collidesTL = typeTL == TILETYPE.SOLID, //true if the tile is solid
collidesTR = typeTR == TILETYPE.SOLID,
collidesBL = typeBL == TILETYPE.SOLID,
collidesBR = typeBR == TILETYPE.SOLID,
collidesUp = false,
collidesDown = false,
collidesLeft = false,
collidesRight = false,
originalX = object.x, //the object's coordinates have already been adjusted according to its velocity, but not according to collisions
originalY = object.y,
px1 = originalX,
px2 = originalX,
py1 = originalY,
py2 = originalY,
vx1 = object.vx,
vx2 = object.vx,
vy1 = object.vy,
vy2 = object.vy,
d1 = 0,
d2 = 0,
conflict1 = false,
conflict2 = false,
tempCollidesTL = collidesTL,
tempCollidesTR = collidesTR,
tempCollidesBL = collidesBL,
tempCollidesBR = collidesBR;
//left and right
//step 1.1
if (object.vx > 0) {
if (collidesTR || collidesBR) {
vx1 = 0;
px1 = xRight - object.width;
conflict1 = true;
tempCollidesTR = false;
tempCollidesBR = false;
}
}
if (object.vx < 0) {
if (collidesTL || collidesBL) {
vx1 = 0;
px1 = xRight;
conflict1 = true;
tempCollidesTL = false;
tempCollidesBL = false;
collidesLeft = true;
}
}
//step 2.1
if (object.vy > 0) {
if (tempCollidesBL || tempCollidesBR) {
vy1 = 0;
py1 = yBottom - object.height;
}
}
if (object.vy < 0) {
if (tempCollidesTL || tempCollidesTR) {
vy1 = 0;
py1 = yBottom;
collidesUp = true;
}
}
//step 3.1
if (conflict1) {
d1 = Math.abs(px1 - originalX) + Math.abs(py1 - originalY);
} else {
object.x = px1;
object.y = py1;
object.vx = vx1;
object.vy = vy1;
return; //(the player's x and y position already correspond to its non-colliding values)
}
//reset the tempCollides variables for another runthrough
tempCollidesTL = collidesTL;
tempCollidesTR = collidesTR;
tempCollidesBL = collidesBL;
tempCollidesBR = collidesBR;
//step 1.2
if (object.vy > 0) {
if (collidesBL || collidesBR) {
vy2 = 0;
py2 = yBottom - object.height;
conflict2 = true;
tempCollidesBL = false;
tempCollidesBR = false;
}
}
if (object.vy < 0) {
if (collidesTL || collidesTR) {
vy2 = 0;
py2 = yBottom;
conflict2 = true;
tempCollidesTL = false;
tempCollidesTR = false;
}
}
//step 2.2
if (object.vx > 0) {
if (tempCollidesTR || tempCollidesBR) {
vx2 = 0;
px2 = xRight - object.width;
conflict2 = true;
}
}
if (object.vx < 0) {
if (tempCollidesTL || tempCollidesTL) {
vx2 = 0;
px2 = xRight;
conflict2 = true;
}
}
//step 3.2
if (conflict2) {
d2 = Math.abs(px2 - originalX) + Math.abs(py2 - originalY);
console.log("d1: " + d1 + "; d2: " + d2);
} else {
object.x = px1;
object.y = py1;
object.vx = vx1;
object.vy = vy1;
return;
}
//step 5
//special case: when colliding with the ceiling and left side (in which case the top right and bottom left tiles are solid)
if (collidesTR && collidesBL) {
if (d1 <= d2) {
object.x = px2;
object.y = py2;
object.vx = vx2;
object.vy = vy2;
} else {
object.x = px1;
object.y = py1;
object.vx = vx1;
object.vy = vy1;
}
return;
}
if (d1 <= d2) {
object.x = px1;
object.y = py1;
object.vx = vx1;
object.vy = vy1;
} else {
object.x = px2;
object.y = py2;
object.vx = vx2;
object.vy = vy2;
}
}
Lưu ý rằng có cũng xuống dịch chuyển xảy ra khi trong nửa dưới của chiều cao ngói. Vấn đề sẽ còn nghiêm trọng hơn nếu gạch bạn đang hạ cánh trong ví dụ bị thiếu, sau đó bạn sẽ được dịch chuyển xuống vị trí giữa không trung với tốc độ 0 và sau đó di chuyển một chút sang bên trái, sau đó lực hấp dẫn sẽ mất hơn một lần nữa tôi đoán. – maraca