Advertisement
  1. Code
  2. Coding Fundamentals
  3. Game Development

Dasar Physics Platformer 2D, Bagian 8: Bidang Miring

Scroll to top
Read Time: 41 min

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

Final product imageFinal product imageFinal product image
What You'll Be Creating

Demo

Demo itu menunjukkan hasil akhir dari implementasi bidang miring. Gunakan WASD untuk menggerakkan karakter. Tombol mouse kanan membuat sebuah petak. Kamu bisa menggunakan rola ulir atau tombol panah untuk memilih petak yang ingin kamu tempatkan. Slider mengubah ukuran karakter pemain.

Demo ini dirilis pada Unity 5.5.2f1, dan source code juga kompatibel untuk versi Unity tersebut.

Sebelum kita mulai...

Seperti bagian sebelumnya pada seri ini, kita akan melanjutkan pekerjaan dari bagian sebelumnya. Sebelumnya kita menghitung dan menyimpan data yang kita butuhkan untuk menggerakkan objek keluar dari tabrakan dengan bidang miring dan mengubah bagaimana pengecekan tabrakan dijalankan terhadap denah petak. Pada bagian ini kita membutuhkan pengaturan yang sama dengan bagian akhir dari seri sebelumnya.

Kamu bisa mengunduh file proyek bagian sebelumnya dan menulis kode didampingi tutorial ini.

Pada bagian ini kita akan mengimplementasi tabrakan dengan bidang miring atau petak khusus lain, menambahkan bidang miring satu arah, dan membuat objek game bisa berjalan di  sepanjang bidang miring dengan mulus.

Implementasi bidang miring

Pengecekan vertikal terhadap bidang miring

Akhirnya kita mencapai implementasi bidang miring! Pertama, kita coba untuk menangani kondisi saat sisi bawah objek ada di dalam petak bidang miring.

Kita lihat fungsi CollidesWithTileBottom, terutama bagian di mana kita menangani petak.

1
switch (tileCollisionType)
2
{
3
    default://slope       
4
        break;
5
    case TileCollisionType.Empty:
6
        break;
7
    case TileCollisionType.Full:
8
        state.onOneWay = false;
9
        state.pushesBottomTile = true;
10
        state.bottomTile = new Vector2i(x, bottomleftTile.y);
11
        return true;
12
}

Untuk bisa melihat apakah objek kita bertabrakan dengan bidang miring, kita perlu offset dari fungsi yang kita buat sebelumnya, yang menjalankan sebagian besar dari tugas kita.

1
Vector2 tileCenter = mMap.GetMapTilePosition(x, bottomleftTile.y);
2
SlopeOffsetI sf = Slopes.GetOffset(tileCenter, bottomLeft.x + 0.5f, topRight.x - 0.5f, bottomLeft.y - 0.5f, topRight.y - 0.5f, tileCollisionType);

Karena kita hanya memeriksa satu piksel di bawah karakter kita, kita perlu mengatur offset.

1
Vector2 tileCenter = mMap.GetMapTilePosition(x, bottomleftTile.y);
2
SlopeOffsetI sf = Slopes.GetOffset(tileCenter, bottomLeft.x + 0.5f, topRight.x - 0.5f, bottomLeft.y - 0.5f, topRight.y - 0.5f, tileCollisionType);
3
sf.freeUp -= 1;
4
sf.collidingBottom -= 1;

Kondisi untuk tabrakan adalah offset freeUp lebih besar atau sama dengan nol, yang berarti antara kita menggerakkan karakter ke atas, atau karakter berdiri di bidang miring.

1
Vector2 tileCenter = mMap.GetMapTilePosition(x, bottomleftTile.y);
2
SlopeOffsetI sf = Slopes.GetOffset(tileCenter, bottomLeft.x + 0.5f, topRight.x - 0.5f, bottomLeft.y - 0.5f, topRight.y - 0.5f, tileCollisionType);
3
sf.freeUp -= 1;
4
sf.collidingBottom -= 1;
5
6
if (sf.freeUp >= 0)
7
{
8
}

Jangan lupa dengan kasus saat kita ingin karakter menempel pada bidang miring. Ini berarti walau karakter bergerak keluar bidang miring, kita ingin karakter berlaku seperti ada di atas bidang miring tersebut. Untuk ini, kita perlu menambahkan sebuah nilai konstan yang akan menyimpan nilai seberapa miring sebuah bidan guntuk dianggal tembok vertikal, bukan bidang miring.

1
public const int cSlopeWallHeight = 4;

Jika offset di bawah ini sama dengan nilai konstanta, seharusnya mungkin untuk sebuah objek bergerak dengan mulus pada bidang miring. Jika sama atau lebih besar, bidang ini perlu dianggap tembok, dan aksi lompat akan dibutuhkan untuk bisa memanjatnya.

Sekarang kita perlu menambah sebuah kondisi untuk pernyataan kita. Kondisi ini akan memeriksa apakah karakter seharusnya menempel pada bidang miring, apakah pada frame sebelumnya karakter berada di atas bidang miring, dan apakah karakter perlu didorong ke bawah atau atas sejumlah piksel yang lebih sedikit dari konstanta cSlopeWallHeight.

1
if (sf.freeUp >= 0 || (mSticksToSlope && state.pushedBottom && sf.freeUp - sf.collidingBottom < Constants.cSlopeWallHeight))
2
{
3
}

Jika kondisi tersebut benar, kita perlu menyimpan petak ini sebagai kandidat yang bisa menabrak  objek. Kita masih perlu mengiterasi semua petak sepanjang sumbu X. Pertama, buat variabel yang akan menyimpan koordinat X dan nilai offset untuk petak yang tabrakan.

1
Vector2i topRightTile = mMap.GetMapTileAtPoint(new Vector2(topRight.x - 0.5f, topRight.y - 0.5f));
2
Vector2i bottomleftTile = mMap.GetMapTileAtPoint(new Vector2(bottomLeft.x + 0.5f, bottomLeft.y - 0.5f));
3
int collidingBottom = int.MinValue;
4
int slopeX = -1;

Sekarang simpan nilai-nilainya, jika kondisi tersebut bernilai benar. Jika kita sudah menemukan petak yang tabrakan, kita perlu membandingkan offset, dan petak terakhir yang tabrakan adalah kita gunakan untuk mengatur offset karakter.

1
if ((sf.freeUp >= 0 || (mSticksToSlope && state.pushedBottom && sf.freeUp - sf.collidingBottom < Constants.cSlopeWallHeight))
2
    && sf.collidingBottom >= collidingBottom)
3
{
4
    collidingBottom = sf.collidingBottom;
5
    slopeX = x;
6
} 

Akhirnya, setelah kita mengiterasi seluruh petak dan menemukan petak yang tabrakan dengan objek, kita perlu mengatur offset objek tersebut.

1
if (slopeX != -1)
2
{
3
    state.pushesBottomTile = true;
4
    state.bottomTile = new Vector2i(slopeX, bottomleftTile.y);
5
    position.y += collidingBottom;
6
    topRight.y += collidingBottom;
7
    bottomLeft.y += collidingBottom;
8
    return true;
9
}
10
11
return false;

Sekian untuk pemeriksaan bagian bawah, sekarang kita buat untuk bagian atas. Bagian ini akan lebih sederhana, kita bahkan tidak perlu menangani kondisi menempel.

1
public bool CollidesWithTileTop(ref Vector2 position, ref Vector2 topRight, ref Vector2 bottomLeft, ref PositionState state)
2
{
3
    Vector2i topRightTile = mMap.GetMapTileAtPoint(new Vector2(topRight.x - 0.5f, topRight.y + 0.5f));
4
    Vector2i bottomleftTile = mMap.GetMapTileAtPoint(new Vector2(bottomLeft.x + 0.5f, bottomLeft.y + 0.5f));
5
    int freeDown = int.MaxValue;
6
    int slopeX = -1;
7
8
    for (int x = bottomleftTile.x; x <= topRightTile.x; ++x)
9
    {
10
        var tileCollisionType = mMap.GetCollisionType(x, topRightTile.y);
11
12
        if (Slopes.IsOneWay(tileCollisionType))
13
            continue;
14
15
        switch (tileCollisionType)
16
        {
17
            default://slope

18
19
                Vector2 tileCenter = mMap.GetMapTilePosition(x, topRightTile.y);
20
                SlopeOffsetI sf = Slopes.GetOffset(tileCenter, bottomLeft.x + 0.5f, topRight.x - 0.5f, bottomLeft.y + 0.5f, topRight.y + 0.5f, tileCollisionType);
21
                sf.freeDown += 1;
22
                sf.collidingTop += 1;
23
24
                if (sf.freeDown < freeDown && sf.freeDown <= 0 && sf.freeDown == sf.collidingTop)
25
                {
26
                    freeDown = sf.freeDown;
27
                    slopeX = x;
28
                }
29
30
                break;
31
            case TileCollisionType.Empty:
32
                break;
33
            case TileCollisionType.Full:
34
                state.pushesTopTile = true;
35
                state.topTile = new Vector2i(x, topRightTile.y);
36
                return true;
37
        }
38
    }
39
40
    if (slopeX != -1)
41
    {
42
        state.pushesTopTile = true;
43
        state.topTile = new Vector2i(slopeX, topRightTile.y);
44
        position.y += freeDown;
45
        topRight.y += freeDown;
46
        bottomLeft.y += freeDown;
47
        return true;
48
    }
49
50
    return false;
51
}

Sekian.

Pengecekan horizontal terhadap bidang miring

Pengecekan horizontal akan lebih rumit, karena di sini kita akan menangani berbagai kasus yang menyulitkan.

Kita mulai dengan menangani bidang miring di sebelah kanan. Ada beberapa hal yang kita perlu ketahui, terutama terkait bergerak ke atas bidang miring. Kita pertimbangkan kondisi berikut.

Different shaped slopesDifferent shaped slopesDifferent shaped slopes

Kita perlu menangani kasus-kasus tersebut dengan hati-hati karena di satu titik ketika kita begerak di bidang miring kita akan menabrak langit-langit. Untuk mencegahnya, kita perlu melakukan pemeriksaan jika karakter bergerak secara horizontal.

Untuk pengecekan vertikal, kita menggerakkan objek ke atas sebuah petak, tapi pada umumnya kita tidak akan menggunakan fungsionalitas itu di sini. Karena kita selalu memeriksa piksel yang tepat di luar batas objek, kita tidak pernah benar-benar tumpang tindih dengan sebuah rintangan. Untuk pemeriksaan horizontal akan sedikit berbeda, karena ini adalah tempat di mana kita menangani gerakan sepanjang bidang miring, jadi pengaturan ketinggian akan dilakukan pada fungsi ini.

Untuk membuat respon tabrakan yang benar untuk kasus pada gambar di atas, akan lebih mudah untuk memeriksa apakah kita bisa masuk sebuah ruang secara horizontal, dan jika memungkinkan lalu periksa apakah objek tidak tumpang tindih dengan piksel solid jika harus digerakkan secara vertikal karena bergerak pada bidang miring. Jika kita gagal menemukan ruang tersebut, kita tahu bahwa tidak mungkin untuk bergerak ke arah tersebut, dan kita ibisa atur penanda tembok horizontal.

Kita lanjutkan ke fungsi CollidesWithTileRight, ke bagian di mana kita menangani bidang miring.

1
default://slope
2
3
    Vector2 tileCenter = mMap.GetMapTilePosition(topRightTile.x, y);
4
    float leftTileEdge = (tileCenter.x - Map.cTileSize / 2);
5
    float rightTileEdge = (leftTileEdge + Map.cTileSize);
6
    float bottomTileEdge = (tileCenter.y - Map.cTileSize / 2);

kita dapatkan offset dengan cara yang sama pada pengecekank vertikal, tapi offset yang kita butuhkan adalah yang lebih besar.

1
var offset = Slopes.GetOffsetHeight(tileCenter, bottomLeft.x + 0.5f, topRight.x + 0.5f, bottomLeft.y + 0.5f, topRight.y - 0.5f, tileCollisionType);
2
slopeOffset = Mathf.Abs(offset.freeUp) < Mathf.Abs(offset.freeDown) ? offset.freeUp : offset.freeDown;

Sekarang, kita lihat apakah karakter kita perlu memperlakukan petak yang diperiksa sebagai tembok. Kita lakukan ini jika offset bidang miring lebih besar atau sama dengan konstanta cSlopeWallHeight atau untuk keluar dari tabrakan kita perlu menggeser karakter ke atas atau bawah saat kita sudah menabrak petak di arah yang sama, artinya objek kita berada di tengah-tengah petak atas dan bawah.

1
var offset = Slopes.GetOffsetHeight(tileCenter, bottomLeft.x + 0.5f, topRight.x + 0.5f, bottomLeft.y + 0.5f, topRight.y - 0.5f, tileCollisionType);
2
slopeOffset = Mathf.Abs(offset.freeUp) < Mathf.Abs(offset.freeDown) ? offset.freeUp : offset.freeDown;
3
4
if (Mathf.Abs(slopeOffset) >= Constants.cSlopeWallHeight || (slopeOffset < 0 && state.pushesBottomTile) || (slopeOffset > 0 && state.pushesTopTile))
5
{
6
    state.pushesRightTile = true;
7
    state.rightTile = new Vector2i(topRightTile.x, y);
8
    return true;
9
}

Jika bukan itu dan offset lebih besar dari nol, artinya kita menabrak bidang miring. Satu masalah di sini adalah kita tidak tahu apakah kita menabrak tembok di petak lain yang belum kita periksa, jadi saat ini kita hanya simpan offset bidang miring dan jenis tabrakan siapa tahu akan kita butuhkan nanti.

1
Vector2i topRightTile = mMap.GetMapTileAtPoint(new Vector2(topRight.x + 0.5f, topRight.y - 0.5f));
2
Vector2i bottomLeftTile = mMap.GetMapTileAtPoint(new Vector2(bottomLeft.x + 0.5f, bottomLeft.y + 0.5f));
3
float slopeOffset = 0.0f, oldSlopeOffset = 0.0f;
4
TileCollisionType slopeCollisionType = TileCollisionType.Empty;

Sekarang kita tidak memeriksa offset bidang miring lebih besar dari nol, melainkan kita bandingkan dengan offset bidang miring pada petak lain, jika kita sudah menemukan tabrakan dengan bidang miring pada iterasi sebelumnya.

1
var offset = Slopes.GetOffsetHeight(tileCenter, bottomLeft.x + 0.5f, topRight.x + 0.5f, bottomLeft.y + 0.5f, topRight.y - 0.5f, tileCollisionType);
2
slopeOffset = Mathf.Abs(offset.freeUp) < Mathf.Abs(offset.freeDown) ? offset.freeUp : offset.freeDown;
3
4
if (Mathf.Abs(slopeOffset) >= Constants.cSlopeWallHeight || (slopeOffset < 0 && state.pushesBottomTile) || (slopeOffset > 0 && state.pushesTopTile))
5
{
6
    state.pushesRightTile = true;
7
    state.rightTile = new Vector2i(topRightTile.x, y);
8
    return true;
9
}
10
else if (Mathf.Abs(slopeOffset) > Mathf.Abs(oldSlopeOffset))
11
{
12
    slopeCollisionType = tileCollisionType;
13
    state.rightTile = new Vector2i(topRightTile.x, y);
14
}
15
else
16
    slopeOffset = oldSlopeOffset;

Menangani kasus terjepit di antara petak

Setelah kita selesai memproses semua petak yang diperlukan, kita lihat apakah kita perlu menggerakkan objek. Kita tangani kasus di mana offset bidang miring bernilai bukan nol.

1
if (slopeOffset != 0.0f)
2
{
3
}

Kita perlu menangani dua kasus di sini, dan kita perlu melakukan hal yang sedikit berbeda tergantung apakah kita perlu offset objek ke atas atau ke bawah.

1
if (slopeOffset != 0.0f)
2
{
3
    if (slopeOffset > 0 && slopeOffset < Constants.cSlopeWallHeight)
4
    {
5
    }
6
}

Pertama, kita perlu memeriksa apakah kita bisa menempati ruang setelah menggeser objek. Jika iya, kita menangani satu dari kasus yang digambarkan di atas. Karakter mencoba untuk bergerak ke kanan, offset positif, namun jika kita menggeser objek maka akan terdorong ke tembok atas, jadi kita tandai bahwa objek bertabrakan dengan tembok di kanan dan mencegah pergerakkan ke arah tersebut.

1
if (slopeOffset > 0 && slopeOffset < Constants.cSlopeWallHeight)
2
{
3
    Vector2 pos = position, tr = topRight, bl = bottomLeft;
4
    pos.y += slopeOffset - Mathf.Sign(slopeOffset);
5
    tr.y += slopeOffset - Mathf.Sign(slopeOffset);
6
    bl.y += slopeOffset - Mathf.Sign(slopeOffset);
7
    PositionState s = new PositionState();
8
9
    if (CollidesWithTileTop(ref pos, ref tr, ref bl, ref s))
10
    {
11
        state.pushesRightTile = true;
12
        state.pushesRightSlope = true;
13
        return true;
14
    }
15
}

Jika kita cukup dalam ruang tersebut, kita tandai bahwa kita tabrakan dengan petak bawah dan menggeser posisi objek sesuai kebutuhan.

1
if (CollidesWithTileTop(ref pos, ref tr, ref bl, ref s))
2
{
3
    state.pushesRightTile = true;
4
    state.pushesRightSlope = true;
5
    return true;
6
}
7
else
8
{
9
    position.y += slopeOffset;
10
    bottomLeft.y += slopeOffset;
11
    topRight.y += slopeOffset;
12
    state.pushesBottomTile = true;
13
    state.pushesBottomSlope = true;
14
}

Kita tangani kasus di mana objek perlu digeser ke bawah dengan cara yang sama.

1
if (slopeOffset != 0.0f)
2
{
3
    if (slopeOffset > 0 && slopeOffset < Constants.cSlopeWallHeight)
4
    {
5
        Vector2 pos = position, tr = topRight, bl = bottomLeft;
6
        pos.y += slopeOffset - Mathf.Sign(slopeOffset);
7
        tr.y += slopeOffset - Mathf.Sign(slopeOffset);
8
        bl.y += slopeOffset - Mathf.Sign(slopeOffset);
9
        PositionState s = new PositionState();
10
11
        if (CollidesWithTileTop(ref pos, ref tr, ref bl, ref s))
12
        {
13
            state.pushesRightTile = true;
14
            state.pushesRightSlope = true;
15
            return true;
16
        }
17
        else
18
        {
19
            position.y += slopeOffset;
20
            bottomLeft.y += slopeOffset;
21
            topRight.y += slopeOffset;
22
            state.pushesBottomTile = true;
23
            state.pushesBottomSlope = true;
24
        }
25
    }
26
    else if (slopeOffset < 0 && slopeOffset > -Constants.cSlopeWallHeight)
27
    {
28
        Vector2 pos = position, tr = topRight, bl = bottomLeft;
29
        pos.y += slopeOffset - Mathf.Sign(slopeOffset);
30
        tr.y += slopeOffset - Mathf.Sign(slopeOffset);
31
        bl.y += slopeOffset - Mathf.Sign(slopeOffset);
32
        PositionState s = new PositionState();
33
34
        if (CollidesWithTileBottom(ref pos, ref tr, ref bl, ref s))
35
        {
36
            state.pushesRightTile = true;
37
            state.pushesRightSlope = true;
38
            return true;
39
        }
40
        else
41
        {
42
            position.y += slopeOffset;
43
            bottomLeft.y += slopeOffset;
44
            topRight.y += slopeOffset;
45
            state.pushesTopTile = true;
46
            state.pushesTopSlope = true;
47
        }
48
    }
49
}

Objek yang bergerak saat pengecekan tabrakan

Fungsi ini akan menggeser objek ke atas atau bawah sesuai kebutuhan jika kita ingin melangkah ke petak di kanan, tapi bagaimana jika kita ingin menggunakan fungsi ini hanya untuk pengecekan, dan kita tidak ingin menggerakkan karakter saat memanggil fungsi ini? Untuk memecahkan masalah ini, kita tambahkan variabel bernama 'move' untuk menandai apakah fungsi bisa menggerakkan objek atau tidak.

1
public bool CollidesWithTileRight(ref Vector2 position, ref Vector2 topRight, ref Vector2 bottomLeft, ref PositionState state, bool move = false)
2
{
3
}

Dan gerakkan objek hanya jika penanda ini bernilai true.

1
if (CollidesWithTileTop(ref pos, ref tr, ref bl, ref s))
2
{
3
    state.pushesRightTile = true;
4
    state.pushesRightSlope = true;
5
    return true;
6
}
7
else if (move)
8
{
9
    position.y += slopeOffset;
10
    bottomLeft.y += slopeOffset;
11
    topRight.y += slopeOffset;
12
    state.pushesBottomTile = true;
13
    state.pushesBottomSlope = true;
14
}
15
16
//...

17
18
if (CollidesWithTileBottom(ref pos, ref tr, ref bl, ref s))
19
{
20
    state.pushesRightTile = true;
21
    state.pushesRightSlope = true;
22
    return true;
23
}
24
else if (move)
25
{
26
    position.y += slopeOffset;
27
    bottomLeft.y += slopeOffset;
28
    topRight.y += slopeOffset;
29
    state.pushesTopTile = true;
30
    state.pushesTopSlope = true;
31
}
32
33
34

Menangani kondisi menempel pada bidang miring

Sekarang kita tangani kondisi menempel pada bidang miring. Cukup sederhana, tapi kita perlu menangani semua kasus sudut dengan benar, agar karakter menempel pada bidang miring tanpa gangguan sepanjang jalan.

Sebelum kita tangani kasus sudut, kita bisa menangani kondisi menempel pada bidang miring pada suatu petak dalam pengecekan tabrakan vertikal. Hal ini cukup dengan menambahkan kondisi berikut pada fungsi CollidesWithTileBottom.

1
if ((sf.freeUp >= 0 && sf.collidingBottom == sf.freeUp)
2
    || (mSticksToSlope && state.pushedBottom && sf.freeUp - sf.collidingBottom < Constants.cSlopeWallHeight && sf.freeUp >= sf.collidingBottom))
3
{
4
    state.onOneWay = isOneWay;
5
    state.oneWayY = bottomleftTile.y;
6
    state.pushesBottomTile = true;
7
    state.bottomTile = new Vector2i(x, bottomleftTile.y);
8
    position.y += sf.collidingBottom;
9
    topRight.y += sf.collidingBottom;
10
    bottomLeft.y += sf.collidingBottom;
11
    return true;
12
}

Kondisi ini membuat karakter terdorong ke bawah jika jarak antara posisi objek dan tanah terdekat di antara nol dan cSlopeWallHeight. Sayangnya hal ini hanya berlaku dalam satu petak saja, gambar berikut menunjukkan masalah yang perlu kita pecahkan.

Slope with three squares marked on itSlope with three squares marked on itSlope with three squares marked on it

Kasus sudut yang kita bicarakan adalah sebagai berikut: karakter bergerak ke bawah dan kiri dari petak nomor satu ke petak nomor dua. Petak nomor dua kosong, jadi kita perlu cek petak di bawahnya dan lihat apakah offset karakter ke petak nomor tiga sudah cukup untuk terus berjalan sepanjang bidang miring di situ.

Menangani kasus sudut

Akan lebih mudah untuk menangani kasus sudut ini pada pemeriksaan tabrakan horizontal, jadi kita kembali ke fungsi CollidesWithTileRight. Kita lihat akhir fungsi dan tangani kasus tersebut di sana.

Pertama, untuk menangani kondisi menempel pada bidang miring, penanda mSticksToSlope perlu diatur, dan objek harus ada di tanah pada frame sebelumnya, dan penanda gerakkan harus menyala.

1
if (mSticksToSlope && state.pushedBottomTile && move)
2
{
3
}

Sekarang kita perlu temukan petak yang harus kita tempel. Karena fungsi ini memeriksa tabrakan pada sisi kanan objek, kita akan menangani kondisi menempel untuk sisi kiri bawah karakter.

1
var nextX = mMap.GetMapTileXAtPoint(topRight.x - 1.5f);
2
var bottomY = mMap.GetMapTileYAtPoint(bottomLeft.y + 1.0f) - 1;
3
4
var prevPos = mMap.GetMapTilePosition(new Vector2i(topRightTile.x, bottomLeftTile.y));
5
var nextPos = mMap.GetMapTilePosition(new Vector2i(nextX, bottomY));
6
7
var prevCollisionType = mMap.GetCollisionType(new Vector2i(topRightTile.x, bottomLeftTile.y));
8
var nextCollisionType = mMap.GetCollisionType(new Vector2i(nextX, bottomY));

Sekarang kita perlu cari cara untuk membandingkan tinggi objek saat ini dengan objek yang akan dilewati. Jika tinggi berikutnya lebih rendah dari yang sekarang, tapi lebih tinggi dari konstanta cSlopeWallHeight, kita dorong objek kita ke tanah.

Mendapatkan ketinggian bidang miring

Kita kembali ke kelas Slope untuk membuat fungsi yang akan mengembalikan tinggi dari bidang miring di suatu posisi tertentu.

1
public static int GetSlopeHeightFromBottom(int x, TileCollisionType type)
2
{
3
    switch (type)
4
    {
5
        case TileCollisionType.Empty:
6
            return 0;
7
        case TileCollisionType.Full:
8
        case TileCollisionType.OneWayPlatform:
9
            return Map.cTileSize;
10
    }
11
}

Parameter untuk fungsi ini adalah nilai x pada bidang miring dan jenis bidang miring. Jika bidang miring kosong kita langsung kembalikan nol dan jika penuh maka kita kembalikan ukuran petak.

Kita bisa mendapatkan tinggi bidang miring dengan menggunakan offset yang kita simpan. Jika petak tidak ditransformasikan, kita cukup mengambil offset untuk sebuah objek dengan lebar satu piksel pada posisi x, dan tingginya sama dengan tinggi petak.

1
public static int GetSlopeHeightFromBottom(int x, TileCollisionType type)
2
{
3
    switch (type)
4
    {
5
        case TileCollisionType.Empty:
6
            return 0;
7
        case TileCollisionType.Full:
8
        case TileCollisionType.OneWayPlatform:
9
            return Map.cTileSize;
10
    }
11
    
12
    var offset = new SlopeOffsetI(slopeOffsets[(int)type][x][0][0][Map.cTileSize - 1]);
13
    return offset.collidingBottom;
14
}

Kita tangani kasus ini untuk berbagai transformasi yang berbeda. Jika bidang miring dicerminkan pada sumbu X, kita cukup mencerminkan parameter x.

1
public static int GetSlopeHeightFromBottom(int x, TileCollisionType type)
2
{
3
    switch (type)
4
    {
5
        case TileCollisionType.Empty:
6
            return 0;
7
        case TileCollisionType.Full:
8
        case TileCollisionType.OneWayPlatform:
9
            return Map.cTileSize;
10
    }
11
    
12
    if (IsFlippedX(type))
13
        x = Map.cTileSize - 1 - x;
14
            
15
    var offset = new SlopeOffsetI(slopeOffsets[(int)type][x][0][0][Map.cTileSize - 1]);
16
    return offset.collidingBottom;
17
}

Jika bidang miring dicerminkan pada sumbu Y, kita perlu mengembalikan offset collidingTop, bukan collidingBottom. Karena collidingTop dalam kasus ini akan negatif, kita juga perlu mengubah tandanya.

1
var offset = new SlopeOffsetI(slopeOffsets[(int)type][x][0][0][Map.cTileSize - 1]);
2
return IsFlippedY(type) ? -offset.collidingTop : offset.collidingBottom;

Akhirnya, jika petak diputar 90 derajat, kita perlu mengembalikan offset collidingLeft atau collidingRight. Selain itu, untuk mendapatkan offset yang sudah tersimpan, kita perlu menukar posisi x dan y dan ukurannya.

1
if (!IsFlipped90(type))
2
{
3
    var offset = new SlopeOffsetI(slopeOffsets[(int)type][x][0][0][Map.cTileSize - 1]);
4
    return IsFlippedY(type) ? -offset.collidingTop : offset.collidingBottom;
5
}
6
else
7
{
8
    var offset = new SlopeOffsetI(slopeOffsets[(int)type][0][x][Map.cTileSize - 1][0]);
9
    return IsFlippedY(type) ? offset.collidingLeft : -offset.collidingRight;
10
}

Itu adalah fungsi akhirnya.

1
public static int GetSlopeHeightFromBottom(int x, TileCollisionType type)
2
{
3
    switch (type)
4
    {
5
        case TileCollisionType.Empty:
6
            return 0;
7
        case TileCollisionType.Full:
8
        case TileCollisionType.OneWayPlatform:
9
            return Map.cTileSize;
10
    }
11
12
    if (IsFlippedX(type))
13
        x = Map.cTileSize - 1 - x;
14
15
    if (!IsFlipped90(type))
16
    {
17
        var offset = new SlopeOffsetI(slopeOffsets[(int)type][x][0][0][Map.cTileSize - 1]);
18
        return IsFlippedY(type) ? -offset.collidingTop : offset.collidingBottom;
19
    }
20
    else
21
    {
22
        var offset = new SlopeOffsetI(slopeOffsets[(int)type][0][x][Map.cTileSize - 1][0]);
23
        return IsFlippedY(type) ? offset.collidingLeft : -offset.collidingRight;
24
    }
25
}

Kembali ke kasus sudut

Kita kembali ke fungsi CollidesWithTileRight, tepat di mana kita menentukan jenis bidang miring untuk petak yang dilewati karakter.

Untuk menggunakan fungsi yang baru kita buat, kita perlu menentukan posisi di mana kita ingin mendapatkan tinggi petak.

1
var prevCollisionType = mMap.GetCollisionType(new Vector2i(bottomLeftTile.x, bottomLeftTile.y));
2
var nextCollisionType = mMap.GetCollisionType(new Vector2i(nextX, bottomY));
3
4
int x1 = (int)Mathf.Clamp((bottomLeft.x - (prevPos.x - Map.cTileSize / 2)), 0.0f, 15.0f);
5
int x2 = (int)Mathf.Clamp((bottomLeft.x + 1.0f - (nextPos.x - Map.cTileSize / 2)), 0.0f, 15.0f);

Sekarang kita hitung tinggi antara dua titik tersebut.

1
int slopeHeight = Slopes.GetSlopeHeightFromBottom(x1, prevCollisionType);
2
int nextSlopeHeight = Slopes.GetSlopeHeightFromBottom(x2, nextCollisionType);
3
4
var offset = slopeHeight + Map.cTileSize - nextSlopeHeight;

Jika offset bernilai antara nol dan konstanta cSlopeWallHeight, maka kita perlu mendorong objek ke bawah, namun kita perlu memeriksa apakah kita bisa mendorong objek ke bawah. Ini adalah aksi yang sama dengan sebelumnya.

1
if (offset < Constants.cSlopeWallHeight && offset > 0)
2
{
3
    Vector2 pos = position, tr = topRight, bl = bottomLeft;
4
    pos.y -= offset - Mathf.Sign(offset);
5
    tr.y -= offset - Mathf.Sign(offset);
6
    bl.y -= offset - Mathf.Sign(offset);
7
    bl.x += 1.0f;
8
    tr.x += 1.0f;
9
    PositionState s = new PositionState();
10
11
    if (!CollidesWithTileBottom(ref pos, ref tr, ref bl, ref s))
12
    {
13
        position.y -= offset;
14
        bottomLeft.y -= offset;
15
        topRight.y -= offset;
16
        state.pushesBottomTile = true;
17
        state.pushesBottomSlope = true;
18
    }
19
}

Lengkapnya, fungsi ini akan terlhat sebagai berikut.

1
public bool CollidesWithTileRight(ref Vector2 position, ref Vector2 topRight, ref Vector2 bottomLeft, ref PositionState state, bool move = false)
2
{
3
    Vector2i topRightTile = mMap.GetMapTileAtPoint(new Vector2(topRight.x + 0.5f, topRight.y - 0.5f));
4
    Vector2i bottomLeftTile = mMap.GetMapTileAtPoint(new Vector2(bottomLeft.x + 0.5f, bottomLeft.y + 0.5f));
5
    float slopeOffset = 0.0f, oldSlopeOffset = 0.0f;
6
    TileCollisionType slopeCollisionType = TileCollisionType.Empty;
7
8
    for (int y = bottomLeftTile.y; y <= topRightTile.y; ++y)
9
    {
10
        var tileCollisionType = mMap.GetCollisionType(topRightTile.x, y);
11
12
        switch (tileCollisionType)
13
        {
14
            default://slope

15
16
                Vector2 tileCenter = mMap.GetMapTilePosition(topRightTile.x, y);
17
                float leftTileEdge = (tileCenter.x - Map.cTileSize / 2);
18
                float rightTileEdge = (leftTileEdge + Map.cTileSize);
19
                float bottomTileEdge = (tileCenter.y - Map.cTileSize / 2);
20
21
                oldSlopeOffset = slopeOffset;
22
23
                var offset = Slopes.GetOffsetHeight(tileCenter, bottomLeft.x + 0.5f, topRight.x + 0.5f, bottomLeft.y + 0.5f, topRight.y - 0.5f, tileCollisionType);
24
                slopeOffset = Mathf.Abs(offset.freeUp) < Mathf.Abs(offset.freeDown) ? offset.freeUp : offset.freeDown;
25
26
                if (Mathf.Abs(slopeOffset) >= Constants.cSlopeWallHeight || (slopeOffset < 0 && state.pushesBottomTile) || (slopeOffset > 0 && state.pushesTopTile))
27
                {
28
                    state.pushesRightTile = true;
29
                    state.rightTile = new Vector2i(topRightTile.x, y);
30
                    return true;
31
                }
32
                else if (Mathf.Abs(slopeOffset) > Mathf.Abs(oldSlopeOffset))
33
                {
34
                    slopeCollisionType = tileCollisionType;
35
                    state.rightTile = new Vector2i(topRightTile.x, y);
36
                }
37
                else
38
                    slopeOffset = oldSlopeOffset;
39
40
                break;
41
            case TileCollisionType.Empty:
42
                break;
43
        }
44
    }
45
46
    if (slopeOffset != 0.0f)
47
    {
48
        if (slopeOffset > 0 && slopeOffset < Constants.cSlopeWallHeight)
49
        {
50
            Vector2 pos = position, tr = topRight, bl = bottomLeft;
51
            pos.y += slopeOffset - Mathf.Sign(slopeOffset);
52
            tr.y += slopeOffset - Mathf.Sign(slopeOffset);
53
            bl.y += slopeOffset - Mathf.Sign(slopeOffset);
54
            PositionState s = new PositionState();
55
56
            if (CollidesWithTileTop(ref pos, ref tr, ref bl, ref s))
57
            {
58
                state.pushesRightTile = true;
59
                state.pushesRightSlope = true;
60
                return true;
61
            }
62
            else if (move)
63
            {
64
                position.y += slopeOffset;
65
                bottomLeft.y += slopeOffset;
66
                topRight.y += slopeOffset;
67
                state.pushesBottomTile = true;
68
                state.pushesBottomSlope = true;
69
            }
70
        }
71
        else if (slopeOffset < 0 && slopeOffset > -Constants.cSlopeWallHeight)
72
        {
73
            Vector2 pos = position, tr = topRight, bl = bottomLeft;
74
            pos.y += slopeOffset - Mathf.Sign(slopeOffset);
75
            tr.y += slopeOffset - Mathf.Sign(slopeOffset);
76
            bl.y += slopeOffset - Mathf.Sign(slopeOffset);
77
            PositionState s = new PositionState();
78
79
            if (CollidesWithTileBottom(ref pos, ref tr, ref bl, ref s))
80
            {
81
                state.pushesRightTile = true;
82
                state.pushesRightSlope = true;
83
                return true;
84
            }
85
            else if (move)
86
            {
87
                position.y += slopeOffset;
88
                bottomLeft.y += slopeOffset;
89
                topRight.y += slopeOffset;
90
                state.pushesTopTile = true;
91
                state.pushesTopSlope = true;
92
            }
93
        }
94
    }
95
96
    if (mSticksToSlope && state.pushedBottomTile && move)
97
    {
98
        var nextX = mMap.GetMapTileXAtPoint(bottomLeft.x + 1.0f);
99
        var bottomY = mMap.GetMapTileYAtPoint(bottomLeft.y + 1.0f) - 1;
100
101
        var prevPos = mMap.GetMapTilePosition(new Vector2i(bottomLeftTile.x, bottomLeftTile.y));
102
        var nextPos = mMap.GetMapTilePosition(new Vector2i(nextX, bottomY));
103
104
        var prevCollisionType = mMap.GetCollisionType(new Vector2i(bottomLeftTile.x, bottomLeftTile.y));
105
        var nextCollisionType = mMap.GetCollisionType(new Vector2i(nextX, bottomY));
106
107
        int x1 = (int)Mathf.Clamp((bottomLeft.x - (prevPos.x - Map.cTileSize / 2)), 0.0f, 15.0f);
108
        int x2 = (int)Mathf.Clamp((bottomLeft.x + 1.0f - (nextPos.x - Map.cTileSize / 2)), 0.0f, 15.0f);
109
110
        int slopeHeight = Slopes.GetSlopeHeightFromBottom(x1, prevCollisionType);
111
        int nextSlopeHeight = Slopes.GetSlopeHeightFromBottom(x2, nextCollisionType);
112
113
        var offset = slopeHeight + Map.cTileSize - nextSlopeHeight;
114
115
        if (offset < Constants.cSlopeWallHeight && offset > 0)
116
        {
117
            Vector2 pos = position, tr = topRight, bl = bottomLeft;
118
            pos.y -= offset - Mathf.Sign(offset);
119
            tr.y -= offset - Mathf.Sign(offset);
120
            bl.y -= offset - Mathf.Sign(offset);
121
            bl.x += 1.0f;
122
            tr.x += 1.0f;
123
            PositionState s = new PositionState();
124
125
            if (!CollidesWithTileBottom(ref pos, ref tr, ref bl, ref s))
126
            {
127
                position.y -= offset;
128
                bottomLeft.y -= offset;
129
                topRight.y -= offset;
130
                state.pushesBottomTile = true;
131
                state.pushesBottomSlope = true;
132
            }
133
        }
134
    }
135
136
    return false;
137
}

Sekarang kita perlu melakukan semuanya lagi untuk fungsi CollidesWithTileLeft. Versi akhir fungsi tersebut akan seperti berikut ini.

1
public bool CollidesWithTileLeft(ref Vector2 position, ref Vector2 topRight, ref Vector2 bottomLeft, ref PositionState state, bool move = false)
2
{
3
    Vector2i topRightTile = mMap.GetMapTileAtPoint(new Vector2(topRight.x - 0.5f, topRight.y - 0.5f));
4
    Vector2i bottomLeftTile = mMap.GetMapTileAtPoint(new Vector2(bottomLeft.x - 0.5f, bottomLeft.y + 0.5f));
5
    float slopeOffset = 0.0f, oldSlopeOffset = 0.0f;
6
    TileCollisionType slopeCollisionType = TileCollisionType.Empty;
7
8
    for (int y = bottomLeftTile.y; y <= topRightTile.y; ++y)
9
    {
10
        var tileCollisionType = mMap.GetCollisionType(bottomLeftTile.x, y);
11
12
        switch (tileCollisionType)
13
        {
14
            default://slope

15
16
                Vector2 tileCenter = mMap.GetMapTilePosition(bottomLeftTile.x, y);
17
                float leftTileEdge = (tileCenter.x - Map.cTileSize / 2);
18
                float rightTileEdge = (leftTileEdge + Map.cTileSize);
19
                float bottomTileEdge = (tileCenter.y - Map.cTileSize / 2);
20
21
                oldSlopeOffset = slopeOffset;
22
23
                var offset = Slopes.GetOffsetHeight(tileCenter, bottomLeft.x - 0.5f, topRight.x - 0.5f, bottomLeft.y + 0.5f, topRight.y - 0.5f, tileCollisionType);
24
                slopeOffset = Mathf.Abs(offset.freeUp) < Mathf.Abs(offset.freeDown) ? offset.freeUp : offset.freeDown;
25
26
                if (Mathf.Abs(slopeOffset) >= Constants.cSlopeWallHeight || (slopeOffset < 0 && state.pushesBottomTile) || (slopeOffset > 0 && state.pushesTopTile))
27
                {
28
                    state.pushesLeftTile = true;
29
                    state.leftTile = new Vector2i(bottomLeftTile.x, y);
30
                    return true;
31
                }
32
                else if (Mathf.Abs(slopeOffset) > Mathf.Abs(oldSlopeOffset))
33
                {
34
                    slopeCollisionType = tileCollisionType;
35
                    state.leftTile = new Vector2i(bottomLeftTile.x, y);
36
                }
37
                else
38
                    slopeOffset = oldSlopeOffset;
39
40
41
                break;
42
            case TileCollisionType.Empty:
43
                break;
44
        }
45
    }
46
47
    if (slopeCollisionType != TileCollisionType.Empty && slopeOffset != 0)
48
    {
49
        if (slopeOffset > 0 && slopeOffset < Constants.cSlopeWallHeight)
50
        {
51
            Vector2 pos = position, tr = topRight, bl = bottomLeft;
52
            pos.y += slopeOffset - Mathf.Sign(slopeOffset);
53
            tr.y += slopeOffset - Mathf.Sign(slopeOffset);
54
            bl.y += slopeOffset - Mathf.Sign(slopeOffset);
55
            PositionState s = new PositionState();
56
57
            if (CollidesWithTileTop(ref pos, ref tr, ref bl, ref s))
58
            {
59
                state.pushesLeftTile = true;
60
                state.pushesLeftSlope = true;
61
                return true;
62
            }
63
            else if (move)
64
            {
65
                position.y += slopeOffset;
66
                bottomLeft.y += slopeOffset;
67
                topRight.y += slopeOffset;
68
                state.pushesBottomTile = true;
69
                state.pushesBottomSlope = true;
70
            }
71
        }
72
        else if (slopeOffset < 0 && slopeOffset > -Constants.cSlopeWallHeight)
73
        {
74
            Vector2 pos = position, tr = topRight, bl = bottomLeft;
75
            pos.y += slopeOffset - Mathf.Sign(slopeOffset);
76
            tr.y += slopeOffset - Mathf.Sign(slopeOffset);
77
            bl.y += slopeOffset - Mathf.Sign(slopeOffset);
78
            PositionState s = new PositionState();
79
80
            if (CollidesWithTileBottom(ref pos, ref tr, ref bl, ref s))
81
            {
82
                state.pushesLeftTile = true;
83
                state.pushesLeftSlope = true;
84
                return true;
85
            }
86
            else if (move)
87
            {
88
                position.y += slopeOffset;
89
                bottomLeft.y += slopeOffset;
90
                topRight.y += slopeOffset;
91
                state.pushesTopTile = true;
92
                state.pushesTopSlope = true;
93
            }
94
        }
95
    }
96
97
    if (mSticksToSlope && state.pushedBottomTile && move)
98
    {
99
        var nextX = mMap.GetMapTileXAtPoint(topRight.x - 1.5f);
100
        var bottomY = mMap.GetMapTileYAtPoint(bottomLeft.y + 1.0f) - 1;
101
102
        var prevPos = mMap.GetMapTilePosition(new Vector2i(topRightTile.x, bottomLeftTile.y));
103
        var nextPos = mMap.GetMapTilePosition(new Vector2i(nextX, bottomY));
104
105
        var prevCollisionType = mMap.GetCollisionType(new Vector2i(topRightTile.x, bottomLeftTile.y));
106
        var nextCollisionType = mMap.GetCollisionType(new Vector2i(nextX, bottomY));
107
108
        int x1 = (int)Mathf.Clamp((topRight.x - 1.0f - (prevPos.x - Map.cTileSize / 2)), 0.0f, 15.0f);
109
        int x2 = (int)Mathf.Clamp((topRight.x - 1.5f - (nextPos.x - Map.cTileSize / 2)), 0.0f, 15.0f);
110
111
        int slopeHeight = Slopes.GetSlopeHeightFromBottom(x1, prevCollisionType);
112
        int nextSlopeHeight = Slopes.GetSlopeHeightFromBottom(x2, nextCollisionType);
113
114
        var offset = slopeHeight + Map.cTileSize - nextSlopeHeight;
115
116
        if (offset < Constants.cSlopeWallHeight && offset > 0)
117
        {
118
            Vector2 pos = position, tr = topRight, bl = bottomLeft;
119
            pos.y -= offset - Mathf.Sign(offset);
120
            tr.y -= offset - Mathf.Sign(offset);
121
            bl.y -= offset - Mathf.Sign(offset);
122
            bl.x -= 1.0f;
123
            tr.x -= 1.0f;
124
            PositionState s = new PositionState();
125
126
            if (!CollidesWithTileBottom(ref pos, ref tr, ref bl, ref s))
127
            {
128
                position.y -= offset;
129
                bottomLeft.y -= offset;
130
                topRight.y -= offset;
131
                state.pushesBottomTile = true;
132
                state.pushesBottomSlope = true;
133
            }
134
        }
135
    }
136
137
    return false;
138
}

Sekitan. Kode tersebut seharusnya bisa menangani berbagai jenis bidang miring yang tidak ditranslasi.

Animation of character moving on slopeAnimation of character moving on slopeAnimation of character moving on slope

Menangani jenis translasi

Sebelum kita menangani petak yang ditransformasi, kita b uat beberapa fungsi yang akan mengembalikan apakah sebuah TileCollisionType ditranslasikan. Enum jenis tabrakan kita struktur sebagai berikut.

1
public enum TileCollisionType
2
{
3
    Empty = 0,              //normal tiles
4
    Full,
5
    OneWayPlatform,
6
    
7
    SlopesStart,            //starting point for slopes
8
    
9
    Slope45,                //basic version of the slope
10
    Slope45FX,              //slope flipped on the X axis
11
    Slope45FY,              //slope flipped on the Y axis
12
    Slope45FXY,             //slope flipped on the X and Y axes
13
    Slope45F90,             //slope rotated 90 degrees
14
    Slope45F90X,            //slope rotated and flipped on X axis
15
    Slope45F90Y,            //slope rotated and flipped on Y axis
16
    Slope45F90XY,           //slope rotated and flipped on both axes
17
    
18
    ...
19
}

Kita bisa menggunakan pola ini untuk mengetahui dari nilai enum apakah sebuah tabrakan ditranslasi. Kita mulai dengan mengidentifikasi pencerminan di sumbu X.

1
public static bool IsFlippedX(TileCollisionType type)
2
{
3
}

Pertama, kita ambil id bidang miring. Kita lakukan itu dengan menghitung offset dari petak bidang miring pertama ke petak yang ingin kita periksa.

1
public static bool IsFlippedX(TileCollisionType type)
2
{
3
    int typeId = (int)type - (int)TileCollisionType.SlopesStart + 1;
4
}

Kita punya delapan jenis translasi, sekarang yang perlu kita lakukan adalah mendapatkan sisa pembagian typeId dengan 8.

1
public static bool IsFlippedX(TileCollisionType type)
2
{
3
    int typeId = ((int)type - ((int)TileCollisionType.SlopesStart + 1)) % 8;
4
}

Sekarang sebuah angka sudah ditunjuk untuk setiap translasi.

1
Slope45,                //0
2
Slope45FX,              //1
3
Slope45FY,              //2
4
Slope45FXY,             //3
5
Slope45F90,             //4
6
Slope45F90X,            //5
7
Slope45F90Y,            //6
8
Slope45F90XY,           //7

Percerminan pada sumbu X terlihat pada tipe 1, 3, 5, dan 7, jadi fungsi perlu mengembalikan nilai true, jika tidak, kembalikan false.

1
public static bool IsFlippedX(TileCollisionType type)
2
{
3
    int typeId = ((int)type - ((int)TileCollisionType.SlopesStart + 1)) % 8;
4
5
    switch (typeId)
6
    {
7
        case 1:
8
        case 3:
9
        case 5:
10
        case 7:
11
            return true;
12
    }
13
14
    return false;
15
}

Dengan cara yang sama, kita buat fungsi yang memberitahu apakah sebuah tipe dicerminkan pada sumbu Y.

1
public static bool IsFlippedY(TileCollisionType type)
2
{
3
    int typeId = ((int)type - ((int)TileCollisionType.SlopesStart + 1)) % 8;
4
5
    switch (typeId)
6
    {
7
        case 2:
8
        case 3:
9
        case 6:
10
        case 7:
11
            return true;
12
    }
13
14
    return false;
15
}

Dan akhirnya, jika tipe tabrakan dirotasi.

1
public static bool IsFlipped90(TileCollisionType type)
2
{
3
    int typeId = ((int)type - ((int)TileCollisionType.SlopesStart + 1)) % 8;
4
5
    return (typeId > 3);
6
}

Itu saja yang kita butuhkan.

Transformasi offset

Kita kembali ke kelas Slopes dan buat fungsi GetOffset mendukung petak yang ditranslasi.

1
public static SlopeOffsetI GetOffset(Vector2 tileCenter, float leftX, float rightX, float bottomY, float topY, TileCollisionType tileCollisionType)
2
{
3
    int posX, posY, sizeX, sizeY;
4
5
    float leftTileEdge = tileCenter.x - Map.cTileSize / 2;
6
    float rightTileEdge = leftTileEdge + Map.cTileSize;
7
    float bottomTileEdge = tileCenter.y - Map.cTileSize / 2;
8
    float topTileEdge = bottomTileEdge + Map.cTileSize;
9
    SlopeOffsetI offset;
10
11
    posX = (int)Mathf.Clamp(leftX - leftTileEdge, 0.0f, Map.cTileSize - 1);
12
    sizeX = (int)Mathf.Clamp(rightX - (leftTileEdge + posX), 0.0f, Map.cTileSize - 1);
13
14
    posY = (int)Mathf.Clamp(bottomY - bottomTileEdge, 0.0f, Map.cTileSize - 1);
15
    sizeY = (int)Mathf.Clamp(topY - (bottomTileEdge + posY), 0.0f, Map.cTileSize - 1);
16
17
    offset = new SlopeOffsetI(slopeOffsets[(int)tileCollisionType][posX][posY][sizeX][sizeY]);
18
19
    if (topTileEdge < topY)
20
    {
21
        if (offset.freeDown < 0)
22
            offset.freeDown -= (int)(topY - topTileEdge);
23
        offset.collidingTop = offset.freeDown;
24
    }
25
    if (bottomTileEdge > bottomY)
26
    {
27
        if (offset.freeUp > 0)
28
            offset.freeUp += Mathf.RoundToInt(bottomTileEdge - bottomY);
29
        offset.collidingBottom = offset.freeUp;
30
    }
31
32
    return offset;
33
}

Seperti biasa, karena kita tidak punya data yang tersimpan untuk bidang miring yang ditranslasi, kita akan melakukan translasi terhadahp posisi dan ukuran objek jadi  hasilnya sama dengan seperti petak ditranslasikan. Kita mulai dengan pencerminan pada sumbu X. Kita perlu mencerminkan objek terhadap tengah petak.

1
if (IsFlippedX(tileCollisionType))
2
{
3
    posX = (int)Mathf.Clamp(rightTileEdge - rightX, 0.0f, Map.cTileSize - 1);
4
    sizeX = (int)Mathf.Clamp((rightTileEdge - posX) - leftX, 0.0f, Map.cTileSize - 1);
5
}
6
else
7
{
8
    posX = (int)Mathf.Clamp(leftX - leftTileEdge, 0.0f, Map.cTileSize - 1);
9
    sizeX = (int)Mathf.Clamp(rightX - (leftTileEdge + posX), 0.0f, Map.cTileSize - 1);
10
}

Begitu pula untuk pencerminan pada sumbu Y.

1
if (IsFlippedY(tileCollisionType))
2
{
3
    posY = (int)Mathf.Clamp(topTileEdge - topY, 0.0f, Map.cTileSize - 1);
4
    sizeY = (int)Mathf.Clamp((topTileEdge - posY) - bottomY, 0.0f, Map.cTileSize - 1);
5
}
6
else
7
{
8
    posY = (int)Mathf.Clamp(bottomY - bottomTileEdge, 0.0f, Map.cTileSize - 1);
9
    sizeY = (int)Mathf.Clamp(topY - (bottomTileEdge + posY), 0.0f, Map.cTileSize - 1);
10
}

Sekarang jika kita cerminkan petak pada sumbu Y, offset yang kita terima sebenarnya tertukar. Kita translasikan agar bekerja seperti offset pada petak yang tidak ditranslasi, artinya atas berarti atas dan bawah berarti bawah.

1
if (IsFlippedY(tileCollisionType))
2
{
3
    int tmp = offset.freeDown;
4
    offset.freeDown = -offset.freeUp;
5
    offset.freeUp = -tmp;
6
    tmp = offset.collidingTop;
7
    offset.collidingTop = -offset.collidingBottom;
8
    offset.collidingBottom = -tmp;
9
}

Sekarang kita tangani rotasi 90 derajat.

1
if (!IsFlipped90(tileCollisionType))
2
{
3
    if (IsFlippedX(tileCollisionType))
4
    {
5
        posX = (int)Mathf.Clamp(rightTileEdge - rightX, 0.0f, Map.cTileSize - 1);
6
        sizeX = (int)Mathf.Clamp((rightTileEdge - posX) - leftX, 0.0f, Map.cTileSize - 1);
7
    }
8
    else
9
    {
10
        posX = (int)Mathf.Clamp(leftX - leftTileEdge, 0.0f, Map.cTileSize - 1);
11
        sizeX = (int)Mathf.Clamp(rightX - (leftTileEdge + posX), 0.0f, Map.cTileSize - 1);
12
    }
13
14
    if (IsFlippedY(tileCollisionType))
15
    {
16
        posY = (int)Mathf.Clamp(topTileEdge - topY, 0.0f, Map.cTileSize - 1);
17
        sizeY = (int)Mathf.Clamp((topTileEdge - posY) - bottomY, 0.0f, Map.cTileSize - 1);
18
    }
19
    else
20
    {
21
        posY = (int)Mathf.Clamp(bottomY - bottomTileEdge, 0.0f, Map.cTileSize - 1);
22
        sizeY = (int)Mathf.Clamp(topY - (bottomTileEdge + posY), 0.0f, Map.cTileSize - 1);
23
    }
24
25
    offset = new SlopeOffsetI(slopeOffsets[(int)tileCollisionType][posX][posY][sizeX][sizeY]);
26
27
    if (IsFlippedY(tileCollisionType))
28
    {
29
        int tmp = offset.freeDown;
30
        offset.freeDown = -offset.freeUp;
31
        offset.freeUp = -tmp;
32
        tmp = offset.collidingTop;
33
        offset.collidingTop = -offset.collidingBottom;
34
        offset.collidingBottom = -tmp;
35
    }
36
}
37
else
38
{
39
}

Di sini semua seharusnya dirotasi 90 derajat, jadi kita tidak akan menggunakan sisi kiri dan kanan objek untuk menentukan posX dan sizeX, kita akan menggunakan sisi atas dan bawah.

1
if (IsFlippedY(tileCollisionType))
2
{
3
    posX = (int)Mathf.Clamp(bottomY - bottomTileEdge, 0.0f, Map.cTileSize - 1);
4
    sizeX = (int)Mathf.Clamp(topY - (bottomTileEdge + posX), 0.0f, Map.cTileSize - 1);
5
}
6
else
7
{
8
    posX = (int)Mathf.Clamp(topTileEdge - topY, 0.0f, Map.cTileSize - 1);
9
    sizeX = (int)Mathf.Clamp((topTileEdge - posX) - bottomY, 0.0f, Map.cTileSize - 1);
10
}
11
12
if (IsFlippedX(tileCollisionType))
13
{
14
    posY = (int)Mathf.Clamp(rightTileEdge - rightX, 0.0f, Map.cTileSize - 1);
15
    sizeY = (int)Mathf.Clamp((rightTileEdge - posY) - leftX, 0.0f, Map.cTileSize - 1);
16
}
17
else
18
{
19
    posY = (int)Mathf.Clamp(leftX - leftTileEdge, 0.0f, Map.cTileSize - 1);
20
    sizeY = (int)Mathf.Clamp(rightX - (leftTileEdge + posY), 0.0f, Map.cTileSize - 1);
21
}

Sekarang kita perlu melakukan hal yang sama seperti sebelumnya jika petak dicerminkan pada sumbu Y, tapi kali ini kita perlu melakukannya untuk rotasi 90 derajat dan pencerminan sumbu Y.

1
if (IsFlippedY(tileCollisionType))
2
{
3
    offset.collidingBottom = offset.collidingLeft;
4
    offset.freeDown = offset.freeLeft;
5
    offset.collidingTop = offset.collidingRight;
6
    offset.freeUp = offset.freeRight;
7
}
8
else
9
{
10
    offset.collidingBottom = -offset.collidingRight;
11
    offset.freeDown = -offset.freeRight;
12
    offset.collidingTop = -offset.collidingLeft;
13
    offset.freeUp = -offset.freeLeft;
14
}

Cukup sekian. Karena offset akhir untuk atas dan bawah diatur untuk ruang dunia, pengaturan batas petak kita akan tetap berjalan dengan benar.

1
public static SlopeOffsetI GetOffsetHeight(Vector2 tileCenter, float leftX, float rightX, float bottomY, float topY, TileCollisionType tileCollisionType)
2
{
3
    int posX, posY, sizeX, sizeY;
4
5
    float leftTileEdge = tileCenter.x - Map.cTileSize / 2;
6
    float rightTileEdge = leftTileEdge + Map.cTileSize;
7
    float bottomTileEdge = tileCenter.y - Map.cTileSize / 2;
8
    float topTileEdge = bottomTileEdge + Map.cTileSize;
9
    SlopeOffsetI offset;
10
11
    if (!IsFlipped90(tileCollisionType))
12
    {
13
        if (IsFlippedX(tileCollisionType))
14
        {
15
            posX = (int)Mathf.Clamp(rightTileEdge - rightX, 0.0f, Map.cTileSize - 1);
16
            sizeX = (int)Mathf.Clamp((rightTileEdge - posX) - leftX, 0.0f, Map.cTileSize - 1);
17
        }
18
        else
19
        {
20
            posX = (int)Mathf.Clamp(leftX - leftTileEdge, 0.0f, Map.cTileSize - 1);
21
            sizeX = (int)Mathf.Clamp(rightX - (leftTileEdge + posX), 0.0f, Map.cTileSize - 1);
22
        }
23
24
        if (IsFlippedY(tileCollisionType))
25
        {
26
            posY = (int)Mathf.Clamp(topTileEdge - topY, 0.0f, Map.cTileSize - 1);
27
            sizeY = (int)Mathf.Clamp((topTileEdge - posY) - bottomY, 0.0f, Map.cTileSize - 1);
28
        }
29
        else
30
        {
31
            posY = (int)Mathf.Clamp(bottomY - bottomTileEdge, 0.0f, Map.cTileSize - 1);
32
            sizeY = (int)Mathf.Clamp(topY - (bottomTileEdge + posY), 0.0f, Map.cTileSize - 1);
33
        }
34
35
        offset = new SlopeOffsetI(slopeOffsets[(int)tileCollisionType][posX][posY][sizeX][sizeY]);
36
37
        if (IsFlippedY(tileCollisionType))
38
        {
39
            int tmp = offset.freeDown;
40
            offset.freeDown = -offset.freeUp;
41
            offset.freeUp = -tmp;
42
            tmp = offset.collidingTop;
43
            offset.collidingTop = -offset.collidingBottom;
44
            offset.collidingBottom = -tmp;
45
        }
46
    }
47
    else
48
    {
49
        if (IsFlippedY(tileCollisionType))
50
        {
51
            posX = (int)Mathf.Clamp(bottomY - bottomTileEdge, 0.0f, Map.cTileSize - 1);
52
            sizeX = (int)Mathf.Clamp(topY - (bottomTileEdge + posX), 0.0f, Map.cTileSize - 1);
53
        }
54
        else
55
        {
56
            posX = (int)Mathf.Clamp(topTileEdge - topY, 0.0f, Map.cTileSize - 1);
57
            sizeX = (int)Mathf.Clamp((topTileEdge - posX) - bottomY, 0.0f, Map.cTileSize - 1);
58
        }
59
60
        if (IsFlippedX(tileCollisionType))
61
        {
62
            posY = (int)Mathf.Clamp(rightTileEdge - rightX, 0.0f, Map.cTileSize - 1);
63
            sizeY = (int)Mathf.Clamp((rightTileEdge - posY) - leftX, 0.0f, Map.cTileSize - 1);
64
        }
65
        else
66
        {
67
            posY = (int)Mathf.Clamp(leftX - leftTileEdge, 0.0f, Map.cTileSize - 1);
68
            sizeY = (int)Mathf.Clamp(rightX - (leftTileEdge + posY), 0.0f, Map.cTileSize - 1);
69
        }
70
71
        offset = new SlopeOffsetI(slopeOffsets[(int)tileCollisionType][posX][posY][sizeX][sizeY]);
72
73
        if (IsFlippedY(tileCollisionType))
74
        {
75
            offset.collidingBottom = offset.collidingLeft;
76
            offset.freeDown = offset.freeLeft;
77
            offset.collidingTop = offset.collidingRight;
78
            offset.freeUp = offset.freeRight;
79
        }
80
        else
81
        {
82
            offset.collidingBottom = -offset.collidingRight;
83
            offset.freeDown = -offset.freeRight;
84
            offset.collidingTop = -offset.collidingLeft;
85
            offset.freeUp = -offset.freeLeft;
86
        }
87
    }
88
89
    if (topTileEdge < topY)
90
    {
91
        if (offset.freeDown < 0)
92
            offset.freeDown -= (int)(topY - topTileEdge);
93
        offset.collidingTop = offset.freeDown;
94
    }
95
    if (bottomTileEdge > bottomY)
96
    {
97
        if (offset.freeUp > 0)
98
            offset.freeUp += Mathf.RoundToInt(bottomTileEdge - bottomY);
99
        offset.collidingBottom = offset.freeUp;
100
    }
101
102
    return offset;
103
}

Sekarang kita bisa menggunakan bidang miring yang sudah ditranslasi.

Animation of character moving on slopeAnimation of character moving on slopeAnimation of character moving on slope

Pada animasi di atas, terdapat bidang miring 45, 22, 15, dan 11 derajat. Dengan bantuan rotasi 90 derajat, kita juga bisa mendapatkan bidang 79, 75, dan 68 derajat tanpa mendefinisikan petak bidang miring tambahan. Kamu juga bisa lihat bidang miring 79 derajat terlalu curam untuk bergerak dengan mulkus karena nilai cSlopeWallHeight.

Menangani pijakan satu arah

Karena semua perubahan ini, kita membuat pijakan satu arah kita tidak bekerja. Kita perlu memperbaikinya, dan menambahkan fungsinya untuk bidang miring. Pijakan satu arah sama pentingnya dengan petak solid, jadi kita tidak bisa kehilangan fitur itu.

Menambahkan tipe satu arah

Hal pertama yang perlu kita lakukan adalah menambahkan jenis tabrakan baru untuk pijakan satu arah. Kita tambahkan mereka setelah tipe tabrakan biasa dan tandai di mana tipe baru ini dimulai, jadi nantinya kita akan lebih mudah mengetahui apakah suatu tabrakan adalah satu arah atau bukan.

1
public enum TileCollisionType
2
{
3
    Empty = 0,
4
    Full,
5
6
    SlopesStart,
7
8
    ...
9
10
    Slope45,
11
    Slope45FX,
12
    Slope45FY,
13
    Slope45FXY,
14
    Slope45F90,
15
    Slope45F90X,
16
    Slope45F90Y,
17
    Slope45F90XY,
18
19
    //...

20
    
21
    OneWayStart,
22
    
23
    OneWaySlope45,
24
    OneWaySlope45FX,
25
    OneWaySlope45FY,
26
    OneWaySlope45FXY,
27
    OneWaySlope45F90,
28
    OneWaySlope45F90X,
29
    OneWaySlope45F90Y,
30
    OneWaySlope45F90XY,
31
32
    //...

33
34
    SlopeEnd = OneWaySlopeMid4RevF90XY,
35
36
    OneWayFull,
37
38
    OneWayEnd,
39
40
    Count,
41
}

Sekarang semua pijakan satu arah ada di antara enum OneWayStart dan OneWayEnd, jadi kita bisa dengan mudah membuat fungsi yang akan mengembalikan informasi tersebut.

1
public static bool IsOneWay(TileCollisionType type)
2
{
3
    return ((int)type > (int)TileCollisionType.OneWayStart && (int)type < (int)TileCollisionType.OneWayEnd);
4
}

Varian satu arah dari bidang miring akan menggunakan data yang digunakan pijakan biasa, jadi tidak perlu takut kita akan menggunakan memori tambahan.

1
case TileCollisionType.Slope45:
2
    slopesHeights[i] = slope45;
3
    slopesExtended[i] = Extend(slopesHeights[i]);
4
    posByHeightCaches[i] = CachePosByHeight(slopesHeights[i]);
5
    slopeHeightByPosAndSizeCaches[i] = CacheSlopeHeightByPosAndLength(slopesHeights[i]);
6
    slopeOffsets[i] = CacheSlopeOffsets(slopesExtended[i]);
7
    break;
8
case TileCollisionType.Slope45FX:
9
case TileCollisionType.Slope45FY:
10
case TileCollisionType.Slope45FXY:
11
case TileCollisionType.Slope45F90:
12
case TileCollisionType.Slope45F90X:
13
case TileCollisionType.Slope45F90XY:
14
case TileCollisionType.Slope45F90Y:
15
case TileCollisionType.OneWaySlope45:
16
case TileCollisionType.OneWaySlope45FX:
17
case TileCollisionType.OneWaySlope45FY:
18
case TileCollisionType.OneWaySlope45FXY:
19
case TileCollisionType.OneWaySlope45F90:
20
case TileCollisionType.OneWaySlope45F90X:
21
case TileCollisionType.OneWaySlope45F90XY:
22
case TileCollisionType.OneWaySlope45F90Y:
23
    slopesHeights[i] = slopesHeights[(int)TileCollisionType.Slope45];
24
    slopesExtended[i] = slopesExtended[(int)TileCollisionType.Slope45];
25
    posByHeightCaches[i] = posByHeightCaches[(int)TileCollisionType.Slope45];
26
    slopeHeightByPosAndSizeCaches[i] = slopeHeightByPosAndSizeCaches[(int)TileCollisionType.Slope45];
27
    slopeOffsets[i] = slopeOffsets[(int)TileCollisionType.Slope45];
28
    break;

Menangani data tambahan

Sekarang kita tambahkan variabel yang membuat sebuah objek mengabaikan pijakan satu arah. Yang pertama adalah penanda objek yang menandai objek secara permanen untuk mengabaikan pijakan satu arah (yang berguna untuk monster terbang dan objek yang tidak perlu menggunakan pijakan), dan penanda lain untuk mengabaikan tabrakan dengan pijakan satu arah untuk sementara, hanya untuk membuat kita bisa jatuh menembusnya.

Variabel pertama akan ada di dalam kelas MovingObject.

1
public bool mIgnoresOneWay = false;
2
public bool mOnOneWayPlatform = false;
3
public bool mSticksToSlope = true;
4
public bool mIsKinematic = false;

Variabel kedua ada di dalam struktur PositionState.

1
public bool onOneWay;
2
public bool tmpIgnoresOneWay;

Kita juga akan menambahkan variabel yang akan menyimpan koordinat Y dari pijakan yang ingin kita lewati.

1
public bool onOneWay;
2
public bool tmpIgnoresOneWay;
3
public int oneWayY;

Untuk membuat pijakan satu arah bekerja, kita cukup mengabaikan satu lapis pijakan horizontal. Saat kita memasukan lapisan lain, itu adalah saat posisi Y karakter kita sudah berubah di koordinat peta, lalu kita atur karakter tersebut untuk kembali bertabrakan dengan pijakan satu arah lagi.

Ubah pengecekan tabrakan

Kita kembali ke fungsi CollidesWithTileBottom. Pertama, saat kita mengiterasi petak, kita periksa apakah petak tersebut adalah pijakan satu arah, dan jika iya, apakah kita harus menabrak petak ini atau tidak.

1
public bool CollidesWithTileBottom(ref Vector2 position, ref Vector2 topRight, ref Vector2 bottomLeft, ref PositionState state)
2
{
3
    Vector2i topRightTile = mMap.GetMapTileAtPoint(new Vector2(topRight.x - 0.5f, topRight.y - 0.5f));
4
    Vector2i bottomleftTile = mMap.GetMapTileAtPoint(new Vector2(bottomLeft.x + 0.5f, bottomLeft.y - 0.5f));
5
    bool isOneWay;
6
7
    for (int x = bottomleftTile.x; x <= topRightTile.x; ++x)
8
    {
9
        var tileCollisionType = mMap.GetCollisionType(x, bottomleftTile.y);
10
11
        isOneWay = Slopes.IsOneWay(tileCollisionType);
12
13
        if ((mIgnoresOneWay || state.tmpIgnoresOneWay) && isOneWay)
14
            continue;

Kita perlu bertabrakan dengan platform satu arah hanya jika jarak dari bagian atas pijakan kurang dari cSlopeWallHeightConstant, agar kita bisa berada di atasnya. Kita tambahkan kondisi tersebut ke kondisi yang sudah ada, dan kita perlu mengisi nilai yang benar untuk state.onOneWay dan state.oneWayY.

1
if (((sf.freeUp >= 0 && sf.collidingBottom == sf.freeUp)
2
        || (mSticksToSlope && state.pushedBottom && sf.freeUp - sf.collidingBottom < Constants.cSlopeWallHeight && sf.freeUp >= sf.collidingBottom))
3
    && !(isOneWay && Mathf.Abs(sf.collidingBottom) >= Constants.cSlopeWallHeight))
4
{
5
    state.onOneWay = isOneWay;
6
    state.oneWayY = bottomleftTile.y;
7
    state.pushesBottomTile = true;
8
    state.bottomTile = new Vector2i(x, bottomleftTile.y);
9
    position.y += sf.collidingBottom;
10
    topRight.y += sf.collidingBottom;
11
    bottomLeft.y += sf.collidingBottom;
12
    return true;
13
}

Untuk fungsi CollidesWithTileTop, kita cukup abaikan pijakan satu arah.

1
 public bool CollidesWithTileTop(ref Vector2 position, ref Vector2 topRight, ref Vector2 bottomLeft, ref PositionState state)
2
{
3
    Vector2i topRightTile = mMap.GetMapTileAtPoint(new Vector2(topRight.x - 0.5f, topRight.y + 0.5f));
4
    Vector2i bottomleftTile = mMap.GetMapTileAtPoint(new Vector2(bottomLeft.x + 0.5f, bottomLeft.y + 0.5f));
5
6
    for (int x = bottomleftTile.x; x <= topRightTile.x; ++x)
7
    {
8
        var tileCollisionType = mMap.GetCollisionType(x, topRightTile.y);
9
10
        if (Slopes.IsOneWay(tileCollisionType))
11
            continue;

Untuk pengecekan tabrakan horizontal, akan ada pekerjaan tambahan. Pertama, kita buat dua boolen tambahan di awal, yang akan menyediakan informasi apakah petak saat ini adalah satu arah, dan apakah petak sebelumnya adalah satu arah.

1
public bool CollidesWithTileRight(ref Vector2 position, ref Vector2 topRight, ref Vector2 bottomLeft, ref PositionState state, bool move = false)
2
{
3
    Vector2i topRightTile = mMap.GetMapTileAtPoint(new Vector2(topRight.x + 0.5f, topRight.y - 0.5f));
4
    Vector2i bottomLeftTile = mMap.GetMapTileAtPoint(new Vector2(bottomLeft.x + 0.5f, bottomLeft.y + 0.5f));
5
    float slopeOffset = 0.0f, oldSlopeOffset = 0.0f;
6
    bool wasOneWay = false, isOneWay;
7
    TileCollisionType slopeCollisionType = TileCollisionType.Empty;

Sekarang kita tertarik untuk mengiterasi pijakan satu arah jika kita sedang bergerak di atasnya. Kita tidak bisa benar-benar bertabrakan dengan pijakan satu arah dari kanan atau kiri, tapi jika karakter begerak sepanjang bidang miring yang merupakan pijakan satu arah, maka perlu ditangani dengan cara yang sama seperti bidang miring biasa.

1
public bool CollidesWithTileRight(ref Vector2 position, ref Vector2 topRight, ref Vector2 bottomLeft, ref PositionState state, bool move = false)
2
{
3
    Vector2i topRightTile = mMap.GetMapTileAtPoint(new Vector2(topRight.x + 0.5f, topRight.y - 0.5f));
4
    Vector2i bottomLeftTile = mMap.GetMapTileAtPoint(new Vector2(bottomLeft.x + 0.5f, bottomLeft.y + 0.5f));
5
    float slopeOffset = 0.0f, oldSlopeOffset = 0.0f;
6
    bool wasOneWay = false, isOneWay;
7
    TileCollisionType slopeCollisionType = TileCollisionType.Empty;
8
    
9
    for (int y = bottomLeftTile.y; y <= topRightTile.y; ++y)
10
    {
11
        var tileCollisionType = mMap.GetCollisionType(topRightTile.x, y);
12
        isOneWay = Slopes.IsOneWay(tileCollisionType);
13
14
        if (isOneWay && (!move || mIgnoresOneWay || state.tmpIgnoresOneWay || y != bottomLeftTile.y))
15
            continue;

Sekarang pastikan kita tidak bisa bertabrakan dengan bidang miring seperti tembok.

1
if (!isOneWay && (Mathf.Abs(slopeOffset) >= Constants.cSlopeWallHeight || (slopeOffset < 0 && state.pushesBottomTile) || (slopeOffset > 0 && state.pushesTopTile)))
2
{
3
    state.pushesRightTile = true;
4
    state.rightTile = new Vector2i(topRightTile.x, y);
5
    return true;
6
}

Jika tidak seperti itu, dan offset cukup kecil untuk dipanjat, ingatlah bahwa kita bergerak pada pijakan satu arah.

1
if (!isOneWay && (Mathf.Abs(slopeOffset) >= Constants.cSlopeWallHeight || (slopeOffset < 0 && state.pushesBottomTile) || (slopeOffset > 0 && state.pushesTopTile)))
2
{
3
    state.pushesRightTile = true;
4
    state.rightTile = new Vector2i(topRightTile.x, y);
5
    return true;
6
}
7
else if (Mathf.Abs(slopeOffset) > Mathf.Abs(oldSlopeOffset))
8
{
9
    wasOneWay = isOneWay;
10
    slopeCollisionType = tileCollisionType;
11
    state.rightTile = new Vector2i(topRightTile.x, y);
12
}

Sekarang yang tersisa adalah memastikan setiap kita mengubah status posisi kita juga perlu memperbarui variabel onOneWay.

1
state.onOneWay = wasOneWay;

Melompat ke bawah

Kita perlu berhenti mengabaikan pijakan satu arah ketika kita mengubah posisi Y pada koordinat peta. Kita akan mengatur kondisi setelah pergerakan pada sumbu Y dalam fungsi Move. Kita perlu menambahkannya di akhir kasus kedua.

1
else if (move.y != 0.0f && move.x == 0.0f)
2
{
3
    MoveY(ref position, ref foundObstacleY, move.y, step.y, ref topRight, ref bottomLeft, ref state);
4
5
    if (step.y > 0.0f)
6
        state.pushesBottomTile = CollidesWithTileBottom(ref position, ref topRight, ref bottomLeft, ref state);
7
    else
8
        state.pushesTopTile = CollidesWithTileTop(ref position, ref topRight, ref bottomLeft, ref state);
9
10
    if (!mIgnoresOneWay && state.tmpIgnoresOneWay && mMap.GetMapTileYAtPoint(bottomLeft.y - 0.5f) != state.oneWayY)
11
        state.tmpIgnoresOneWay = false;
12
}

Dan juga di akhir kasus ketiga.

1
else
2
{
3
    float speedRatio = Mathf.Abs(speed.y) / Mathf.Abs(speed.x);
4
    float vertAccum = 0.0f;
5
6
    while (!foundObstacleX && !foundObstacleY && (move.x != 0.0f || move.y != 0.0f))
7
    {
8
        vertAccum += Mathf.Sign(step.y) * speedRatio;
9
10
        MoveX(ref position, ref foundObstacleX, step.x, step.x, ref topRight, ref bottomLeft, ref state);
11
        move.x -= step.x;
12
13
        while (!foundObstacleY && move.y != 0.0f && (Mathf.Abs(vertAccum) >= 1.0f || move.x == 0.0f))
14
        {
15
            move.y -= step.y;
16
            vertAccum -= step.y;
17
18
            MoveY(ref position, ref foundObstacleX, step.y, step.y, ref topRight, ref bottomLeft, ref state);
19
        }
20
    }
21
22
    if (step.x > 0.0f)
23
        state.pushesLeftTile = CollidesWithTileLeft(ref position, ref topRight, ref bottomLeft, ref state);
24
    else
25
        state.pushesRightTile = CollidesWithTileRight(ref position, ref topRight, ref bottomLeft, ref state);
26
27
    if (step.y > 0.0f)
28
        state.pushesBottomTile = CollidesWithTileBottom(ref position, ref topRight, ref bottomLeft, ref state);
29
    else
30
        state.pushesTopTile = CollidesWithTileTop(ref position, ref topRight, ref bottomLeft, ref state);
31
32
    if (!mIgnoresOneWay && state.tmpIgnoresOneWay && mMap.GetMapTileYAtPoint(bottomLeft.y - 0.5f) != state.oneWayY)
33
        state.tmpIgnoresOneWay = false;
34
}

Sekian. Sekarang yang perlu kita lakukan adalah mengatur tmpIgnoresOneWay menjadi true agar karakter bisa melompat turun dari pijakan satu arah.

1
if (KeyState(KeyInput.GoDown))
2
    mPS.tmpIgnoresOneWay = true;

Kita lihat hasil jadinya.

New animation of character moving on slopeNew animation of character moving on slopeNew animation of character moving on slope

Kesimpulan

Fiuh, banyak sekali yang sudah kita kerjakan, tapi semuanya setimpal dengan hasilnya yang sangat fleksibel dan kuat. Kita bisa mendefinisikan bidang miring apa saja karena penanganan bitmap tabrakan, translasi petak, dan mengubahnya menjadi platform satu arah.

Implementasi ini belum optimal, dan saya yakin saya melewatkan banyak kesempatan optimasi menggunakan metode integrasi satu piksel kita. Saya juga cukup yakin banyak pengecekan tabrakan yang bisa dilewatkan, jadi jika kamu mengembangkan implementasi ini maka beri tahu saya di bagian komentar!

Terima kasih sudah bertahan sejauh ini dengan saya, saya harap tutorial ini berguna untuk kamu!

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