Bagaimana cara mengembangkan platformer lain menggunakan Unity. Tutorial lain

Halo, Habr!


Artikel lain sedang menunggu Anda di bawah potongan, yang akan memberi tahu Anda tentang bagaimana saya menetapkan diri saya tujuan pemrograman game, berdasarkan pada terjemahan artikel di Habr yang disebut Level Design Patterns untuk game 2D .


Artikel ini memiliki banyak teks (baik reguler dan sumber) dan banyak gambar.


Sebelum saya memulai artikel pertama saya, mari kenali Anda. Nama saya Denis. Saya bekerja sebagai administrator sistem dengan pengalaman total 7 tahun. Bukan bagi saya untuk memberi tahu Anda bahwa administrator sistem adalah tipe staf TI yang dengan hati-hati menerapkan sekali, dan kemudian merenungkan kelipatan berbagai karakter pada monitor. Seiring waktu, saya sampai pada kesimpulan bahwa sudah waktunya untuk memperluas batas pengetahuan dan beralih ke pemrograman. Tanpa merinci, saya mencoba membuat proyek dalam C ++ dan Python. Tetapi setelah satu tahun belajar, saya sampai pada kesimpulan bahwa pemrograman aplikasi dan perangkat lunak sistem saya bukan milik saya. Karena berbagai alasan.


Setelah berpikir lebih dalam, saya bertanya pada diri sendiri: apa yang benar-benar ingin saya lakukan dengan berbagai jenis peralatan komputasi? Pertanyaan saya kepada diri saya sendiri membuat saya jauh ke masa kecil, yaitu pada jam-jam bahagia yang dihabiskan untuk PS1, PS2, Railroad Tycoon 3 untuk PC ..., Ya, Anda mengerti. Video game!


Dengan jumlah berbagai materi pelatihan, pilihan jatuh pada Unity (jangan menemukan kembali roda?). Setelah sebulan membaca dan melihat berbagai bahan, saya memutuskan untuk merilis permainan anak-anak yang sangat sederhana pertama ke pasar bermain. Untuk mengatasi rasa takut maka untuk berbicara. Lagi pula, merilis aplikasi di pasar bermain tidak menakutkan, bukan?


Setelah beberapa bulan, saya merilis platformer yang sudah lebih kompleks. Kemudian ada jeda (setelah semua, pekerjaan harus dikerjakan, setelah semua).


Sekitar dua minggu lalu, saya melihat terjemahan sebuah artikel di hub yang disebut Level Design Patterns untuk game 2D ( https://habr.com/en/post/456152/ ) dan berpikir dalam hati - mengapa tidak? Artikel ini memiliki tabel sederhana dan jelas dengan daftar apa yang harus ada dalam permainan sehingga menarik. Saya dengan baik hati menyalin tabel untuk diri sendiri di OneNote dan menandai setiap pola dengan Kasus tag (yang dapat ditandai selesai).


Apa yang ingin saya dapatkan sebagai hasilnya? Kritik anda Seperti yang saya suka katakan pada diri sendiri - jika Anda ingin belajar berenang, menyelam dengan kepala Anda. Jika Anda berpikir bahwa saya melakukan sesuatu dengan baik - tuliskan kepada saya tentang hal ini dalam komentar. Jika Anda berpikir bahwa saya melakukan sesuatu yang buruk - tulislah tentang hal itu dua kali lipat.


Saya akan memulai program panjang saya untuk pemrograman platformer lain.


Avatar


Entitas yang dikendalikan oleh pemain dalam game. Misalnya, Mario dan Luigi di Super Mario Bros (Nintendo, 1985).


Ada beberapa subtugas yang perlu diimplementasikan untuk memberikan kehidupan pahlawan. Yaitu:


•   ( ) •      •      •       •        

Untuk mengimplementasikan animasi, kita perlu mengonversi satu sprite menjadi multi sprite. Ini dilakukan dengan sangat sederhana. Tambahkan sprite ke folder proyek dan temukan di Explorer Explorer Unity Explorer. Kemudian, mengklik sprite di jendela inspektur, mengubah nilai properti SptiteMode dari Tunggal ke Banyak .


gambar


Klik Terapkan , lalu SpriteEditor .


Di dalam jendela Sprite Editor , Anda perlu memilih setiap bingkai animasi masa depan dengan mouse seperti yang ditunjukkan pada gambar di bawah ini.


Juga, Unity menyediakan kemampuan untuk secara otomatis menyorot batas objek dalam sprite. Untuk melakukan ini, di jendela Sprite Editor , Anda harus mengklik tombol Slice . Di menu drop-down Anda harus memiliki Type => Automatic, Pivot => Center . Yang harus Anda lakukan adalah mengklik tombol Slice . Setelah itu, semua objek di dalam sprite akan dipilih secara otomatis.



Mari kita lakukan operasi ini untuk semua animasi lainnya. Selanjutnya, Anda harus mengkonfigurasi status animasi dan peralihannya. Ini dilakukan dalam dua langkah. Tindakan pertama, kode program.
Buat objek game kosong. Untuk melakukan ini, klik kanan di tab Hirarki dan pilih Buat Kosong dari menu drop-down.



Objek permainan kosong, yang dibuat di atas panggung, secara default hanya memiliki satu komponen - Transform . Komponen ini menentukan posisi objek di atas panggung, sudut kemiringan dan skalanya.


Anda dapat menemukan kata transform dalam dua arti yang berbeda:


  • Transform adalah kelas. Karena ini adalah kelas, Transform mendeskripsikan implementasi perangkat lunak dari koordinat apa objek ini akan ditempatkan dan apa dimensi itu.
  • Transform adalah turunan dari sebuah kelas. Artinya, Anda bisa merujuk ke objek tertentu dan mengubah posisi atau skala di tempat kejadian. Misalnya, lebih lanjut dalam kode akan ada baris:
     transform.position = new Vector2 (transform.position.x + dirX, transform.position.y + dirY);. 


    Baris ini akan bertanggung jawab atas pergerakan Lucas di atas panggung.

Untuk membuat komponen Anda sendiri, klik tombol Tambahkan Komponen di tab inspektur objek. Selanjutnya, kotak pencarian muncul di antara komponen standar. Cukup dengan mulai mengetik nama skrip yang akan datang (atau komponen yang sudah diterapkan), jika tidak ada nama yang sesuai, Unity akan menawarkan Anda untuk membuat komponen baru. Saya menyebut komponen ini HeroScript.cs .



Pertama, kami menjelaskan bidang yang akan menyimpan informasi tentang komponen visual dan fisik Lucas:


Judul spoiler
 private Animator animator; //      . private Rigidbody2D rb2d; //rb     

Selanjutnya, bidang yang akan merespons pergerakan karakter:


Judul spoiler
 /*  ,     */ Vector3 localScale; bool facingRight = true; [SerializeField] private float dirX, dirY; //    [Range(1f, 20f)] [SerializeField] private float moveSpeed; //    private SpriteRenderer sprite; //   SpriteRenderer /*   ,     */ 

Sebuah awal telah dibuat, luar biasa. Selanjutnya, enumerasi akan dijelaskan dan sebuah properti akan ditulis yang akan bertanggung jawab untuk mengganti status animasi. Enumerasi ini harus ditulis di luar kelas:


Judul spoiler
 /* *      . *         *    . */ public enum CharState { idle, //   0 Run, //   1 Walk, //   2 Die //   3 } 

Kami menerapkan properti yang akan menerima dan menetapkan status animasi baru:


Judul spoiler
 public CharState State { get {//    CharState    animator       int return (CharState)animator.GetInteger("State"); } set { //    animator     int    State       ,      int. animator.SetInteger("State", (int)value); } } 

Bagian perangkat lunak selesai. Sekarang kami memiliki enumerasi dan properti yang akan dikaitkan dengan pengalihan animasi. Selanjutnya, langkah kedua. Di editor Unity, Anda harus mengikat status animasi dan menunjukkan pada nilai int apa yang perlu diubah.


Untuk melakukan ini, Anda harus mengaitkan multi sprite yang dibuat sebelumnya dengan objek game kosong. Yang perlu Anda lakukan adalah memilih frame di Unity Explorer dan menyeretnya ke objek game kosong, yang sebelumnya telah kami perbaiki skripnya.


gambar


Lakukan ini dengan setiap animasi berikutnya. Juga, di penjelajah dengan animasi Anda akan menemukan tampilan objek dengan diagram blok dan tombol Play . Mengklik dua kali akan membuka tab Animator . Di dalam, Anda akan melihat beberapa blok dengan animasi dan pada awalnya, hanya status Entri dan set animasi pertama yang terhubung yang terhubung. AnyState dan animasi lainnya akan ditampilkan sebagai kotak abu-abu biasa. Untuk mengikat semuanya, Anda perlu mengklik status AnyState dan pilih satu-satunya menu drop-down Lakukan Transaksi dan ikat ke blok abu-abu. Operasi ini harus dilakukan untuk setiap kondisi. Pada akhirnya, Anda harus mendapatkan sesuatu seperti yang Anda lihat pada tangkapan layar di bawah ini.


gambar


Selanjutnya, Anda harus secara eksplisit menunjukkan negara apa yang seharusnya untuk memulai animasi yang diperlukan. Perhatikan tangkapan layar, yaitu bagian kirinya. Tab parameter . Variabel tipe int State dibuat di dalamnya. Selanjutnya, perhatikan sisi kanan. Pertama-tama, dari transisi animasi, Anda harus menghapus centang pada kotak centang Transaction To To Self . Operasi ini akan menyelamatkan Anda dari transisi animasi yang aneh dan kadang-kadang benar-benar tidak dapat dipahami ke dirinya sendiri dan bagian Kondisi , di mana kami mengindikasikan bahwa transisi animasi ini diberi nilai 3 dari variabel State . Setelah itu, Unity akan tahu animasi mana yang harus dijalankan.
Semuanya dilakukan untuk gerakan karakter animasi. Mari kita lanjutkan.


Langkah selanjutnya adalah mengajar Lucas untuk bergerak di atas panggung. Ini sepenuhnya pemrograman. Untuk memindahkan karakter di sekitar adegan, Anda perlu tombol, mengklik di mana Lucas akan bolak-balik. Untuk melakukan ini, di tab Toko Aset , kita perlu mengimpor Aset Standar, tetapi tidak semuanya, hanya beberapa komponen tambahan, yaitu:


• CrossPlatformInput
• Editor
• Lingkungan


Setelah mengimpor Aset, jendela utama Unity harus dimodifikasi dan satu tab tambahan Input Seluler akan muncul. Kami mengaktifkannya.


Mari kita buat elemen UI baru di atas panggung - tombol kontrol. Buat 4 tombol di setiap arah. Naik, turun, maju dan mundur. Dalam komponen Gambar, kami menetapkan tombol untuk gambar yang akan sesuai dengan gambar, yang berarti kemampuan untuk bergerak. Seharusnya terlihat seperti tangkapan layar di bawah ini:



Untuk setiap tombol, tambahkan komponen AxisTouchButton . Skrip ini hanya memiliki 4 bidang. Bidang axisName berarti nama mana yang ditanggapi saat dipanggil. Bidang axisValue bertanggung jawab atas arah perpindahan Lucas. Bidang responseSpeed bertanggung jawab atas seberapa cepat Lucas akan mengembangkan kecepatannya. Bidang returnToCentreSpeed bertanggung jawab atas seberapa cepat tombol kembali ke tengah. Untuk tombol Maju, biarkan apa adanya. Untuk tombol kembali, ubah nilai axisValue ke -1 sehingga Lucas bergerak kembali. Untuk tombol Atas dan Bawah, ubah axisName ke Vertical . Untuk Nilai sumbu tombol Atas, atur nilainya menjadi 1, untuk Bawah -1.


Selanjutnya, ubah HeroScript.cs . Tambahkan namespace ke direktif menggunakan


 using UnityStandardAssets.CrossPlatformInput; //    . 

Judul spoiler
        : /*  ,     */ Vector3 localScale; //   bool facingRight = true; [SerializeField] private float dirX, dirY; //    [Range(1f, 20f)] [SerializeField] private float moveSpeed; //    private SpriteRenderer sprite; //   SpriteRenderer /*   ,     */ 

Dalam metode Mulai standar, tambahkan kode berikut:


Judul spoiler
  void Start() { localScale = transform.localScale; animator = GetComponent<Animator>(); //     . sprite = GetComponent<SpriteRenderer>(); //  SpriteRenderer rb = GetComponent<Rigidbody2D>(); State = CharState.idle; } 

Kami membuat metode yang akan bertanggung jawab untuk memindahkan pahlawan:


Judul spoiler
 public void MoveHero() { dirX = CrossPlatformInputManager.GetAxis ("Horizontal") * moveSpeed * Time.deltaTime; dirY = CrossPlatformInputManager.GetAxis ("Vertical") * moveSpeed * Time.deltaTime; transform.position = new Vector2 (transform.position.x + dirX, transform.position.y + dirY); } 

Seperti yang Anda lihat, semuanya sederhana. Bidang dirX dan DirY merekam informasi tentang arah Axis ( Horizontlal dan Vertical ) dikalikan dengan kecepatan (yang perlu ditentukan dalam editor) dan dikalikan dengan waktu tempuh dari frame terakhir.
transform.position menulis posisi baru ke komponen Transform objek permainan kami.


Di sisi praktis masalah ini, Anda dapat menjalankan adegan dan melihat bagaimana Lucas jatuh ke dalam jurang, karena tidak ada benda di bawahnya yang dapat mencegah hal ini. Lucas selalu dalam animasi Idle dan tidak berbalik ketika kita mengarahkannya kembali. Untuk ini, skrip perlu dimodifikasi. Buat metode yang menentukan ke arah mana Lucas melihat:


Judul spoiler
 void CheckWhereToFace () { if (dirX > 0) { facingRight = true; State = CharState.Walk; } if (dirX < 0) { facingRight = false; State = CharState.Walk; } if (dirX == 0) { State = CharState.idle; } if (dirY < 0) { State = CharState.Walk; } if (dirY > 0) { State = CharState.Walk; } if (((facingRight) && (localScale.x < 0)) || ((!facingRight) && (localScale.x > 0))) localScale.x *= -1; transform.localScale = localScale; 

Bagian kode ini juga tidak sulit. Metode ini menjelaskan bahwa jika dirX > 0 (jika kita pergi ke kanan), maka kita memutar sprite ke arah ini dan memulai animasi jalan. Jika kurang dari 0, maka putar Lucas 180 derajat dan mulai animasi. Jika dirX adalah nol, maka Lucas berdiri dan Anda harus memulai animasi tunggu.


Mengapa lebih baik menggunakan operasi dengan Skala dalam kasus ini daripada menggunakan flipX = true ? Di masa depan, saya akan menjelaskan kemampuan untuk mengambil benda apa pun di tangan dan secara alami, Lucas dapat berbalik memegang sesuatu di tangannya. Jika saya menggunakan refleksi biasa, objek yang akan saya pegang di tangan saya akan tetap berada di sisi kanan (misalnya) ketika Lucas melihat ke kiri dan sebaliknya. Memperbesar akan memindahkan objek yang menahan Lucas ke arah yang sama dengan yang dituju oleh Lucas sendiri.


Kami menempatkan fungsi CheckWhereToFace () di fungsi Pembaruan () , untuk pemantauan frame-by-frame-nya.


Bagus 2 poin pertama dari 5 selesai. Mari kita beralih ke kebutuhan Lucas. Katakanlah Lucas akan memiliki 3 macam kebutuhan yang harus dipenuhi agar tetap hidup. Ini adalah standar hidup, tingkat kelaparan dan tingkat kehausan. Anda perlu membuat panel sederhana dan mudah dimengerti ini dengan indikator setiap item. Untuk membuat panel seperti itu, klik kanan dan pilih UI => Panel .


Mari kita tandai kira-kira seperti yang ditunjukkan di bawah ini


gambar


Panel terdiri dari tiga gambar (Gambar) dari masing-masing kebutuhan (kiri). Di sebelah kanan adalah panel itu sendiri. Pada lapisan pertama (kita akan begini), ada indikator warna (Gambar) yang tidak memiliki transparansi, objek Gambar disalin di bawahnya, yang transparan. Gambar ini setengah transparan ke aslinya. Juga, Gambar, yang tidak memiliki transparansi, memiliki properti Image Type = Filled . Fitur ini akan memungkinkan kita untuk mensimulasikan penurunan skala kepenuhan kebutuhan.


gambar


gambar


Tentukan variabel statis baru:


Judul spoiler
 /*  ,     */ [SerializeField] public static float Health = 100, Eat = 100, Water = 100, _Eat = 0.05f, _Water = 0.1f; //        .    _  . /*   ,     */ /*  ,      */ [SerializeField] Image iHealt, iEat, iWater; //      /*   ,      */ 

Dalam hal ini, saya menggunakan bidang statis. Ini dilakukan agar bidang ini unik untuk seluruh kelas. Juga, ini akan memungkinkan kita untuk secara langsung mengakses bidang-bidang ini dengan nama kelas. Kami menulis beberapa fungsi sederhana:


Judul spoiler
 private float fEat(float x) { Eat = Eat - x * Time.deltaTime; iEat.fillAmount = Eat / 100f; // ,        return Eat; } private float fWater(float x) { Water = Water - x * Time.deltaTime; iWater.fillAmount = Water / 100; return Water; } 

Kemudian, kami menulis metode yang mengumpulkan informasi tentang keinginan untuk makan dan minum:


Judul spoiler
 private void Needs() { if (fEat(_Eat) < 0) { Debug.Log(Eat); } else if (fEat(0) == 0) { StartCoroutine(ifDie()); } if (fWater(_Water) < 0) { Debug.Log(Water); } else if (fWater(0) == 0) { StartCoroutine(ifDie()); } 

Fungsi Needs () ditempatkan di fungsi Update () dan setiap frame disebut. Dengan demikian, sesuai


 if (fEat(_Eat) < 0) 

sebuah fungsi dipanggil yang berfungsi sebagai parameter berapa banyak yang harus diambil dari variabel Eat and Water . Jika hasil dari fungsi ini bukan 0, maka Lucas belum mati kehausan atau kelaparan. Jika Lucas mati karena kelaparan atau luka yang mematikan, maka kita melakukan coroutine


 StartCoroutine(ifDie()); 

yang memulai animasi kematian dan memulai kembali level:


Judul spoiler
 IEnumerator ifDie() { State = CharState.Die; yield return new WaitForSeconds(2); SceneManager.LoadScene("WoodDay", LoadSceneMode.Single); } 

Ubin keras


Objek permainan yang tidak memungkinkan pemain melewatinya. Contoh: gender dalam Super Mario Bros (Nintendo, 1985).


Untuk mewujudkan bumi dan mencegah Lucas agar tidak jatuh melewatinya, Anda perlu menghubungkan komponen BoxCollider2D dan Rigidbody2D ke Lucas. Juga, Anda memerlukan sprite dari bumi di mana komponen BoxCollider2D akan berada . Komponen BoxCollider2D mengimplementasikan colliders dan perilaku collision mereka. Pada tahap ini, kita tidak perlu apa-apa selain mencegah kegagalan Lucas di bawah tanah. Yang dapat diedit secara opsional adalah batas dari collider. Dalam kasus saya, sprite tanah memiliki permukaan rumput dan sehingga sepertinya rumput tidak mampu menopang berat Lucas, saya akan mengedit batas-batas komponen.



Sekarang, proses penandaan level yang menarik. Untuk kenyamanan, Anda dapat mengekspor kubus tanah ini ke cetakan. Prefab adalah wadah dari objek game, setelah modifikasi yang Anda dapat secara otomatis menerapkan perubahan ke semua objek game yang dibuat dari prefab ini. Selanjutnya, klon cetakan ini dengan CTRL + D (setelah memilihnya di tab hierarki) dan letakkan di atas panggung.


gambar


Layar


Bagian dari level / dunia permainan yang saat ini dapat dilihat oleh pemain.


Siapkan kamera yang akan mengikuti pemain untuk menampilkan bagian dari adegan. Selanjutnya, akan ada skrip yang sangat sederhana untuk diterapkan:


Judul spoiler
 public GameObject objectToFollow; public float speed = 2.0f; void Update () { CamFoll(); } private void CamFoll() { float interpolation = speed * Time.deltaTime; Vector3 position = this.transform.position; position.y = Mathf.Lerp(this.transform.position.y, objectToFollow.transform.position.y, interpolation); position.x = Mathf.Lerp(this.transform.position.x, objectToFollow.transform.position.x, interpolation); this.transform.position = position; } 

Di bidang objectToFollow dari jenis GameObject , sebuah objek akan ditugaskan untuk dipantau, dan di bidang kecepatan, kecepatan yang diperlukan untuk bergerak dengan lancar di belakang GameObject yang ditugaskan.


Informasi tentang kecepatan gerakan sejak frame terakhir direkam dalam bidang interpolasi. Selanjutnya, metode Lerp akan digunakan, yang akan memastikan kelancaran pergerakan kamera di belakang Lucas ketika bergerak di sepanjang sumbu X dan U. Sayangnya, saya tidak bisa menjelaskan pengoperasian saluran


 position.y = Mathf.Lerp(this.transform.position.y, objectToFollow.transform.position.y, interpolation); 

dalam hal matematika. Oleh karena itu, saya akan mengatakan lebih sederhana - metode ini akan memperpanjang waktu eksekusi tindakan apa pun. Dalam kasus kami, ini adalah pergerakan kamera di belakang objek.


Bahaya


Judul spoiler

Entitas yang mencegah pemain menyelesaikan tugasnya. Contoh: paku dari 1001 Paku (Nicalis and 8bits Fanatics, 2014).


Mari kita mulai menambahkan sesuatu yang tidak hanya akan mencegah Lukas dari melewati tahap sampai akhir, tetapi akan mempengaruhi jumlah hidupnya dan kemampuan untuk mati (untuk satu, kita akan menerapkan subproblem kelima untuk menerapkan kisah hidup Lukas - Pahlawan dapat dibunuh atau bisa mati).
Dalam hal ini, kami menyebarkan paku di atas panggung yang akan disembunyikan di balik vegetasi dan hanya perhatian pemain yang akan membantu melewatinya.


Buat GameObject kosong dan sambungkan komponen SpriteRenderer dan PolygonCollider2D ke sana. Dalam komponen SpriteRenderer , kami menghubungkan sprite dari tombol berduri atau objek lain yang diinginkan. Juga, tetapkan tag = Thorn untuk spike.


Selanjutnya, pada GameObject Lucas , kami membuat skrip yang akan bertanggung jawab atas apa yang akan terjadi padanya jika Lucas bertabrakan dengan collider lainnya. Dalam kasus saya, saya menyebutnya ColliderReaction.cs


Judul spoiler
 private Rigidbody2D rb2d; void Start() { rb2d = GetComponent<Rigidbody2D>(); } public void OnTriggerEnter2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Thorn": { rb2d.AddForce(transform.up * 4, ForceMode2D.Impulse); HeroScript.Health = HeroScript.Health - 5; } break; } } 

Inti dari skrip ini sesederhana 2x2. Ketika tag Thorn bertabrakan dengan objek game, pernyataan Switch membandingkan dengan kandidat yang kami tentukan. Dalam kasus kami, untuk saat ini, itu Thorn . Pertama, Lucas muntah, dan kemudian kita beralih ke variabel statis dan mengambil 5 unit kehidupan dari Lucas. Ke depan, saya dapat mengatakan bahwa masuk akal untuk menggambarkan hal yang sama untuk konflik dengan musuh:


Judul spoiler
 case "Enemy": { rb2d.AddForce(transform.up * 2, ForceMode2D.Impulse); HeroScript.Health = HeroScript.Health - 10; } break; 

Selanjutnya, saya mengusulkan untuk membunuh dua burung dengan satu batu.


Barang dan Aturan yang Dikumpulkan.


Objek permainan yang bisa diambil pemain.


Kami mengusulkan aturan bahwa jika Lucas ingin pergi di antara pulau-pulau dan memanjat, maka Anda perlu mengumpulkan pohon untuk membangun jembatan dan tangga.
Menurut metode yang sudah terlewati, kita akan membuat pohon dan tangga.


Kami akan menghubungkan skrip ke hierarki yang akan bertanggung jawab untuk berapa banyak log yang dapat Anda ketuk dari itu jika Anda mulai memotongnya. Karena hanya animasi serangan yang diusulkan dalam set sprite, kami akan menggunakannya ketika kami memotong pohon (biaya produksi).
Script yang ada di pohon:


Judul spoiler
 [SerializeField] private Transform inst; //     [SerializeField] private GameObject FireWoodPref; //   [SerializeField] private int fireWood; //        

Ketika level dimulai, kami menulis nilai acak di fireWood :


Judul spoiler
 void Awake() { fireWood = Random.Range(4,10); } 

Menjelaskan metode dengan parameter yang akan bertanggung jawab untuk berapa banyak log akan jatuh dalam satu langkah:


Judul spoiler
 public int fireWoodCounter(int x) { for (int i = 0; i < fireWood; i++) { fireWood = fireWood - x; InstantiateFireWood(); } return fireWood; } 

Metode yang akan membuat klon log di atas panggung.
private void InstantiateFireWood ():


Judul spoiler
  { Instantiate(FireWoodPref, inst.position, inst.rotation); } 


Mari kita membuat log dan menghubungkan skrip dengan kode berikut untuk itu:


Judul spoiler
 public void OnTriggerEnter2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Player": { if (InventoryOnHero.woodCount > 10) { Debug.Log("   !"); } else { InventoryOnHero.woodCount = InventoryOnHero.woodCount + 1; Destroy(this.gameObject); } } break; } } 

Selanjutnya, kami juga akan membuat kelas yang akan bertanggung jawab atas inventaris.


Pertama, periksa apakah ada ruang di dalam tas. Jika tidak, maka kesalahan dan log tetap bohong, jika ada ruang, maka kami mengisi inventaris dengan satu unit dan menghancurkan log.
Selanjutnya, Anda perlu melakukan sesuatu dengan sumber daya ini. Seperti disebutkan di atas, kami menawarkan pemain kesempatan untuk membangun jembatan dan tangga.


Untuk membuat jembatan, kita membutuhkan 2 cetakan awal dengan bagian kiri dan kanan jembatan. BoxCollider2D . , , - , .


:


Judul spoiler
 [SerializeField] private Transform inst1, inst2; //        [SerializeField] private GameObject bridgePref1, bridgePref2; //   [SerializeField] private int BridgeCount; //   ,   .    

:


Judul spoiler
 public void BuildBridge() { if (InventoryOnHero.woodCount == 0) { Debug.LogWarning (" !"); } if (InventoryOnHero.woodCount > 0) { BridgeCount = BridgeCount - 1; InventoryOnHero.woodCount = InventoryOnHero.woodCount - 1; } switch (BridgeCount) { case 5: Inst1(); break; case 0: Inst2(); break; default: Debug.LogWarning("-      "); break; } } 

, , . , 10 , 12 8.


, , , , . , 1 , 1 . , 5, , . 0, . , .


.


, ColliderReaction.cs :


Judul spoiler
 void OnTriggerStay2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Ladder": { rb2d.gravityScale = 0; } break; } } void OnTriggerExit2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Ladder": { rb2d.gravityScale = 1; } break; } } 

OnTriggerStay2D , . , 0. , . OnTriggerExit2D , .



, .


19 , . , , , , , .


GO, SpriteRenderer , BoxCollider2D , Rigidbody2D . , — , . , ru.stackoverflow.com.


gambar


Trees .


, . , -, , Raycast 2 (4 ). , , , ( ). ( ), . , . , , . , . , , ( , ).


, . , - .


:


Judul spoiler
 [SerializeField] private GameObject area; private bool m1 = true, m2; // m  move private void fGreenMonster() { float dist = Vector3.Distance(greenMonster.transform.position, area.transform.position); Debug.Log(dist); if (m1) { if (dist < 3f) { transform.position += new Vector3(speed,0,0) * Time.deltaTime; SR.flipX = true; } else { m1 = false; m2 = true; } } if (m2) { if(dist >= 1f) { transform.position += new Vector3(-speed,0,0) * Time.deltaTime; SR.flipX = false; } else { m2 = false; m1 = true; } } } 

Update() , . , 3 , . 3, , .


gambar


, .


Judul spoiler
 private void fSunFlower() { canBullet = canBullet - minus * Time.deltaTime; if (canBullet <= 0 && SR.flipX == false) { GameObject newArrow = Instantiate(sunFlowerBullet) as GameObject; newArrow.transform.position = transform.position; Rigidbody2D rb = newArrow.GetComponent<Rigidbody2D>(); rb.velocity = sunFlowerTrans.transform.forward * -sunFlowerBulletSpeed; canBullet = 2; } if (canBullet <= 0 && SR.flipX == true) { GameObject newArrow = Instantiate(sunFlowerBullet) as GameObject; newArrow.transform.position = transform.position; Rigidbody2D rb = newArrow.GetComponent<Rigidbody2D>(); rb.velocity = sunFlowerTrans.transform.forward * sunFlowerBulletSpeed; canBullet = 2; } 

 canBullet = canBullet - minus * Time.deltaTime; 

, .


Judul spoiler
 if (canBullet <= 0 && SR.flipX == false) { GameObject newArrow = Instantiate(sunFlowerBullet) } 

, , , , :


Judul spoiler
 public int Damage(int x) { Health = Health - x; return Health; } 

, , :


Judul spoiler
 public void ifDie() { if (Damage(0) <= 0) { Destroy(this.gameObject); } } 

0, .


, :


Judul spoiler
 if (bGreenMonster) { fGreenMonster(); } if (bSunFlower) { fSunFlower(); } 

, .


gambar


.


, ?


, .


:



:


Judul spoiler
 [SerializeField] private Transform Hero; //         [SerializeField] private float distWhatHeroSee; //   [SerializeField] private LayerMask Tree, BridgeBuild, LadderBuild ,drinkingWater, lEnemy; //   

, :


Judul spoiler
 private void AttackBtn() { if (CrossPlatformInputManager.GetButtonDown("Attack")) { GameObject.Find("Hero").GetComponent<HeroScript>().State = CharState.AttackA; Collider2D[] Trees = Physics2D.OverlapCircleAll(Hero.position, distWhatHeroSee, Tree); for (int i = 0; i < Trees.Length; i++) { Trees[i].GetComponent<TreeControl>().fireWoodCounter(1); Debug.Log("Trees Collider"); HeroScript.Water = HeroScript.Water - 0.7f; } // BB  BridgeBuild Collider2D[] BB = Physics2D.OverlapCircleAll(Hero.position, distWhatHeroSee, BridgeBuild); for (int i = 0; i < BB.Length; i++) { BB[i].GetComponent<BridgeBuilding>().BuildBridge(); HeroScript.Water = HeroScript.Water - 0.17f; } 

 GameObject.Find("Hero").GetComponent<HeroScript>().State = CharState.AttackA; 

, .
, :


Judul spoiler
 Collider2D[] Trees = Physics2D.OverlapCircleAll(Hero.position, distWhatHeroSee, Tree); for (int i = 0; i < Trees.Length; i++) { Trees[i].GetComponent<TreeControl>().fireWoodCounter(1); Debug.Log("Trees Collider"); HeroScript.Water = HeroScript.Water - 0.7f; } 

Trees , . , , , . .
, . Simple as that!


, - :


gambar


, — .
, . , , , .


2 , .


!


.


https://opengameart.org/ , :


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


All Articles