Dasar Physics Platformer 2D, Bagian 5: Deteksi Tabrakan Antar Objek
() translation by (you can also view the original English article)
Pada bagian ini, kita akan mulai bekerja membuat objek bisa berinteraksi secara fisik bukan hanya dengan tilemap tapi juga dengan objek lain, dengan membuat mekanisme deteksi tabrakan antar objek dalam game.
Demo
Demo ini menunjukkan hasil akhir dari tutorial ini. Gunakan WASD untuk menggerakkan karakter. Tombol tengah pada mouse memunculkan platform satu arah, klik kanan memunculkan petak solid, dan tombol spasi memunculkan karakter salinan. Slider mengubah ukuran dari karakter pemain. Objek yang mendeteksi tabrakan dibuat semi-transparan.
Demo ini dibuat dengan Unity 5.4.0f3, dan source code juga kompatibel dengan versi Unity tersebut.
Deteksi Tabrakan
Sebelum kita membahas tentang respon tabrakan, seperti membuat objek tidak bisa menembus satu sama lain, kita pertama perlu tahu apakah kedua objek tersebut tumpang tindih.
Hal ini mungkin akan menjadi operasi yang sangat mahal jika kita hanya memeriksa setiap objek terhadap setiap objek lain dalam game, tergantung dari berapa banyak objek yang aktif yang perlu ditangani dalam game. Jadi untuk membantu pemain dengan prosesor yang lemah kita perlu menggunakan..
Pembagian Ruang!
Hal ini pada dasarnya membagi ruangan dalam game menjadi beberapa area yang lebih kecil, yang membuat kita bisa memeriksa tabrakan antar objek di area yang sama. Optimasi ini sangat dibutuhkan pada game seperti Terarria, di mana dunia dan jumlah objek yang mungkin bertabrakan sangat besar dan posisi objek sangat tersebar. Pada game dengan satu layar, di mana jumlah objek sangat terbatas karena ukuran layar, pembagian ruang tidak selalu dibutuhkan, tapi masih bisa berguna.
Metode Pembagian Ruang
Metode pembagian ruang yang paling populer untuk ruang 2D adalah quad tree; kamu bisa membaca deskripsinya di tutorial ini. Untuk game saya, saya menggunakan struktur rata, yang berarti area permainan dibagi menjadi persegi dengan ukuran tertentu, dan saya memeriksa tabrakan dengan bojek di area persegi yang sama.



Ada satu kekurangan: sebuah objek bisa menempati lebih dari satu subruang dalam satu waktu. Hal itu tidak apa-apa, itu hanya berarti kita perlu mendeteksi objek yang ada pada setiap area objek kita berada.



Data untuk Pembagian Ruang
Sistem dasarnya sederhana. Kita perlu tahu seberapa besar masing-masing sel, dan sebuah array dua dimensi, di mana masing-masing lemen adalah sebuah daftar objek yang ada pada sebuah area. Kita perlu menempatkan data ini di kelas Map.
1 |
public int mGridAreaWidth = 16; |
2 |
public int mGridAreaHeight = 16; |
3 |
public List<MovingObject>[,] mObjectsInArea; |
Dalam kasus ini, saya memutusakan untuk membuat ukuran partisi dalam bentuk petak, jadi setiap partisi berukuran 16 x 16 petak.
Untuk setiap objek kita, kita ingin sebuah daftar area di mana objek tersebut berada, dan indeksnya di masing-masing partisi. Kita tambahkan ini di kelas MovingObject
.
1 |
public List<Vector2i> mAreas = new List<Vector2i>(); |
2 |
public List<int> mIdsInAreas = new List<int>(); |
Daripada menggunakan dua list, kita bisa menggunakan sebuah kamus, tapi sayangnya biaya komputasi menggunakan kontainer data kompleks di Unity saat ini belum begitu baik, jadi kita akan tetap menggunakan list untuk demo ini.
Menginisialisasi Partisi
Kita lanjut ke menghitung berapa banyak partisi yang kita butuhkan untuk mencakup semua area pada peta. Asumsinya tidak ada objek yang bisa berada di luar batasan peta.
1 |
mHorizontalAreasCount = Mathf.CeilToInt((float)mWidth / (float)mGridAreaWidth); |
2 |
mVerticalAreasCount = Mathf.CeilToInt((float)mHeight / (float)mGridAreaHeight); |
Tentu saja, tergantung ukuran peta, ukuran partisi tidak perlu tepat dengan batasan peta. Karena itulah kita menggunakan pembulatan ke atas dari perhitungan nilai untuk memastikan kita bisa memenuhi keseluruhan peta.
Kita mulai buat partisinya sekarang.
1 |
mObjectsInArea = new List<MovingObject>[mHorizontalAreasCount, mVerticalAreasCount]; |
2 |
|
3 |
for (var y = 0; y < mVerticalAreasCount; ++y) |
4 |
{
|
5 |
for (var x = 0; x < mHorizontalAreasCount; ++x) |
6 |
mObjectsInArea[x, y] = new List<MovingObject>(); |
7 |
}
|
Tidak ada hal yang rumit, kita hanya memastikan setiap sel memiliki daftar objek yang siap untuk dioperasikan.
Menentukan Partisi suatu Objek
Sekarang kita perlu membuat fungsi untuk mengupdate area di mana sebuah objek berada.
1 |
public void UpdateAreas(MovingObject obj) |
2 |
{
|
3 |
}
|
Pertama, kitia perlu tahu objek tersebut tumpang tindih dengan petak mana di peta. Karena kita hanya menggunakan AABB, kita hanya perlu memeriksa petak mana masing-masing pojok AABB berada.
1 |
var topLeft = GetMapTileAtPoint(obj.mAABB.center + new Vector2(-obj.mAABB.HalfSize.x, obj.mAABB.HalfSizeY)); |
2 |
var topRight = GetMapTileAtPoint(obj.mAABB.center + obj.mAABB.HalfSize); |
3 |
var bottomLeft = GetMapTileAtPoint(obj.mAABB.center - obj.mAABB.HalfSize); |
4 |
var bottomRight = new Vector2i(); |
Sekarang untuk mendapat koordinat dari ruang yang dipartisi, kita cukup membagi posisi petak dengan ukuran partisi. Kita tidak perlu menghitung pojok kanan bawah partisi, karena koordinat x kan sama dengan pojok kanan atas, dan koordinat y akank sama dengan pojok kiri bawah.
1 |
topLeft.x /= mGridAreaWidth; |
2 |
topLeft.y /= mGridAreaHeight; |
3 |
|
4 |
topRight.x /= mGridAreaWidth; |
5 |
topRight.y /= mGridAreaHeight; |
6 |
|
7 |
bottomLeft.x /= mGridAreaWidth; |
8 |
bottomLeft.y /= mGridAreaHeight; |
9 |
|
10 |
bottomRight.x = topRight.x; |
11 |
bottomRight.y = bottomLeft.y; |
Semua ini akan bekerja dengan asumsi tidak ada objek yang akan bergerak ke luar batas peta. Jika tidak, kita harus membuat pemeriksaan tambahan untuk mengabaikan objek yang berada di luar batas peta.
Sebuah objek bisa menempati satu partisi, dua partisi, atau berada tepat di mana empat partisi bertemu. Hal ini dengan asumsi tidak ada objek yang lebih besar dari ukuran partisi, jika tidak, objek tersebut bisa menutupi peta dan semua partisi jika terlalu besar. Sejauh ini saya selalu menggunakan asumsi tersebut, jadi kita akan menangani objek seperti itu di tutorial ini. Modifikasi untuk memperbolehkan objek yang besar cukup mudah, jadi saya akan menjelaskannya juga.
Kita mulai dengan memeriksa area yang tumpang tindih dengan karakter. Jika partisi semua pojok karakter sama, maka objek tersebut hanya menempati satu area.



1 |
if (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) |
2 |
{
|
3 |
mOverlappingAreas.Add(topLeft); |
4 |
}
|
Jika tidak, dan koordinat partisi pada sumbu x bernilai sama, maka objek tumpang tindih dengan dua partisi berbeda secara vertikal.



1 |
if (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) |
2 |
{
|
3 |
mOverlappingAreas.Add(topLeft); |
4 |
}
|
5 |
else if (topLeft.x == topRight.x) |
6 |
{
|
7 |
mOverlappingAreas.Add(topLeft); |
8 |
mOverlappingAreas.Add(bottomLeft); |
9 |
}
|
Jika kita mendukung objek yang lebih besar dari partisi, kita cukup menambahkan semua partisi dari pojok kiri atas ke pojok kiri bawah menggunakan loop.
Logika yang sama berlaku jika hanya koordinat vertikal yang sama.



1 |
if (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) |
2 |
{
|
3 |
mOverlappingAreas.Add(topLeft); |
4 |
}
|
5 |
else if (topLeft.x == topRight.x) |
6 |
{
|
7 |
mOverlappingAreas.Add(topLeft); |
8 |
mOverlappingAreas.Add(bottomLeft); |
9 |
}
|
10 |
else if (topLeft.y == bottomLeft.y) |
11 |
{
|
12 |
mOverlappingAreas.Add(topLeft); |
13 |
mOverlappingAreas.Add(topRight); |
14 |
}
|
Akhirnya, jika semua koordinat berbeda, kita perlu menambahkan keempat area tersebut.



1 |
if (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) |
2 |
{
|
3 |
mOverlappingAreas.Add(topLeft); |
4 |
}
|
5 |
else if (topLeft.x == topRight.x) |
6 |
{
|
7 |
mOverlappingAreas.Add(topLeft); |
8 |
mOverlappingAreas.Add(bottomLeft); |
9 |
}
|
10 |
else if (topLeft.y == bottomLeft.y) |
11 |
{
|
12 |
mOverlappingAreas.Add(topLeft); |
13 |
mOverlappingAreas.Add(topRight); |
14 |
}
|
15 |
else
|
16 |
{
|
17 |
mOverlappingAreas.Add(topLeft); |
18 |
mOverlappingAreas.Add(bottomLeft); |
19 |
mOverlappingAreas.Add(topRight); |
20 |
mOverlappingAreas.Add(bottomRight); |
21 |
}
|
Sebelum kita berpindah dari fungsi ini, kita perlu untuk bisa menambah dan menghapus objek dari suatu partisi tertentu. Mari buat fungsi-fungsi berikut, mulai dari menambahkan objek.
1 |
public void AddObjectToArea(Vector2i areaIndex, MovingObject obj) |
2 |
{
|
3 |
var area = mObjectsInArea[areaIndex.x, areaIndex.y]; |
4 |
|
5 |
//save the index of the object in the area
|
6 |
obj.mAreas.Add(areaIndex); |
7 |
obj.mIdsInAreas.Add(area.Count); |
8 |
|
9 |
//add the object to the area
|
10 |
area.Add(obj); |
11 |
}
|
Seperti yang bisa kamu lihat, prosedurnya cukup sederhana, kita menambahkan indeks area ke daftar area pada objek, kita tambahakan indeks yang bersangkutan di daftar id pada objek, dan tambahkan objek pada partisi tersebut.
Sekarang kita buat fungsi untuk menghapus objek.
1 |
public void RemoveObjectFromArea(Vector2i areaIndex, int objIndexInArea, MovingObject obj) |
2 |
{
|
3 |
}
|
Seperti yang bisa kamu lihat, kita menggunakan koordinat dari area yang sudah tidak tumpang tindih dengan karakter, indeksnya pada daftar objek di area tersebut, dan referensi ke objek yang perlu kita hapus.
Untuk menghapus objek, kita akan menukarnya dengan objek terakhir pada daftar. Hal ini membutuhkan kita untuk memastikan indeks objek pada area ini diupdate menjadi indeks objek yang baru kita hapus. Jika kita tidak menukar objek, kita perlu mengupdate indeks semua objek setelah objek yang kita hapus. Tapi dengan menukar objek kita hanya perlu mengupdate objek yang kita tukar.
Menggunakan fitur kamus di sini akan mengurangi kesulitan kita, tapi menghapus objek adalah operasi yang lebih jarang dibanding mengiterasi kamus, yang perlu dilakukank setiap frame untuk setiap objek saat kita mengupdate area yang tumpang tindih terhadap suatu objek.
1 |
//swap the last item with the one we are removing
|
2 |
var tmp = area[area.Count - 1]; |
3 |
area[area.Count - 1] = obj; |
4 |
area[objIndexInArea] = tmp; |
Sekarang kita perlu menemukan area yang kita butuhkan di daftar area pada objek yang ditukar, dan mengubah indeks pada daftar id menjadi indeks objek yang dihapus.
1 |
var tmpIds = tmp.mIdsInAreas; |
2 |
var tmpAreas = tmp.mAreas; |
3 |
for (int i = 0; i < tmpAreas.Count; ++i) |
4 |
{
|
5 |
if (tmpAreas[i] == areaIndex) |
6 |
{
|
7 |
tmpIds[i] = objIndexInArea; |
8 |
break; |
9 |
}
|
10 |
}
|
Akhirnya, kita bisa menghapus objek terakhir dari partisi tersebut, yang sekarang menjadi referensi ke objek yang perlu kita hapus.
1 |
area.RemoveAt(area.Count - 1); |
Keseluruhan fungsi tersebut akan terlihat seperti ini.
1 |
public void RemoveObjectFromArea(Vector2i areaIndex, int objIndexInArea, MovingObject obj) |
2 |
{ |
3 |
var area = mObjectsInArea[areaIndex.x, areaIndex.y]; |
4 |
|
5 |
//swap the last item with the one we are removing |
6 |
var tmp = area[area.Count - 1]; |
7 |
area[area.Count - 1] = obj; |
8 |
area[objIndexInArea] = tmp; |
9 |
|
10 |
var tmpIds = tmp.mIdsInAreas; |
11 |
var tmpAreas = tmp.mAreas; |
12 |
for (int i = 0; i < tmpAreas.Count; ++i) |
13 |
{ |
14 |
if (tmpAreas[i] == areaIndex) |
15 |
{ |
16 |
tmpIds[i] = objIndexInArea; |
17 |
break; |
18 |
} |
19 |
} |
20 |
|
21 |
//remove the last item |
22 |
area.RemoveAt(area.Count - 1); |
23 |
} |
Kita kembali ke fungsi UpdateAreas.
Kita tahu area mana yang tumpang tindih dengan karakter di frame ini, tapi di frame sebelumnya, objek bisa di area yang sama atau dipindah dari area berbeda. Pertama, kita periksa area sebelumnya, dan jika objek sudah tidak tumpang tindih dengan area tersebut, kita perlu menghapus objek dari area yang bersangkutan.
1 |
var areas = obj.mAreas; |
2 |
var ids = obj.mIdsInAreas; |
3 |
for (int i = 0; i < areas.Count; ++i) |
4 |
{
|
5 |
if (!mOverlappingAreas.Contains(areas[i])) |
6 |
{
|
7 |
RemoveObjectFromArea(areas[i], ids[i], obj); |
8 |
//object no longer has an index in the area
|
9 |
areas.RemoveAt(i); |
10 |
ids.RemoveAt(i); |
11 |
--i; |
12 |
}
|
13 |
}
|
Kita periksa area baru, jika objek belum dipindah ke area tersebut, kita perlu menambahkan objek tersebut sekarang.
1 |
for (var i = 0; i < mOverlappingAreas.Count; ++i) |
2 |
{
|
3 |
var area = mOverlappingAreas[i]; |
4 |
if (!areas.Contains(area)) |
5 |
AddObjectToArea(area, obj); |
6 |
}
|
Akhirnya, bersihkan daftar area tumpang tindih agar siap untuk memproses objek berikutnya.
1 |
mOverlappingAreas.Clear(); |
Sekian! Fungsi akhir seharusnya terlihat seperti ini:
1 |
public void UpdateAreas(MovingObject obj) |
2 |
{
|
3 |
//get the areas at the aabb's corners
|
4 |
var topLeft = GetMapTileAtPoint(obj.mAABB.center + new Vector2(-obj.mAABB.HalfSize.x, obj.mAABB.HalfSizeY)); |
5 |
var topRight = GetMapTileAtPoint(obj.mAABB.center + obj.mAABB.HalfSize); |
6 |
var bottomLeft = GetMapTileAtPoint(obj.mAABB.center - obj.mAABB.HalfSize); |
7 |
var bottomRight = new Vector2i(); |
8 |
|
9 |
topLeft.x /= mGridAreaWidth; |
10 |
topLeft.y /= mGridAreaHeight; |
11 |
|
12 |
topRight.x /= mGridAreaWidth; |
13 |
topRight.y /= mGridAreaHeight; |
14 |
|
15 |
bottomLeft.x /= mGridAreaWidth; |
16 |
bottomLeft.y /= mGridAreaHeight; |
17 |
|
18 |
bottomRight.x = topRight.x; |
19 |
bottomRight.y = bottomLeft.y; |
20 |
|
21 |
//see how many different areas we have
|
22 |
if (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) |
23 |
{
|
24 |
mOverlappingAreas.Add(topLeft); |
25 |
}
|
26 |
else if (topLeft.x == topRight.x) |
27 |
{
|
28 |
mOverlappingAreas.Add(topLeft); |
29 |
mOverlappingAreas.Add(bottomLeft); |
30 |
}
|
31 |
else if (topLeft.y == bottomLeft.y) |
32 |
{
|
33 |
mOverlappingAreas.Add(topLeft); |
34 |
mOverlappingAreas.Add(topRight); |
35 |
}
|
36 |
else
|
37 |
{
|
38 |
mOverlappingAreas.Add(topLeft); |
39 |
mOverlappingAreas.Add(bottomLeft); |
40 |
mOverlappingAreas.Add(topRight); |
41 |
mOverlappingAreas.Add(bottomRight); |
42 |
}
|
43 |
|
44 |
var areas = obj.mAreas; |
45 |
var ids = obj.mIdsInAreas; |
46 |
|
47 |
for (int i = 0; i < areas.Count; ++i) |
48 |
{
|
49 |
if (!mOverlappingAreas.Contains(areas[i])) |
50 |
{
|
51 |
RemoveObjectFromArea(areas[i], ids[i], obj); |
52 |
//object no longer has an index in the area
|
53 |
areas.RemoveAt(i); |
54 |
ids.RemoveAt(i); |
55 |
--i; |
56 |
}
|
57 |
}
|
58 |
|
59 |
for (var i = 0; i < mOverlappingAreas.Count; ++i) |
60 |
{
|
61 |
var area = mOverlappingAreas[i]; |
62 |
if (!areas.Contains(area)) |
63 |
AddObjectToArea(area, obj); |
64 |
}
|
65 |
|
66 |
mOverlappingAreas.Clear(); |
67 |
}
|
Deteksi Tabrakan Antar Objek
Pertama, pastikan kita memanggil UpdateAreas
pada semua objek dalam game. Kita bisa melakukannya di update loop utama, setelah fungsi update masing-masing objek.
1 |
void FixedUpdate() |
2 |
{
|
3 |
for (int i = 0; i < mObjects.Count; ++i) |
4 |
{
|
5 |
switch (mObjects[i].mType) |
6 |
{
|
7 |
case ObjectType.Player: |
8 |
case ObjectType.NPC: |
9 |
((Character)mObjects[i]).CustomUpdate(); |
10 |
mMap.UpdateAreas(mObjects[i]); |
11 |
break; |
12 |
}
|
13 |
}
|
14 |
}
|
Sebelum kita buat fungsi untuk memeriksa semua tabrakan, kita perlu buat struktur data untuk menyimpan data tabrakan.
Hal ini akan sangat berguna, karena kita akan bisa menyimpan data seperti saat terjadi tabrakan, jika kita hanya menyimpan referensi objek yang kita tabrak, tidak hanya kita hanya memiliki sedikit data untuk bisa digunakan, tapi posisi dan variabel lain pada objek yang kita tabrak bisa jadi sudah berubah saat kita memproses tabrakan pada update loop objek tersebut.
1 |
public struct CollisionData |
2 |
{
|
3 |
public CollisionData(MovingObject other, Vector2 overlap = default(Vector2), Vector2 speed1 = default(Vector2), Vector2 speed2 = default(Vector2), Vector2 oldPos1 = default(Vector2), Vector2 oldPos2 = default(Vector2), Vector2 pos1 = default(Vector2), Vector2 pos2 = default(Vector2)) |
4 |
{
|
5 |
this.other = other; |
6 |
this.overlap = overlap; |
7 |
this.speed1 = speed1; |
8 |
this.speed2 = speed2; |
9 |
this.oldPos1 = oldPos1; |
10 |
this.oldPos2 = oldPos2; |
11 |
this.pos1 = pos1; |
12 |
this.pos2 = pos2; |
13 |
}
|
14 |
|
15 |
public MovingObject other; |
16 |
public Vector2 overlap; |
17 |
public Vector2 speed1, speed2; |
18 |
public Vector2 oldPos1, oldPos2, pos1, pos2; |
19 |
}
|
Data yang akan kita simpan adalah referensi ke objek yang kita tabrak, informasi kejadian tumpang tindih, kecepatan kedua objek saat tabrakan, posisi kedua benda, dan posisi mereka tepat sebelum tabrakan.
Kita berpindah ke kelas MovingObject
dan buat sebuah penyimpanan untuk data tabrakan yang perlu kita deteksi.
1 |
public List<CollisionData> mAllCollidingObjects = new List<CollisionData>(); |
Sekarang kembali ke kelas Map
dan buat sebuah fungsi CheckCollisions
. Fungsi ini akan menjadi fungsi besar di mana kita mendeteksi tabrakan diantara semua objek dalam game.
1 |
public void CheckCollisions() |
2 |
{
|
3 |
}
|
Untuk mendeteksi tabrakan, kita akan proses semua partisi.
1 |
for (int y = 0; y < mVerticalAreasCount; ++y) |
2 |
{ |
3 |
for (int x = 0; x < mHorizontalAreasCount; ++x) |
4 |
{ |
5 |
var objectsInArea = mObjectsInArea[x, y]; |
6 |
} |
7 |
} |
Untuk setiap partisi, kita akan mengiterasi semua objek di dalamnya.
1 |
for (int y = 0; y < mVerticalAreasCount; ++y) |
2 |
{ |
3 |
for (int x = 0; x < mHorizontalAreasCount; ++x) |
4 |
{ |
5 |
var objectsInArea = mObjectsInArea[x, y]; |
6 |
for (int i = 0; i < objectsInArea.Count - 1; ++i) |
7 |
{ |
8 |
var obj1 = objectsInArea[i]; |
9 |
} |
10 |
} |
11 |
} |
Untuk setiap objek, kita hanya periksa objek lain yang memiliki indeks lebih besar di partisi yang sama. Dengan ini kita hanya akan memeriksa tabrakan masing-masing satu kali saja.
1 |
for (int y = 0; y < mVerticalAreasCount; ++y) |
2 |
{
|
3 |
for (int x = 0; x < mHorizontalAreasCount; ++x) |
4 |
{
|
5 |
var objectsInArea = mObjectsInArea[x, y]; |
6 |
for (int i = 0; i < objectsInArea.Count - 1; ++i) |
7 |
{
|
8 |
var obj1 = objectsInArea[i]; |
9 |
for (int j = i + 1; j < objectsInArea.Count; ++j) |
10 |
{
|
11 |
var obj2 = objectsInArea[j]; |
12 |
}
|
13 |
}
|
14 |
}
|
15 |
}
|
Sekarang kita bisa periksa apakah AABB objek saling tumpang tindih.
1 |
Vector2 overlap; |
2 |
|
3 |
for (int y = 0; y < mVerticalAreasCount; ++y) |
4 |
{
|
5 |
for (int x = 0; x < mHorizontalAreasCount; ++x) |
6 |
{
|
7 |
var objectsInArea = mObjectsInArea[x, y]; |
8 |
for (int i = 0; i < objectsInArea.Count - 1; ++i) |
9 |
{
|
10 |
var obj1 = objectsInArea[i]; |
11 |
for (int j = i + 1; j < objectsInArea.Count; ++j) |
12 |
{
|
13 |
var obj2 = objectsInArea[j]; |
14 |
|
15 |
if (obj1.mAABB.OverlapsSigned(obj2.mAABB, out overlap)) |
16 |
{
|
17 |
}
|
18 |
}
|
19 |
}
|
20 |
}
|
21 |
}
|
Berikut adalah yang terjadi pada fungsi OverlapsSigned
pada AABB.
1 |
public bool OverlapsSigned(AABB other, out Vector2 overlap) |
2 |
{
|
3 |
overlap = Vector2.zero; |
4 |
|
5 |
if (HalfSizeX == 0.0f || HalfSizeY == 0.0f || other.HalfSizeX == 0.0f || other.HalfSizeY == 0.0f |
6 |
|| Mathf.Abs(center.x - other.center.x) > HalfSizeX + other.HalfSizeX |
7 |
|| Mathf.Abs(center.y - other.center.y) > HalfSizeY + other.HalfSizeY) return false; |
8 |
|
9 |
overlap = new Vector2(Mathf.Sign(center.x - other.center.x) * ((other.HalfSizeX + HalfSizeX) - Mathf.Abs(center.x - other.center.x)), |
10 |
Mathf.Sign(center.y - other.center.y) * ((other.HalfSizeY + HalfSizeY) - Mathf.Abs(center.y - other.center.y))); |
11 |
|
12 |
return true; |
13 |
}
|
Seperti yang bisa kamu lihat, jika ukuran AABB pada sebuah sumbu adalah nol, kita tidak bisa bertabrakan dengan objek tersebut. Hal lain yang perlu diperhatikan adalah walau tumpang tindih bernilai nol, fungsi ini akan mengembalikan nilai trus, karena fungsi ini akan mengabaikan kasus di mana jarak antar AABB lebih besar dari nol. Hal tersebut karena jika objek menyentuh satu sama lain tapi tidak tumpang tindih, kita tetap butuh informasi tentang kejadian tersebut, jadi kita ingin ini tetap diproses.
Terakhir, begitu tabrakan dideteksi, kita hitung seberapa banyak AABB tumpang tindih dengan AABB lain. Tumpang tindih tersebut akan bertanda, jadi jika terjadi di sisi kanan, tumpang tindih pada sumbu X akan bernilai negatif, sedangkan jika terjadi di sisi kiri, nilainya akan berupa positif. Ini akan membuat lebih mudah untuk keluar dari posisi tumpang tindih, karena kita tahu arah objek akan bergerak.
Kembali ke fungsi CheckCollisions
, jika tidak ada tumpang tindih, selesai, kita bisa lanjut ke objek berikutnya, tapi jika terjadi tumpang tindih, kita perlu menambahkan data tabrakan ke kedua objek yang bersangkutan.
1 |
if (obj1.mAABB.OverlapsSigned(obj2.mAABB, out overlap)) |
2 |
{
|
3 |
obj1.mAllCollidingObjects.Add(new CollisionData(obj2, overlap, obj1.mSpeed, obj2.mSpeed, obj1.mOldPosition, obj2.mOldPosition, obj1.mPosition, obj2.mPosition)); |
4 |
obj2.mAllCollidingObjects.Add(new CollisionData(obj1, -overlap, obj2.mSpeed, obj1.mSpeed, obj2.mOldPosition, obj1.mOldPosition, obj2.mPosition, obj1.mPosition)); |
5 |
}
|
Untuk memudahkan kita, kita asumsikan benda 1 (speed1, pos1, oldPos1) dalam CollisionData mengacu pada pemilik data tabrakan, dan benda adalah objek lawan.
Selanjutnya, tumpang tindih akan dihitung berdasarkan sudut pandang obj1. Tumpang tindih obj2 perlu dibuat negatif, sehingga jika obj1 perlu bergerak ke kiri untuk keluar dari tabrakan, obj2 perlu bergerak ke kanan untuk keluar dari tabrakan yang sama.
Satu hal lagi yang perlu ditangani, karena kita mengiterasi partisi peta, dan satu benda bisa berada di lebih dari satu partis, sampai empat partisi dalam kasus kita, mungkin kita mendeteksi tumpang tindih untuk objek yang sama hingga empat kali.
Untuk menghilangkan kemungkinan ini, kita cukup memeriksa apakah kita sudah mendeteksi tabrakan antara dua objek tersebut. Jika iya, kita bisa mengabaikan pemeriksaannya.
1 |
if (obj1.mAABB.OverlapsSigned(obj2.mAABB, out overlap) && !obj1.HasCollisionDataFor(obj2)) |
2 |
{
|
3 |
obj1.mAllCollidingObjects.Add(new CollisionData(obj2, overlap, obj1.mSpeed, obj2.mSpeed, obj1.mOldPosition, obj2.mOldPosition, obj1.mPosition, obj2.mPosition)); |
4 |
obj2.mAllCollidingObjects.Add(new CollisionData(obj1, -overlap, obj2.mSpeed, obj1.mSpeed, obj2.mOldPosition, obj1.mOldPosition, obj2.mPosition, obj1.mPosition)); |
5 |
}
|
Fungsi HasCollisionDataFor
diimplementasi sebagai berikut.
1 |
public bool HasCollisionDataFor(MovingObject other) |
2 |
{
|
3 |
for (int i = 0; i < mAllCollidingObjects.Count; ++i) |
4 |
{
|
5 |
if (mAllCollidingObjects[i].other == other) |
6 |
return true; |
7 |
}
|
8 |
|
9 |
return false; |
10 |
}
|
Fungsi tersebut akan mengiterasi semua data tabrakan dan melihat apakah ada data yang dimiliki objek yang kita akan periksa tabrakannya.
Hal ini akan aman karena pada umumnya satu objek tidak akan tabrakan dengan banyak objek lain, jadi mencari dari daftar tersebut akan cepat. Tapi, pada skenario lain, mungkin lebih baik untuk mengganti keseluruhan list CollisionData
dengan sebuah kamus, jadi kita tidak perlu mengiterasi keseluruhan data.
Selain itu, pemeriksaan ini mencegah kita menambahkan banyak salinan dari tabrakan yang sama ke daftar, tapi jika objek tidak bertabrakan, kita tetap akan memeriksa tumpang tindih beberapa kali jika kedua objek berada pada partisi yang sama.
Hal ini tidak perlu menjadi masalah besar, karena pemeriksaan tabrakan murah dan situasi tersebut tidak umum, tapi jika itu menjadi masalah, solusinya adalah untum memiliki matriks berisi tabrakan yang sudah diperiksa, atau kamus dua arah, yang diisi sembari tabrakan diperiksa, dan me-resetnya tepat sebelum kita panggil fungsi CheckCollisions.
Sekarang kita tambahan fungsi yang baru kita selesaikan ke game loop utama.
1 |
void FixedUpdate() |
2 |
{
|
3 |
for (int i = 0; i < mObjects.Count; ++i) |
4 |
{
|
5 |
switch (mObjects[i].mType) |
6 |
{
|
7 |
case ObjectType.Player: |
8 |
case ObjectType.NPC: |
9 |
((Character)mObjects[i]).CustomUpdate(); |
10 |
mMap.UpdateAreas(mObjects[i]); |
11 |
mObjects[i].mAllCollidingObjects.Clear(); |
12 |
break; |
13 |
}
|
14 |
}
|
15 |
|
16 |
mMap.CheckCollisions(); |
17 |
}
|
Sekian! Sekarang semua objek sudah memiliki data tentang tabrakan.
Untuk menguji apakah semua bekerja dengan benar, kita buat jika karakter bertabrakan dengan objek, sprite karakter akan menjadi semi-transparan.



Seperti yang kamu bisa lihat, deteksi tabrakan bekerja dengan baik!
Ringkasan
Selesai sudah satu bagian dari seri dasar physics platformer 2D. Kita berhasil mengimplementasi sistem pembagian ruang yang sangat sederhana dan mendeteksi tabrakan antar objek.
Jika kamu punya pertanyaan, tips untuk membuat sesuatu lebih baik, atau punya pendapat tentang tutorial ini, gunakan bagian komentar untuk memberitahu saya!