Saya tahu bahwa lajang itu jahat, mesin gim lama saya menggunakan objek 'Gim' tunggal yang menangani semuanya, mulai dari memegang semua data hingga perulangan gim yang sebenarnya. Sekarang saya membuat yang baru.
Masalahnya adalah, untuk menggambar sesuatu di SFML yang Anda gunakan di window.draw(sprite)
mana jendela adalah sf::RenderWindow
. Ada 2 opsi yang saya lihat di sini:
- Buat objek Game tunggal yang diambil setiap entitas dalam game (apa yang saya gunakan sebelumnya)
- Jadikan ini sebagai konstruktor untuk entitas:
Entity(x, y, window, view, ...etc)
(ini hanya konyol dan menjengkelkan)
Apa cara yang tepat untuk melakukan ini sambil mempertahankan konstruktor Entity hanya x dan y?
Saya bisa mencoba dan melacak semua yang saya buat di loop game utama, dan secara manual menggambar sprite mereka di loop game, tapi itu juga tampak berantakan, dan saya juga ingin kontrol penuh mutlak atas seluruh fungsi draw untuk entitas.
c++
design-patterns
oop
Aki
sumber
sumber
Jawaban:
Hanya menyimpan data yang diperlukan untuk merender sprite di dalam setiap entitas, lalu mengambilnya dari entitas dan meneruskannya ke jendela untuk rendering. Tidak perlu menyimpan jendela apa pun atau melihat data di dalam entitas.
Anda bisa memiliki kelas Game atau Engine tingkat atas yang memegang kelas Level (menampung semua entitas yang sedang digunakan), dan kelas Renderer (berisi jendela, tampilan, dan apa pun lainnya untuk dirender).
Jadi lingkaran pembaruan game di kelas tingkat atas Anda dapat terlihat seperti:
sumber
Logger::getInstance().Log(...)
bukan hanyaLog(...)
? Mengapa menginisialisasi kelas secara acak ketika ditanya apakah Anda dapat melakukannya secara manual hanya sekali? Fungsi global yang mereferensikan global statis jauh lebih sederhana untuk dibuat dan digunakan.Pendekatan sederhana adalah dengan hanya membuat hal yang dulu
Singleton<T>
globalT
. Global juga memiliki masalah, tetapi mereka tidak mewakili banyak kerja ekstra dan kode boilerplate untuk menegakkan batasan sepele. Ini pada dasarnya satu-satunya solusi yang tidak akan melibatkan (berpotensi) menyentuh konstruktor entitas.Pendekatan yang lebih sulit, tetapi mungkin lebih baik adalah meneruskan ketergantungan Anda ke tempat Anda membutuhkannya . Ya, ini mungkin melibatkan pengalihan
Window *
ke banyak objek (seperti entitas Anda) dengan cara yang terlihat kotor. Fakta bahwa itu terlihat kotor harus memberi tahu Anda sesuatu: desain Anda mungkin kotor.Alasan ini lebih sulit (di luar melibatkan lebih banyak mengetik) adalah bahwa ini sering mengarah pada refactoring antarmuka Anda sehingga hal yang Anda "perlu" lewati diperlukan oleh lebih sedikit kelas level daun. Hal ini membuat banyak keburukan yang melekat dalam meneruskan renderer Anda ke semuanya hilang, dan itu juga meningkatkan pemeliharaan umum kode Anda dengan mengurangi jumlah dependensi dan penggandengan, sejauh mana Anda membuatnya sangat jelas dengan mengambil dependensi sebagai parameter . Ketika dependensinya adalah lajang atau global, itu kurang jelas bagaimana sistem Anda saling berhubungan.
Tapi ini berpotensi menjadi usaha besar . Melakukannya pada suatu sistem setelah fakta bisa sangat menyakitkan. Mungkin jauh lebih pragmatis bagi Anda untuk hanya meninggalkan sistem Anda sendirian, dengan singleton, untuk saat ini (terutama jika Anda mencoba untuk benar-benar mengirimkan permainan yang berfungsi dengan baik; pemain umumnya tidak akan peduli jika Anda memiliki satu atau empat di sana).
Jika Anda ingin mencoba melakukan ini dengan desain yang ada, Anda mungkin perlu memposting lebih banyak detail tentang implementasi Anda saat ini karena sebenarnya tidak ada daftar periksa umum untuk melakukan perubahan ini. Atau datang mendiskusikannya di obrolan .
Dari apa yang telah Anda posting, saya pikir langkah besar dalam arah "tanpa singleton" adalah untuk menghindari kebutuhan entitas Anda untuk memiliki akses ke jendela atau tampilan. Ini menunjukkan bahwa mereka menggambar diri mereka sendiri, dan Anda tidak perlu memiliki entitas menggambar diri mereka sendiri . Anda dapat mengadopsi metodologi di mana entitas hanya berisi informasi yang memungkinkanmereka harus ditarik oleh beberapa sistem eksternal (yang memiliki jendela dan melihat referensi). Entitas hanya memperlihatkan posisinya, dan sprite itu harus digunakan (atau semacam referensi untuk sprite tersebut, jika Anda ingin menembolokkan sprite aktual dalam renderer itu sendiri untuk menghindari duplikat instance). Penyaji hanya diminta untuk menggambar daftar entitas tertentu, yang dilaluinya, membaca data dari, dan menggunakan objek jendela yang dipegang secara internal untuk memanggil
draw
dengan sprite mencari entitas tersebut.sumber
Mewarisi dari sf :: RenderWindow
SFML sebenarnya mendorong Anda untuk mewarisi dari kelasnya.
Dari sini, Anda membuat fungsi menggambar anggota untuk menggambar entitas.
Sekarang Anda bisa melakukan ini:
Anda bahkan dapat mengambil ini selangkah lebih jauh jika Entitas Anda akan menyimpan sprite unik mereka sendiri dengan membuat Entity mewarisi dari sf :: Sprite.
Sekarang
sf::RenderWindow
bisa menggambar Entitas, dan entitas sekarang memiliki fungsi sepertisetTexture()
dansetColor()
. Entity bahkan dapat menggunakan posisi sprite sebagai posisinya sendiri, memungkinkan Anda untuk menggunakansetPosition()
fungsi untuk memindahkan Entity DAN sprite-nya.Pada akhirnya , cukup menyenangkan jika Anda hanya memiliki:
Di bawah ini adalah beberapa implementasi contoh cepat
ATAU
sumber
Anda menghindari lajang dalam pengembangan game dengan cara yang sama Anda menghindarinya dalam setiap jenis pengembangan perangkat lunak lain: Anda melewati dependensi .
Dengan itu keluar dari jalan, Anda dapat memilih untuk lulus dependensi langsung sebagai jenis telanjang (seperti
int
,Window*
, dll) atau Anda dapat memilih untuk melewati mereka dalam satu atau lebih kustom pembungkus jenis (sepertiEntityInitializationOptions
).Cara pertama dapat mengganggu (seperti yang telah Anda ketahui), sedangkan yang terakhir akan memungkinkan Anda untuk melewatkan semuanya dalam satu objek dan memodifikasi bidang (dan bahkan mengkhususkan jenis opsi) tanpa berkeliling dan mengubah setiap konstruktor entitas. Saya pikir cara yang terakhir lebih baik.
sumber
Lajang tidak buruk. Sebaliknya mereka mudah disalahgunakan. Di sisi lain, global bahkan lebih mudah disalahgunakan dan memiliki banyak masalah.
Satu-satunya alasan yang sah untuk mengganti singleton dengan global adalah untuk menenangkan pembenci singleton religius.
Masalahnya adalah memiliki desain yang mencakup kelas-kelas yang hanya ada satu contoh global yang pernah ada, dan yang perlu diakses dari mana-mana. Ini pecah segera setelah Anda akhirnya memiliki beberapa contoh dari singleton, misalnya dalam permainan ketika Anda menerapkan layar split, atau dalam aplikasi perusahaan yang cukup besar ketika Anda melihat satu logger tidak selalu merupakan ide yang hebat setelah semua .
Intinya, jika Anda benar-benar memiliki kelas di mana Anda memiliki instance global tunggal yang tidak dapat Anda bagikan dengan referensi , singleton sering kali merupakan salah satu solusi yang lebih baik dalam kumpulan solusi suboptimal.
sumber
Menyuntikkan dependensi. Manfaat melakukan ini sekarang adalah Anda dapat membuat berbagai jenis dependensi ini melalui pabrik. Sayangnya, mencabut lajang dari kelas yang menggunakannya seperti menarik kucing dengan kaki belakangnya melintasi karpet. Tetapi jika Anda menyuntikkannya, Anda dapat menukar implementasi, mungkin dengan cepat.
Sekarang Anda bisa menyuntikkan berbagai jenis windows. Ini memungkinkan Anda untuk menulis tes terhadap RenderSystem dengan berbagai jenis windows sehingga Anda dapat melihat bagaimana RenderSystem Anda akan pecah, atau berkinerja. Ini tidak mungkin, atau lebih sulit, jika Anda menggunakan lajang langsung di dalam "RenderSystem".
Sekarang lebih dapat diuji, modular, dan juga dipisahkan dari implementasi tertentu. Itu hanya tergantung pada antarmuka, bukan implementasi konkret.
sumber