Cara terbaik untuk menyusun / mengelola ratusan karakter 'dalam game'


10

Saya telah membuat game RTS sederhana, yang berisi ratusan karakter seperti Crusader Kings 2, di Unity. Untuk menyimpannya, pilihan termudah adalah menggunakan objek yang dapat skrip, tetapi itu bukan solusi yang baik karena Anda tidak dapat membuat yang baru saat runtime.

Jadi saya membuat kelas C # yang disebut "Karakter" yang berisi semua data. Semuanya berfungsi dengan baik, tetapi saat game disimulasikan, ia terus-menerus menciptakan karakter baru dan membunuh beberapa karakter (saat peristiwa dalam game terjadi). Saat gim terus-menerus disimulasikan, ia menciptakan ribuan karakter. Saya menambahkan cek sederhana untuk memastikan karakter "Alive" saat memproses fungsinya. Jadi itu membantu kinerja, tetapi saya tidak dapat menghapus "Karakter" jika dia sudah mati, karena saya memerlukan informasinya saat membuat silsilah keluarga.

Apakah daftar adalah cara terbaik untuk menyimpan data untuk game saya? Atau itu akan memberikan masalah setelah ada 10.000 karakter yang dibuat? Salah satu solusi yang mungkin adalah membuat daftar lain setelah daftar mencapai jumlah tertentu dan memindahkan semua karakter mati di dalamnya.


9
Satu hal yang saya lakukan di masa lalu ketika saya membutuhkan subset data karakter setelah kematiannya adalah membuat objek "batu nisan" untuk karakter itu. Batu nisan itu dapat membawa informasi yang perlu saya cari nanti, tetapi itu bisa lebih kecil dan lebih jarang diulang karena tidak memerlukan simulasi konstan seperti karakter yang hidup.
DMGregory


2
Apakah gim ini seperti CK2, atau hanya bagian dari memiliki banyak karakter? Saya memahaminya karena seluruh permainan seperti CK2. Dalam hal ini, banyak jawaban di sini tidak salah dan mengandung pengetahuan yang baik, tetapi mereka melewatkan inti pertanyaan. Itu tidak membantu bahwa Anda menyebut CK2 game strategi real-time padahal sebenarnya itu adalah game strategi besar . Itu mungkin tampak lucu, tetapi sangat relevan dengan masalah yang Anda hadapi.
Raphael Schmitz

1
Misalnya, ketika Anda menyebutkan "1000-an karakter", orang-orang memikirkan 1000-an model 3D atau sprite di layar pada saat bersamaan - jadi, di Unity, 1000-an GameObjects. Di CK2, jumlah karakter maksimum yang saya lihat pada saat yang sama adalah ketika saya melihat ke pengadilan dan melihat 10-15 orang di sana (saya tidak bermain terlalu jauh). Sama halnya, pasukan dengan 3000 tentara hanya satu GameObject, menampilkan angka "3000".
Raphael Schmitz

1
@ R.Schmitz Ya saya harus membuat bagian itu jelas setiap Karakter tidak memiliki lampiran gameobject kepada mereka. Kapan saja diperlukan, seperti memindahkan karakter dari satu titik ke titik lainnya. Entitas terpisah dibuat yang berisi semua informasi Karakter itu dengan logika Ai.
paul p

Jawaban:


24

Ada tiga hal yang harus Anda pertimbangkan:

  1. Apakah ini benar - benar menyebabkan masalah kinerja? 1000-an sebenarnya tidak banyak. Komputer modern sangat cepat dan dapat menangani banyak hal. Pantau berapa banyak waktu yang dibutuhkan pemrosesan karakter dan lihat apakah itu benar-benar akan menimbulkan masalah sebelum terlalu khawatir.

  2. Kesetiaan karakter yang saat ini minim aktif. Kesalahan yang sering terjadi dari Pemrogram Game pemula adalah terobsesi dengan memperbarui karakter di luar layar secara tepat dengan cara yang sama seperti di layar. Ini adalah kesalahan, tidak ada yang peduli. Alih-alih, Anda perlu mencari kesan bahwa karakter di luar layar masih berakting. Dengan mengurangi jumlah karakter pembaruan yang diterima di luar layar, Anda dapat secara dramatis mengurangi waktu pemrosesan.

  3. Pertimbangkan Data-Oriented-Design. Alih-alih memiliki 1000 objek karakter dan memanggil fungsi yang sama untuk masing-masing, memiliki larik data untuk 1000 karakter dan memiliki satu loop fungsi lebih dari 1000 karakter yang memperbarui masing-masing secara bergantian. Optimalisasi semacam ini dapat secara dramatis meningkatkan kinerja.


3
Entitas / Komponen / Sistem berfungsi dengan baik untuk ini. Bangun setiap "sistem" untuk apa yang Anda butuhkan, minta ia menyimpan ribuan atau puluhan ribu karakter (komponennya), dan berikan "ID karakter" ke sistem. Ini memungkinkan Anda untuk memisahkan model-data yang berbeda, dan lebih kecil, dan Anda juga dapat menghapus karakter mati dari sistem yang tidak membutuhkannya. (Anda juga dapat membongkar sistem sepenuhnya, jika Anda tidak menggunakannya saat ini.)
Der Kommissar

1
Dengan mengurangi jumlah karakter pembaruan yang diterima di luar layar, Anda dapat secara dramatis meningkatkan waktu pemrosesan. Apakah maksud Anda mengurangi?
Tejas Kale

1
@TejasKale: Ya, diperbaiki.
Jack Aidley

2
Seribu karakter tidak banyak, tetapi ketika masing-masing dari mereka terus-menerus memeriksa untuk melihat apakah mereka dapat mengebiri satu sama lain itu memang mulai memiliki dampak besar pada kinerja keseluruhan ...
curiousdannii

1
Selalu yang terbaik untuk benar-benar memeriksa, tetapi umumnya anggapan yang aman bahwa orang Romawi ingin saling mengebiri;)
curiousdannii

11

Dalam situasi ini, saya sarankan menggunakan Komposisi :

Prinsip bahwa kelas harus mencapai perilaku polimorfik dan penggunaan kembali kode oleh komposisi mereka (dengan memuat instance kelas lain yang mengimplementasikan fungsi yang diinginkan)


Dalam hal ini, sepertinya Characterkelas Anda telah menjadi seperti dewa , dan berisi semua detail tentang bagaimana karakter beroperasi pada semua tahap siklus hidupnya.

Misalnya, Anda perhatikan bahwa karakter mati masih diperlukan - seperti yang digunakan di pohon keluarga. Namun, kecil kemungkinan bahwa semua informasi dan fungsionalitas karakter hidup Anda, masih diperlukan hanya untuk menampilkannya di pohon keluarga. Mereka mungkin misalnya, hanya perlu nama, tanggal lahir dan ikon potret.


Solusinya, adalah untuk membagi bagian-bagian yang terpisah dari Anda Characterke dalam sub-kelas, yang Charactermemiliki instance dari. Sebagai contoh:

  • CharacterInfo dapat berupa struktur data sederhana dengan nama, tanggal lahir, tanggal kematian dan fraksi,

  • Equipmentmungkin memiliki semua item yang dimiliki karakter Anda, atau aset mereka saat ini. Mungkin juga memiliki logika yang mengatur fungsi-fungsi ini.

  • CharacterAIatau CharacterControllermungkin memiliki semua informasi yang diperlukan tentang tujuan karakter saat ini, fungsi utilitas mereka dll. Dan mungkin juga memiliki logika pembaruan aktual yang mengkoordinasikan pengambilan keputusan / interaksi antara bagian-bagian individu itu.

Setelah Anda membagi karakter, Anda tidak perlu lagi memeriksa bendera Alive / Dead di loop pembaruan.

Sebagai gantinya, Anda cukup membuat AliveCharacterObjectyang memiliki CharacterController, CharacterEquipmentdan CharacterInfoskrip terlampir. Untuk "membunuh" karakter, Anda cukup menghapus bagian-bagian yang tidak lagi relevan (seperti CharacterController) - sekarang tidak akan membuang-buang memori, atau waktu pemrosesan.

Perhatikan, bagaimana CharacterInfokemungkinan satu-satunya data yang benar-benar dibutuhkan untuk silsilah keluarga. Dengan mendekomposisi kelas Anda menjadi bagian-bagian kecil dari fungsionalitas - Anda dapat lebih mudah menyimpan objek data kecil itu setelah kematian, tanpa perlu menjaga seluruh karakter yang digerakkan AI.


Patut disebutkan bahwa paradigma ini adalah satu-satunya yang dibangun Unity untuk digunakan - dan itulah sebabnya ia menangani banyak hal dengan banyak skrip terpisah. Membangun objek dewa besar jarang merupakan cara terbaik untuk menangani data Anda di Unity.


8

Ketika Anda memiliki sejumlah besar data untuk ditangani dan tidak setiap titik data diwakili oleh objek game yang sebenarnya, maka biasanya bukan ide yang buruk untuk melepaskan kelas khusus Unity dan hanya pergi dengan objek C # biasa. Dengan begitu Anda meminimalkan overhead. Jadi Anda tampaknya berada di jalur yang benar di sini.

Menyimpan semua karakter, hidup atau mati, dalam satu Daftar ( atau array ) dapat berguna karena indeks dalam daftar itu dapat berfungsi sebagai ID karakter kanonik. Mengakses posisi daftar berdasarkan indeks adalah operasi yang sangat cepat. Tetapi mungkin berguna untuk menyimpan daftar ID yang terpisah dari semua karakter yang hidup, karena Anda mungkin perlu mengulangi yang jauh lebih sering daripada Anda membutuhkan karakter yang mati.

Saat implementasi mekanika game Anda membuat kemajuan, Anda mungkin juga ingin melihat jenis pencarian apa yang paling banyak Anda lakukan. Seperti "semua karakter yang hidup di lokasi tertentu" atau "semua leluhur yang hidup atau mati dari karakter tertentu". Mungkin bermanfaat untuk membuat beberapa struktur data sekunder yang dioptimalkan untuk jenis pertanyaan ini. Ingatlah bahwa masing-masing dari mereka harus tetap up-to-date. Ini membutuhkan pemrograman tambahan dan akan menjadi sumber bug tambahan. Jadi lakukan saja jika Anda mengharapkan peningkatan kinerja yang penting.

CKII " memangkas " karakter dari basis datanya ketika dianggap tidak penting untuk menghemat sumber daya. Jika tumpukan karakter Anda yang mati menghabiskan terlalu banyak sumber daya dalam game yang sudah berjalan lama, maka Anda mungkin ingin melakukan hal serupa (saya tidak ingin menyebut ini "pengumpulan sampah". Mungkin "incremator terhormat"?).

Jika Anda benar-benar memiliki objek game untuk setiap karakter dalam game, maka sistem Unity ECS dan Jobs yang baru mungkin berguna bagi Anda. Ini dioptimalkan untuk menangani sejumlah besar objek game yang sangat mirip dengan cara yang performan. Tapi itu memaksa arsitektur perangkat lunak Anda menjadi beberapa pola yang sangat kaku.

Ngomong-ngomong, saya sangat suka CKII dan cara itu mensimulasikan dunia dengan ribuan karakter yang dikontrol AI yang unik, jadi saya berharap untuk bermain dalam genre Anda.


Halo, Terima kasih atas jawabannya. Semua perhitungan dibuat oleh Satu GameObject Manager tunggal. Saya hanya menugaskan objek permainan ke Aktor Individu bila perlu (seperti menunjukkan Tentara Karakter bergerak dari satu posisi ke posisi lain).
paul p

1
Like "all living characters in a specific location" or "all living or dead ancestors of a specific character". It might be beneficial to create some more secondary data-structures optimized for these kinds of queries.Dari pengalaman saya dengan modding CK2, ini dekat dengan bagaimana CK2 menangani data. CK2 tampaknya menggunakan indeks yang pada dasarnya adalah indeks basis data, yang membuatnya lebih cepat untuk menemukan karakter untuk situasi tertentu. Alih-alih memiliki daftar karakter, tampaknya memiliki database karakter internal, dengan semua kelemahan dan manfaat yang menyertainya.
Morfildur

1

Anda tidak perlu mensimulasikan / memperbarui ribuan karakter ketika hanya beberapa yang berada di dekat pemain. Anda hanya perlu memperbarui apa yang sebenarnya bisa dilihat oleh pemain pada saat ini, sehingga karakter yang jauh dari pemain harus ditangguhkan sampai pemain lebih dekat dengan mereka.

Jika ini tidak berhasil karena mekanik permainan Anda membutuhkan karakter yang jauh untuk menunjukkan berlalunya waktu, Anda dapat memperbaruinya dalam satu pembaruan "besar" ketika pemain semakin dekat. Jika mekanik gim Anda mengharuskan setiap karakter untuk benar-benar merespons peristiwa dalam gim saat hal itu terjadi, di mana pun karakter tersebut terkait dengan pemain atau acara, maka itu mungkin berhasil mengurangi frekuensi di mana karakter lebih jauh dari pemain berada. diperbarui (yaitu mereka masih diperbarui secara sinkron dengan sisa permainan, tetapi tidak sering, sehingga akan ada sedikit keterlambatan sebelum karakter yang jauh merespons suatu peristiwa tetapi ini tidak mungkin menyebabkan masalah atau bahkan diperhatikan oleh pemain) ). Atau Anda mungkin ingin menggunakan pendekatan hybrid,


ini adalah RTS. Kita harus mengasumsikan bahwa pada waktu tertentu, sejumlah besar unit sebenarnya ada di layar.
Tom

Dalam RTS, dunia perlu melanjutkan sementara pemain tidak melihat. Pembaruan besar akan memakan waktu sama tetapi akan menjadi ledakan besar ketika Anda memindahkan kamera.
PStag

1

Klarifikasi pertanyaan

Saya telah membuat game RTS sederhana, yang berisi ratusan karakter seperti Crusader Kings 2 in a Unity.

Dalam jawaban ini, saya mengasumsikan bahwa seluruh permainan seharusnya seperti CK2, bukan hanya bagian tentang memiliki banyak karakter. Semua yang Anda lihat di layar dalam CK2 mudah dilakukan dan tidak akan membahayakan kinerja Anda atau menjadi rumit untuk diterapkan di Unity. Data di baliknya, di situlah menjadi kompleks.

CharacterKelas tanpa fungsi

Jadi saya membuat kelas C # yang disebut "Karakter" yang berisi semua data.

Baik, karena karakter dalam permainan Anda adalah hanya data. Apa yang Anda lihat di layar hanyalah representasi dari data itu. CharacterKelas - kelas ini adalah jantung dari permainan dan karena itu dalam bahaya menjadi " objek dewa ". Jadi saya akan menyarankan langkah-langkah ekstrem terhadap itu: Hapus semua fungsi dari kelas-kelas itu. Metode GetFullName()yang menggabungkan nama depan dan belakang, OK, tetapi tidak ada kode yang benar-benar "melakukan sesuatu". Masukan yang kode ke dalam kelas khusus yang melakukan satu tindakan; misalnya kelas Birtherdengan metode Character CreateCharacter(Character father, Character mother)akan menjadi jauh lebih bersih daripada memiliki fungsi itu di Characterkelas.

Jangan menyimpan data dalam kode

Untuk menyimpannya, opsi termudah adalah dengan menggunakan objek skrip

Tidak. Simpan dalam format JSON, menggunakan Unity's JsonUtility. Dengan Characterkelas tanpa fungsi itu, hal itu seharusnya sepele untuk dilakukan. Itu akan bekerja untuk pengaturan awal gim serta menyimpannya di savegames. Namun, itu masih hal yang membosankan untuk dilakukan, jadi saya hanya memberikan opsi termudah dalam situasi Anda. Anda juga bisa menggunakan XML atau YAML atau format apa pun, asalkan masih bisa dibaca oleh manusia ketika disimpan dalam file teks. CK2 melakukan hal yang sama, sebenarnya sebagian besar permainan melakukannya. Ini juga merupakan pengaturan yang sangat baik untuk memungkinkan orang mengubah permainan Anda, tetapi itu adalah pemikiran untuk nanti.

Berpikir abstrak

Saya menambahkan tanda centang sederhana untuk memastikan karakter "Alive" saat memproses [...] tetapi saya tidak dapat menghapus "Karakter" jika dia mati karena saya memerlukan informasinya saat membuat silsilah keluarga.

Yang ini lebih mudah diucapkan daripada dilakukan, karena sering bertabrakan dengan cara berpikir yang wajar. Anda berpikir dengan cara "alami", tentang "karakter". Namun, dalam hal gim Anda, sepertinya ada setidaknya 2 jenis data berbeda yang "adalah karakter": Saya akan menyebutnya ActingCharacterdan FamilyTreeEntry. Karakter mati FamilyTreeEntrytidak perlu diperbarui dan mungkin membutuhkan data jauh lebih sedikit daripada yang aktif ActingCharacter.


0

Saya akan berbicara dari sedikit pengalaman, beralih dari desain OO yang kaku ke desain Entity-Component-System (ECS).

Beberapa waktu yang lalu saya sama seperti Anda , saya memiliki banyak jenis benda yang memiliki sifat serupa dan saya membangun berbagai benda dan mencoba menggunakan warisan untuk menyelesaikannya. Orang yang sangat pintar bilang tidak melakukannya, dan sebagai gantinya, gunakan Entity-Component-System.

Sekarang, ECS adalah konsep besar, dan sulit untuk melakukannya dengan benar. Ada banyak pekerjaan yang dilakukan di dalamnya, membangun entitas, komponen, dan sistem dengan benar. Namun, sebelum kita dapat melakukan itu, kita perlu mendefinisikan persyaratannya.

  1. Entitas : inilah masalahnya , pemain, hewan, NPC, apa pun . Itu adalah hal yang membutuhkan komponen yang melekat padanya.
  2. Komponen : ini adalah atribut atau properti , seperti "Nama" atau "Usia", atau "Orangtua", dalam kasus Anda.
  3. Sistem : ini adalah logika di balik komponen, atau perilaku . Biasanya, Anda membangun satu sistem per komponen, tetapi itu tidak selalu mungkin. Selain itu, terkadang sistem perlu memengaruhi sistem lain .

Jadi, di sinilah saya akan pergi dengan ini:

Pertama dan terpenting, buat IDuntuk karakter Anda. An int,, Guidterserah kamu. Ini adalah "Entitas".

Kedua, mulailah berpikir tentang berbagai perilaku yang telah Anda lakukan. Hal-hal seperti "Family Tree" - itu adalah perilaku. Alih-alih memodelkan itu sebagai atribut pada entitas, membangun sistem yang menampung semua informasi itu . Sistem kemudian dapat memutuskan apa yang harus dilakukan dengannya.

Demikian juga, kami ingin membangun sistem untuk "Apakah karakternya hidup atau mati?" Ini adalah salah satu sistem terpenting dalam desain Anda, karena memengaruhi semua yang lain. Beberapa sistem dapat menghapus karakter "mati" (seperti sistem "sprite"), sistem lain secara internal dapat mengatur ulang hal-hal untuk lebih mendukung status baru.

Anda akan membangun sistem "Sprite" atau "Drawing" atau "Rendering", misalnya. Sistem ini akan memiliki tanggung jawab untuk menentukan karakter apa yang perlu ditampilkan dengan sprite, dan bagaimana cara menampilkannya. Kemudian, ketika sebuah karakter mati, hapus mereka.

Selain itu, sistem "AI" yang dapat memberi tahu karakter apa yang harus dilakukan, ke mana harus pergi, dll. Ini harus berinteraksi dengan banyak sistem lain, dan membuat keputusan berdasarkan pada mereka. Sekali lagi, karakter mati mungkin dapat dihapus dari sistem ini, karena mereka tidak benar-benar melakukan apa-apa lagi.

Sistem "Nama" dan sistem "Pohon Keluarga" Anda mungkin harus menyimpan karakter (hidup atau mati) di memori. Sistem ini perlu mengingat kembali info itu, apa pun keadaan karakternya. (Jim masih Jim, bahkan setelah kami menguburkannya.)

Ini juga memberi Anda manfaat mengubah ketika sistem bereaksi lebih efisien: sistem memiliki timer sendiri. Beberapa sistem perlu menyala dengan cepat, beberapa tidak. Di sinilah kita mulai masuk ke apa yang membuat permainan berjalan efisien. Kita tidak perlu menghitung ulang cuaca setiap milidetik, kita mungkin bisa melakukannya setiap 5 atau lebih.

Ini juga memberi Anda pengungkitan yang lebih kreatif: Anda dapat membangun sistem "Pathfinder" yang dapat menangani perhitungan lintasan dari A-ke-B, dan dapat memperbarui seperlunya, memungkinkan sistem Gerakan untuk mengatakan "di mana saya harus pergi selanjutnya? " Kami sekarang dapat sepenuhnya memisahkan masalah ini, dan alasan tentang mereka lebih efektif. Gerakan tidak perlu menemukan jalan, hanya perlu membawa Anda ke sana.

Anda akan ingin mengekspos beberapa bagian dari sistem ke luar. Di Pathfindersistem Anda, Anda mungkin menginginkan Vector2 NextPosition(int entity). Dengan cara ini, Anda dapat menyimpan elemen-elemen itu dalam array atau daftar yang dikontrol ketat. Anda dapat menggunakan structtipe yang lebih kecil, yang dapat membantu Anda menjaga komponen dalam blok memori yang lebih kecil dan bersebelahan, yang dapat membuat pembaruan sistem lebih cepat. (Terutama jika pengaruh eksternal ke sistem minimal, sekarang hanya perlu peduli dengan keadaan internal, seperti Name.)

Tapi, dan saya tidak bisa cukup menekankan hal ini, sekarang Entityini hanyalah sebuah ID, termasuk ubin, objek, dll. Jika suatu entitas tidak termasuk dalam suatu sistem, maka sistem tidak akan melacaknya. Ini berarti kita dapat membuat objek "Pohon", menyimpannya di dalam Spritedan Movementsistem (pohon tidak akan bergerak, tetapi mereka memiliki komponen "Posisi"), dan menjauhkan mereka dari sistem lain. Kita tidak lagi memerlukan daftar khusus untuk pohon, karena memberikan pohon tidak berbeda dengan karakter, selain dari penggantungan kertas. (Yang Spritedapat dikontrol sistem, atau Paperdollsistem yang dapat dikontrol.) Sekarang kita NextPositiondapat ditulis ulang:, Vector2? NextPosition(int entity)dan dapat mengembalikan nullposisi untuk entitas yang tidak dipedulikannya. Kami juga menerapkan ini pada kami NameSystem.GetName(int entity), ia mengembalikan nullpohon dan batu.


Saya akan mengakhiri ini, tetapi idenya di sini adalah untuk memberi Anda beberapa latar belakang tentang ECS, dan bagaimana Anda benar - benar dapat memanfaatkannya untuk memberi Anda desain yang lebih baik pada gim Anda. Anda dapat meningkatkan kinerja, memisahkan elemen-elemen yang tidak terkait, dan menjaga hal-hal dalam cara yang lebih terorganisir. (Ini juga berpasangan dengan baik dengan bahasa / pengaturan fungsional, seperti F # dan LINQ, yang saya sangat merekomendasikan memeriksa F # jika Anda belum melakukannya, itu berpasangan sangat baik dengan C # ketika Anda menggunakannya bersama-sama.)


Halo, Terima kasih atas respons detailnya. Saya hanya menggunakan One GameObject Manager yang berisi referensi ke semua Karakter Dalam-Game lainnya.
paul p

Berkembang dalam Persatuan berputar di sekitar entitas yang disebut GameObjectyang tidak berbuat banyak tetapi memiliki daftar Componentkelas yang melakukan pekerjaan yang sebenarnya. Ada perubahan paradigma tentang bundaran ECS satu dekade lalu , karena menempatkan kode akting di kelas sistem yang terpisah lebih bersih. Persatuan baru-baru ini menerapkan sistem seperti itu juga, tetapi GameObjectsistem mereka selalu ECS. OP sudah menggunakan ECS.
Raphael Schmitz

-1

Ketika Anda melakukan ini di Unity, pendekatan termudah adalah ini:

  • buat satu objek game per karakter atau tipe unit
  • simpan ini sebagai cetakan
  • Anda kemudian dapat membuat cetakan di rumah kapan pun diperlukan
  • ketika sebuah karakter terbunuh, hancurkan gameobject dan itu tidak akan memakan CPU atau memori lagi

Dalam kode Anda, Anda dapat menyimpan referensi ke objek dalam sesuatu seperti Daftar agar Anda tidak menggunakan Find () dan variasinya setiap saat. Anda berdagang siklus CPU untuk memori, tetapi daftar petunjuk cukup kecil sehingga bahkan dengan beberapa ribu objek di dalamnya yang seharusnya tidak menjadi masalah.

Saat Anda maju melalui gim, Anda akan menemukan bahwa memiliki objek gim individual memberi Anda banyak keuntungan, termasuk navigasi dan AI.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.