Advertisement
  1. Game Development

Dasar Physics Platformer 2D, Bagian 2

Scroll to top
Read Time: 16 min
This post is part of a series called Basic 2D Platformer Physics .
Basic 2D Platformer Physics, Part 1
Basic 2D Platformer Physics, Part 3

() translation by (you can also view the original English article)

Geometri Level

Ada dua pendekatan dasar untuk membuat level platformer. Pertama adalah menggunakan grid dan menempatkan petak di dalam sel, dan yang kedua adalah cara yang lebih bebas, kamu bisa menempatkan geometri pada level di manapun yang kamu inginkan.

Masing-masing pendekatan memiliki kelebihan dan kekurangan. Kita akan menggunakan pendekatan grid, yang jika dibanding pendekatan satunya memiliki kelebihan sebagai berikut:

  • Performa yang lebih baik, pada umumnya deteksi tabrakan terhadap grid lebih murah dibanding terhadap objek yang diletakkan secara bebas.
  • Lebih mudah untuk membuat pathfinding.
  • Tile atau petak lebih akurat dan bisa ditebak dibanding objek yang diletakkan bebas, terutama jika mempertimbangkan terrain yang bisa dihancurkan.

Membuat Kelas Map

Kita mulai dengan membuat kelas Map. Kelas tersebut akan menyimpan semua data peta yang terkait.

1
public class Map
2
{
3
}

Sekarang kita perlu mendefinisikan semua petak yang dimiliki sebuah peta, tapi sebelum itu, kita perlu tahu jenis petak ayang ada dalam game kita. Untuk sekarang kita hanya memiliki tiga: petak kosong, petak solid, dan platform satu arah.

1
public enum TileType
2
{
3
    Empty,
4
    Block,
5
    OneWay
6
}

Pada demo, tipe petak berhubungan langsung dengan tipe tabrakan yang yang ingin kita terapkan pada petak tersebut, walaupun pada game sebenarnya tidak harus seperti itu. Begitu kamu punya banyak petak yang tampilannya berbeda, lebihbaik menambahkan tipe baru seperti GrassBlock, GrassOneWay, dan seterusnya, agar enum TileType tidak hanya menentukan tipe tabrakan tapi juga tampilan petak tersebut.

Sekarang tambahkan array yang berisi petak pada kelas peta.

1
public class Map
2
{
3
    private TileType[,] mTiles;
4
}

Tentu saja tilemap yang tidak bisa kita lihat tidak banyak berguna untuk kita, maka kita perlu sprite untuk mendukung data petak. Sebenarnya di Unity, membuat setiap petak sebagai objek yang berbeda adalah sangat tidak efisien. Tapi karena kita hanya menggunakannya untuk menguji physics, masih tidak apa-apa jika kita gunakan dalam demo ini.

1
private SpriteRenderer[,] mTilesSprites;

Peta ini juga membutuhkan posisi di dunia permainan, jadi jika kita membutuhkan lebih dari satu peta, kita bisa memisahkan kedua peta tersebut.

1
public Vector3 mPosition;

Lebar dan tinggi dalam satuan petak.

1
public int mWidth = 80;
2
public int mHeight = 60;

Dan ukuran petak, dalam demo ini kita gunakan petak yang kecil dengan ukuran 16 x 16 piksel.

1
public const int cTileSize = 16;

Data untuk kelas peta sudah cukup, sekarang kita perlu membuat beberapa fungsi pembantu untuk memudahkan akses untuk data peta. Kita mulai dengan membuat fungsi untuk menbuah koordinat dunia ke koordinat petak.

1
public Vector2i GetMapTileAtPoint(Vector2 point)
2
{
3
}

Seperti yang bisa kamu lihat, fungsi ini menerima parameter Vector2 dan mengembalikan Vector2i, yang merupakan vektor 2D yang berisi bilangan bulat, bukan bilangan real.

Mengubah posisi dunia ke posisi peta cukup sederhana, kita cukup menggeser point dengan mPosition sehingga kita mengembalikan petak relatif terhatap posisi peta, lalu membaginya dengan ukuran petak.

1
public Vector2i GetMapTileAtPoint(Vector2 point)
2
{
3
    return new Vector2i((int)((point.x - mPosition.x + cTileSize / 2.0f) / (float)(cTileSize)),
4
                (int)((point.y - mPosition.y + cTileSize / 2.0f) / (float)(cTileSize)));
5
}

Perhatikan bahwa kita perlu menggeser point terhadap cTileSize / 2.0f, karena titik pusat petak berada di tengahnya. Kita buat dua fungsi tambahan yang akan mengembalikan komponen X dan Y dari posisi pada peta yang akan berguna nantinya.

1
public int GetMapTileYAtPoint(float y)
2
{
3
    return (int)((y - mPosition.y + cTileSize / 2.0f) / (float)(cTileSize));
4
}
5
6
public int GetMapTileXAtPoint(float x)
7
{
8
    return (int)((x - mPosition.x + cTileSize / 2.0f) / (float)(cTileSize));
9
}

Kita juga perlu membuat fungsi pelengkap yang jika diberikan sebuah petak, akan mengembalikan posisinya di koordinat dunia.

1
public Vector2 GetMapTilePosition(int tileIndexX, int tileIndexY)
2
{
3
    return new Vector2(
4
            (float)(tileIndexX * cTileSize) + mPosition.x,
5
            (float)(tileIndexY * cTileSize) + mPosition.y
6
        );
7
}
8
9
public Vector2 GetMapTilePosition(Vector2i tileCoords)
10
{
11
    return new Vector2(
12
        (float)(tileCoords.x * cTileSize) + mPosition.x,
13
        (float)(tileCoords.y * cTileSize) + mPosition.y
14
        );
15
}

Selain mengubah posisi, kita juga perlu beberapa fungsi untuk melihat apakah sebuah tile di posisi tertentu adalah kosong, solid, atau platform satu arah. Kita mulai dengan fungsi dasar GetTile, yang akan mengembalikan jenis dari suatu petak.

1
public TileType GetTile(int x, int y)
2
{
3
    if (x < 0 || x >= mWidth
4
        || y < 0 || y >= mHeight)
5
        return TileType.Block;
6
7
    return mTiles[x, y];
8
}

Seperti yang bisa kamu lihat, sebelum kita mengembalikan jenis petak, kita periksa apakah posisi tersebut di luar batas. Jika iya, kita perlu perlakukan sebagai petak solid, jika tidak, kita kembalikan jenis petak sebenarnya.

Berikutnya adalah fungsi untuk memeriksa apakah sebuah petak adalah rintangan.

1
public bool IsObstacle(int x, int y)
2
{
3
    if (x < 0 || x >= mWidth
4
        || y < 0 || y >= mHeight)
5
        return true;
6
7
    return  (mTiles[x, y] == TileType.Block);
8
}

Sama seperti sebelumnya, kita periksa apakah petak di luar batas, jika iya kita akan mengembalikan nilai true, sehingga semua petak di luar batas dianggap rintangan.

Sekarang periksa apakah sebuah petak adalah petak tanah. Kita bisa berdiri pada petak tanah dan platform satu arah, jadi kita perlu kembalikan nilai true jika petak ini adalah salah satu dari dua jenis petak tersebut.

1
public bool IsGround(int x, int y)
2
{
3
    if (x < 0 || x >= mWidth
4
       || y < 0 || y >= mHeight)
5
        return false;
6
7
    return (mTiles[x, y] == TileType.OneWay || mTiles[x, y] == TileType.Block);
8
}

Terakhir, tambahkan fungsi IsOneWayPlatform dan IsEmpty dengan cara yang serupa.

1
public bool IsOneWayPlatform(int x, int y)
2
{
3
    if (x < 0 || x >= mWidth
4
        || y < 0 || y >= mHeight)
5
        return false;
6
7
    return (mTiles[x, y] == TileType.OneWay);
8
}
9
10
public bool IsEmpty(int x, int y)
11
{
12
    if (x < 0 || x >= mWidth
13
        || y < 0 || y >= mHeight)
14
        return false;
15
16
    return (mTiles[x, y] == TileType.Empty);
17
}

Cukup sekian fungsi pada kelas peta kita. Sekarang kita bisa lanjut dan mengimplementasi tabrakan karakter terhadap peta.

Tabrakan Karakter dan Peta

Mari kembali ke kelas MovingObject. Kita perlu membuat beberapa fungsi untuk mendeteksi apakah karakter bertabrakan dengan tilemap.

Cara kita menentukan apakah karakter bertabrakan dengan petak atau tidak cukup sederhana. Kita akan periksa semua tile yang berada tepat di luar AABB objek karakter.


Kotak kuning melambangkan AABB karakter, dan kita akan memeriksa petak-petak sepanjang garis merah. Jika suatu garis merah tumpang tindih dengan suatu petak, atur variabel tabrakan menjadi true (seperti mOnGround, mPushesLeftWall, mAtCeiling, atau mPushesRightWall).

Kita mulai dengan membuat fungsi HasGround, yang akan memeriksa jika karakter bertabrakan dengan petak tanah.

1
public bool HasGround(Vector2 oldPosition, Vector2 position, Vector2 speed, out float groundY)
2
{
3
}

Fungsi ini mengembalikan nilai trus jika karakter tumpang tindih dengan petak tanah apapun. Fungsi ini menerima parameter posisi lama, posisi baru, dan kecepatan saat ini, dan mengembalikan posisi Y dari bagian atas tile yang bertabrakan dengan kita, dan apakah kita bertabrakan dengan platform satu arah atau bukan.

Hal pertama yang perlu kita lakukan adalah menghitung pusat AABB.

1
public bool HasGround(Vector2 oldPosition, Vector2 position, Vector2 speed, out float groundY)
2
{
3
    var center = position + mAABBOffset;
4
}

Setelah kita mendapatkannya, untuk pemeriksaan tabrakan bagian bawah kita perlu hitung bagian awal dan akhir dari garis deteksi bawah. Garis deteksi tersebut satu piksel di bawah AABB.

1
public bool HasGround(Vector2 oldPosition, Vector2 position, Vector2 speed, out float groundY)
2
{
3
    var center = position + mAABBOffset;
4
    var bottomLeft = center - mAABB.halfSize - Vector2.up + Vector2.right;
5
    var bottomRight = new Vector2(bottomLeft.x + mAABB.halfSize.x * 2.0f - 2.0f, bottomLeft.y);
6
}

bottomLeft dan bottomRight melambangkan dua ujung garis deteksi tersebut. Dengan ini kita bisa menghitung petak mana yang perlu kita periksa. Kita mulai dengan membuat loop yang akan memeriksa setiap petak dari kiri ke kanan.

1
for (var checkedTile = bottomLeft; ; checkedTile.x += Map.cTileSize)
2
{
3
}

Perhatikan di sini belum ada kondisi untuk keluar dari loop, kita akan membuatnya di akhir loop.

Hal pertama yang kita lakukan dalam loop adalah memastikan checkedTile.x tidak lebih besar dari ujung kanan garis deteksi. Hal ini bisa terjadi karena kita menggerakkan titik yang diperiksa berdasarkan kelipatan ukuran petak, contohnya, jika pemain lebarnya 1.5 petak, kita perlu periksa petak di ujung kiri garis deteksi, lalu satu petak ke kanan, dan 1.5 petak ke kanan, bukan 2.

1
for (var checkedTile = bottomLeft; ; checkedTile.x += Map.cTileSize)
2
{
3
    checkedTile.x = Mathf.Min(checkedTile.x, bottomRight.x);
4
}

Sekarang kita perlu mendapatkan koordinat petak di koordinat peta untuk bisa memeriksa jenis petak.

1
int tileIndexX, tileIndexY;
2
3
for (var checkedTile = bottomLeft; ; checkedTile.x += Map.cTileSize)
4
{
5
    checkedTile.x = Mathf.Min(checkedTile.x, bottomRight.x);
6
    
7
    tileIndexX = mMap.GetMapTileXAtPoint(checkedTile.x);
8
    tileIndexY = mMap.GetMapTileYAtPoint(checkedTile.y);
9
}

Pertama, hitung posisi bagian atas petak.

1
int tileIndexX, tileIndexY;
2
3
for (var checkedTile = bottomLeft; ; checkedTile.x += Map.cTileSize)
4
{
5
    checkedTile.x = Mathf.Min(checkedTile.x, bottomRight.x);
6
7
    tileIndexX = mMap.GetMapTileXAtPoint(checkedTile.x);
8
    tileIndexY = mMap.GetMapTileYAtPoint(checkedTile.y);
9
    
10
    groundY = (float)tileIndexY * Map.cTileSize + Map.cTileSize / 2.0f + mMap.mPosition.y;
11
}

Sekarang, jika petak yang diperiksa adalah rintangan, kita bisa dengan mudah kembalikan nilai true.

1
int tileIndexX, tileIndexY;
2
3
for (var checkedTile = bottomLeft; ; checkedTile.x += Map.cTileSize)
4
{
5
    checkedTile.x = Mathf.Min(checkedTile.x, bottomRight.x);
6
    
7
    tileIndexX = mMap.GetMapTileXAtPoint(checkedTile.x);
8
    tileIndexY = mMap.GetMapTileYAtPoint(checkedTile.y);
9
    
10
    groundY = (float)tileIndexY * Map.cTileSize + Map.cTileSize / 2.0f + mMap.mPosition.y;
11
    
12
    if (mMap.IsObstacle(tileIndexX, tileIndexY))
13
        return true;
14
}

Akhirnya, periksa apakah kita sudah memeriksa semua petak yang beririsan dengan garis deteksi. Jika iya, maka kita bisa keluar dari loop. Jika kita keluar dari loop tapi tidak menemukan petak yang bertabrakan dengan pemain, kita perlu mengembalikan false agar pemanggil fungsi tahu bahwa tidak ada tanah di bawah objek.

1
int tileIndexX, tileIndexY;
2
3
for (var checkedTile = bottomLeft; ; checkedTile.x += Map.cTileSize)
4
{
5
    checkedTile.x = Mathf.Min(checkedTile.x, bottomRight.x);
6
    
7
    tileIndexX = mMap.GetMapTileXAtPoint(checkedTile.x);
8
    tileIndexY = mMap.GetMapTileYAtPoint(checkedTile.y);
9
    
10
    groundY = (float)tileIndexY * Map.cTileSize + Map.cTileSize / 2.0f + mMap.mPosition.y;
11
    
12
    if (mMap.IsObstacle(tileIndexX, tileIndexY))
13
        return true;
14
        
15
    if (checkedTile.x >= bottomRight.x)
16
        break;
17
}
18
19
return false;

Itu adalah versi paling dasar dari pemeriksaannya. Kita perlu mencobanya agar bisa bekerja dengan baik. Kembali ke fungsi UpdatePhysics, pemeriksaan tanah yang lama terlihat seperti ini.

1
if (mPosition.y <= 0.0f)
2
{
3
    mPosition.y = 0.0f;
4
    mOnGround = true;
5
}
6
else
7
    mOnGround = false;

Mari kita ganti dengan fungsi yang baru dibuat. Jika karakter jatuh dan kita menemukan rintangan di jalur kita jatuh, maka kita perlu memindahkannya keluar dari tabrakan dan mengatur mOnGround menjadi true. Kita mulai dengan kondisinya.

1
float groundY = 0;
2
if (mSpeed.y <= 0.0f 
3
    && HasGround(mOldPosition, mPosition, mSpeed, out groundY))
4
{
5
}

Jika kondisi tersebut terpenuhi, kita perlu memindahkan karakter ke atas petak yang kita tabrak.

1
float groundY = 0;
2
if (mSpeed.y <= 0.0f 
3
    && HasGround(mOldPosition, mPosition, mSpeed, out groundY))
4
{
5
    mPosition.y = groundY + mAABB.halfSize.y - mAABBOffset.y;
6
}

Seperti yang bisa kamu lihat, fungsi tersebut sangat sederhana karena mengembalikan tanah untuk mengatur posisi objek. Setelah ini, kita perlu mengatur kecepatan menjadi nol dan mOnGround menjadi true.

1
float groundY = 0;
2
if (mSpeed.y <= 0.0f 
3
    && HasGround(mOldPosition, mPosition, mSpeed, out groundY))
4
{
5
    mPosition.y = groundY + mAABB.halfSize.y - mAABBOffset.y;
6
    mSpeed.y = 0.0f;
7
    mOnGround = true;
8
}

Jika kecepatan vertikal kita lebih besar dari nol atau kita tidak menyentuh tanah, kita perlu mengatur mOnGround menjadi false.

1
float groundY = 0;
2
if (mSpeed.y <= 0.0f 
3
    && HasGround(mOldPosition, mPosition, mSpeed, out groundY))
4
{
5
    mPosition.y = groundY + mAABB.halfSize.y - mAABBOffset.y;
6
    mSpeed.y = 0.0f;
7
    mOnGround = true;
8
}
9
else
10
    mOnGround = false;

Sekarang kita lihat fungsi ini beraksi.

Seperti yang bisa kamu lihat, fungsinya bekerja dengan baik! Deteksi tabrakan untuk tembok di kedua sisi dan di atas pemain belum ada, tapi karakter berhenti setiap bertemu tanah. Kita masih perlu mengerjakan fungsi pengecekan tabrakan agar lebih baik.

Salah satu masalah yang perlu kita pecahkan muncul saat offset karakter dari satu frame ke frame lain terlalu besar untuk mendeteksi tabrakandengan baik. Hal ini diilustrasi di gambar berikut.

Situasi ini tidak muncul karena kita mengunci kecepatan jatuh maksimum ke nilai yang masuk akal dan mengupdate physics dengan frekuensi 60 FPS, jadi perbedaan posisi antatra frame ukup kecil. Kita lihat apa yang terjadi kita update physics hanya 30 kali per detik.

Seperti yang bisa kamu lihat, dalam skenario ini pemeriksaan tabrakan dengan tanah gagal. Untuk memperbaiki ini kita tidak bisa hanya memeriksa jika karakter memiliki tanah di bawah posisinya saat ini, tapi kita perlu tahu apakah ada rintangan sepanjang jalan dari posisi di frame sebelumnya.

Kita kembali ke fungsi HasGround. Di sini, selain menghitung titik pusat, kita juga perlu menghitung pusat frame sebelumnya.

1
public bool HasGround(Vector2 oldPosition, Vector2 position, Vector2 speed, out float groundY)
2
{
3
    var oldCenter = oldPosition + mAABBOffset;
4
    var center = position + mAABBOffset;

Kita juga perlu posisi garis deteksi di frame sebelumnya.

1
public bool HasGround(Vector2 oldPosition, Vector2 position, Vector2 speed, out float groundY)
2
{
3
    var oldCenter = oldPosition + mAABBOffset;
4
    var center = position + mAABBOffset;
5
    
6
    var oldBottomLeft = oldCenter - mAABB.halfSize - Vector2.up + Vector2.right;
7
    var bottomLeft = center - mAABB.halfSize - Vector2.up + Vector2.right;
8
    var bottomRight = new Vector2(bottomLeft.x + mAABB.halfSize.x * 2.0f - 2.0f, bottomLeft.y);

Sekarang kita perlu menghitung pada petak mana kita perlu mulai memeriksa tabrakan, dan di petak mana kitai perlu berhenti.

1
public bool HasGround(Vector2 oldPosition, Vector2 position, Vector2 speed, out float groundY)
2
{
3
    var oldCenter = oldPosition + mAABBOffset;
4
    var center = position + mAABBOffset;
5
    
6
    var oldBottomLeft = oldCenter - mAABB.halfSize - Vector2.up + Vector2.right;
7
    var bottomLeft = center - mAABB.halfSize - Vector2.up + Vector2.right;
8
    var bottomRight = new Vector2(bottomLeft.x + mAABB.halfSize.x * 2.0f - 2.0f, bottomLeft.y);
9
    
10
    int endY = mMap.GetMapTileYAtPoint(bottomLeft.y);
11
    int begY = Mathf.Max(mMap.GetMapTileYAtPoint(oldBottomLeft.y) - 1, endY);

Kitia mulai mencari dari petak yang berada di posisi garis deteksi di frame sebelumnya, dan kita akhiri di posisi garis deteksi saat ini. Karena pengecekan tabrakan dengan tanah akan digunakan saat kita jauh, artinya kita bergerak dari posisi yang tinggi ke yang lebih rendah.

Akhirnya, kita perlu sebuah loop tambahan. Sebelum kita isi kode pada loop ini, pertimbangkan skenario berikut.

Sekarang kamu bisa lihat sebuah panah bergerak cepat. Contoh ini menujukkan bahwa kita tidak hanya perlu mengiterasi semua tile yang kita perlu lewati secara verikal, tapi juga mengintrepolasi posisi ojbek untuk seiap petak yang kita lewati sesuai jalur perkiraan dari posisi frame sebelumnya ke posisi saat ini. Jika kita hanya menggunakan posisi objek saat ini, maka pada contoh di atas akan terdeteksi tabrakan, padahal seharusnya tidak.

Mari ganti nama bottomLeft dan bottomRight sebagai newBottomLeft dan newBottomRight, agar kita tahu bahwa mereka adalah posisi garis deteksi di frame berikutnya.

1
public bool HasGround(Vector2 oldPosition, Vector2 position, Vector2 speed, out float groundY)
2
{
3
    var oldCenter = oldPosition + mAABBOffset;
4
    var center = position + mAABBOffset;
5
    
6
    var oldBottomLeft = oldCenter - mAABB.halfSize - Vector2.up + Vector2.right;
7
    var newBottomLeft = center - mAABB.halfSize - Vector2.up + Vector2.right;
8
    var newBottomRight = new Vector2(newBottomLeft.x + mAABB.halfSize.x * 2.0f - 2.0f, newBottomLeft.y);
9
    
10
    int endY = mMap.GetMapTileYAtPoint(newBottomLeft.y);
11
    int begY = Mathf.Max(mMap.GetMapTileYAtPoint(oldBottomLeft.y) - 1, endY);
12
    
13
    int tileIndexX;
14
15
    for (int tileIndexY = begY; tileIndexY >= endY; --tileIndexY)
16
    {
17
    }
18
    
19
    return false;
20
}

Sekarang, dalam loop baru ini, kita menginterpolasi posisi garis deteksi, agar di awal loop kita berasumsi sensor berada di posisi frame sebelumnya, dan di akhir akan di bawah posisi frame saat ini.

1
public bool HasGround(Vector2 oldPosition, Vector2 position, Vector2 speed, out float groundY)
2
{
3
    var oldCenter = oldPosition + mAABBOffset;
4
    var center = position + mAABBOffset;
5
    
6
    var oldBottomLeft = oldCenter - mAABB.halfSize - Vector2.up + Vector2.right;
7
    var newBottomLeft = center - mAABB.halfSize - Vector2.up + Vector2.right;
8
    var newBottomRight = new Vector2(newBottomLeft.x + mAABB.halfSize.x * 2.0f - 2.0f, newBottomLeft.y);
9
    
10
    int endY = mMap.GetMapTileYAtPoint(newBottomLeft.y);
11
    int begY = Mathf.Max(mMap.GetMapTileYAtPoint(oldBottomLeft.y) - 1, endY);
12
    int dist = Mathf.Max(Mathf.Abs(endY - begY), 1);
13
    
14
    int tileIndexX;
15
16
    for (int tileIndexY = begY; tileIndexY >= endY; --tileIndexY)
17
    {
18
        var bottomLeft = Vector2.Lerp(newBottomLeft, oldBottomLeft, (float)Mathf.Abs(endY - tileIndexY) / dist);
19
        var bottomRight = new Vector2(bottomLeft.x + mAABB.halfSize.x * 2.0f - 2.0f, bottomLeft.y);
20
    }
21
    
22
    return false;
23
}

Perhatikan bahwa kita menginterpolasi vektor berdasarkank perbedaan petak di sumbu Y. Saat posisi lama dan baru ada di petak yang sama, jarak vertikal akan jadi nol, dalam kasus tersebut kita tidak akan bisa membaginya dengan jarak. Untuk memecahkan masalah tersebut, kita buat jarak memiliki nilai minimum 1, sehingga jika kasus tersebut terjadi (dan kemungkinan terjadi cukup sering), kita cukup menggunakan posisi baru untuk deteksi tabrakan.

Akhirnya, untuk setiap iterasi kita perlu menjalankan kode yang sama dengan memeriksa tabrakan terhadap tanah sepanjang lebar objek.

1
public bool HasGround(Vector2 oldPosition, Vector2 position, Vector2 speed, out float groundY)
2
{
3
    var oldCenter = oldPosition + mAABBOffset;
4
    var center = position + mAABBOffset;
5
    
6
    var oldBottomLeft = oldCenter - mAABB.halfSize - Vector2.up + Vector2.right;
7
    var newBottomLeft = center - mAABB.halfSize - Vector2.up + Vector2.right;
8
    var newBottomRight = new Vector2(newBottomLeft.x + mAABB.halfSize.x * 2.0f - 2.0f, newBottomLeft.y);
9
    
10
    int endY = mMap.GetMapTileYAtPoint(newBottomLeft.y);
11
    int begY = Mathf.Max(mMap.GetMapTileYAtPoint(oldBottomLeft.y) - 1, endY);
12
    int dist = Mathf.Max(Mathf.Abs(endY - begY), 1);
13
    
14
    int tileIndexX;
15
16
    for (int tileIndexY = begY; tileIndexY >= endY; --tileIndexY)
17
    {
18
        var bottomLeft = Vector2.Lerp(newBottomLeft, oldBottomLeft, (float)Mathf.Abs(endY - tileIndexY) / dist);
19
        var bottomRight = new Vector2(bottomLeft.x + mAABB.halfSize.x * 2.0f - 2.0f, bottomLeft.y);
20
    
21
        for (var checkedTile = bottomLeft; ; checkedTile.x += Map.cTileSize)
22
        {
23
            checkedTile.x = Mathf.Min(checkedTile.x, bottomRight.x);
24
            
25
            tileIndexX = mMap.GetMapTileXAtPoint(checkedTile.x);
26
            
27
            groundY = (float)tileIndexY * Map.cTileSize + Map.cTileSize / 2.0f + mMap.mPosition.y;
28
            
29
            if (mMap.IsObstacle(tileIndexX, tileIndexY))
30
                return true;
31
                
32
            if (checkedTile.x >= bottomRight.x)
33
                break;
34
        }
35
    }
36
    
37
    return false;
38
}

Cukup demikian. Seperti yang kamu bayangkan, jika objek dalam game bergerak dengan sangat cepat, dengan memeriksa tabrakan seperti ini akan lebih mahal, tapi teknik itu memastikan kita tidak akan ada kesalahan aneh seperti objek yang menembus tembok solid.

Ringkasan

Fiuh, itu lebih banyak kode dari yang kita bayangkan. Jika kamu menemukan error atau jalan pintas yang bisa diambil, beri tahu saya dan semua orang di komentar! Pemeriksaan tabrakan tersebut seharusnya cukup baik sehingga kita tidak perlu khawatir dengan kejadian objek menembus melewati blok pada tilemap.

Sebagian besar kode ditulis untuk memastikan tidak ada objek melewati petak dalam kecepatan tinggi, tapi jika hal tersebut tidak menjadi masalah untuk suatu game, kita bia menghapus kode tambahan tersebut untuk meningkatkan performa game. Akan lebih baik untuk menandai objek tertentu yang bergerak dengan cepat, jadi hanya objek-objek tersebut yang akan menggunakan versi yang lebih mahal dari pemeriksaan tersebut.

Kita masih memiliki banyak hal untuk ditangani, tapi kita sudah berhasil membuat pemeriksaan tabrakan dengan tanah yang baik, yang bisa diadaptasi untuk tiga arah lain. Kita akan lakukan itu di bagian berikutnya.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.