Implementasikan data pemain dan sistem pembelian

*Konten ini diterjemahkan menggunakan AI (Beta) dan mungkin mengandung kesalahan. Untuk melihat halaman ini dalam bahasa Inggris, klik di sini.

Latar Belakang

Roblox menyediakan serangkaian API untuk berinteraksi dengan penyimpanan data melalui DataStoreService .Kasus penggunaan paling umum untuk API ini adalah untuk menyimpan, memuat, dan mereplikasi data pemain.Artinya, data yang terkait dengan kemajuan pemain, pembelian, dan karakteristik sesi lain yang bertahan di antara sesi bermain individu.

Sebagian besar pengalaman di Roblox menggunakan API ini untuk menerapkan beberapa bentuk sistem data pemain.Implementasi ini berbeda dalam pendekatannya, tetapi umumnya berusaha untuk memecahkan masalah yang sama.

Masalah umum

Di bawah ini adalah beberapa masalah paling umum yang sistem data pemain coba untuk memecahkan:

  • Dalam Akses Memori: DataStoreService permintaan membuat permintaan web yang beroperasi secara asinkron dan berada di bawah batas tingkat.Ini sesuai untuk beban awal pada awal sesi, tetapi tidak untuk operasi baca dan tulis frekuensi tinggi selama kursus normal gameplay.Sebagian besar sistem data pemain pengembang menyimpan data ini dalam memori di server Roblox, membatasi DataStoreService permintaan ke skenario berikut:

    • Membaca awal pada awal sesi
    • Tulisan terakhir pada akhir sesi
    • Menulis secara berkala pada interval untuk mengurangi skenario di mana penulisan terakhir gagal
    • Menulis untuk memastikan data disimpan saat memproses pembelian
  • Penyimpanan Efisien: Menyimpan semua data sesi pemain di satu tabel memungkinkan Anda untuk memperbarui banyak nilai secara atomik dan menangani jumlah data yang sama dalam lebih sedikit permintaan.Ini juga menghapus risiko desinkronisasi nilai antara dan membuat rollback lebih mudah untuk dipertimbangkan.

    Beberapa pengembang juga menerapkan serialisasi khusus untuk mengompresi struktur data besar (biasanya untuk menyimpan konten yang dihasilkan oleh pengguna dalam game).

  • Replikasi: Klien membutuhkan akses reguler ke data pemain (misalnya, untuk memperbarui UI).Pendekatan umum untuk mereplikasi data pemain ke klien memungkinkan Anda mengirimkan informasi ini tanpa harus membuat sistem replikasi khusus untuk setiap komponen data.Pengembang sering ingin opsi untuk menjadi selektif tentang apa yang dan tidak direplikasikan ke klien.

  • Penanganan Kesalahan: Ketika DataStores tidak dapat diakses, kebanyakan solusi akan menerapkan mekanisme pencobaan ulang dan cadangan ke data 'default'.Perawatan khusus diperlukan untuk memastikan data cadangan tidak kemudian menulis ulang data 'nyata', dan bahwa ini dikomunikasikan kepada pemain dengan tepat.

  • Pencobaan kembali: Ketika penyimpanan data tidak dapat diakses, kebanyakan solusi menerapkan mekanisme pencobaan ulang dan cadangan untuk data default.Berhati-hati khusus untuk memastikan bahwa data cadangan tidak kemudian menghapus data "nyata", dan komunikasikan situasi kepada pemain dengan tepat.

  • Pengunci Sesi: Jika data pemain tunggal dimuat dan dalam memori di beberapa server, masalah dapat terjadi di mana salah satu server menyimpan informasi kadaluarsa.Ini dapat menyebabkan kerugian data dan celah duplikasi item umum.

  • Penanganan Pembelian Atomik: Verifikasikan, berikan penghargaan, dan catat pembelian atomik untuk mencegah item hilang atau diberikan beberapa kali.

kodesampel

Roblox memiliki kode referensi untuk membantu Anda merancang dan membangun sistem data pemain.Sisa halaman ini memeriksa latar belakang, rincian implementasi, dan peringatan umum.


Setelah Anda menge导入 model ke Studio, Anda harus melihat struktur folder berikut:

Explorer window showing the purchasing system model.

Arsitektur

Diagram tingkat tinggi ini menggambarkan sistem kunci dalam sampel dan bagaimana mereka berinteraksi dengan kode di sisa pengalaman.

An architecture diagram for the code sample.

Pencobaan ulang

Kelas: DataStoreWrapper

Latar Belakang

Sebagai DataStoreService membuat permintaan web di bawah kap mesin, permintaannya tidak dijamin berhasil.Ketika ini terjadi, metode DataStore membuang kesalahan, memungkinkan Anda untuk menanganinya.

Sebuah "gotcha" umum dapat terjadi jika Anda mencoba menangani kegagalan penyimpanan data seperti ini:


local function retrySetAsync(dataStore, key, value)
for _ = 1, MAX_ATTEMPTS do
local success, result = pcall(dataStore.SetAsync, dataStore, key, value)
if success then
break
end
task.wait(TIME_BETWEEN_ATTEMPTS)
end
end

Meskipun ini adalah mekanisme pencobaan ulang yang benar-benar valid untuk fungsi umum, itu tidak cocok untuk permintaan DataStoreService karena tidak menjamin urutan di mana permintaan dibuat.Mempertahankan urutan permintaan penting untuk permintaan DataStoreService karena mereka berinteraksi dengan negara.Pertimbangkan skenario berikut:

  1. Permintaan A dibuat untuk mengatur nilai kunci K menjadi 1.
  2. Permintaan gagal, jadi ulangi dijadwalkan untuk dijalankan dalam 2 detik.
  3. Sebelum percobaan ulang terjadi, minta B menetapkan nilai K ke 2, tetapi percobaan ulang permintaan A segera menggantikan nilai ini dan menetapkan K ke 1.

Meskipun UpdateAsync beroperasi pada versi terbaru dari nilai kunci, UpdateAsync permintaan masih harus diproses untuk menghindari keadaan transisi yang tidak valid (misalnya, pembelian mengurangi koin sebelum penambahan koin diproses, yang menyebabkan koin negatif).

Sistem data pemain kami menggunakan kelas baru, DataStoreWrapper, yang menyediakan upaya ulang yang dijamin diproses sesuai dengan unit.

Pendekatan

An process diagram illustrating the retry system

DataStoreWrapper memberikan metode yang sesuai dengan metode DataStore : DataStore:GetAsync() , DataStore:SetAsync() , DataStore:UpdateAsync() dan DataStore:RemoveAsync() .

Metode ini, saat dipanggil:

  1. Tambahkan permintaan ke antrian.Setiap kunci memiliki antrian sendiri, di mana permintaan diproses secara berurutan dan berurutan.Thread permintaan menghasilkan sampai permintaan selesai.

    Fungsi ini didasarkan pada kelas ThreadQueue , yang merupakan penjadwal tugas berbasis korutin dan pembatas tingkat.Daripada mengembalikan janji, ThreadQueue menghasilkan thread saat ini sampai operasi selesai dan membuang kesalahan jika gagal.Ini lebih konsisten dengan pola Luau asinkronik idiomatik.

  2. Jika permintaan gagal, ia mencoba kembali dengan penundaan eksponensial yang dapat disetel ulang.Percobaan ulang ini merupakan bagian dari panggilan balas yang diajukan ke ThreadQueue , jadi mereka dijamin selesai sebelum permintaan berikutnya dalam antrian untuk kunci ini dimulai.

  3. Ketika permintaan selesai, metode permintaan kembali dengan pola success, result

DataStoreWrapper juga mengekspos metode untuk mendapatkan panjang antrian untuk kunci tertentu dan membersihkan permintaan lama.Opsi terakhir sangat berguna dalam skenario ketika server dimatikan dan tidak ada waktu untuk memproses permintaan apa pun tetapi yang paling baru.

Peringatan

DataStoreWrapper mengikuti prinsip bahwa, di luar skenario ekstrim, setiap permintaan penyimpanan data harus diizinkan untuk diselesaikan (berhasil atau lainnya), bahkan jika permintaan yang lebih baru membuatnya redundan.Ketika permintaan baru terjadi, permintaan lama tidak dihapus dari antrian, tetapi diizinkan untuk diselesaikan sebelum permintaan baru dimulai.Alasan untuk ini berasal dari kemampuan modul ini sebagai utilitas penyimpanan data umum daripada alat khusus untuk data pemain, dan sebagai berikut:

  1. Sulit untuk memutuskan atas sekumpulan aturan intuitif ketika permintaan aman untuk dihapus dari antrian. Pertimbangkan antrian berikut:

    Value=0, SetAsync(1), GetAsync(), SetAsync(2)

    Perilaku yang diharapkan adalah bahwa GetAsync() akan kembali 1 , tetapi jika kami menghapus permintaan SetAsync() dari antrian karena dibuat redundan oleh yang terbaru, itu akan kembali 0 .

    Perkembangan logisnya adalah bahwa ketika permintaan tulisan baru ditambahkan, hanya memotong permintaan lama sejauh mungkin seperti permintaan baca terbaru. UpdateAsync() , dengan jauh yang paling umum operasi (dan satu-satunya yang digunakan oleh sistem ini), dapat membaca dan menulis, sehingga akan sulit untuk diselesaikan dalam desain ini tanpa menambahkan kompleksitas ekstra.

    DataStoreWrapper mungkin memerlukan anda untuk menentukan apakah permintaan UpdateAsync() diizinkan untuk dibaca dan/atau ditulis, tetapi tidak akan ada kaitannya dengan sistem data pemutar kami, di mana ini tidak dapat ditentukan sebelum waktu karena mekanisme kunci sesi (dibahas secara lebih terperinci nanti).

  2. Setelah dihapus dari antrian, sulit untuk memutuskan aturan intuitif untuk bagaimana ini harus ditangani.Ketika permintaan DataStoreWrapper dibuat, thread saat ini diberikan sampai selesai.Jika kami menghapus permintaan lama dari antrian, kami harus memutuskan apakah akan mengembalikan false, "Removed from queue" atau tidak pernah mengembalikan dan membuang thread aktif.Kedua pendekatan datang dengan kekurangan sendiri dan mengalihkan kompleksitas tambahan ke konsumen.

Pada akhirnya, pandangan kami adalah bahwa pendekatan sederhana (memproses setiap permintaan) lebih diinginkan di sini dan menciptakan lingkungan yang lebih jelas untuk dijelajahi saat mendekati masalah kompleks seperti pengunci sesi.Satu-satunya pengecualian untuk ini adalah selama DataModel:BindToClose() , di mana membersihkan antrian menjadi perlu untuk menyimpan semua data pengguna dalam waktu dan nilai fungsi individ yang dipanggil tidak lagi menjadi perhatian terus menerus.Untuk mengatasi ini, kami mengekspos metode skipAllQueuesToLastEnqueued .Untuk lebih banyak konteks, lihat Data Pemain.

Pengunci sesi

Kelas: SessionLockedDataStoreWrapper

Latar Belakang

Data pemain disimpan di memori di server dan hanya dibaca dan ditulis ke penyimpanan data dasar saat diperlukan.Anda dapat membaca dan memperbarui data pemutar dalam memori secara instan tanpa memerlukan permintaan web dan menghindari melebihi batas DataStoreService .

Untuk model ini berfungsi seperti yang dimaksudkan, penting agar tidak lebih dari satu server dapat memuat data pemain ke memori dari DataStore pada saat yang sama.

Sebagai contoh, jika server A memuat data pemain, server B tidak dapat memuat data itu sampai server A melepaskan kunci di atasnya selama penyimpanan terakhir.Tanpa mekanisme pengunci, server B dapat memuat data pemain kadaluarsa dari penyimpanan data sebelum server A memiliki kesempatan untuk menyimpan versi yang lebih baru yang dimilikinya di memori.Kemudian jika server A menyimpan data baru setelah server B memuat data kadaluarsa, server B akan menulis ulang data baru itu selama penyimpanan berikutnya.

Meskipun Roblox hanya mengizinkan klien terhubung ke satu server sekaligus, Anda tidak dapat menganggap bahwa data dari satu sesi selalu disimpan sebelum sesi berikutnya dimulai.Pertimbangkan skenario berikut yang dapat terjadi ketika seorang pemain meninggalkan server A:

  1. Server A membuat permintaan DataStore untuk menyimpan data mereka, tetapi permintaan gagal dan memerlukan beberapa upaya untuk diselesaikan dengan sukses.Selama periode pencobaan ulang, pemain bergabung dengan server B.
  2. Server A membuat terlalu banyak UpdateAsync() panggilan ke kunci yang sama dan dibatasi.Permintaan penyimpanan terakhir ditempatkan di antrian.Sementara permintaan ada di antrian, pemain bergabung dengan server B.
  3. Di server A, beberapa kode yang terhubung ke acara PlayerRemoving terhubung sebelum data pemain disimpan.Sebelum operasi ini selesai, pemain bergabung dengan server B.
  4. Kinerja server A telah turun ke titik di mana penyimpanan akhir ditunda sampai pemain bergabung dengan server B.

Skenario ini seharusnya langka, tetapi mereka terjadi, terutama dalam situasi di mana pemain terputus dari satu server dan terhubung ke yang lain dalam serangkaian cepat (misalnya, saat teleportasi).Beberapa pengguna jahat bahkan mungkin berusaha menyalahgunakan perilaku ini untuk menyelesaikan tindakan tanpa mereka bertahan.Ini bisa sangat berdampak dalam game yang memungkinkan pemain untuk berdagang dan merupakan sumber umum untuk serangan duplikasi item.

Pengunci sesi mengatasi kelemahan ini dengan memastikan bahwa ketika kunci pemain DataStore dibaca pertama oleh server, server secara atomik menulis kunci ke metadata dalam panggilan yang sama UpdateAsync() .Jika nilai kunci kunci ini hadir saat server lain mencoba membaca atau menulis unit, server tidak melanjutkan.

Pendekatan

An process diagram illustrating the session locking system

SessionLockedDataStoreWrapper adalah meta-wrapper di sekitar kelas DataStoreWrapper. DataStoreWrapper memberikan fungsi antrian dan pencobaan ulang, yang SessionLockedDataStoreWrapper diimbangi dengan pemblokiran sesi.

SessionLockedDataStoreWrapper melewati setiap permintaan DataStore —terlepas dari apakah itu GetAsync , SetAsync atau UpdateAsync —melalui UpdateAsync .Ini karena UpdateAsync memungkinkan kunci untuk dibaca dan ditulis ke atomik.Juga dimungkinkan untuk meninggalkan penulisan berdasarkan nilai yang dibaca dengan mengembalikan nil dalam panggil balik transformasi.

Fungsi transformasi dilewati ke UpdateAsync untuk setiap permintaan melakukan operasi berikut:

  1. Memeriksa kunci aman untuk diakses, meninggalkan operasi jika tidak. "Aman untuk diakses" berarti:

    • Objek metadata kunci tidak mencakup nilai tidak diakui LockId yang terakhir diperbarui kurang dari waktu kedaluwarsa kunci sebelumnya.Ini menjelaskan menghormati kunci yang ditempatkan oleh server lain dan mengabaikan kunci itu jika kedaluwarsa.

    • Jika server ini telah menempatkan nilai sendiri LockId di metadata unitsebelumnya, maka nilai ini masih ada di metadata unit.Ini menjelaskan situasi di mana server lain telah mengambil alih kunci server ini (oleh kedaluwarsa atau dengan kekuatan) dan kemudian dirilis.Alternatifnya dikatakan, bahkan jika LockId adalah nil , server lain masih bisa menggantikan dan menghapus kunci pada waktu sejak Anda mengunci unit.

  2. UpdateAsync melakukan operasi DataStore yang diminta konsumen dari SessionLockedDataStoreWrapper . Misalnya, GetAsync() diterjemahkan ke function(value) return value end .

  3. Tergantung pada parameter yang ditransmisikan ke dalam permintaan, UpdateAsync mengunci atau membuka kunci unit:

    1. Jika kunci harus dikunci, UpdateAsync menetapkan LockId dalam metadata unitke GUID.UID ini disimpan dalam memori di server sehingga dapat diperiksa saat berikutnya mengakses unit.Jika server sudah memiliki kunci pada unitini, ia tidak melakukan perubahan.Ini juga menjadwalkan tugas untuk memperingatkan Anda jika Anda tidak mengakses kunci lagi untuk mempertahankan kunci dalam waktu kedaluwarsa kunci.

    2. Jika kunci harus dibuka, UpdateAsync menghapus LockId di metadata unit.

Pengelola ulang khusus diberikan ke dalam dasar DataStoreWrapper sehingga operasi diulang jika dibatalkan pada langkah 1 karena sesi diblokir.

Pesan kesalahan khusus juga dikembalikan ke konsumen, memungkinkan sistem data pemain melaporkan kesalahan alternatif dalam kasus pemblokiran sesi ke klien.

Peringatan

Regime pengunci sesi bergantung pada server selalu melepaskan kunci pada kunci saat selesai dengannya.Ini harus selalu terjadi melalui instruksi untuk membuka kunci kunci sebagai bagian dari penulisan akhir di PlayerRemoving atau BindToClose() .

Namun, pembukaan kunci bisa gagal dalam situasi tertentu. Misalnya:

  • Server mogok atau DataStoreService tidak dapat digunakan untuk semua upaya untuk mengakses unit.
  • Karena kesalahan dalam logika atau bug serupa, instruksi untuk membuka kunci kunci tidak dibuat.

Untuk mempertahankan kunci pada unit, Anda harus secara teratur mengaksesnya selama masih dimuat di memori.Ini biasanya dilakukan sebagai bagian dari loop penyimpanan otomatis yang berjalan di latar belakang di sebagian besar sistem data pemain, tetapi sistem ini juga mengekspos metode refreshLockAsync jika Anda perlu melakukannya secara manual.

Jika waktu kedaluwarsa kunci telah melebihi batas waktu, maka server mana pun bebas untuk mengambil alih kunci.Jika server yang berbeda mengambil kunci, upaya oleh server saat ini untuk membaca atau menulis kunci gagal kecuali jika menetapkan kunci baru.

Pemrosesan Produk Pengembang

Tunggal: ReceiptHandler

Latar Belakang

Panggilan balik ProcessReceipt melakukan tugas kritis untuk menentukan kapan harus menyelesaikan pembelian.ProcessReceipt dipanggil dalam skenario yang sangat spesifik.Untuk garansi beruntunnya, lihat MarketplaceService.ProcessReceipt .

Meskipun definisi "penanganan" pembelian dapat berbeda antara pengalaman, kami menggunakan kriteria berikut

  1. Pembelian belum ditangani sebelumnya.

  2. Pembelian dicerminkan dalam sesi saat ini.

  3. Pembelian telah disimpan ke DataStore .

    Setiap pembelian, bahkan konsumsi satu kali, harus dicerminkan dalam DataStore sehingga riwayat pembelian pengguna termasuk dengan data sesi mereka.

Ini memerlukan melakukan operasi berikut sebelum kembali PurchaseGranted :

  1. Verifikasi PurchaseId belum dicatat sebagai ditangani.
  2. Hadiah pembelian dalam data pemain dalam memori pemain.
  3. Catat PurchaseId sebagai ditangani dalam data pemutar dalam memori pemain.
  4. Tulis data pemain dalam memori pemain pemain ke DataStore.

Pengunci sesi mempermudah aliran ini, karena Anda tidak perlu lagi khawatir tentang skenario berikut:

  • Data pemutar dalam memori di server saat ini mungkin sudah kadaluarsa, memerlukan Anda untuk mengambil nilai terbaru dari DataStore sebelum memverifikasi riwayat PurchaseId
  • Panggil balasan untuk pembelian yang sama yang berjalan di server lain, memerlukan Anda untuk membaca dan menulis sejarah PurchaseId dan menyimpan data pemain yang diperbarui dengan pembelian yang dicerminkan secara atomik untuk mencegah kondisi balapan

Pengunci sesi menjamin bahwa, jika upaya untuk menulis ke DataStore pemain berhasil, tidak ada server lain yang berhasil membaca atau menulis ke DataStore pemain antara data yang dimuat dan disimpan di server ini.Singkatnya, data pemutar dalam memori di server ini adalah versi terbaru yang tersedia.Ada beberapa peringatan, tetapi mereka tidak mempengaruhi perilaku ini.

Pendekatan

Komentar di ReceiptProcessor menggambarkan pendekatan:

  1. Verifikasikan bahwa data pemain saat ini dimuat di server ini dan bahwa itu dimuat tanpa kesalahan.

    Karena sistem ini menggunakan pengunci sesi, pemeriksaan ini juga memverifikasi bahwa data dalam memori adalah versi terbaru.

    Jika data pemain belum dimuat (yang diharapkan ketika pemain bergabung dengan game), tunggu data pemain load.Sistem juga mendengarkan pemain meninggalkan permainan sebelum data mereka dimuat, karena seharusnya tidak menghasilkan secara permanen dan memblokir panggilan ulang panggilan balik ini di server ini untuk pembelian ini jika pemain bergabung kembali.

  2. Verifikasikan bahwa PurchaseId tidak sudah dicatat sebagai diproses dalam data pemain.

    Karena penguncian sesi, array dari PurchaseIds sistem yang ada di memori adalah versi terbaru.Jika PurchaseId tercatat sebagai diproses dan dicerminkan dalam nilai yang telah dimuat ke atau disimpan ke DataStore , kembalikan PurchaseGranted .Jika dicatat sebagai diproses, tetapi tidak dicerminkan dalam DataStore , kembalikan NotProcessedYet .

  3. Perbarui Data Pemain secara lokal di server ini untuk "menghadiahkan" pembelian.

    ReceiptProcessor mengambil pendekatan panggilan umum dan menugaskan panggilan balasan yang berbeda untuk setiap DeveloperProductId .

  4. Perbarui data pemain secara lokal di server ini untuk menyimpan PurchaseId .

  5. Kirimkan permintaan untuk menyimpan data dalam memori ke DataStore , mengembalikan PurchaseGranted jika permintaan berhasil. Jika tidak, return NotProcessedYet .

    Jika permintaan penyimpanan ini tidak berhasil, permintaan penyimpanan kemudian untuk menyimpan data sesi memori pemain masih bisa berhasil.Selama panggilan berikutnya ProcessReceipt , langkah 2 menangani situasi ini dan kembalikan PurchaseGranted .

Data pemain

Tunggal: PlayerData.Server , PlayerData.Client

Latar Belakang

Modul yang memberikan antarmuka untuk kode permainan untuk membaca dan menulis data sesi pemain secara sinkron adalah umum dalam pengalaman Roblox.Bagian ini mencakup PlayerData.Server dan PlayerData.Client.

Pendekatan

PlayerData.Server dan PlayerData.Client tangani hal mengikuti:

  1. Memuat data pemain ke dalam memori, termasuk menangani kasus di mana gagal load
  2. Memberikan antarmuka untuk kode server untuk menanyakan dan mengubah data pemain
  3. Mengulangi perubahan dalam data pemain ke klien sehingga kode klien dapat mengaksesnya
  4. Mengulangi kesalahan pemuatan dan/atau penyimpanan ke klien sehingga dapat menampilkan dialog kesalahan
  5. Menyimpan data pemain secara berkala, saat pemain pergi, dan saat server dimatikan

Memuat data pemain

An process diagram illustrating the loading system
  1. SessionLockedDataStoreWrapper membuat permintaan getAsync ke penyimpan data.

    Jika permintaan ini gagal, data default digunakan dan profil ditandai sebagai "salah" untuk memastikan tidak ditulis ke penyimpanan data nanti.

    Opsi alternatif adalah menendang pemain, tetapi kami sarankan membiarkan pemain bermain dengan data default dan pesan yang jelas tentang apa yang terjadi daripada menghapusnya dari pengalaman.

  2. Beban awal dikirim ke PlayerDataClient berisi data yang dimuat dan status kesalahan (jika ada).

  3. Setiap thread yang dihasilkan menggunakan waitForDataLoadAsync untuk pemain dilanjutkan.

Berikan antarmuka untuk kode server

  • PlayerDataServer adalah tunggal yang dapat diperlukan dan diakses oleh kode server mana pun yang berjalan di lingkungan yang sama.
  • Data pemain diatur menjadi kamus kunci dan nilai.Anda dapat memanipulasi nilai-nilai ini di server menggunakan metode setValue, getValue, updateValue dan removeValue.Semua metode ini beroperasi secara sinkron tanpa menyerah.
  • Metode hasLoaded dan waitForDataLoadAsync tersedia untuk memastikan data telah dimuat sebelum Anda mengaksesnya.Kami sarankan melakukan ini sekali selama layar pemuatan sebelum sistem lain dimulai untuk menghindari harus memeriksa kesalahan beban sebelum setiap interaksi dengan data di klien.
  • Sebuah metode hasErrored dapat menanyakan apakah beban awal pemain gagal, menyebabkan mereka menggunakan data default.Periksa metode ini sebelum mengizinkan pemain melakukan pembelian, karena pembelian tidak dapat disimpan ke data tanpa loaddengan sukses.
  • Sinyal A playerDataUpdated terbakar dengan player , key , dan value setiap kali data pemain diubah.Sistem individu dapat berlangganan ini.

Replikasikan perubahan ke klien

  • Setiap perubahan pada data pemain di PlayerDataServer diulang ke PlayerDataClient kecuali kunci itu ditandai sebagai pribadi menggunakan setValueAsPrivate
    • setValueAsPrivate digunakan untuk menunjukkan kunci yang tidak boleh dikirim ke klien
  • PlayerDataClient termasuk metode untuk mendapatkan nilai kunci (mendapatkan) dan sinyal yang menyala saat diperbarui (diperbarui).Metode A hasLoaded dan sinyal A loaded juga disertakan, sehingga klien dapat menunggu data dimuat & direplikasi sebelum memulai sistemnya
  • PlayerDataClient adalah tunggal yang dapat diperlukan dan diakses oleh kode klien mana pun yang berjalan di lingkungan yang sama

Replikasikan kesalahan ke klien

  • Status kesalahan yang dijumpai saat menyimpan atau memuat data pemain diulang ke PlayerDataClient .
  • Akses informasi ini dengan metode getLoadError dan getSaveError serta sinyal loaded dan saved.
  • Ada dua jenis kesalahan: DataStoreError (permintaan DataStoreService gagal) dan SessionLocked (lihat Mengunci Sesi ).
  • Gunakan peristiwa ini untuk menonaktifkan prompt pembelian klien dan menerapkan dialog peringatan. Gambar ini menunjukkan contoh dialog:
A screenshot of an example warning that could be shown when player data fails to load

Simpan data pemain

A process diagram illustrating the saving system
  1. Ketika pemain meninggalkan game, sistem mengambil langkah berikut:

    1. Periksa apakah aman untuk menulis data pemain ke tokodata.Skenario di mana tidak aman termasuk data pemain gagal dimuat atau masih dalam proses pemuatan.
    2. Buat permintaan melalui SessionLockedDataStoreWrapper untuk menulis nilai data saat ini dalam memori ke penyimpanan data dan menghapus kunci sesi setelah selesai.
    3. Menghapus data pemain (dan variabel lain seperti metadata dan status kesalahan) dari memori server.
  2. Pada lingkaran periodik, server menulis data setiap pemain ke penyimpanan data (asalkan aman untuk disimpan).Redundansi sambutan ini mengurangi kerugian jika terjadi kesalahan server dan juga diperlukan untuk mempertahankan kunci sesi.

  3. Ketika permintaan untuk mematikan server diterima, hal berikut terjadi dalam panggilan balik BindToClose:

    1. Permintaan dibuat untuk menyimpan data setiap pemain di server, setelah proses yang biasanya dilalui saat pemain meninggalkan server.Permintaan ini dibuat secara paralel, karena BindToClose panggilan balik hanya memiliki waktu 30 detik untuk diselesaikan.
    2. Untuk mempercepat penyimpanan, semua permintaan lain dalam antrian setiap unitdihapus dari basis DataStoreWrapper (lihat Pengulangan ).
    3. Panggil balasan tidak kembali sampai semua permintaan selesai.