2017-05-26 19 views
5

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 đề:

gif of ledge issue

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:

  1. 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)

  2. 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.

  3. 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 ô đó.

  4. 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.

  5. 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:

See this image.

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:

gif showing why special case is needed

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; 
 
    } 
 
}

+0

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

Trả lời

1

Nó xảy ra bởi vì trước hết bạn phát hiện va chạm theo cả hai hướng và sau đó bạn điều chỉnh vị trí. "lên/xuống" được cập nhật đầu tiên (hướng của lực hấp dẫn). Điều chỉnh "trái/phải" đầu tiên sẽ chỉ làm cho vấn đề tồi tệ hơn (sau mỗi mùa thu bạn có thể được dịch chuyển sang phải hoặc trái).

duy nhất nhanh chóng và dơ bẩn sửa chữa tôi có thể đưa ra (hấp dẫn-bất biến):

  1. Tính sự va chạm của hai điểm có liên quan theo một hướng (ví dụ như khi đi lại duy nhất còn lại hai điểm quan trọng). Sau đó điều chỉnh tốc độ và vị trí theo hướng đó.

  2. Tính toán va chạm của hai điểm có liên quan (được điều chỉnh) theo một hướng khác. Điều chỉnh vị trí và tốc độ của directin đó khi va chạm.

  3. Nếu không có va chạm ở bước 1. thì bạn có thể tiếp tục thay đổi và trả lại. Nếu không tính khoảng cách dx + dy so với vị trí ban đầu trước bước 1.

  4. Lặp lại bước 1. đến 3. nhưng lần này bạn bắt đầu với hướng khác trước.

  5. Thực hiện thay đổi với khoảng cách nhỏ hơn (trừ khi bạn đã tìm thấy thay đổi tốt ở bước 3.).

EDIT: Ví dụ

sizes: sTile = 50, sPlayer = 20 
old position (fine, top-left corner): oX = 27, oY = 35 
speeds: vX = 7, vY = 10 
new position: x = oX + vX = 34, y = oY + vY = 45 => (34, 45) 
solid: tile at (50, 50) 

1.1. Checking x-direction, relevant points for positive vX are the ones to the right: 
    (54, 45) and (54, 65). The latter gives a conflict and we need to correct the 
    position to p1 = (30, 45) and speed v1 = (0, 10). 

2.1. Checking y-direction based on previous position, relevant points: (30, 65) and 
    (50, 65). There is no conflict, p1 and v1 remain unchanged. 

3.1. There was a conflict in step 1.1. so we cannot return the current result 
    immediately and have to calculate the distance d1 = 4 + 0 = 4. 

1.2. Checking y-direction first this time, relevant points: (34, 65) and (54, 65). 
    Because the latter gives a conflict we calculate p2 = (34, 30) and v2 = (7, 0). 

2.2. Checking x-direction based on step 1.2., relevant points: (54, 30) and (54, 50). 
    There is no conflict, p2 and v2 remain unchanged. 

3.2. Because there was a conflict in step 1.2. we calculate the distance d2 = 15. 

5. Change position and speed to p1 and v1 because d1 is smaller than d2. 
+0

Xin chào, cảm ơn bạn đã phản hồi! Thật không may là hành vi tương tự vẫn xảy ra khi tôi làm theo giải pháp của bạn. Có lẽ tôi hiểu lầm nó? Tôi đã thêm bài thực hiện của mình vào bài đăng đầu tiên. Nếu không, tôi có thể chuyển sang một thuật toán khác với nhau. – myohmywhoami

+0

Có vẻ như bạn đã nhầm lẫn. Bạn chỉ tách séc nhưng không tuân theo thuật toán như được mô tả ở trên. Về cơ bản bạn phải sao chép vị trí ban đầu hai lần, sau đó sửa một hướng x đầu tiên và hướng y khác đầu tiên (và điều chỉnh!). Dựa trên hai vị trí mới, bạn thực hiện kiểm tra hướng khác cho cả hai vị trí (và điều chỉnh). Bây giờ bạn tính toán khoảng cách cho bản gốc cho cả hai khả năng và lấy cái tốt hơn (trong trường hợp ví dụ của bạn một lần người chơi sẽ bị dịch chuyển sang trái và thời gian khác anh ta được di chuyển lên một khoảng cách lớn hơn, vì vậy nó sẽ hoạt động) – maraca

+0

@myohmywhoami thêm ví dụ. – maraca

Các vấn đề liên quan