Bagi mereka yang melewatkan bagian pertama -
Bagian 1Bagian Berikutnya -
Bagian 3Jika ada yang tertarik membaca tentang agregator yang digunakan oleh acara tersebut, maka di
sinilah Anda, tetapi ini tidak perlu.
Jadi, kami mulai mengumpulkan semuanya dalam tumpukan
Roket:Kelas roket dasarusing DG.Tweening; using GlobalEventAggregator; using UnityEngine; namespace PlayerRocket { public class Rocket : PlayerRocketBase { [SerializeField] private float pathorrectionTime = 10; private Vector3 movingUp = new Vector3(0, 1, 0); protected override void StartEventReact(ButtonStartPressed buttonStartPressed) { transform.SetParent(null); rocketState = RocketState.MOVE; transform.DORotate(Vector3.zero, pathorrectionTime); } protected override void Start() { base.Start(); EventAggregator.Invoke(new RegisterUser { playerHelper = this }); if (rocketState == RocketState.WAITFORSTART) return; RocketBehaviour(); } private void FixedUpdate() { RocketBehaviour(); } private void RocketBehaviour() { switch (rocketState) { case RocketState.WAITFORSTART: if (inputController.OnTouch && !inputController.OnDrag) rocketHolder.RotateHolder(inputController.worldMousePos); break; case RocketState.MOVE: rigidbody.AddRelativeForce(Vector3.up*(config.Speed*Time.deltaTime)); forceModel.AddModificator(); break; case RocketState.STOP: Debug.Log(" "); rigidbody.velocity = Vector3.zero; rigidbody.drag = 50; rocketState = RocketState.COMPLETESTOP; break; case RocketState.COMPLETESTOP: break; default: rocketState = RocketState.COMPLETESTOP; break; } } } }
Apa yang kita butuhkan agar roket lepas landas? Di ruang permainan, kita membutuhkan planet bersyarat yang dengannya kita mulai, tombol start dan roket. Apa yang bisa dilakukan roket?
- Tunggu permulaannya
- Untuk terbang
- Dipengaruhi oleh pengubah
- Untuk berhenti
Artinya, kita memiliki perilaku / keadaan roket yang berbeda, tergantung pada keadaan saat ini, roket harus memberikan perilaku yang berbeda. Dalam pemrograman, kita terus-menerus dihadapkan pada situasi di mana suatu objek dapat memiliki banyak perilaku yang sangat berbeda.
Untuk perilaku objek yang kompleks - lebih baik menggunakan pola perilaku, misalnya, pola keadaan. Untuk yang sederhana, programmer pemula sering menggunakan banyak jika jika lain. Saya sarankan menggunakan sakelar dan enum. Pertama, ini adalah pembagian logika yang lebih jelas menjadi tahapan-tahapan spesifik, berkat ini kita akan tahu persis keadaan kita sekarang, dan apa yang terjadi, ada lebih sedikit peluang untuk mengubah kode itu menjadi mie dari puluhan pengecualian.
Cara kerjanya:Pertama kita mulai enum dengan status yang kita butuhkan:
public enum RocketState { WAITFORSTART = 0, MOVE = 1, STOP = 2, COMPLETESTOP = 3, }
Di kelas induk kami memiliki bidang -
protected RocketState rocketState;
Secara default, nilai pertama diberikan padanya. Enum sendiri yang menetapkan nilai default, tetapi untuk data yang dapat diubah dari atas atau dikonfigurasikan oleh desainer game - saya secara manual mengatur nilai, untuk apa? Agar dapat menambahkan nilai lain ke inam di mana saja dan tidak melanggar data yang disimpan. Saya juga menyarankan Anda untuk mempelajari flag enum.
Selanjutnya:Kami mendefinisikan perilaku itu sendiri dalam pembaruan, tergantung pada nilai bidang rocketState
private void FixedUpdate() { RocketBehaviour(); } private void RocketBehaviour() { switch (rocketState) { case RocketState.WAITFORSTART: if (inputController.OnTouch && !inputController.OnDrag) rocketHolder.RotateHolder(inputController.worldMousePos); break; case RocketState.MOVE: rigidbody.AddRelativeForce(Vector3.up*(config.Speed*Time.deltaTime)); forceModel.AddModificator(); break; case RocketState.STOP: Debug.Log(" "); rigidbody.velocity = Vector3.zero; rigidbody.drag = 50; rocketState = RocketState.COMPLETESTOP; break; case RocketState.COMPLETESTOP: break; default: rocketState = RocketState.COMPLETESTOP; break; } }
Saya akan menguraikan apa yang terjadi:- Ketika kita menunggu, kita cukup memutar roket ke arah kursor mouse, sehingga mengatur lintasan awal
- Keadaan kedua - kami terbang, mempercepat roket ke arah yang benar, dan memperbarui model pengubah untuk tampilan objek yang mempengaruhi lintasan.
- Keadaan ketiga adalah ketika tim tiba pada kita untuk berhenti, di sini kita mengerjakan semuanya sehingga roket berhenti dan diterjemahkan menjadi keadaan - kita benar-benar berhenti.
- Keadaan terakhir adalah kita tidak melakukan apa-apa.
Kenyamanan dari pola saat ini - semuanya sangat mudah diperluas dan disesuaikan, tetapi ada satu hal kecuali tautan yang lemah - ini adalah saat kita dapat memiliki keadaan yang menggabungkan sejumlah negara bagian lain. Di sini ada flag inam, dengan komplikasi pemrosesan, atau sudah beralih ke pola yang lebih "berat".
Kami menemukan roketnya. Langkah selanjutnya adalah objek yang sederhana namun lucu - tombol start.
Tombol mulai
Fungsionalitas berikut diperlukan untuk mengkliknya, ia memberi tahu bahwa mereka mengkliknya.
Mulai Kelas Tombol using UnityEngine; using UnityEngine.EventSystems; public class StartButton : MonoBehaviour, IPointerDownHandler { private bool isTriggered; private void ButtonStartPressed() { if (isTriggered) return; isTriggered = true; GlobalEventAggregator.EventAggregator.Invoke(new ButtonStartPressed()); Debug.Log(""); } public void OnPointerDown(PointerEventData eventData) { ButtonStartPressed(); } } public struct ButtonStartPressed { }
Menurut desain game, ini adalah objek 3D di atas panggung, tombol seharusnya diintegrasikan ke dalam desain planet awal. Baiklah, ada nuansa - bagaimana cara melacak klik pada suatu objek dalam sebuah adegan?
Jika kita google, kita akan menemukan banyak metode OnMouse, di antaranya akan ada klik. Tampaknya ini pilihan yang mudah, tetapi ini sangat buruk, dimulai dengan fakta bahwa sering bekerja dengan bengkok (ada banyak nuansa untuk melacak klik), "mahal", diakhiri dengan fakta bahwa itu tidak memberikan ton roti yang ada di UnityEngine.EventSystems.
Pada akhirnya, saya sarankan menggunakan UnityEngine.EventSystems dan antarmuka IPointerDownHandler, IPointerClickHandler. Dalam metode mereka, kami menyadari reaksi terhadap penekanan, tetapi ada beberapa nuansa.
- Sebuah EventSystem harus ada di tempat kejadian, ini adalah objek / kelas / komponen unit, biasanya dibuat ketika kita membuat kanvas untuk antarmuka, tetapi Anda juga dapat membuatnya sendiri.
- Fisika RayCaster harus ada di kamera (ini untuk 3D, untuk grafis 2D ada racaster terpisah)
- Harus ada collider di fasilitas itu
Dalam proyek itu, terlihat seperti ini:
Sekarang objek melacak klik dan metode ini disebut:
public void OnPointerDown(PointerEventData eventData) { ButtonStartPressed(); } private void ButtonStartPressed() { if (isTriggered) return; isTriggered = true; GlobalEventAggregator.EventAggregator.Invoke(new ButtonStartPressed()); Debug.Log(""); }
Apa yang terjadi di sini:Kami memiliki bidang Boolean tempat kami melacak apakah tombol itu ditekan atau tidak (ini adalah perlindungan terhadap penekanan berulang sehingga kami tidak memiliki skrip start yang dijalankan setiap kali).
Selanjutnya, kita memanggil acara - tombol ditekan, yang berlangganan kelas roket, dan menempatkan roket dalam keadaan bergerak.
Melompat sedikit ke depan - mengapa ada di sana-sini untuk acara? Ini adalah pemrograman berorientasi peristiwa. Pertama, model acara lebih murah daripada pemrosesan data kontinu untuk mengetahui perubahannya. Kedua, ini adalah koneksi terlemah, kita tidak perlu tahu di roket bahwa ada tombol, bahwa seseorang menekannya, dan seterusnya, kita hanya tahu bahwa ada acara untuk memulai, kita menerimanya dan bertindak. Lebih jauh, acara ini menarik tidak hanya untuk roket, misalnya, panel dengan pengubah ditandatangani untuk acara yang sama, tetapi tersembunyi di awal roket. Juga, acara ini mungkin menarik bagi pengontrol input - dan input pengguna mungkin tidak diproses atau diproses secara berbeda setelah peluncuran roket.
Mengapa tidak banyak programmer yang menyukai paradigma acara? Karena satu ton acara dan berlangganan acara ini dengan mudah mengubah kode menjadi mie, di mana sama sekali tidak jelas harus mulai dari mana dan apakah itu akan berakhir di suatu tempat, belum lagi fakta bahwa Anda juga perlu memantau berhenti berlangganan / berlangganan dan menjaga semua benda tetap hidup.
Dan itulah mengapa untuk implementasi acara saya menggunakan agregator acara saya, yang sebenarnya tidak mengirimkan peristiwa, tetapi wadah data melalui acara, dan kelas berlangganan data yang menarik bagi mereka. Juga, agregator itu sendiri memantau benda hidup dan melemparkan benda mati dari pelanggan. Berkat pemindahan wadah, injeksi juga dimungkinkan, Anda dapat mengirimkan tautan ke kelas yang diminati kepada kami. Dengan wadah, Anda dapat dengan mudah melacak siapa yang memproses dan mengirim data ini. Untuk prototyping adalah hal yang hebat.
Rotasi roket untuk menentukan jalur awal

Menurut desain game, roket harus dapat berputar di sekitar planet untuk menentukan lintasan awal, tetapi tidak lebih dari sudut tertentu. Rotasi dilakukan oleh sentuhan - roket hanya mengikuti jari dan selalu diarahkan ke tempat di mana kita menyodok layar. Ngomong-ngomong, itu hanya prototipe yang memungkinkan untuk menentukan bahwa ini adalah titik lemah dan ada banyak episode yang terkait dengan manajemen yang berbatasan dengan fungsi ini.
Tetapi dalam urutan:- Kita membutuhkan roket untuk berputar relatif terhadap planet ke arah gerobak dorong
- Kita perlu menjepit sudut rotasi
Adapun rotasi relatif terhadap planet ini - Anda dapat secara diam-diam memutar di sekitar sumbu dan menghitung sumbu rotasi, atau Anda dapat dengan mudah membuat objek dengan dummy yang berpusat di dalam planet, memindahkan roket ke sana, dan diam-diam memutar boneka di sekitar sumbu Z, boneka akan memiliki kelas yang akan menentukan perilaku objek. Roket akan berputar dengannya. Objek yang saya sebut RocketHolder. Kami menemukan jawabannya.
Sekarang, tentang batasan untuk berputar dan berbalik ke arah gerobak dorong:
kelas RocketHolder using UnityEngine; public class RocketHolder : MonoBehaviour { [SerializeField] private float clampAngle = 45; private void Awake() { GlobalEventAggregator.EventAggregator.AddListener(this, (InjectEvent<RocketHolder> obj) => obj.inject(this)); } private float ClampAngle(float angle, float from, float to) { if (angle < 0f) angle = 360 + angle; if (angle > 180f) return Mathf.Max(angle, 360 + from); return Mathf.Min(angle, to); } private Vector3 ClampRotationVectorZ (Vector3 rotation ) { return new Vector3(rotation.x, rotation.y, ClampAngle(rotation.z, -clampAngle, clampAngle)); } public void RotateHolder(Vector3 targetPosition) { var diff = targetPosition - transform.position; diff.Normalize(); float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg; transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90); transform.eulerAngles = ClampRotationVectorZ(transform.rotation.eulerAngles); } }
Terlepas dari kenyataan bahwa permainan ini dalam teori 3D, tetapi keseluruhan logika dan gameplay sebenarnya 2D. Dan kita hanya perlu mengencangkan roket di sekitar sumbu Z ke arah tempat penekanan. Pada akhir metode, kami menjepit derajat rotasi dengan nilai yang ditentukan dalam inspektur. Dalam metode Sedarlah, Anda dapat melihat implementasi injeksi kelas yang paling benar melalui agregator.
Inputcontroller
Salah satu kelas yang paling penting, dialah yang mengumpulkan dan memproses perilaku pengguna. Menekan hotkey, tombol gamepad, keyboard, dll. Saya punya input yang cukup sederhana dalam prototipe, sebenarnya Anda hanya perlu tahu 3 hal:
- Apakah ada klik dan koordinasinya
- Apakah ada gesekan vertikal dan berapa banyak menggesek
- Apakah saya beroperasi dengan antarmuka / pengubah
InputController kelas using System; using UnityEngine; using UnityEngine.EventSystems; public class InputController : MonoBehaviour { public const float DirectionRange = 10; private Vector3 clickedPosition; [Header(" ")] [SerializeField] private float afterThisDistanceWeGonnaDoSwipe = 0.5f; [Header(" ")] [SerializeField] private float speedOfVerticalScroll = 2; public ReactiveValue<float> ReactiveVerticalScroll { get; private set; } public Vector3 worldMousePos => Camera.main.ScreenToWorldPoint(Input.mousePosition); public bool OnTouch { get; private set; } public bool OnDrag { get; private set; }
Semuanya ada di dahi dan tanpa masalah, menarik mungkin merupakan implementasi primitif dari kepemilikan reaktif - ketika saya baru mulai memprogram, selalu menarik bagaimana mengetahui bahwa data telah berubah, tanpa ventilasi data yang konstan. Baiklah, itu saja.
Ini terlihat seperti ini:
kelas ReactiveValue public class ReactiveValue<T> where T: struct { private T currentState; public Action<T> OnChange; public T CurrentValue { get => currentState; set { if (value.Equals(currentState)) return; else { currentState = value; OnChange?.Invoke(currentState); } } } }
Kami berlangganan OnChange, dan kami bergerak-gerak jika hanya nilainya yang berubah.
Mengenai prototipe dan arsitektur - tipnya sama, hanya properti dan metode publik, semua data hanya boleh diubah secara lokal. Pemrosesan dan perhitungan apa pun - dijumlahkan sesuai dengan metode terpisah. Akibatnya, Anda selalu dapat mengubah implementasi / perhitungan, dan ini tidak akan memengaruhi pengguna eksternal kelas. Itu saja untuk saat ini, di bagian akhir ketiga - tentang pengubah dan antarmuka (drag drop). Dan saya berencana untuk menempatkan proyek pada git sehingga saya bisa melihat / merasakan. Jika Anda memiliki pertanyaan tentang pembuatan prototipe - tanyakan, saya akan mencoba menjawab dengan jelas.