Membuat hook kucing di Unity. Bagian 2

gambar

Catatan : tutorial ini ditujukan untuk pengguna mahir dan berpengalaman, dan tidak mencakup topik seperti menambahkan komponen, membuat skrip GameObject baru, dan sintaksis C #. Jika Anda perlu meningkatkan keterampilan Unity Anda, lihat tutorial Memulai Penggunaan dengan Unity dan Pengantar Script Unity kami .

Di bagian pertama tutorial, kami belajar cara membuat kait kucing dengan mekanisme melilitkan tali di sekeliling rintangan. Namun, kami ingin lebih: tali dapat membungkus benda di tingkat, tetapi tidak lepas ketika Anda kembali.

Mulai bekerja


Buka proyek jadi dari bagian pertama di Unity atau unduh draf untuk bagian tutorial ini, lalu buka 2DGrapplingHook-Part2-Starter . Seperti pada bagian pertama, kita akan menggunakan versi Unity 2017.1 atau lebih tinggi.

Buka adegan Game di editor dari folder proyek Scenes .


Luncurkan adegan Game dan coba kaitkan kait kucing di atas batu di atas karakter, lalu ayun untuk membuat tali membungkus sepasang tepi batu.

Ketika Anda kembali, Anda akan melihat bahwa titik-titik batu tempat tali yang digunakan untuk berbalik tidak terlepas lagi.


Pikirkan tentang titik di mana tali harus dibuka. Untuk menyederhanakan tugas, lebih baik menggunakan kasing saat tali membungkus ujung-ujungnya.

Jika siput, menempel pada batu di atas kepalanya, berayun ke kanan, maka tali akan bengkok setelah ambang batas di mana ia melintasi titik sudut 180 derajat dengan tepi yang saat siput terpasang. Pada gambar di bawah, ini ditunjukkan oleh titik hijau yang disorot.


Ketika siput itu berayun kembali ke arah lain, tali itu harus kembali terlepas pada titik yang sama (disorot dengan warna merah pada gambar di atas):


Logika tidak terputus-putus


Untuk menghitung momen ketika Anda perlu melepas tali pada titik-titik yang dibungkus sebelumnya, kita perlu pengetahuan tentang geometri. Secara khusus, kami akan menggunakan perbandingan sudut untuk menentukan kapan tali harus dilepas dari tepi.

Tugas ini mungkin tampak sedikit mengintimidasi. Matematika dapat menginspirasi horor dan keputusasaan bahkan dalam yang paling berani.

Untungnya, Unity memiliki beberapa fungsi penolong matematika yang hebat yang dapat membuat hidup kita sedikit lebih mudah.

Buka skrip RopeSystem di IDE dan buat metode baru yang disebut HandleRopeUnwrap() .

 private void HandleRopeUnwrap() { } 

Buka Update() dan tambahkan pada akhirnya panggilan ke metode baru kami.

 HandleRopeUnwrap(); 

Sementara HandleRopeUnwrap() tidak melakukan apa-apa, tetapi sekarang kita dapat memproses logika yang terkait dengan seluruh proses melepaskan dari tepi.

Seperti yang Anda ingat dari bagian pertama tutorial, kami menyimpan posisi pembungkus tali dalam koleksi yang disebut ropePositions , yang merupakan koleksi List<Vector2> . Setiap kali tali melilit ujung, kami menjaga posisi titik bungkus ini dalam koleksi ini.

Untuk membuat proses lebih efisien, kami tidak akan menjalankan logika apa pun di HandleRopeUnwrap() jika jumlah posisi yang disimpan dalam koleksi sama dengan atau kurang dari 1.

Dengan kata lain, ketika siput telah terhubung ke titik awal dan tali belum melilit ujungnya, jumlah ropePositions adalah 1, dan kami tidak akan mengikuti logika pemrosesan yang tidak diputar.

Tambahkan return sederhana ini ke bagian atas HandleRopeUnwrap() untuk menghemat siklus CPU yang berharga, karena metode ini dipanggil dari Update() berkali-kali per detik.

 if (ropePositions.Count <= 1) { return; } 

Menambahkan Variabel Baru


Di bawah pengujian baru ini, kami akan menambahkan beberapa dimensi dan referensi ke berbagai sudut yang diperlukan untuk menerapkan dasar dari logika yang tidak terputar. Tambahkan kode berikut ke HandleRopeUnwrap() :

 // Hinge =       // Anchor =     Hinge // Hinge Angle =   anchor  hinge // Player Angle =   anchor  player // 1 var anchorIndex = ropePositions.Count - 2; // 2 var hingeIndex = ropePositions.Count - 1; // 3 var anchorPosition = ropePositions[anchorIndex]; // 4 var hingePosition = ropePositions[hingeIndex]; // 5 var hingeDir = hingePosition - anchorPosition; // 6 var hingeAngle = Vector2.Angle(anchorPosition, hingeDir); // 7 var playerDir = playerPosition - anchorPosition; // 8 var playerAngle = Vector2.Angle(anchorPosition, playerDir); 

Ada banyak variabel di sini, jadi saya akan menjelaskan masing-masing, serta menambahkan ilustrasi yang nyaman yang akan membantu untuk memahami tujuan mereka.

  1. anchorIndex adalah indeks dalam koleksi ropePositions di dua posisi dari akhir koleksi. Kita dapat menganggapnya sebagai titik di dua posisi pada tali dari posisi siput. Pada gambar di bawah ini, ini adalah titik pertama pengait kait ke permukaan. Saat mengisi koleksi ropePositions titik pembungkus baru, titik ini akan selalu tetap menjadi titik pembungkus pada jarak dua posisi dari siput.
  2. hingeIndex adalah indeks koleksi yang menyimpan titik engsel saat ini; dengan kata lain, posisi tali saat ini sedang membungkus titik terdekat dengan ujung tali dari siput. Itu selalu pada jarak satu posisi dari siput, itulah sebabnya kami menggunakan ropePositions.Count - 1 .
  3. anchorPosition dihitung dengan merujuk tempat ropePositions koleksi ropePositions dan merupakan nilai Vector2 sederhana dari posisi itu.
  4. hingePosition dihitung dengan merujuk tempat hingeIndex dalam koleksi ropePositions dan merupakan nilai Vector2 sederhana dari posisi itu.
  5. hingeDir adalah vektor yang diarahkan dari anchorPosition ke hingePosition . Ini digunakan dalam variabel berikut untuk mendapatkan sudut.
  6. hingeAngle - fungsi pembantu yang berguna Vector2.Angle() digunakan di sini untuk menghitung sudut antara anchorPosition dan titik engsel.
  7. playerDir adalah vektor yang diarahkan dari anchorPosition ke posisi siput saat ini (playerPosition)
  8. Kemudian, menggunakan sudut antara titik jangkar dan pemain (siput), playerAngle dihitung.


Semua variabel ini dihitung menggunakan posisi yang disimpan sebagai nilai Vector2 dalam koleksi ropePositions dan membandingkan posisi ini dengan posisi lain atau posisi pemain saat ini (slug).

Dua variabel penting yang digunakan untuk perbandingan adalah hingeAngle dan playerAngle .

Nilai yang disimpan dalam hingeAngle harus tetap statis karena selalu sudut yang konstan antara titik di dua "lipatan tali" dari siput dan "lipatan tali" saat ini yang paling dekat dengan siput yang tidak bergerak sampai tali tidak terpilin atau setelah dilipat titik tikungan baru akan ditambahkan.

Saat siput playerAngle berubah. Dengan membandingkan sudut ini dengan hingeAngle , dan juga memeriksa apakah siput di sebelah kiri atau kanan sudut ini, kita dapat menentukan apakah titik lipat saat ini yang paling dekat dengan siput harus dilepaskan.

Di bagian pertama tutorial ini, kami menyimpan posisi lipatan dalam kamus yang disebut wrapPointsLookup . Setiap kali kami menyimpan titik tekuk, kami menambahkannya ke kamus dengan posisi sebagai kunci dan dengan 0 sebagai nilainya. Namun, nilai 0 ini agak misterius, bukan?

Kami akan menggunakan nilai ini untuk menyimpan posisi siput relatif terhadap sudutnya dengan titik engsel (titik lipat saat ini paling dekat dengan siput).

Jika Anda menetapkan nilai -1 , maka sudut siput ( playerAngle ) kurang dari sudut engsel ( hingeAngle ), dan dengan nilai 1, sudut playerAngle lebih besar daripada hingeAngle .

Karena fakta bahwa kita menyimpan nilai-nilai dalam kamus, setiap kali kita membandingkan playerAngle dengan hingeAngle , kita dapat memahami apakah siput baru saja melewati batas yang setelah itu tali harus dilepas.

Ini dapat dijelaskan secara berbeda: jika sudut siput baru saja diperiksa dan lebih kecil dari sudut engsel, tetapi terakhir kali disimpan dalam kamus titik lengkung itu ditandai dengan nilai yang menunjukkan bahwa itu berada di sisi lain sudut ini, maka titik tersebut harus dihapus !

Tali pemisah


Lihatlah screenshot di bawah ini dengan catatan. Siput kami menempel di batu, bergoyang ke atas, melilitkan tali di tepi batu pada saat naik.


Anda mungkin memperhatikan bahwa pada posisi ayunan paling atas, di mana siputnya buram, titik lipatan terdekat saat ini (ditandai dengan titik putih) akan disimpan dalam kamus wrapPointsLookup dengan nilai 1 .

Dalam perjalanan turun, ketika playerAngle menjadi lebih kecil daripada hingeAngle (dua garis hijau putus-putus), seperti yang ditunjukkan oleh panah biru, pemeriksaan dilakukan, dan jika nilai terakhir (saat ini) dari titik bengkok adalah 1 , maka titik bengkok harus dihapus.

Sekarang mari kita terapkan logika ini dalam kode. Tetapi sebelum kita mulai, mari kita buat metode kosong yang akan kita gunakan untuk bersantai. Karena ini, setelah membuat logika, itu tidak akan menyebabkan kesalahan.

Tambahkan metode UnwrapRopePosition(anchorIndex, hingeIndex) baru UnwrapRopePosition(anchorIndex, hingeIndex) dengan memasukkan baris berikut:

 private void UnwrapRopePosition(int anchorIndex, int hingeIndex) { } 

Setelah melakukan ini, kembali ke HandleRopeUnwrap() . Di bawah variabel yang baru ditambahkan, tambahkan logika berikut, yang akan menangani dua kasus: playerAngle kurang dari hingeAngle dan playerAngle lebih dari hingeAngle :

 if (playerAngle < hingeAngle) { // 1 if (wrapPointsLookup[hingePosition] == 1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 2 wrapPointsLookup[hingePosition] = -1; } else { // 3 if (wrapPointsLookup[hingePosition] == -1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 4 wrapPointsLookup[hingePosition] = 1; } 

Kode ini harus sesuai dengan penjelasan logika yang dijelaskan di atas untuk kasus pertama (ketika playerAngle < hingeAngle ), tetapi juga menangani kasus kedua (ketika playerAngle > hingeAngle ).

  1. Jika titik lipat saat ini yang paling dekat dengan siput memiliki nilai 1 pada titik di mana playerAngle < hingeAngle , maka kami menghapus titik ini dan melakukan pengembalian sehingga sisa metode tidak dieksekusi.
  2. Jika tidak, jika titik bengkok tidak ditandai terakhir dengan nilai 1 , tetapi playerAngle kurang dari hingeAngle , maka -1 ditugaskan.
  3. Jika titik lipat saat ini yang paling dekat dengan siput adalah -1 pada titik di mana playerAngle > hingeAngle , kemudian hapus titik dan kembali.
  4. Jika tidak, kami menetapkan entri dalam kamus titik lengkung pada posisi engsel ke 1 .

Kode ini memastikan bahwa kamus wrapPointsLookup selalu diperbarui, memastikan bahwa nilai titik tekuk saat ini (paling dekat dengan siput) cocok dengan sudut siput saat ini relatif terhadap titik tekuk.

Jangan lupa bahwa nilainya -1 ketika sudut slug kurang dari sudut engsel (relatif ke titik referensi), dan 1 ketika sudut slug lebih besar dari sudut engsel.

Sekarang kita akan UnwrapRopePosition() dalam skrip RopeSystem dengan kode yang akan langsung terlibat dalam pelepasan, memindahkan posisi referensi dan menetapkan nilai jarak baru ke nilai jarak tali DistanceJoint2D. Tambahkan baris berikut ke disk metode yang dibuat sebelumnya:

  // 1 var newAnchorPosition = ropePositions[anchorIndex]; wrapPointsLookup.Remove(ropePositions[hingeIndex]); ropePositions.RemoveAt(hingeIndex); // 2 ropeHingeAnchorRb.transform.position = newAnchorPosition; distanceSet = false; // Set new rope distance joint distance for anchor position if not yet set. if (distanceSet) { return; } ropeJoint.distance = Vector2.Distance(transform.position, newAnchorPosition); distanceSet = true; 

  1. Indeks titik jangkar saat ini (posisi kedua tali dari siput) menjadi posisi baru dari engsel, dan posisi lama dari engsel dilepas (yang sebelumnya paling dekat dengan siput dan yang sekarang kita "lepaskan"). Variabel newAnchorPosition diberikan nilai anchorIndex dalam daftar posisi tali. Kemudian akan digunakan untuk memposisikan posisi yang diperbarui dari titik jangkar.
  2. Tali-sambungan RigidBody2D (dimana tali DistanceJoint2D terpasang) mengubah posisinya ke posisi baru dari titik jangkar. Hal ini memastikan gerakan siput terus menerus yang lancar pada tali ketika terhubung ke DistanceJoint2D, dan koneksi ini harus memungkinkannya untuk terus berayun relatif terhadap posisi baru, yang menjadi referensi - dengan kata lain, relatif ke titik berikutnya turun tali dari posisinya.
  3. Maka Anda perlu memperbarui nilai jarak distanceJoint2D untuk memperhitungkan perubahan tajam dalam jarak dari siput ke titik referensi baru. Jika ini belum dilakukan, pemeriksaan cepat dari flag distanceSet dilakukan, dan jarak diberikan nilai jarak yang dihitung antara siput dan posisi baru dari titik jangkar.

Simpan skrip dan kembali ke editor. Mulai permainan lagi dan perhatikan bagaimana tali terlepas dari tepi ketika siput melewati nilai ambang batas dari setiap titik tikungan!


Meskipun logikanya sudah siap, kami akan menambahkan beberapa kode pembantu ke HandleRopeUnwrap() tepat sebelum membandingkan playerAngle dengan hingeAngle ( if (playerAngle < hingeAngle) ).

 if (!wrapPointsLookup.ContainsKey(hingePosition)) { Debug.LogError("We were not tracking hingePosition (" + hingePosition + ") in the look up dictionary."); return; } 

Sebenarnya, ini seharusnya tidak terjadi, karena kami mendefinisikan ulang dan melepaskan kait kucing ketika membungkus sekitar satu tulang rusuk dua kali, tetapi jika ini masih terjadi, kita dapat dengan mudah keluar dari metode ini dengan return sederhana dan pesan kesalahan di konsol.

Selain itu, berkat ini, kami akan lebih mudah menangani kasus pembatas seperti itu; Selain itu, kami mendapatkan pesan kesalahan kami sendiri jika terjadi sesuatu yang tidak perlu.

Ke mana harus pergi selanjutnya?


Berikut ini tautan ke proyek yang sudah selesai pada bagian kedua dan terakhir dari tutorial ini.

Selamat telah menyelesaikan seri tutorial ini! Ketika datang untuk membandingkan sudut dan posisi, semuanya menjadi sangat rumit, tetapi kami selamat dari ini dan sekarang kami memiliki sistem kait-kucing dan tali yang indah yang dapat berakhir pada objek dalam permainan.


Tahukah Anda bahwa tim pengembangan Persatuan kami telah menulis buku? Jika tidak, lihat Unity Games By Tutorials . Game ini akan mengajarkan Anda cara membuat empat game yang sudah jadi dari awal:

  • Penembak dua tongkat
  • Penembak orang pertama
  • Game menara pertahanan (dengan dukungan VR!)
  • Platformer 2D

Setelah membaca buku ini, Anda akan belajar cara membuat game sendiri untuk Windows, macOS, iOS, dan platform lainnya!

Buku ini ditujukan untuk pemula dan mereka yang ingin meningkatkan keterampilan Persatuan mereka ke tingkat profesional. Untuk menguasai buku ini, Anda harus memiliki pengalaman pemrograman (dalam bahasa apa pun).

Source: https://habr.com/ru/post/id415217/


All Articles