Masalah tabrakan 2D Platformer AABB

51

http://dl.dropbox.com/u/3724424/Programming/Gifs/game6.gif

Saya memiliki masalah dengan resolusi tabrakan AABB.


Saya menyelesaikan persimpangan AABB dengan menyelesaikan sumbu X terlebih dahulu, lalu sumbu Y. Ini dilakukan untuk mencegah bug ini: http://i.stack.imgur.com/NLg4j.png


Metode saat ini berfungsi dengan baik ketika suatu benda bergerak ke pemain dan pemain harus didorong secara horizontal. Seperti yang dapat Anda lihat di .gif, paku horizontal mendorong pemain dengan benar.


Ketika paku vertikal bergerak ke pemain, bagaimanapun, sumbu X masih diselesaikan terlebih dahulu. Ini membuat "menggunakan paku sebagai tumpangan" tidak mungkin.

Ketika pemain bergerak ke paku vertikal (dipengaruhi oleh gravitasi, jatuh ke dalamnya), dia mendorong sumbu Y, karena tidak ada tumpang tindih pada sumbu X untuk memulai.


Sesuatu yang saya coba adalah metode yang dijelaskan dalam jawaban pertama tautan ini: Deteksi tabrakan objek persegi panjang 2D

Namun paku dan objek bergerak bergerak dengan mengubah posisi, bukan kecepatan, dan saya tidak menghitung posisi prediksi berikutnya hingga metode Pembaruan () mereka dipanggil. Tak perlu dikatakan solusi ini juga tidak berhasil. :(


Saya perlu memecahkan tabrakan AABB dengan cara yang kedua kasus yang dijelaskan di atas berfungsi sebagaimana dimaksud.

Ini adalah kode sumber tabrakan saya saat ini: http://pastebin.com/MiCi3nA1

Saya akan sangat berterima kasih jika seseorang dapat melihat ini, karena bug ini telah ada di mesin sejak awal, dan saya telah berjuang untuk menemukan solusi yang baik, tanpa hasil. Ini serius membuat saya menghabiskan malam melihat kode tabrakan dan mencegah saya untuk sampai ke "bagian yang menyenangkan" dan coding logika permainan :(


Saya mencoba menerapkan sistem tabrakan yang sama seperti pada demo platformer AppHub XNA (dengan menyalin-menempelkan sebagian besar barang). Namun bug "jumping" muncul di gim saya, sementara itu tidak muncul di demo AppHub. [bug melompat: http://i.stack.imgur.com/NLg4j.png ]

Untuk melompat saya memeriksa apakah pemain "onGround", lalu tambahkan -5 ke Velocity.Y.

Karena Velocity.X pemain lebih tinggi dari Velocity.Y (lihat panel keempat dalam diagram), onGround diatur ke true ketika seharusnya tidak, dan dengan demikian memungkinkan pemain melompat di udara.

Saya percaya ini tidak terjadi dalam demo AppHub karena Velocity.X pemain tidak akan pernah lebih tinggi dari Velocity.Y, tapi saya mungkin salah.

Saya memecahkan ini sebelumnya dengan menyelesaikan pada sumbu X terlebih dahulu, kemudian pada sumbu Y. Tapi itu mengacaukan tabrakan dengan paku seperti yang saya sebutkan di atas.

Vittorio Romeo
sumber
5
Sebenarnya tidak ada yang salah. Metode yang saya gunakan mendorong pada sumbu X pertama, kemudian pada sumbu Y. Itu sebabnya pemain didorong secara horizontal bahkan pada paku vertikal. Saya perlu menemukan solusi yang menghindari "masalah melompat" dan mendorong pemain pada sumbu dangkal (sumbu dengan penetrasi paling sedikit), tetapi saya tidak dapat menemukannya.
Vittorio Romeo
1
Anda bisa mendeteksi wajah obstruksi yang disentuh pemain, dan atasi yang itu
Ioachim
4
@Johnathan Hobbs, baca pertanyaannya. Vee tahu persis apa yang dilakukan kodenya, tetapi tidak tahu bagaimana menyelesaikan masalah tertentu. Melangkah melalui kode tidak akan membantunya dalam situasi ini.
AttackingHobo
4
@Maik @ Jonathan: Tidak ada yang "salah" dengan program - dia mengerti persis di mana dan mengapa algoritmenya tidak melakukan apa yang dia inginkan. Dia hanya tidak tahu bagaimana mengubah algoritma untuk melakukan apa yang dia inginkan. Jadi debugger tidak berguna dalam hal ini.
BlueRaja - Danny Pflughoeft
1
@AttackingHobo @BlueRaja Saya setuju dan saya sudah menghapus komentar saya. Itu saya hanya melompat ke kesimpulan tentang apa yang sedang terjadi. Saya benar-benar minta maaf karena membuat Anda harus menjelaskan hal itu dan telah menyesatkan setidaknya satu orang. Jujur, Anda dapat mengandalkan saya untuk benar-benar menyerap pertanyaan sebelum saya meninggalkan jawaban lain kali.
doppelgreener

Jawaban:

9

OK, saya menemukan mengapa demo platformer XNA AppHub tidak memiliki bug "melompat": demo menguji ubin tabrakan dari atas ke bawah . Ketika berhadapan dengan "dinding" pemain mungkin tumpang tindih beberapa ubin. Urutan resolusi penting karena menyelesaikan satu tabrakan juga dapat menyelesaikan tabrakan lainnya (tetapi dalam arah yang berbeda). The onGroundproperti hanya mengatur kapan tabrakan diselesaikan dengan mendorong pemain pada sumbu y. Resolusi ini tidak akan terjadi jika resolusi sebelumnya mendorong pemain ke bawah dan / atau secara horizontal.

Saya dapat mereproduksi bug "melompat" di demo XNA dengan mengubah baris ini:

for (int y = topTile; y <= bottomTile; ++y)

untuk ini:

for (int y = bottomTile; y >= topTile; --y)

(Saya juga mengubah beberapa konstanta yang berhubungan dengan fisika, tetapi ini seharusnya tidak menjadi masalah.)

Mungkin mengurutkan bodiesToCheckpada sumbu y sebelum menyelesaikan tabrakan di game Anda akan memperbaiki bug "melompat". Saya menyarankan untuk menyelesaikan tabrakan pada sumbu penetrasi "dangkal", seperti yang ditunjukkan oleh demo XNA dan Trevor . Juga perhatikan pemutar demo XNA dua kali lebih tinggi dari ubin bertabrakan, membuat kasus tabrakan ganda lebih mungkin.

Leftium
sumber
Ini kedengarannya menarik ... Apakah menurut Anda memesan semuanya oleh koordinat Y sebelum mendeteksi tabrakan dapat menyelesaikan semua masalah saya? Apakah ada tangkapan?
Vittorio Romeo
Aaa dan aku menemukan "tangkapan". Dengan menggunakan metode ini, bug jumping diperbaiki, tetapi jika saya mengatur Velocity.Y ke 0 setelah mencapai langit-langit, itu terjadi lagi.
Vittorio Romeo
@Vee: atur Position = nextPositionlangsung di dalam forloop, jika tidak resolusi tabrakan yang tidak diinginkan (pengaturan onGround) masih terjadi. Pemain harus didorong ke bawah secara vertikal (dan tidak pernah naik) ketika mengenai langit-langit sehingga onGroundtidak boleh diatur. Ini adalah bagaimana demo XNA melakukannya, dan saya tidak dapat mem-repro bug "plafon" di sana.
Leftium
pastebin.com/TLrQPeBU - ini adalah kode saya: Saya benar-benar bekerja pada Posisi itu sendiri, tanpa menggunakan variabel "nextPosition". Seharusnya berfungsi seperti di demo XNA, tetapi jika saya menjaga garis yang mengatur Velocity.Y ke 0 (baris 57) tidak dikomando maka bug jumping terjadi. Jika saya menghapusnya, pemain terus mengambang ketika melompat ke langit-langit. Bisakah ini diperbaiki?
Vittorio Romeo
@Vee: kode Anda tidak menunjukkan bagaimana onGrounddiatur, jadi saya tidak bisa menyelidiki mengapa lompatan tidak diizinkan. Demo XNA memperbarui isOnGroundproperti analognya di dalam HandleCollisions(). Juga, setelah diatur Velocity.Yke 0, mengapa gravitasi tidak mulai menggerakkan pemain ke bawah, lagi? Dugaan saya adalah onGroundproperti tidak disetel dengan benar. Lihatlah bagaimana demo XNA memperbarui previousBottomdan isOnGround( IsOnGround).
Leftium
9

Solusi paling sederhana bagi Anda adalah memeriksa kedua arah tumbukan terhadap setiap objek di dunia sebelum menyelesaikan setiap tumbukan, dan hanya menyelesaikan yang lebih kecil dari dua "tumbukan gabungan" yang dihasilkan. Ini berarti bahwa Anda menyelesaikan dengan jumlah sekecil mungkin, alih-alih selalu menyelesaikan x pertama, atau selalu menyelesaikan y pertama.

Kode Anda akan terlihat seperti ini:

// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
  float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
  float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
  bool xIsColliding = (xDistanceToResolve != 0.f);

  // if we aren't colliding on x (not possible for normal solid collision 
  // shapes, but can happen for unidirectional collision objects, such as 
  // platforms which can be jumped up through, but support the player from 
  // above), or if a correction along y would simply require a smaller move 
  // than one along x, then resolve our collision by moving along y.

  if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
  {
    object->Move( 0.f, yDistanceToResolve );
  }
  else // otherwise, resolve the collision by moving along x
  {
    object->Move( xDistanceToResolve, 0.f );
  }
}

revisi besar : Dari membaca komentar tentang jawaban lain, saya pikir saya akhirnya memperhatikan asumsi yang tidak dinyatakan, yang akan menyebabkan pendekatan ini tidak berfungsi (dan yang menjelaskan mengapa saya tidak dapat memahami masalah yang sebagian - tetapi tidak semua - orang melihat dengan pendekatan ini). Untuk menguraikan, ini ada beberapa pseudocode, yang menunjukkan lebih jelas apa fungsi yang saya rujuk sebelumnya seharusnya lakukan:

bool StillCollidingWithSomething( MovingObject object )
{
  // loop over every collision object in the world.  (Implementation detail:
  // don't test 'object' against itself!)
  for( int i = 0; i < collisionObjectCount; i++ )
  {
    // if the moving object overlaps any collision object in the world, then
    // it's colliding
    if ( Overlaps( collisionObject[i], object ) )
      return true;
  }
  return false;
}

float XDistanceToMoveToResolveCollisions( MovingObject object )
{
  // check how far we'd have to move left or right to stop colliding with anything
  // return whichever move is smaller
  float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
  float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
  float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );

  return minimumMove;
}

float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
  Vector2D cursor = position;
  bool colliding = true;
  // until we stop colliding...
  while ( colliding )
  {
    colliding = false;
    // loop over all collision objects...
    for( int i = 0; i < collisionObjectCount; i++ )
    {
      // and if we hit an object...
      if ( Overlaps( collisionObject[i], cursor ) )
      {
        // move outside of the object, and repeat.
        cursor.x = collisionObject[i].rightSide;
        colliding = true;

        // break back to the 'while' loop, to re-test collisions with
        // our new cursor position
        break;
      }
    }
  }
  // return how far we had to move, to reach empty space
  return cursor.x - position.x;  
}

Ini bukan tes "per objek pasangan"; itu tidak bekerja dengan menguji dan menyelesaikan objek yang bergerak terhadap setiap ubin peta dunia secara individual (pendekatan itu tidak akan pernah bekerja dengan andal, dan gagal dengan cara yang semakin dahsyat ketika ukuran ubin berkurang). Sebaliknya, ia menguji objek bergerak terhadap setiap objek di peta dunia secara bersamaan, dan kemudian menyelesaikan berdasarkan tabrakan terhadap seluruh peta dunia.

Ini adalah bagaimana Anda memastikan bahwa (misalnya) ubin dinding individu di dalam dinding tidak pernah memantul pemain naik dan turun di antara dua ubin yang berdekatan, mengakibatkan pemain terjebak di beberapa ruang 'antara' mereka yang tidak ada; jarak resolusi tabrakan selalu dihitung sampai ruang kosong di dunia, tidak hanya untuk batas ubin tunggal yang kemudian mungkin memiliki ubin padat lain di atasnya.

Trevor Powell
sumber
1
i.stack.imgur.com/NLg4j.png - ini terjadi jika saya menggunakan metode di atas.
Vittorio Romeo
1
Mungkin saya tidak memahami kode Anda dengan benar, tetapi izinkan saya menjelaskan masalah dalam diagram: karena pemain lebih cepat pada sumbu X, penetrasi X>> daripada penetrasi Y. Collision memilih sumbu Y (karena penetrasi lebih kecil) dan mendorong pemain secara vertikal. Ini tidak terjadi jika pemain cukup lambat sehingga penetrasi Y selalu> dari penetrasi X.
Vittorio Romeo
1
@ Ve Panel mana dari diagram yang Anda maksudkan, di sini, sebagai memberikan perilaku yang salah menggunakan pendekatan ini? Saya mengasumsikan panel 5, di mana pemain jelas menembus kurang dalam X daripada di Y, yang akan mengakibatkan didorong keluar sepanjang sumbu X - yang merupakan perilaku yang benar. Anda hanya mendapatkan perilaku buruk jika Anda memilih sumbu untuk resolusi berdasarkan kecepatan (seperti pada gambar Anda), dan tidak berdasarkan pada penetrasi aktual (seperti yang saya sarankan).
Trevor Powell
1
@ Trevor: Ah, saya mengerti apa yang Anda katakan. Dalam hal ini, Anda bisa membuatnyaif(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja - Danny Pflughoeft
1
@BlueRaja Poin bagus. Saya telah mengedit jawaban saya untuk menggunakan lebih sedikit persyaratan, seperti yang Anda sarankan. Terima kasih!
Trevor Powell
6

Sunting

Saya telah memperbaikinya

Sepertinya hal kunci yang saya ubah adalah mengklasifikasikan persimpangan.

Persimpangan adalah:

  • Gundukan langit-langit
  • Serangan darat (mendorong keluar dari lantai)
  • Tabrakan dinding udara
  • Tabrakan dinding yang diarde

dan saya menyelesaikannya dalam urutan itu

Tetapkan tabrakan tanah sebagai pemain setidaknya 1/4 jalan di ubin

Jadi ini adalah tabrakan tanah dan pemain ( biru ) akan duduk di atas ubin ( hitam ) tabrakan tanah

Tapi ini BUKAN tabrakan tanah dan pemain akan "tergelincir" di sisi kanan ubin ketika dia mendarat di atasnya tidak gonig untuk menjadi pemain tabrakan tanah akan lewat

Dengan metode ini pemain tidak akan lagi terjebak di sisi dinding

bobobobo
sumber
Saya mencoba metode Anda (lihat implementasi saya: pastebin.com/HarnNnnp ) tetapi saya tidak tahu di mana mengatur Velocity pemain menjadi 0. Saya mencoba mengaturnya ke 0 jika encrY <= 0 tetapi yang memungkinkan pemain berhenti di udara sambil meluncur di sepanjang dinding dan juga memungkinkannya berulang kali melompati dinding.
Vittorio Romeo
Namun, ini berfungsi dengan benar ketika pemain berinteraksi dengan paku (seperti yang ditunjukkan pada .gif). Saya akan sangat menghargai jika Anda dapat membantu saya memecahkan masalah melompati-dinding (juga tersangkut di dinding). Perlu diingat bahwa dinding saya terbuat dari beberapa ubin AABB.
Vittorio Romeo
Maaf telah memposting komentar lain, tetapi setelah menyalin kode Anda di proyek baru, mengubah sp menjadi 5 dan membuat ubin muncul dalam bentuk dinding, bug lompatan terjadi (lihat diagram ini: i.stack.imgur.com /NLg4j.png)
Vittorio Romeo
Ok, coba sekarang.
bobobobo
+1 untuk contoh kode yang berfungsi (tidak ada blok "lonjakan" yang dipindahkan secara horizontal :)
Leftium
3

Forenote:
Mesin saya menggunakan kelas deteksi tabrakan yang memeriksa tabrakan dengan semua objek secara real time, hanya ketika suatu objek bergerak, dan hanya di kotak kisi yang saat ini ditempati.

Solusi Saya:

Ketika saya berlari melawan masalah seperti ini di platformer 2d saya, saya melakukan sesuatu seperti berikut:

- (mesin mendeteksi kemungkinan tabrakan, dengan cepat)
- (permainan memberi tahu engine untuk memeriksa tabrakan yang tepat untuk objek tertentu) [mengembalikan vektor objek *]
- (objek melihat kedalaman penetrasi, serta posisi sebelumnya relatif terhadap posisi sebelumnya objek lain, untuk menentukan sisi mana yang akan digeser keluar)
- (objek bergerak, dan rata-rata kecepatannya dengan kecepatan objek (jika objek bergerak))

ultifinitus
sumber
"(objek melihat kedalaman penetrasi, serta posisi sebelumnya relatif terhadap posisi sebelumnya objek lain, untuk menentukan sisi mana yang akan dilepaskan)" ini adalah bagian yang saya minati. Dapatkah Anda menguraikan? Apakah berhasil dalam situasi yang ditunjukkan oleh .gif dan diagram?
Vittorio Romeo
Saya belum menemukan situasi (selain kecepatan tinggi) bahwa metode ini tidak berhasil.
ultifinitus
Kedengarannya bagus, tetapi bisakah Anda benar-benar menjelaskan bagaimana Anda menyelesaikan penetrasi? Apakah Anda selalu memecahkan pada sumbu terkecil? Apakah Anda memeriksa sisi tabrakan?
Vittorio Romeo
Sebenarnya, sumbu yang lebih kecil bukanlah cara yang baik (utama). MENURUT OPINI SAYA. Saya biasanya menyelesaikan berdasarkan sisi mana yang sebelumnya di luar sisi objek lain, itu yang paling umum. Ketika itu gagal, maka saya periksa berdasarkan kecepatan dan kedalaman.
ultifinitus
2

Dalam permainan video saya telah memprogram pendekatannya adalah untuk memiliki fungsi yang mengatakan apakah posisi Anda valid, yaitu bool ValidPos (int x, int y, int wi, int he);

Fungsi memeriksa kotak pembatas (x, y, wi, he) terhadap semua geometri dalam game dan mengembalikan false jika ada persimpangan.

Jika Anda ingin bergerak, katakan ke kanan, ambil posisi pemain, tambahkan 4 ke x dan periksa apakah posisi itu valid, jika tidak, periksa dengan + 3, + 2 dll hingga benar.

Jika Anda juga perlu memiliki gravitasi, Anda memerlukan variabel yang tumbuh selama Anda tidak menyentuh tanah (memukul tanah: ValidPos (x, y +1, wi, he) == true, y positif di bawah sini). jika Anda dapat memindahkan jarak itu (mis. ValidPos (x, y + gravity, wi, he) mengembalikan true) Anda jatuh, kadang-kadang berguna ketika Anda seharusnya tidak dapat mengontrol karakter Anda saat jatuh.

Sekarang, masalah Anda adalah bahwa Anda memiliki benda-benda di dunia Anda yang bergerak sehingga pertama-tama Anda perlu memeriksa apakah posisi lama Anda masih valid!

Jika tidak, Anda perlu menemukan posisi itu. Jika objek ingame tidak dapat bergerak lebih cepat daripada mengatakan 2 piksel per revolusi game, Anda perlu memeriksa apakah posisi (x, y-1) valid, lalu (x, y-2) lalu (x + 1, y) dll dll. seluruh ruang antara (x-2, y-2) hingga (x + 2, y + 2) harus diperiksa. Jika tidak ada posisi yang valid maka itu berarti Anda telah 'hancur'.

HTH

Valmond

Valmond
sumber
Saya dulu menggunakan pendekatan ini di masa Game Maker saya. Saya dapat memikirkan cara untuk mempercepat / meningkatkannya dengan pencarian yang lebih baik (katakanlah pencarian biner pada ruang yang Anda tempuh, meskipun tergantung pada jarak yang Anda tempuh dan geometri, mungkin lebih lambat), tetapi kelemahan utama dari pendekatan ini adalah bahwa alih-alih mengatakan satu cek terhadap semua kemungkinan tabrakan, Anda melakukan apa pun perubahan posisi Anda.
Jeff
"Anda melakukan apa pun perubahan posisi Anda adalah pemeriksaan" Maaf tapi apa sebenarnya artinya? BTW tentu saja Anda akan menggunakan teknik partisi ruang (BSP, Octree, quadtree, Tilemap dll) untuk mempercepat permainan tetapi Anda akan selalu perlu melakukan itu (jika peta besar), pertanyaannya bukan tentang itu tetapi tentang algoritma yang digunakan untuk memindahkan (dengan benar) pemain.
Valmond
@Valmond Saya pikir @Jeff berarti bahwa jika pemain Anda bergerak 10 piksel ke kiri, Anda dapat memiliki hingga 10 deteksi tabrakan yang berbeda.
Jonathan Connell
Jika itu yang dia maksudkan maka dia benar dan seharusnya seperti itu (siapa yang bisa mengatakan kalau kita berhenti di +6 dan bukan +7?). Komputer cepat, saya menggunakan ini pada S40 (~ 200mhz dan kvm yang tidak terlalu cepat) dan bekerja seperti pesona. Anda selalu dapat mencoba mengoptimalkan algo awal tetapi itu akan selalu memberi Anda kasus sudut seperti yang dimiliki OP.
Valmond
Itulah yang saya maksud
Jeff
2

Saya punya beberapa pertanyaan sebelum mulai menjawab ini. Pertama, dalam bug asli di mana Anda terjebak di dinding, apakah ubin-ubin di ubin individu kiri bertentangan dengan satu ubin besar? Dan jika ya, apakah pemain terjebak di antara mereka? Jika ya untuk kedua pertanyaan itu, pastikan posisi baru Anda valid . Itu berarti Anda harus memeriksa apakah ada tabrakan di mana Anda memberi tahu pemain untuk pindah. Jadi selesaikan perpindahan minimum seperti yang dijelaskan di bawah ini, dan kemudian gerakkan pemain Anda berdasarkan itu hanya jika ia mampubergerak kesana. Hampir terlalu di bawah hidung: P Ini sebenarnya akan memperkenalkan bug lain, yang saya sebut "kasus sudut". Pada dasarnya dalam hal sudut (seperti kiri bawah di mana paku horisontal keluar di .gif Anda, tetapi jika tidak ada paku) tidak akan menyelesaikan tabrakan, karena akan berpikir bahwa tidak ada resolusi yang Anda hasilkan mengarah ke posisi yang valid . Untuk mengatasi ini, cukup pertahankan apakah tabrakan telah diselesaikan, serta daftar semua resolusi penetrasi minimum. Setelah itu, jika tabrakan belum terselesaikan, lewati setiap resolusi yang Anda hasilkan, dan pantau resolusi X dan Y maksimum maksimum (maksimum tidak harus berasal dari resolusi yang sama). Kemudian atasi tabrakan pada maksimum itu. Ini tampaknya menyelesaikan semua masalah Anda dan juga yang saya temui.

    List<Vector2> collisions = new List<Vector2>();
        bool resolved = false;
        foreach (Platform p in testPlat)
        {
            Vector2 dif = p.resolveCollision(player.getCollisionMask());

            RectangleF newPos = player.getCollisionMask();

            newPos.X -= dif.X;
            newPos.Y -= dif.Y;


            if (!PlatformCollision(newPos)) //This checks if there's a collision (ie if we're entering an invalid space)
            {
                if (dif.X != 0)
                    player.velocity.X = 0; //Do whatever you want here, I like to stop my player on collisions
                if (dif.Y != 0)
                    player.velocity.Y = 0;

                player.MoveY(-dif.Y);
                player.MoveX(-dif.X);
                resolved = true;
            }
            collisions.Add(dif);
        }

        if (!resolved)
        {
            Vector2 max = Vector2.Zero;

            foreach (Vector2 v in collisions)
            {
                if (Math.Abs(v.X) > Math.Abs(max.X))
                {
                    max.X = v.X;
                }
                if (Math.Abs(v.Y) > Math.Abs(max.Y))
                {
                    max.Y = v.Y;
                }
            }

            player.MoveY(-max.Y);

            if (max.Y != 0)
                player.velocity.Y = 0;
            player.MoveX(-max.X);

            if (max.X != 0)
                player.velocity.X = 0;
        }

Pertanyaan lain, apakah paku yang Anda tampilkan satu ubin, atau ubin individu? Jika mereka ubin individu yang tipis, Anda mungkin harus menggunakan pendekatan yang berbeda untuk yang horizontal dan vertikal daripada yang saya jelaskan di bawah ini. Tetapi jika semuanya ubin ini harus bekerja.


Baiklah, jadi pada dasarnya inilah yang digambarkan oleh @Trevor Powell. Karena Anda hanya menggunakan AABB, yang harus Anda lakukan adalah menemukan berapa banyak satu persegi panjang menembus yang lain. Ini akan memberi Anda jumlah dalam sumbu X dan Y. Pilih minimum dari keduanya, dan pindahkan objek bertabrakan Anda sepanjang sumbu yang jumlahnya. Hanya itu yang Anda butuhkan untuk menyelesaikan tabrakan AABB. Anda TIDAK AKAN PERNAH perlu bergerak lebih dari satu sumbu dalam tabrakan seperti itu, jadi Anda tidak perlu bingung tentang apa yang harus bergerak terlebih dahulu, karena Anda hanya akan memindahkan minimum.

Perangkat lunak Metanet memiliki tutorial klasik tentang pendekatan di sini . Ini juga masuk ke bentuk lain juga.

Berikut ini adalah fungsi XNA yang saya buat untuk menemukan vektor tumpang tindih dari dua persegi panjang:

    public Point resolveCollision(Rectangle otherRect)
    {
        if (!isCollision(otherRect))
        {
            return Point.Zero;
        }

        int minOtherX = otherRect.X;
        int maxOtherX = otherRect.X + otherRect.Width;

        int minMyX = collisionMask.X;
        int maxMyX = collisionMask.X + collisionMask.Width;

        int minOtherY = otherRect.Y;
        int maxOtherY = otherRect.Y + otherRect.Height;

        int minMyY = collisionMask.Y;
        int maxMyY = collisionMask.Y + collisionMask.Height;

        int dx, dy;

        if (maxOtherX - minMyX < maxMyX - minOtherX)
        {
            dx = (maxOtherX - minMyX);
        }
        else
        {
            dx = -(maxMyX - minOtherX);
        }

        if (maxOtherY - minMyY < maxMyY - minOtherY)
        {
            dy = (maxOtherY - minMyY);
        }
        else
        {
            dy = -(maxMyY - minOtherY);
        }

        if (Math.Abs(dx) < Math.Abs(dy))
            return new Point(dx, 0);
        else
            return new Point(0, dy);
    }

(Saya harap ini mudah diikuti, karena saya yakin ada cara yang lebih baik untuk menerapkannya ...)

isCollision (Rectangle) secara harfiah hanya panggilan ke XNA's Rectangle.Intersects (Rectangle).

Saya sudah menguji ini dengan platform bergerak dan sepertinya berfungsi dengan baik. Saya akan melakukan beberapa tes lebih mirip dengan .gif Anda untuk memastikan dan melaporkan kembali jika itu tidak berhasil.


Jeff
sumber
Saya agak skeptis terhadap komentar yang saya buat di atasnya yang tidak bekerja dengan platform yang tipis. Saya tidak bisa memikirkan alasan mengapa itu tidak terjadi. Juga jika Anda menginginkan sumber, saya dapat mengunggah proyek XNA untuk Anda
Jeff
Ya saya baru saja menguji ini lagi dan itu kembali ke masalah Anda yang sama terjebak di dinding. Itu karena di mana kedua ubin berbaris satu sama lain, itu memberitahu Anda untuk naik karena yang lebih rendah, dan kemudian keluar (tepat di kasus Anda) untuk yang atas, secara efektif meniadakan gerakan gravitasi jika kecepatan Anda cukup lambat . Saya harus mencari solusi untuk ini, saya sepertinya ingat pernah mengalami masalah ini sebelumnya.
Jeff
Oke, saya telah menemukan cara yang sangat rumit untuk mengatasi masalah pertama Anda. Solusinya melibatkan menemukan penetrasi minimum untuk setiap AABB, menyelesaikan hanya pada satu dengan X terbesar, kemudian lulus kedua menyelesaikan hanya pada Y terbesar. Kelihatannya buruk ketika Anda sedang dihancurkan, tetapi mengangkat bekerja dan Anda dapat didorong secara horizontal, dan masalah menempel asli Anda hilang. Ini adalah hackey, dan saya tidak tahu bagaimana ini akan diterjemahkan ke bentuk lain. Kemungkinan lain mungkin untuk mengelas geometri menyentuh, tetapi saya bahkan belum mencobanya
Jeff
Dinding terbuat dari ubin 16x16. Paku adalah ubin 16x16. Jika saya menyelesaikan pada sumbu minimum, bug jumping terjadi (lihat diagram). Apakah solusi "sangat peretasan" Anda berfungsi dengan situasi yang ditunjukkan di .gif?
Vittorio Romeo
Yap, bekerja di tambang. Berapa ukuran karakter Anda?
Jeff
1

Itu mudah.

Tulis ulang paku. Ini salah paku.

Tabrakan Anda harus terjadi dalam unit yang terpisah dan nyata. Masalahnya sejauh yang saya bisa lihat adalah:

1. Player is outside spikes
2. Spikes move into intersection position with the player
3. Player resolves incorrectly

Masalahnya adalah dengan langkah 2, bukan 3 !!

Jika Anda mencoba membuat sesuatu terasa solid, Anda tidak boleh membiarkan barang-barang meluncur satu sama lain seperti itu. Setelah Anda berada di posisi persimpangan, jika Anda kehilangan tempat, masalahnya menjadi lebih sulit untuk dipecahkan.

Paku idealnya harus memeriksa keberadaan pemain dan, ketika mereka bergerak, mereka harus mendorongnya seperlunya.

Cara mudah untuk mencapai ini adalah agar Player memiliki moveXdan moveYfungsi - fungsi yang memahami lanskap dan akan mendorong pemain dengan delta tertentu atau sejauh yang mereka bisa tanpa mengenai rintangan .

Biasanya mereka akan dipanggil oleh loop acara. Namun mereka juga bisa dipanggil oleh objek untuk mendorong pemain.

function spikes going up:

    move spikes up

    if intersecting player:
        d = player bottom edge + spikes top edge

        player.moveY(-d)
    end if

end function

Jelas pemain masih perlu bereaksi terhadap paku jika dia masuk ke mereka .

Aturan menyeluruh adalah, setelah objek bergerak, itu harus berakhir pada posisi tanpa persimpangan. Dengan cara ini, tidak mungkin untuk mendapatkan gangguan yang Anda lihat.

Chris Burt-Brown
sumber
dalam kasus ekstrem di mana moveYgagal menghapus persimpangan (karena tembok lain menghalangi) Anda dapat memeriksa lagi untuk persimpangan dan coba moveX atau bunuh saja.
Chris Burt-Brown