Judulnya sengaja hiperbolik dan mungkin hanya pengalaman saya dengan polanya tapi inilah alasan saya:
Cara "biasa" atau bisa langsung dari entitas pelaksana adalah dengan mengimplementasikannya sebagai objek dan mensubklasifikasikan perilaku umum. Ini mengarah ke masalah klasik "adalah EvilTree
subclass dari Tree
atau Enemy
?". Jika kita mengizinkan beberapa warisan, masalah intan muncul. Sebagai gantinya, kita dapat menarik fungsionalitas gabungan Tree
dan Enemy
lebih jauh ke atas hierarki yang mengarah ke kelas-kelas Tuhan, atau kita dapat dengan sengaja meninggalkan perilaku di kelas kita Tree
dan Entity
(membuat mereka menjadi penghubung dalam kasus ekstrem) sehingga EvilTree
dapat mengimplementasikannya sendiri - yang mengarah ke duplikasi kode jika kita pernah punya SomewhatEvilTree
.
Sistem Entitas-Komponen mencoba untuk memecahkan masalah ini dengan membagi Tree
dan Enemy
objek menjadi komponen yang berbeda - katakanlah Position
, Health
dan AI
- dan menerapkan sistem, seperti AISystem
yang mengubah posisi Entitiy sesuai dengan keputusan AI. Sejauh ini bagus tapi bagaimana jika EvilTree
bisa mengambil powerup dan menangani kerusakan? Pertama kita perlu a CollisionSystem
dan DamageSystem
(kita mungkin sudah memiliki ini). The CollisionSystem
kebutuhan untuk berkomunikasi dengan DamageSystem
: Setiap kali dua hal bertabrakan dengan CollisionSystem
mengirimkan pesan ke DamageSystem
kesehatan sehingga dapat mengurangi. Kerusakan juga dipengaruhi oleh powerups sehingga kita perlu menyimpannya di suatu tempat. Apakah kita membuat yang baru PowerupComponent
yang kita lampirkan ke entitas? Tapi kemudianDamageSystem
perlu tahu tentang sesuatu yang lebih baik tidak tahu tentang - setelah semua, ada juga hal-hal yang menangani kerusakan yang tidak dapat mengambil powerups (misalnya a Spike
). Apakah kami mengizinkan PowerupSystem
untuk memodifikasi StatComponent
yang juga digunakan untuk perhitungan kerusakan yang mirip dengan jawaban ini ? Tetapi sekarang dua sistem mengakses data yang sama. Ketika permainan kami menjadi lebih kompleks, itu akan menjadi grafik ketergantungan yang tidak berwujud di mana komponen dibagi di antara banyak sistem. Pada titik itu kita bisa menggunakan variabel statis global dan menyingkirkan semua boilerplate.
Apakah ada cara yang efektif untuk menyelesaikan ini? Satu ide yang saya miliki adalah membiarkan komponen memiliki fungsi tertentu, misalnya memberikan StatComponent
attack()
yang baru saja mengembalikan integer secara default tetapi dapat dikomposisi ketika powerup terjadi:
attack = getAttack compose powerupBy(20) compose powerdownBy(40)
Ini tidak menyelesaikan masalah yang attack
harus disimpan dalam komponen yang diakses oleh banyak sistem, tetapi setidaknya saya bisa mengetik fungsi dengan benar jika saya memiliki bahasa yang mendukungnya secara memadai:
// In StatComponent
type Strength = PrePowerup | PostPowerup
type Damage = Int
type PrePowerup = Int
type PostPowerup = Int
attack: Strength = getAttack //default value, can be changed by systems
getAttack: PrePowerup
// these functions can be defined in other components or in PowerupSystems
powerupBy: Strength -> PostPowerup
powerdownBy: Strength -> PostPowerup
subtractArmor: Strength -> Damage
// in DamageSystem
dealDamage: Damage -> () = attack compose subtractArmor compose hurtSomeEntity
Dengan cara ini saya setidaknya menjamin urutan yang benar dari berbagai fungsi yang ditambahkan oleh sistem. Either way, sepertinya saya dengan cepat mendekati pemrograman reaktif fungsional di sini jadi saya bertanya pada diri sendiri apakah saya seharusnya tidak menggunakannya sejak awal (saya baru saja melihat ke FRP, jadi saya mungkin salah di sini). Saya melihat bahwa ECS merupakan peningkatan dari hirarki kelas yang kompleks tetapi saya tidak yakin itu ideal.
Apakah ada solusi untuk ini? Apakah ada fungsi / pola yang saya lewatkan untuk memisahkan ECS dengan lebih bersih? Apakah FRP hanya lebih cocok untuk masalah ini? Apakah masalah ini hanya muncul karena kompleksitas yang melekat pada apa yang saya coba programkan; yaitu apakah FRP akan memiliki masalah yang sama?
sumber
Jawaban:
ECS benar-benar merusak data yang disembunyikan. Ini adalah trade-off dari pola tersebut.
ECS sangat baik dalam decoupling. ECS yang baik memungkinkan sistem pemindahan menyatakan bahwa ia bekerja pada entitas apa pun yang memiliki komponen kecepatan dan posisi, tanpa harus peduli tentang jenis entitas apa yang ada, atau sistem lain yang mengakses komponen ini. Ini setidaknya setara dalam kekuatan decoupling untuk memiliki objek game mengimplementasikan antarmuka tertentu.
Dua sistem yang mengakses komponen yang sama adalah fitur, bukan masalah. Ini sepenuhnya diharapkan, dan tidak memadukan sistem dengan cara apa pun. Memang benar bahwa sistem akan memiliki grafik dependensi implisit, tetapi dependensi tersebut melekat dalam dunia yang dimodelkan. Mengatakan bahwa sistem kerusakan seharusnya tidak memiliki ketergantungan implisit pada sistem powerup adalah dengan mengklaim bahwa powerup tidak mempengaruhi kerusakan, dan itu mungkin salah. Namun, ketika ketergantungan ada, sistem tidak digabungkan - Anda dapat menghapus sistem powerup dari permainan tanpa mempengaruhi sistem kerusakan, karena komunikasi terjadi melalui komponen stat dan sepenuhnya implisit.
Menyelesaikan dependensi ini dan sistem pemesanan dapat dilakukan di satu lokasi pusat, mirip dengan bagaimana resolusi dependensi dalam sistem DI bekerja. Ya, permainan yang kompleks akan memiliki grafik sistem yang kompleks, tetapi kompleksitas ini melekat, dan setidaknya itu terkandung.
sumber
Hampir tidak ada cara untuk mengatasi kenyataan bahwa suatu sistem perlu mengakses beberapa komponen. Agar sesuatu seperti VelocitySystem berfungsi, mungkin perlu akses ke VelocityComponent dan PositionComponent. Sementara itu RenderingSystem juga perlu mengakses data ini. Apa pun yang Anda lakukan, pada titik tertentu sistem rendering perlu tahu ke mana harus me-render objek dan VelocitySystem perlu tahu ke mana harus memindahkan objek.
Apa yang Anda butuhkan untuk ini adalah kesaksian ketergantungan. Setiap sistem perlu secara eksplisit tentang data apa yang akan dibaca dan data apa yang akan ditulisnya. Ketika suatu sistem ingin mengambil komponen tertentu, ia harus mampu melakukan ini secara eksplisit saja . Dalam bentuknya yang paling sederhana, ia hanya memiliki komponen untuk setiap jenis yang diperlukannya (mis. RenderSystem membutuhkan RenderComponents dan PositionComponents) sebagai argumennya dan mengembalikan apa pun yang telah diubah (mis. Hanya RenderComponents).
Anda dapat memesan dalam desain seperti itu. Tidak ada yang mengatakan bahwa untuk ECS sistem Anda harus independen dari pesanan atau hal semacam itu.
Menggunakan desain sistem-komponen Entitas dan FRP ini tidak eksklusif satu sama lain. Pada kenyataannya, sistem dapat dilihat sebagai tidak memiliki keadaan, hanya melakukan transformasi data (komponen).
FRP tidak akan menyelesaikan masalah karena harus menggunakan informasi yang Anda butuhkan untuk melakukan beberapa operasi.
sumber