Membuat prototipe game seluler, mulai dari mana, dan bagaimana melakukannya. Bagian 3 (final)

Pada bagian pertama saya berbicara tentang mengapa membuat prototipe dan secara umum di mana untuk memulai
- Bagian 1

Di bagian kedua, sedikit dijalankan melalui kelas utama
dan arsitektur - Bagian 2

Dan bagian ketiga - ini sebenarnya akan memiliki sedikit diskusi, kita akan menganalisis bagaimana pengubah bekerja, dan jatuh di lapangan (ini tidak sulit, tetapi ada nuansa). Dan sedikit tentang arsitektur, saya akan coba tanpa kebosanan.

Tangkapan layar tentang roket pada umumnya membosankan, jadi saya sarankan menonton video prototipe lain, yang dirakit dalam 2 minggu bersama dengan gambar, dan ditinggalkan karena fakta bahwa dalam genre platformer tidak ada jalan lain. Ngomong-ngomong, ini adalah salah satu ide kunci di sekitar prototipe - untuk berkumpul, melihat apakah ini omong kosong, apakah itu perlu? Dan dengan jujur ​​lemparkan keranjang jika jawabannya tidak meyakinkan Anda. Tapi! Ini tidak berlaku untuk proyek-proyek kreatif - kadang-kadang kreativitas terjadi demi kreativitas).

Jadi vidosik, Anda perlu melihat teks yang disusun berdasarkan level, dan bukan pada gameplay):



Penyimpangan kecil


Dalam artikel terakhir, saya menerima kode ulasan dan berterima kasih untuk ini, kritik membantu untuk mengembangkan bahkan jika Anda tidak setuju dengan itu.

Tapi saya ingin menggunakan untuk arsitektur dan sintaksis mengenai prototipe:
  1. Tidak peduli seberapa kerennya Anda, Anda tidak dapat memperkirakan bahwa itu tidak akan digadaikan, dan akan ada banyak yang digadaikan dari apa yang tidak diperlukan. Karena itu, dalam hal apa pun, refactoring atau ekspansi akan diperlukan. Jika Anda tidak dapat menggambarkan manfaat spesifik dari kode / pendekatan, lebih baik tidak menghabiskan banyak waktu untuk kode ini.
  2. Mengapa menurut saya OOP / Model Acara / Komposisi lebih mudah untuk prototipe daripada ECS, Unity COOP, DI FrameWorks, Kerangka Reaktif, dll. Lebih sedikit coretan, semua koneksi terlihat dalam kode, karena tugas utama prototipe adalah menjawab pertanyaan utama - apakah mungkin untuk memainkannya, dan sejumlah yang sekunder - yang lebih baik untuk gameplay, ini atau itu. Karena itu, Anda perlu mengimplementasikan fungsi yang diperlukan secepat mungkin. Mengapa memperkenalkan kerangka kerja pada proyek kecil, meresepkan semua sampah untuk mengimplementasikan tiga entitas game? Masing-masing sebenarnya adalah kelas 50-100 baris. Arsitektur harus dipikirkan sebagai bagian dari tugas prototipe, dan sebagai bagian dari kemungkinan perluasan ke alpha, tetapi yang kedua diperlukan lebih di kepala daripada di kode, sehingga Anda tidak membakar ketika menambahkan kode


Tentang pengubah:


Dan akhirnya tentang modifikator itu sendiri:
Di sini dan sebelumnya, saya menyebut pengubah medan gaya yang disentuh roket dan yang memengaruhi jalurnya, dalam prototipe ada dua jenisnya - akselerasi dan defleksi.

Kelas pengubah
public class PushSideModificator : MonoBehaviour { [SerializeField] TypeOfForce typeOfForce = TypeOfForce.Push; [SerializeField] private float force; [SerializeField] DropPanelConfig dropPanelConfig; private float boundsOfCollider; private void OnTriggerEnter(Collider other) { boundsOfCollider = other.bounds.extents.x; GlobalEventAggregator.EventAggregator.Invoke(new SpaceForces { TypeOfForce = typeOfForce, Force = force, ColliderBound = boundsOfCollider, CenterOfObject = transform.position, IsAdded = true }); } private void OnTriggerExit(Collider other) { GlobalEventAggregator.EventAggregator.Invoke(new SpaceForces { CenterOfObject = transform.position, IsAdded = false }); } } 


Ini adalah kelas yang sangat sederhana yang tugasnya melewati dua peristiwa:
  1. Entri pemain ke lapangan (yang sebenarnya merupakan pemicu persatuan fisik), dan semua konteks yang diperlukan - jenis pengubah, posisinya, ukuran collider, kekuatan pengubah, dan sebagainya. Dalam hal ini ditambah acara agregator, ia dapat menyampaikan konteks apa pun kepada pihak yang berkepentingan. Dalam nada ini, ini adalah model roket yang memproses pengubah.
  2. Acara kedua - pemain meninggalkan lapangan. Untuk menghapus pengaruhnya pada lintasan pemain

Untuk apa model acara? Siapa lagi yang mungkin membutuhkan acara ini?
Dalam proyek saat ini ini tidak diterapkan, tetapi:
  1. Akting suara (menerima peristiwa bahwa seseorang memasuki lapangan - kami memutar suara yang sesuai, seseorang keluar - sama halnya)
  2. Marker UI, katakanlah untuk setiap bidang kita akan menghapus beberapa bahan bakar dari roket, tooltips akan muncul bahwa kita memasuki lapangan dan kehilangan bahan bakar, baik, atau mendapatkan poin untuk setiap klik di lapangan, ada banyak pilihan antarmuka tertarik pemain masuk ke lapangan.
  3. Spesial efek - ketika terkena dalam jenis bidang yang berbeda, efek yang berbeda dapat ditumpangkan, baik pada roket itu sendiri maupun pada ruang di sekitar roket / bidang. Spesial efek dapat ditangani oleh entitas / pengontrol terpisah, yang juga akan berlangganan acara dari pengubah.
  4. Ya, ini adalah kode minimum, pencari lokasi, agregasi, dependensi, dll. Tidak diperlukan.


Dasar Gameplay


Dalam prototipe ini, inti dari gameplay adalah menempatkan pengubah di lapangan bermain, menyesuaikan jalur penerbangan roket, untuk terbang di sekitar rintangan dan mengenai titik / planet tujuan. Untuk melakukan ini, kami memiliki panel di sebelah kanan, di mana ikon pengubah berada.



Kelas Panel
  [RequireComponent (typeof(CanvasGroup))] public class DragAndDropModifiersPanel : MonoBehaviour { [SerializeField] private DropModifiersIcon iconPrfb; [SerializeField] private DropPanelConfig config; private CanvasGroup canvasGroup; private void Awake() { GlobalEventAggregator.EventAggregator.AddListener<ButtonStartPressed>(this, RocketStarted); canvasGroup = GetComponent<CanvasGroup>(); } private void RocketStarted(ButtonStartPressed obj) { canvasGroup.DOFade(0, 1); (canvasGroup.transform as RectTransform).DOAnchorPosX(100, 1); } private void Start() { for (var x = 0; x< 3; x++) { var mod = config.GetModifierByType(TypeOfForce.Push); var go = Instantiate(iconPrfb, transform); go.Init(mod); } for (var x = 0; x< 1; x++) { var mod = config.GetModifierByType(TypeOfForce.AddSpeed); var go = Instantiate(iconPrfb, transform); go.Init(mod); } } } 



Mengantisipasi pertanyaan:
 for (var x = 0; x< 3; x++) for (var x = 0; x< 1; x++) 


3 dan 1 - yang disebut angka ajaib yang hanya diambil dari kepala dan dimasukkan ke dalam kode, ini harus dihindari, tetapi mengapa mereka ada di sini? Prinsip dimana panel kanan dibentuk belum pernah ditentukan, dan diputuskan hanya untuk menguji prototipe hanya dengan sejumlah pengubah pada prototipe.

Bagaimana cara melakukannya dengan benar? - setidaknya memasukkannya ke dalam bidang serializable, dan atur jumlah yang dibutuhkan melalui inspektur. Mengapa saya terlalu malas dan Anda harus melakukannya? Di sini kita harus melanjutkan dari gambaran besar, entitas terpisah dan konfigurasi masih akan bertanggung jawab untuk pembentukan jumlah pengubah yang diperlukan, jadi di sini saya terlalu malas mengharapkan banyak refactoring di masa depan. Tapi lebih baik jangan malas! )

Tentang konfigurasi - ketika kuliah pertama tentang ScriptableObject muncul, saya menyukai gagasan menyimpan data sebagai aset. Anda mendapatkan data yang diperlukan di mana pun Anda membutuhkannya, tanpa terikat dengan turunan tunggal. Lalu ada kuliah tentang pendekatan pengembangan game yang melibatkan ScriptableObject, di mana mereka dulu menyimpan pengaturan instance. Sebenarnya preset / pengaturan dari sesuatu yang disimpan dalam aset adalah konfigurasi.

Pertimbangkan kelas konfigurasi:
Kelas konfigurasi
  [CreateAssetMenu(fileName = "DropModifiersPanel", menuName = "Configs/DropModifier", order = 2)] public class DropPanelConfig : ScriptableObject { [SerializeField] private ModifierBluePrintSimple[] modifierBluePrintSimples; public DropModifier GetModifierByType(TypeOfForce typeOfModifiers) { return modifierBluePrintSimples.FirstOrDefault(x => x.GetValue.TypeOfModifier == typeOfModifiers).GetValue; } } [System.Serializable] public class DropModifier { public TypeOfForce TypeOfModifier; public Sprite Icon; public GameObject Modifier; public Material Material; } 



Apa esensi dari karyanya? Ini menyimpan kelas data pengubah disesuaikan.
  public class DropModifier { public TypeOfForce TypeOfModifier; public Sprite Icon; public GameObject Modifier; public Material Material; } 


Jenis pengubah diperlukan untuk identifikasi, ikon untuk antarmuka, mode game dari objek game pengubah, materi di sini sehingga dapat diubah selama konfigurasi. Pengubah mungkin sudah terletak di lapangan bermain, dan katakanlah perancang permainan mengubah tipenya, sekarang ia memberikan akselerasi, init pengubah dari konfigurasi dan memperbarui semua bidang, termasuk materi, menurut jenis pengubah ini.

Bekerja dengan konfigurasi sangat sederhana - kita beralih ke konfigurasi untuk data untuk tipe tertentu, kita mendapatkan data ini, pengaturan visual dan kemungkinan intim dari data ini.

Di mana untungnya?
Keuntungannya adalah fleksibilitas yang sangat besar, misalnya, Anda ingin mengubah materi dan ikon pada pengubah akselerasi, atau katakanlah untuk mengganti seluruh proyek game. Alih-alih menulis ulang dan meneruskan ke bidang inspektur, kami hanya mengubah data ini dalam satu konfigurasi dan voila - semuanya akan diperbarui bersama kami, di semua adegan / level / panel.

Dan jika ada beberapa data untuk pengubah akselerator dalam konfigurasi?
Dalam prototipe, Anda dapat dengan mudah melacaknya secara manual sehingga data tidak digandakan, pada draft kerja, Anda perlu tes dan validasi data.

Dari ikon ke lapangan bermain


Kelas Ikon Pengubah
 public class DropModifiersIcon : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler { [SerializeField] private Image icon; [Header("       ")] [SerializeField] private RectTransform canvas; private CanvasGroup canvasGroup; private DropModifier currentModifier; private Vector3 startPoint; private Vector3 outV3; private GameObject currentDraggedObj; private void Start() { canvasGroup = GetComponent<CanvasGroup>(); startPoint = transform.position; canvas = GetComponentInParent<Canvas>().transform as RectTransform; } public void Init(DropModifier dropModifier) { icon.sprite = dropModifier.Icon; currentModifier = dropModifier; } public void OnBeginDrag(PointerEventData eventData) { BlockRaycast(false); currentDraggedObj = Instantiate(currentModifier.Modifier, WorldSpaceCoord(), Quaternion.identity); GlobalEventAggregator.EventAggregator.Invoke(new ImOnDragEvent { IsDragging = true }); } private void BlockRaycast(bool state) { canvasGroup.blocksRaycasts = state; } public void OnDrag(PointerEventData eventData) { Vector2 outV2; RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, Input.mousePosition, null, out outV2); transform.position = canvas.transform.TransformPoint(outV2); if (currentDraggedObj != null) currentDraggedObj.transform.position = WorldSpaceCoord(); } private Vector3 WorldSpaceCoord() { RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas, Input.mousePosition, Camera.main, out outV3); return outV3; } public void OnEndDrag(PointerEventData eventData) { GlobalEventAggregator.EventAggregator.Invoke(new ImOnDragEvent { IsDragging = false }); if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.layer == 5) { Destroy(currentDraggedObj); transform.SetAsLastSibling(); canvasGroup.blocksRaycasts = true; } else Destroy(gameObject); } } public struct ImOnDragEvent { public bool IsDragging; } 



Apa yang sedang terjadi di sini?
Kami ambil ikon dari panel, di bawahnya kami membuat game blog pengubah itu sendiri. Dan sebenarnya kami mengatur koordinat dari click / gerobak ke ruang permainan, jadi kami memindahkan pengubah di ruang permainan bersama dengan ikon di UI, dengan cara saya menyarankan Anda untuk membaca tentang RectTransformUtility, ini adalah kelas pembantu yang hebat di mana ada banyak fitur untuk antarmuka.

Katakanlah kita berubah pikiran tentang menempatkan pengubah dan mengembalikannya ke panel,
  if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.layer == 5) 

Sepotong kode ini memungkinkan kita untuk memahami apa yang ada di bawah klik. Mengapa pengecekan layer juga ditunjukkan di sini? Dan mengapa lagi adalah angka ajaib 5? Seperti yang kita ingat dari bagian kedua, kita menggunakan grafik rakester tidak hanya untuk UI, tetapi juga untuk tombol yang ada di layar, juga jika kita menambahkan fungsionalitas untuk menghapus pengubah yang sudah ditempatkan di lapangan atau memindahkannya, maka mereka juga akan jatuh di bawah grafik rake, oleh karena itu ada juga pemeriksaan tambahan untuk milik lapisan UI. Ini adalah lapisan default, dan urutannya tidak berubah, jadi angka 5 di sini umumnya bukan angka ajaib.

Akibatnya, ternyata jika kita melepaskan ikon di atas panel, itu akan kembali ke panel, jika di atas lapangan bermain pengubah tetap di lapangan, ikon akan dihapus.

1 hari kerja dihabiskan untuk kode prototipe. Ditambah sedikit keributan pada file dan grafik. Secara umum, gameplay ditemukan cocok, meskipun ada banyak pertanyaan tentang chip desain seni dan game. Misi Selesai.

Kesimpulan dan rekomendasi


  1. Lay arsitektur minimal, tetapi arsitektur tetap
  2. Ikuti prinsip-prinsip dasar, tetapi tanpa fanatisme)
  3. Pilih solusi sederhana
  4. Antara keserbagunaan dan kecepatan - lebih baik memilih kecepatan untuk prototipe
  5. Untuk proyek besar / menengah, menyiratkan bahwa lebih baik untuk menulis ulang proyek dari awal. Misalnya, sekarang tren di Unity adalah DOTS, Anda harus menulis banyak komponen dan sistem, itu buruk untuk jangka pendek, Anda kehilangan waktu, untuk jangka panjang - ketika semua komponen dan sistem terdaftar, ketika gain waktu dimulai. Saya tidak berpikir itu keren untuk menghabiskan banyak waktu pada arsitektur yang sedang tren dan mencari tahu apa prototipnya.

Prototipe yang berhasil untuk semua.

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


All Articles