AABB 2D và giải quyết nhiều va chạm


8

Được rồi, vì vậy đây là một vấn đề tôi đã cố gắng tìm ra khá lâu. Mine là một trò chơi platformer 2D với một thế giới được tạo thành từ (thường) các ô bất động và các họa tiết di động, cả hai đều sử dụng AABB để thể hiện các hitbox của họ. Trò chơi này KHÔNG dựa trên lưới do một số biến chứng với việc di chuyển các lớp gạch.

Tôi có thể phát hiện va chạm và dễ dàng tìm ra độ sâu của vụ va chạm. Tôi sử dụng "phương pháp trục nông nhất" để xác định cách giải quyết va chạm giữa sprite và gạch. Nếu sprite sâu hơn theo chiều ngang so với chiều dọc, hướng để giải quyết là lên hoặc xuống. Nếu sprite sâu hơn theo chiều dọc so với chiều ngang, hướng để giải quyết là trái hoặc phải.

Sơ đồ số 1

Điều này là đủ đơn giản, và nó hoạt động khá tốt. Đó là, cho đến khi bạn có một sprite va chạm với nhiều hơn một ô. Vì, về bản chất, mỗi va chạm phải được kiểm tra riêng, các va chạm khác nhau có thể có hướng giải quyết khác nhau. Ví dụ: nếu một sprite đang cố gắng đi qua một hàng gạch, đối với một khung hình, chúng sẽ giao nhau với ô tiếp theo như vậy độ sâu ngang ngắn hơn độ sâu dọc. Khi va chạm nói "giải quyết trái", nó sẽ bị đẩy lùi và bị kẹt ở góc.

Sơ đồ # 2

Tôi đã nghiên cứu vấn đề này nhiều lần, trong một thời gian và một số giải pháp đã đến với tôi, nhưng tất cả đều có sai sót. Tôi có thể đánh dấu một số mặt nhất định là không thể truy cập được, nhưng không có động cơ dựa trên lưới, việc xác định "không thể truy cập" rất phức tạp, đặc biệt là với các lớp gạch di chuyển luôn là một khả năng.

Một phương pháp khả thi khác là dự đoán va chạm trước khi chúng xảy ra và "hoạt động trở lại" chuyển động đến điểm va chạm, tôi cho rằng, nhưng tôi không chắc toán học trên đó hoạt động như thế nào.

Tôi cảm thấy rằng tôi đang thiếu một cái gì đó cực kỳ rõ ràng, đặc biệt là vì các trò chơi từ những năm 80 đã giải quyết được vấn đề này.


Bạn chỉ có thể thay đổi vị trí của người chơi dựa trên ô nào xuất hiện đầu tiên trong séc của bạn
Chachmu

Câu trả lời:


6

Vấn đề

Vấn đề nằm ở phương pháp giải quyết va chạm của bạn. Phương pháp của bạn diễn ra như sau:

  1. Di chuyển người chơi.
  2. Kiểm tra va chạm.
  3. Xác định độ sâu va chạm ngắn nhất.
  4. Giải quyết va chạm.

Vấn đề với điều này, là nó có thể dễ dàng di chuyển người chơi sai hướng. Bạn có thể thấy điều này có thể xảy ra trong hình ảnh dưới đây:

Lỗi va chạm

Bởi vì người chơi đang di chuyển xuống bên phải, và ở trên mặt đất, bạn sẽ mong đợi người chơi hạ cánh trên mặt đất (bằng ô màu xanh lá cây). Nhưng thay vào đó, nó bị đẩy ra khỏi mặt đất bên trái (đại diện bởi hộp màu đỏ). Đây có thể là một vấn đề nếu người chơi đang cố gắng nhảy từ nền tảng này sang nền tảng khác, bởi vì người chơi cuối cùng có thể rơi vào cái chết của mình do mã va chạm xấu.

Giải pháp

Giải pháp cho vấn đề này thực sự khá đơn giản. Thay vì sử dụng phương pháp trên, bạn giải quyết xung đột như vậy:

  1. Di chuyển người chơi dọc theo trục X.
  2. Kiểm tra va chạm gạch.
  3. Giải quyết va chạm X.
  4. Di chuyển người chơi dọc theo trục Y.
  5. Kiểm tra va chạm gạch.
  6. Giải quyết va chạm Y.

Bây giờ tôi hy vọng rằng bạn đã không ném mã kiểm tra độ sâu của mình đi, bởi vì bạn vẫn sẽ cần nó cho các bước 3 và 6.

Để giải quyết va chạm giữa các ô trên một trong hai trục (sau khi di chuyển người chơi), trước tiên bạn phải lấy độ sâu của va chạm. Sau đó, bạn lấy độ sâu của va chạm và trừ đi từ các trục mà bạn hiện đang kiểm tra va chạm. Lưu ý rằng độ sâu phải âm nếu bạn di chuyển sang trái, để người chơi di chuyển theo hướng phải.

Sử dụng phương pháp này, bạn không những không phải lo lắng về các lỗi va chạm như lỗi trong kịch bản trong hình trên mà phương pháp này còn có thể xử lý xung đột với nhiều ô.

Mã ví dụ:

void move(velocity)
{
    top = player.y / TILE_HEIGHT;
    bottom = top + (player.height / TILE_HEIGHT);
    left = player.x / TILE_WIDTH;
    right = left + (player.width / TILE_WIDTH);

    // Check X

    player.x += velocity.x;
    player.updateAABB();
    for(int tx = left - 1; tx <= right + 1; tx++)
    {
        for(int ty = top - 1; ty <= bottom + 1; ty++)
        {
            aabb = world.getTileAABB(tx, ty);
            if(aabb.collidesWith(player.aabb))
            {
                depth = player.aabb.getXDepth(aabb);
                player.x -= depth;
            }
        }
    }

    // Now check Y

    player.y += velocity.y;
    player.updateAABB();
    for(int tx = left - 1; tx <= right + 1; tx++)
    {
        for(int ty = top - 1; ty <= bottom + 1; ty++)
        {
            aabb = world.getTileAABB(tx, ty);
            if(aabb.collidesWith(player.aabb))
            {
                depth = player.aabb.getYDepth(aabb);
                player.y -= depth;
            }
        }
    }

    player.updateAABB();
}

Hấp dẫn, nhưng tôi vẫn thấy một vấn đề. Trong kịch bản thứ hai của tôi, sprite va chạm với một hàng gạch. Nếu tôi kiểm tra va chạm X trước, sẽ có một phát hiện không chính xác trong kịch bản đó và nó vẫn sẽ được giải quyết ở bên trái không đúng.
Celarix

@Celarix Kịch bản thứ hai không nên xảy ra vì bạn không chỉ kiểm tra trục X trước, bạn sẽ di chuyển dọc theo nó trước. Sprite sẽ không bao giờ nằm ​​trong một hàng gạch, bởi vì thử nghiệm va chạm Y từ chuyển động trước đó sẽ ngăn bạn va chạm với một hàng gạch như thế. Chỉ cần chắc chắn rằng va chạm luôn được giải quyết đúng cách. Tôi đã từng có một số vấn đề được gây ra bởi thực tế là tôi đang sử dụng floats để lưu trữ tọa độ của mình. Vì vậy, nó đã gây ra sự rung chuyển. Giải pháp là làm tròn các coords khi tôi giải quyết xong va chạm.
Lysol

Bạn nói đúng, và tôi nghĩ rằng đây có thể là giải pháp cho vấn đề của tôi trong gần hai năm. (Tôi là một nhà phát triển chậm.) Cảm ơn bạn rất nhiều!
Celarix

Có một số vấn đề với câu trả lời của tôi. Nó hoạt động trong hầu hết các tình huống, nhưng lưu ý rằng sẽ có các kết quả khác nhau tùy thuộc vào việc bạn kiểm tra va chạm X trước hay va chạm Y trước. Cũng nên nhớ rằng đường hầm là một vấn đề; điều này sẽ thất bại với các vật thể có vận tốc cao vì chúng sẽ bỏ qua các ô.
Lysol

Tôi nghĩ rằng tôi đã đi với X trước để dốc sẽ hoạt động tốt. Bullet-through-paper thực sự không phải là vấn đề trong platformer của tôi bởi vì không có gì di chuyển đủ nhanh để vượt qua các ô. Cảm ơn cho đầu vào thêm!
Celarix

0

Bạn đang xem xét vấn đề và giải quyết một vài vấn đề. Nhưng điều đó không sao bởi vì, như bạn đã nói, đây là một vấn đề rất được giải quyết với rất nhiều câu trả lời tuyệt vời ngoài kia.

Hãy phá vỡ nó:

  1. Cá rô phi . Ví dụ đầu tiên của bạn là một sprite đi ngang qua một loạt các viên gạch được đặt theo chiều ngang (hoặc trượt xuống một bức tường gạch được đặt theo chiều dọc, chúng là đẳng cấu). Một giải pháp rất hay cho vấn đề này là đơn giản là không kiểm tra các cạnh của gạch mà chúng ta biết rằng một sprite không thể có được, chẳng hạn như các cạnh "ngầm" hoặc các cạnh viền một gạch hoàn toàn khác.

    Bạn đúng rằng sprite sẽ giảm xuống do trọng lực, sau đó di chuyển sang bên, sau đó bị kẹt ... nhưng câu trả lời là không quan tâm đến các cạnh trái hoặc phải của gạch nằm dưới lòng đất . Bằng cách đó, thói quen giải quyết va chạm của bạn chỉ di chuyển sprite theo chiều dọc - và sprite của bạn có thể đi theo cách vui vẻ của nó.

    Kiểm tra các hướng dẫn gạch Metanet để được giải thích từng bước về điều này. Bạn nói trong câu hỏi của bạn rằng bạn không sử dụng một tilemap truyền thống, nhưng điều đó cũng không sao: các ô tĩnh nằm trong tilemap và cập nhật như trên, trong khi di chuyển các nền tảng và cập nhật như # 2 bên dưới.

  2. Các AABB khác . Bạn sẽ chỉ gặp vấn đề nếu trong một khung hình duy nhất, sprite của bạn có thể di chuyển một khoảng cách lớn hơn chiều rộng / chiều cao của hầu hết các AABB trong trò chơi của bạn. Nếu không thể, bạn là vàng: giải quyết từng vụ va chạm và nó sẽ hoạt động tốt.

    Nếu các AABB có thể di chuyển rất nhanh trong một khung hình duy nhất thì bạn nên "quét" chuyển động khi kiểm tra va chạm: chia nhỏ chuyển động thành các phân số nhỏ hơn và kiểm tra va chạm ở mỗi bước.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.