Oyuncu verilerini ve satın alma sistemlerini uygulama

*Bu içerik, yapay zekâ (beta) kullanılarak çevrildi ve hatalar içerebilir. Sayfayı İngilizce görüntülemek için buraya tıkla.

Arka Plan

Roblox, DataStoreService aracılığıyla veri depolarıyla etkileşim kurmak için bir dizi API sağlar. Bu API'lerin en yaygın kullanım durumu, oyuncu verilerini kaydetmek, yüklemek ve çoğaltmaktır. Yani, oyuncunun ilerlemesi, satın alımları ve bireysel oyun oturumları arasında devam eden diğer oturum özellikleri ile ilişkili veriler.

Roblox'taki çoğu oyun, bu API'leri kullanarak bir oyuncu veri sistemi uygulamaktadır. Bu uygulamalar yaklaşımlarında farklılık gösterir, ancak genellikle aynı dizi sorunları çözmeyi amaçlar.

Yaygın problemler

Aşağıda, oyuncu veri sistemlerinin çözmeye çalıştığı en yaygın problemlerden bazıları bulunmaktadır:

  • Bellek Erişimi: DataStoreService istekleri, asenkron olarak çalışan web istekleri yapar ve oran sınırlamalarına tabidir. Bu, oturumun başlangıcında bir ilk yükleme için uygundur, ancak normal oyun akışı sırasında yüksek frekanslı okuma ve yazma işlemleri için uygun değildir. Çoğu geliştiricinin oyuncu veri sistemleri, bu verileri Roblox sunucusunda bellekte saklar ve DataStoreService isteklerini aşağıdaki senaryolarla sınırlar:

    • Oturumun başlangıcında ilk okuma
    • Oturumun sonunda son yazma
    • Son yazmanın başarısız olduğu senaryoyu hafifletmek için belirli aralıklarla periyodik yazmalar
    • Bir satın alma işlemi işlenirken verilerin kaydedildiğinden emin olmak için yazmalar
  • Verimli Depolama: Bir oyuncunun oturum verilerini tek bir tabloda saklamak, birden fazla değeri atomik olarak güncellemenizi sağlar ve aynı miktarda veriyi daha az istekle işleyebilirsiniz. Ayrıca, değerler arası senkronizasyon riskini ortadan kaldırır ve geri alma işlemlerini daha kolay hale getirir.

    Bazı geliştiriciler, büyük veri yapılarını sıkıştırmak için özel serileştirme de uygular (genellikle oyun içi kullanıcı tarafından oluşturulan içerikleri kaydetmek için).

  • Çoğaltma: İstemcinin bir oyuncunun verilerine düzenli erişimi gerekir (örneğin, UI'yi güncellemek için). Oyuncu verilerini istemciye çoğaltmak için genel bir yaklaşım, bu bilgiyi her veri bileşeni için özel çoğaltma sistemleri oluşturmadan iletmenizi sağlar. Geliştiriciler genellikle istemciye çoğaltılacak ve çoğaltılmayacak veriler konusunda seçici olma seçeneği ister.

  • Hata Yönetimi: Veri Depolarına erişilemediğinde, çoğu çözüm bir yeniden deneme mekanizması ve 'varsayılan' verilere geri dönüş uygulayacaktır. Geri dönüş verilerinin daha sonra 'gerçek' verileri geçersiz kılmadığından emin olmak için özel dikkat gösterilmelidir ve bu durum oyuncuya uygun bir şekilde iletilmelidir.

  • Yeniden Denemeler: Veri depoları erişilemez olduğunda, çoğu çözüm bir yeniden deneme mekanizması ve varsayılan verilere geri dönüş uygular. Geri dönüş verilerinin daha sonra "gerçek" verileri geçersiz kılmadığından emin olmak için özel dikkat gösterin ve durumu oyuncuya uygun bir şekilde iletin.

  • Oturum Kilitleme: Tek bir oyuncunun verileri birden fazla sunucuda yüklendiğinde ve bellekte bulunduğunda, bir sunucunun güncel olmayan bilgileri kaydetmesi gibi sorunlar ortaya çıkabilir. Bu, veri kaybına ve yaygın eşya çoğaltma açıklarına yol açabilir.

  • Atomik Satın Alma İşlemleri: Eşyaların kaybolmasını veya birden fazla kez verilmesini önlemek için satın alımları atomik olarak doğrulayın, ödüllendirin ve kaydedin.

Örnek kod

Roblox, oyuncu veri sistemleri tasarlamanıza ve oluşturmanıza yardımcı olacak referans kodu sağlamaktadır. Bu sayfanın geri kalanı, arka plan, uygulama detayları ve genel uyarılar üzerinde durmaktadır.


Modeli Studio'ya içe aktardıktan sonra, aşağıdaki klasör yapısını görmelisiniz:

Satın alma sistemi modelini gösteren Explorer penceresi.

Mimari

Bu yüksek seviyeli diyagram, örnekteki ana sistemleri ve bunların oyunun geri kalanındaki kodla nasıl etkileşimde bulunduğunu göstermektedir.

Kod örneği için bir mimari diyagram.

Yeniden Denemeler

Sınıf: DataStoreWrapper

Arka Plan

DataStoreService arka planda web istekleri yaptığından, isteklerinin başarılı olacağı garanti edilmez. Bu olduğunda, DataStore yöntemleri hata fırlatır, böylece bunları yönetebilirsiniz.

Veri deposu hatalarını yönetmeye çalışırken yaygın bir "tuzağa düşme" durumu şu şekilde olabilir:

local MAX_ATTEMPTS = 5
local BASE_DELAY = 2
local MAX_DELAY = 32
local function retrySetAsync(dataStore, key, value)
for attempt = 1, MAX_ATTEMPTS do
local success, result = pcall(dataStore.SetAsync, dataStore, key, value)
if success then
return result
end
if attempt < MAX_ATTEMPTS then
local backoff = math.min(MAX_DELAY, BASE_DELAY * (2 ^ (attempt - 1)))
local jitter = math.random() * backoff
task.wait(math.min(MAX_DELAY, backoff + jitter))
end
end
end

Geçici hataları, sunucuların aynı anda yeniden denemelerini önlemek için üstel geri dönüş ve rastgele jitter ile yeniden deneyin. Gecikmeyi ve deneme sayısını sınırlayın.

Bu gecikme desenine rağmen, bu yeniden deneme mekanizması DataStoreService istekleri için uygun değildir çünkü isteklerin hangi sırayla yapıldığını garanti etmez. İsteklerin sırasını korumak, DataStoreService istekleri için önemlidir çünkü bunlar durumla etkileşimde bulunur. Aşağıdaki senaryoyu düşünün:

  1. Anahtar K değerini 1 olarak ayarlamak için A isteği yapılır.
  2. İstek başarısız olur, bu nedenle bir yeniden deneme, geri dönüş gecikmesinden sonra çalıştırılmak üzere planlanır.
  3. Yeniden deneme gerçekleşmeden önce, B isteği K değerini 2 olarak ayarlar, ancak A isteğinin yeniden denemesi hemen bu değeri geçersiz kılarak K'yı 1 olarak ayarlar.

UpdateAsync anahtarın en son sürümü üzerinde çalışsa da, UpdateAsync isteklerinin geçersiz geçici durumları önlemek için sırayla işlenmesi gerekir (örneğin, bir satın alma işlemi, bir madeni para eklenmeden önce madeni paraları çıkarır ve bu da negatif madeni paralara yol açar).

Oyuncu veri sistemimiz, her anahtar için sırayla işleneceği garanti edilen yeniden denemeleri sağlayan yeni bir sınıf olan DataStoreWrapper'ı kullanır.

Yaklaşım

Yeniden deneme sistemini gösteren bir süreç diyagramı

DataStoreWrapper, DataStore yöntemlerine karşılık gelen yöntemler sağlar: DataStore:GetAsync(), DataStore:SetAsync(), DataStore:UpdateAsync() ve DataStore:RemoveAsync().

Bu yöntemler çağrıldığında:

  1. İsteği bir kuyruğa ekler. Her anahtarın kendi kuyruğu vardır, burada istekler sırayla ve seri olarak işlenir. İsteği yapan iş parçacığı, istek tamamlanana kadar bekler.

    Bu işlevsellik, bir coroutine tabanlı görev zamanlayıcı ve oran sınırlayıcı olan ThreadQueue sınıfına dayanmaktadır. Bir vaatte bulunmak yerine, ThreadQueue mevcut iş parçacığını işlemin tamamlanana kadar bekletir ve başarısız olursa hata fırlatır. Bu, Luau'nun yerleşik asenkron desenleriyle daha tutarlıdır.

  2. Bir istek başarısız olursa, yapılandırılabilir bir üstel geri dönüş ile yeniden dener. Bu yeniden denemeler, ThreadQueue'ya sunulan geri çağrının bir parçasıdır, bu nedenle bu anahtar için kuyruktaki bir sonraki isteğin başlamasından önce tamamlanması garanti edilir.

  3. Bir istek tamamlandığında, istek yöntemi success, result desenini döndürür.

DataStoreWrapper, belirli bir anahtar için kuyruk uzunluğunu almak ve eski istekleri temizlemek için yöntemler de sunar. İkinci seçenek, sunucu kapandığında ve yalnızca en son isteklerin işlenmesi için zaman kalmadığında özellikle yararlıdır.

Uyarılar

DataStoreWrapper, aşırı senaryolar dışında, her veri deposu isteğinin tamamlanmasına (başarılı veya başarısız) izin verilmesi gerektiği ilkesini takip eder; daha yeni bir istek gereksiz hale gelse bile. Yeni bir istek gerçekleştiğinde, eski istekler kuyruktan kaldırılmaz, bunun yerine yeni isteğin başlamasından önce tamamlanmasına izin verilir. Bunun nedeni, bu modülün oyuncu verileri için özel bir araç değil, genel bir veri deposu aracı olarak uygulanabilirliğidir ve şu şekildedir:

  1. Bir isteğin kuyruktan ne zaman güvenli bir şekilde kaldırılacağına dair sezgisel bir kural seti belirlemek zordur. Aşağıdaki kuyruğu düşünün:

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

    Beklenen davranış, GetAsync()'nin 1 döndürmesidir, ancak en son yapılan istek tarafından gereksiz hale getirildiği için SetAsync() isteğini kuyruktan kaldırırsak 0 döndürür.

    Mantıksal ilerleme, yeni bir yazma isteği eklendiğinde, yalnızca en son okuma isteğine kadar eski isteklerin budanmasıdır. UpdateAsync(), bu sistem tarafından kullanılan en yaygın işlem (ve tek işlem) olduğundan, bu tasarım içinde uzlaşmak zor olacaktır ve ek karmaşıklık eklemeden bunu yapmak zordur.

    DataStoreWrapper, bir UpdateAsync() isteğinin okuma ve/veya yazma için izin verilip verilmediğini belirtmenizi gerektirebilir, ancak bu, oturum kilitleme mekanizması nedeniyle önceden belirlenemediğinden oyuncu veri sistemimiz için geçerli değildir (daha sonra daha ayrıntılı olarak ele alınacaktır).

  2. Kuyruktan kaldırıldıktan sonra, bunun nasıl ele alınacağına dair sezgisel bir kural belirlemek zordur. Bir DataStoreWrapper isteği yapıldığında, mevcut iş parçacığı tamamlanana kadar bekletilir. Eski istekleri kuyruktan kaldırırsak, false, "Kuyruktan kaldırıldı" döndürüp döndürmeyeceğimize veya asıl iş parçacığını atlayıp atlamayacağımıza karar vermemiz gerekir. Her iki yaklaşımın da kendi dezavantajları vardır ve tüketiciye ek karmaşıklık yükler.

Sonuç olarak, basit yaklaşımın (her isteği işlemek) burada tercih edilebilir olduğunu ve oturum kilitleme gibi karmaşık sorunlarla başa çıkarken daha net bir ortam yarattığını düşünüyoruz. Bunun tek istisnası, DataModel:BindToClose() sırasında, kuyrukların temizlenmesinin tüm kullanıcıların verilerini zamanında kaydetmek için gerekli olduğu ve bireysel işlev çağrılarının döndürdüğü değerin artık devam eden bir endişe olmadığıdır. Bunu hesaba katmak için, bir skipAllQueuesToLastEnqueued yöntemi sunuyoruz. Daha fazla bağlam için Oyuncu Verileri bölümüne bakın.

Oturum kilitleme

Sınıf: SessionLockedDataStoreWrapper

Arka Plan

Oyuncu verileri sunucuda bellekte saklanır ve yalnızca gerektiğinde temel veri depolarından okunur ve yazılır. Bellekteki oyuncu verilerine anında erişebilir ve web istekleri gerektirmeden DataStoreService sınırlarını aşmaktan kaçınabilirsiniz.

Bu modelin istenen şekilde çalışabilmesi için, bir oyuncunun verisinin DataStore'dan belleğe yüklenmesine yalnızca bir sunucunun izin vermesi zorunludur.

Örneğin, eğer sunucu A bir oyuncunun verisini yüklerse, sunucu B bu veriyi, sunucu A'nın son kaydetme sırasında kilidini serbest bırakana kadar yükleyemez. Kilitleme mekanizması olmadan, sunucu B, sunucu A'nın belleğinde daha güncel bir sürüm kaydetme şansı olmadan veri deposundan güncel olmayan oyuncu verilerini yükleyebilir. Sonra, eğer sunucu A, sunucu B'nin güncel olmayan verileri yüklemesinden sonra daha yeni verilerini kaydederse, sunucu B, bir sonraki kaydetme işlemi sırasında o daha yeni veriyi geçersiz kılacaktır.

Roblox, bir istemcinin aynı anda yalnızca bir sunucuya bağlı olmasına izin verse de, bir oturumdan alınan verilerin her zaman kaydedildiğini varsayamazsınız. Bir oyuncunun sunucu A'dan ayrıldığı durumları düşünün:

  1. Sunucu A, verilerini kaydetmek için bir DataStore isteği yapar, ancak istek başarısız olur ve başarılı bir şekilde tamamlanması için birkaç yeniden deneme gerektirir. Yeniden deneme süresi boyunca, oyuncu sunucu B'ye katılır.
  2. Sunucu A, aynı anahtar için çok fazla UpdateAsync() çağrısı yapar ve sınırlanır. Son kaydetme isteği bir kuyruğa yerleştirilir. İstek kuyrukta iken, oyuncu sunucu B'ye katılır.
  3. Sunucu A'da, PlayerRemoving olayına bağlı bazı kod, oyuncunun verisi kaydedilmeden önce bekletilir. Bu işlem tamamlanmadan önce, oyuncu sunucu B'ye katılır.
  4. Sunucu A'nın performansı, son kaydetmenin sunucu B'ye katılmadan sonra gecikmesine neden olacak kadar kötüleşmiştir.

Bu senaryolar nadir olmalıdır, ancak özellikle bir oyuncunun bir sunucudan ayrılıp başka birine hızlı bir şekilde bağlandığı durumlarda (örneğin, teleportasyon sırasında) meydana gelebilir. Bazı kötü niyetli kullanıcılar, bu davranışı, eylemleri kalıcı hale getirmeden tamamlamak için kötüye kullanmaya çalışabilir. Bu, oyuncuların ticaret yapmasına izin veren oyunlarda özellikle etkili olabilir ve yaygın eşya çoğaltma açıklarının bir kaynağıdır.

Oturum kilitleme, bir oyuncunun DataStore anahtarı ilk kez sunucu tarafından okunduğunda, sunucunun aynı UpdateAsync() çağrısı içinde anahtarın meta verilerine atomik olarak bir kilit yazmasını sağlayarak bu açığı kapatır. Eğer bu kilit değeri, başka bir sunucu anahtarı okumaya veya yazmaya çalıştığında mevcutsa, sunucu işlemi devam etmez.

Yaklaşım

Oturum kilitleme sistemini gösteren bir süreç diyagramı

SessionLockedDataStoreWrapper, DataStoreWrapper sınıfının etrafında bir meta-sarmalayıcıdır. DataStoreWrapper, kuyruklama ve yeniden deneme işlevselliği sağlar, SessionLockedDataStoreWrapper ise oturum kilitleme ile bunu tamamlar.

SessionLockedDataStoreWrapper, her DataStore isteğini—ister GetAsync, SetAsync veya UpdateAsync olsun—UpdateAsync üzerinden geçirir. Bunun nedeni, UpdateAsync anahtarın atomik olarak hem okunmasına hem de yazılmasına izin vermesidir. Ayrıca, dönüş işlevinde nil döndürerek yazmayı iptal etmek de mümkündür.

Her isteğe geçirilen dönüş işlevi, aşağıdaki işlemleri gerçekleştirir:

  1. Anahtarın güvenli bir şekilde erişilebilir olduğunu doğrular, eğer değilse işlemi iptal eder. "Güvenli erişim" şunları ifade eder:

    • Anahtarın meta veri nesnesi, kilit süresi dolmadan daha az bir süre önce güncellenmiş tanınmamış bir LockId değeri içermemelidir. Bu, başka bir sunucu tarafından yerleştirilen bir kilidi dikkate almayı ve bu kilidi süresi dolduğunda göz ardı etmeyi sağlar.

    • Eğer bu sunucu daha önce anahtarın meta verilerine kendi LockId değerini yerleştirmişse, bu değer hala anahtarın meta verilerinde bulunur. Bu, başka bir sunucunun bu sunucunun kilidini (süresi dolarak veya zorla) devraldığı ve daha sonra serbest bıraktığı durumu dikkate alır. Alternatif olarak ifade edilirse, LockId nil olsa bile, başka bir sunucu kilidi değiştirmiş ve kaldırmış olabilir.

  2. UpdateAsync, SessionLockedDataStoreWrapper tüketicisinin talep ettiği DataStore işlemini gerçekleştirir. Örneğin, GetAsync() function(value) return value end olarak çevrilir.

  3. İsteğe geçirilen parametrelere bağlı olarak, UpdateAsync anahtarı kilitler veya kilidini açar:

    1. Anahtar kilitlenecekse, UpdateAsync anahtarın meta verilerine bir GUID olarak LockId ayarlar. Bu GUID, sunucuda bellekte saklanır, böylece anahtara eriştiğinde doğrulanabilir. Eğer sunucu zaten bu anahtar üzerinde bir kilide sahipse, herhangi bir değişiklik yapmaz. Ayrıca, kilidin süresi dolmadan anahtara tekrar erişmezseniz sizi uyarmak için bir görev planlar.

    2. Anahtarın kilidi açılacaksa, UpdateAsync anahtarın meta verilerinden LockId'yi kaldırır.

Temel DataStoreWrapper'a özel bir yeniden deneme yöneticisi geçirilir, böylece işlem, kilitli oturum nedeniyle 1. adımda iptal edilirse yeniden denenecektir.

Ayrıca, tüketiciye özel bir hata mesajı döndürülür, böylece oyuncu veri sistemi, istemciye oturum kilitleme durumunda alternatif bir hata raporlayabilir.

Uyarılar

Oturum kilitleme rejimi, bir sunucunun bir anahtar üzerindeki kilidini işini bitirdiğinde her zaman serbest bırakmasını gerektirir. Bu, her zaman PlayerRemoving veya BindToClose() içindeki son yazma işlemi sırasında anahtarın kilidini açma talimatı ile gerçekleşmelidir.

Ancak, kilidin açılması bazı durumlarda başarısız olabilir. Örneğin:

  • Sunucu çökmüş veya DataStoreService anahtara erişim için tüm girişimlerde çalışamaz hale gelmiştir.
  • Mantık hatası veya benzeri bir hata nedeniyle, anahtarın kilidini açma talimatı verilmemiştir.

Bir anahtar üzerindeki kilidi korumak için, bellekte yüklü olduğu sürece düzenli olarak erişmeniz gerekir. Bu, genellikle çoğu oyuncu veri sisteminde arka planda çalışan otomatik kaydetme döngüsü aracılığıyla yapılır, ancak bu sistem ayrıca manuel olarak yapmanız gerekiyorsa bir refreshLockAsync yöntemi de sunar.

Eğer kilit süresi dolmuşsa ve kilit güncellenmemişse, herhangi bir sunucu kilidi devralmakta serbesttir. Eğer farklı bir sunucu kilidi devralırsa, mevcut sunucunun anahtarı okuma veya yazma girişimleri başarısız olur, yeni bir kilit kurmadıkça.

Geliştirici Ürün İşleme

Singleton: ReceiptHandler

Arka Plan

ProcessReceipt geri çağrısı, bir satın almanın ne zaman tamamlanacağını belirlemede kritik bir işlevi yerine getirir. ProcessReceipt, çok özel senaryolarda çağrılır. Garantileri için MarketplaceService.ProcessReceipt'e bakın.

Bir satın almanın "işlenmesi" tanımının oyunlar arasında farklılık gösterebileceği halde, aşağıdaki kriterleri kullanıyoruz:

  1. Satın alma daha önce işlenmemiştir.

  2. Satın alma mevcut oturumda yansıtılmaktadır.

  3. Satın alma, DataStore'a kaydedilmiştir.

    Her satın alma, tek seferlik tüketilebilirler bile olsa, kullanıcıların satın alma geçmişinin oturum verileriyle birlikte dahil edilmesi için DataStore'da yansıtılmalıdır.

Bu, PurchaseGranted döndürmeden önce aşağıdaki işlemleri gerçekleştirmeyi gerektirir:

  1. PurchaseId'nin daha önce işlenmediğini doğrulayın.
  2. Satın almayı oyuncunun bellekteki oyuncu verilerine ödüllendirin.
  3. PurchaseId'yi oyuncunun bellekteki oyuncu verilerinde işlenmiş olarak kaydedin.
  4. Oyuncunun bellekteki oyuncu verilerini DataStore'a yazın.

Oturum kilitleme, bu akışı basitleştirir, çünkü artık aşağıdaki senaryolar hakkında endişelenmenize gerek yoktur:

  • Mevcut sunucudaki bellekteki oyuncu verilerinin güncel olmaması, PurchaseId geçmişini doğrulamadan önce en son değeri DataStore'dan almanızı gerektirir.
  • Aynı satın alma için geri çağrının başka bir sunucuda çalışması, PurchaseId geçmişini hem okumayı hem de yazmayı gerektirir ve güncellenmiş oyuncu verilerini atomik olarak kaydetmek için yarış koşullarını önlemek gerekir.

Oturum kilitleme, eğer oyuncunun DataStore'una yazma girişimi başarılı olursa, başka bir sunucunun oyuncunun DataStore'unu yükleme veya yazma girişiminde bulunmadığını garanti eder. Kısacası, bu sunucudaki bellekteki oyuncu verileri mevcut en güncel sürümdür. Bazı uyarılar vardır, ancak bunlar bu davranışı etkilemez.

Yaklaşım

ReceiptProcessor içindeki yorumlar yaklaşımı özetler:

  1. Oyuncunun verisinin şu anda bu sunucuda yüklü olduğunu ve hatasız yüklendiğini doğrulayın.

    Bu sistem oturum kilitlemesini kullandığı için, bu kontrol aynı zamanda bellekteki verilerin en güncel sürüm olduğunu doğrular.

    Eğer oyuncunun verisi henüz yüklenmemişse (bu, bir oyuncu bir oyuna katıldığında beklenen bir durumdur), oyuncunun verisinin yüklenmesini bekleyin. Sistem ayrıca, oyuncunun verisi yüklenmeden önce oyundan ayrılmasını dinler, çünkü bu geri çağrının bu sunucuda tekrar çağrılmasını engellememelidir.

  2. PurchaseId'nin oyuncu verilerinde daha önce işlenmediğini doğrulayın.

    Oturum kilitlemesi nedeniyle, sistemin bellekteki PurchaseIds dizisi en güncel sürümdür. Eğer PurchaseId işlenmiş olarak kaydedilmişse ve DataStore'da yansıtılmışsa, PurchaseGranted döndürün. Eğer işlenmiş olarak kaydedilmişse, ancak DataStore'da yansıtılmamışsa, NotProcessedYet döndürün.

  3. Bu sunucudaki oyuncu verilerini yerel olarak güncelleyerek satın almayı "ödüllendirin".

    ReceiptProcessor, genel bir geri çağırma yaklaşımı benimser ve her DeveloperProductId için farklı bir geri çağırma atar.

  4. Oyuncu verilerini yerel olarak güncelleyerek PurchaseId'yi saklayın.

  5. Bellekteki verileri DataStore'a kaydetmek için bir istek gönderin, istek başarılıysa PurchaseGranted döndürün. Aksi takdirde, NotProcessedYet döndürün.

    Eğer bu kaydetme isteği başarılı olmazsa, oyuncunun bellekteki oturum verilerini kaydetmek için daha sonraki bir istek başarılı olabilir. Bir sonraki ProcessReceipt çağrısında, adım 2 bu durumu ele alır ve PurchaseGranted döndürür.

Oyuncu verileri

Singletonlar: PlayerData.Server, PlayerData.Client

Arka Plan

Kodun oyuncu oturum verilerini senkronize bir şekilde okumak ve yazmak için bir arayüz sağladığı modüller, Roblox oyunlarında yaygındır. Bu bölüm, PlayerData.Server ve PlayerData.Client'i kapsar.

Yaklaşım

PlayerData.Server ve PlayerData.Client aşağıdakileri yönetir:

  1. Oyuncunun verilerini belleğe yükleme, yüklemenin başarısız olduğu durumları yönetme
  2. Sunucu kodunun oyuncu verilerini sorgulaması ve değiştirmesi için bir arayüz sağlama
  3. Oyuncunun verilerindeki değişiklikleri istemciye çoğaltma, böylece istemci kodu buna erişebilir
  4. Yükleme ve/veya kaydetme hatalarını istemciye çoğaltma, böylece hata diyalogları gösterebilir
  5. Oyuncunun verilerini periyodik olarak, oyuncu oyundan ayrıldığında ve sunucu kapandığında kaydetme

Oyuncu verilerini yükleme

Yükleme sistemini gösteren bir süreç diyagramı
  1. SessionLockedDataStoreWrapper, veri deposuna bir getAsync isteği yapar.

    Eğer bu istek başarısız olursa, varsayılan veriler kullanılır ve profil "hatalı" olarak işaretlenir, böylece daha sonra veri deposuna yazılmaz.

    Alternatif bir seçenek, oyuncuyu atmak olsa da, oyuncunun varsayılan verilerle oynamasına izin vermeyi ve ne olduğunu açık bir şekilde iletmeyi öneriyoruz, oyuncuyu oyundan çıkarmak yerine.

  2. Yüklenen veriler ve hata durumu (varsa) içeren bir başlangıç yükü PlayerDataClient'e gönderilir.

  3. waitForDataLoadAsync ile bekletilen tüm iş parçacıkları devam ettirilir.

Sunucu kodu için bir arayüz sağlama

  • PlayerDataServer, aynı ortamda çalışan herhangi bir sunucu kodu tarafından gereksinim duyulabilen bir singleton'dır.
  • Oyuncu verileri, anahtarlar ve değerler sözlüğü olarak düzenlenmiştir. Bu değerleri sunucuda setValue, getValue, updateValue ve removeValue yöntemlerini kullanarak manipüle edebilirsiniz. Bu yöntemler, bekletme olmadan senkronize bir şekilde çalışır.
  • Verilerin yüklendiğinden emin olmak için hasLoaded ve waitForDataLoadAsync yöntemleri mevcuttur. Diğer sistemler başlamadan önce bir yükleme ekranında bunu bir kez yapmayı öneriyoruz, böylece istemciyle veri etkileşiminde her seferinde yükleme hatalarını kontrol etmek zorunda kalmazsınız.
  • Oyuncunun ilk yüklemesinin başarısız olup olmadığını sorgulamak için bir hasErrored yöntemi vardır, bu da varsayılan verilerle oynamasına neden olur. Oyuncunun herhangi bir satın alma işlemi yapmasına izin vermeden önce bu yöntemi kontrol edin, çünkü satın almalar başarılı bir yükleme olmadan verilere kaydedilemez.
  • Bir oyuncunun verileri değiştiğinde, playerDataUpdated sinyali player, key ve value ile tetiklenir. Bireysel sistemler buna abone olabilir.

Değişiklikleri istemciye çoğaltma

  • PlayerDataServer'deki oyuncu verilerindeki herhangi bir değişiklik, PlayerDataClient'e çoğaltılır, bu anahtar setValueAsPrivate kullanılarak özel olarak işaretlenmedikçe.
    • setValueAsPrivate, istemciye gönderilmemesi gereken anahtarları belirtmek için kullanılır.
  • PlayerDataClient, bir anahtarın değerini almak için bir yöntem (get) ve güncellendiğinde tetiklenen bir sinyal (updated) içerir. Verilerin yüklenmesini beklemek ve çoğaltmak için istemcinin bekleyebilmesi için bir hasLoaded yöntemi ve bir loaded sinyali de bulunmaktadır.
  • PlayerDataClient, aynı ortamda çalışan herhangi bir istemci kodu tarafından gereksinim duyulabilen bir singleton'dır.

Hataları istemciye çoğaltma

  • Oyuncu verilerini kaydederken veya yüklerken karşılaşılan hata durumları PlayerDataClient'e çoğaltılır.
  • Bu bilgilere getLoadError ve getSaveError yöntemleri ile erişilir, ayrıca loaded ve saved sinyalleri de vardır.
  • İki tür hata vardır: DataStoreError ( DataStoreService isteği başarısız oldu) ve SessionLocked (bkz. Oturum Kilitleme).
  • Bu olayları kullanarak istemci satın alma istemlerini devre dışı bırakabilir ve uyarı diyalogları uygulayabilirsiniz. Bu resim, oyuncu verileri yüklenemediğinde gösterilebilecek bir örnek diyalog gösterir:
Oyuncu verileri yüklenemediğinde gösterilebilecek bir uyarı örneğinin ekran görüntüsü

Oyuncu verilerini kaydetme

Kaydetme sistemini gösteren bir süreç diyagramı
  1. Oyuncu oyundan ayrıldığında, sistem aşağıdaki adımları izler:

    1. Oyuncunun verilerini veri deposuna yazmanın güvenli olup olmadığını kontrol edin. Güvenli olmayacağı senaryolar, oyuncunun verisinin yüklenmesinin başarısız olması veya hala yüklenmekte olmasıdır.
    2. SessionLockedDataStoreWrapper aracılığıyla mevcut bellekteki veri değerini veri deposuna yazmak ve tamamlandığında oturum kilidini kaldırmak için bir istek yapın.
    3. Oyuncunun verilerini (ve meta veriler gibi diğer değişkenleri ve hata durumlarını) sunucu belleğinden temizleyin.
  2. Periyodik bir döngüde, sunucu her oyuncunun verisini veri deposuna yazar (kaydetmenin güvenli olduğu sürece). Bu hoş bir yedeklilik, sunucu çökmesi durumunda kaybı azaltır ve oturum kilidini korumak için de gereklidir.

    Örnek, AUTO_SAVE_INTERVAL saniye (varsayılan olarak 180) sonra bir paylaşılan döngü başlatır ve ardından her yüklü oyuncuyu paralel olarak kaydeder. Bu döngü, sunucuları veya oyuncuları kaydırmaz, bu nedenle benzer zamanlarda başlayan sunucular birlikte yazabilir.

    Her oyuncunun ilk kaydını, canlı sunucuların aynı anda yazmaması için aralık içinde rastgele bir süre ile kaydırın:

    local AUTO_SAVE_INTERVAL = 180
    local function startAutoSave(player)
    task.spawn(function()
    task.wait(math.random() * AUTO_SAVE_INTERVAL)
    while player.Parent do
    if canSave(player) then
    savePlayerData(player)
    end
    task.wait(AUTO_SAVE_INTERVAL)
    end
    end)
    end
  3. Sunucunun kapatılması için bir istek alındığında, aşağıdakiler BindToClose geri çağrısında gerçekleşir:

    1. Sunucudaki her oyuncunun verisini kaydetmek için bir istek yapılır, bu, oyuncu sunucudan ayrıldığında normalde izlenen süreçten geçer. Bu istekler paralel olarak yapılır, çünkü BindToClose geri çağrılarının tamamlanması için yalnızca 30 saniyesi vardır.
    2. Kaydetmeleri hızlandırmak için, her anahtarın kuyruğundaki tüm diğer istekler temel DataStoreWrapper'dan temizlenir (bkz. Yeniden Denemeler).
    3. Geri çağrı, tüm istekler tamamlanana kadar döndürülmez.
©2026 Roblox Corporation. Roblox, the Roblox logo and Powering Imagination are among our registered and unregistered trademarks in the U.S. and other countries.