Hintergrund
Roblox bietet eine Reihe von APIs, um sich mit Datenlagern über DataStoreService zu verbinden.Der häufigste Anwendungsfall für diese APIs ist zum Speichern, Laden und Replizieren von Spielerdaten.Das heißt, Daten, die mit dem Fortschritt des Spieler:in, Käufen und anderen Sitzungsmerkmalen verbunden sind, die zwischen einzelnen Spielsitzungen bestehen bleiben.
Die meisten Erlebnisse auf Roblox verwenden diese APIs, um eine Art von Spielerdaten-System zu implementieren.Diese Implementierungen unterscheiden sich in ihrem Ansatz, aber sie streben im Allgemeinen danach, dasselbe Problem zu lösen.
Gewöhnliche Probleme
Im Folgenden sind einige der häufigsten Probleme, die Spielerdatensysteme versuchen zu lösen:
Im Speicherzugriff: DataStoreService Anfragen stellen Webanfragen bereit, die asynchron arbeiten und von Geschwindigkeitsgrenzen unterliegen.Dies ist für eine initiale Belastung am Beginn der Sitzung angemessen, aber nicht für häufige Lesen- und Schreiboperationen während des normalen Gameplay.Die meisten Spieldatensysteme von Entwicklern speichern diese Daten in der Speicher auf dem Roblox-Server, begrenzen DataStoreService Anfragen auf die folgenden Szenarien:
- Initialer Lesen am Beginn einer Sitzung
- Letzte Schrift am Ende der Sitzung
- Periodische Schreibungen werden mit einem Intervall durchgeführt, um das Szenario zu mindern, in dem die endgültige Schreibung fehlschlägt
- Schreibt, um sicherzustellen, dass Daten gespeichert werden, während ein kaufenverarbeitet wird
Effiziente Speicherung: Das Speichern aller Sitzungsdaten eines Spieler:inin einer einzigen Tabelle ermöglicht es Ihnen, mehrere Werte atomar zu aktualisieren und die gleiche Menge an Daten in weniger Anfragen zu verarbeiten.Es entfernt auch das Risiko der Desynchronisierung zwischen Werten und macht Rückrollungen leichter zu begründen.
Einige Entwickler implementieren auch benutzerdefinierte Serialize, um große Datenstrukturen zu komprimieren (normalerweise, um in-game erzeugten Benutzerinhalt zu speichern).
Replikation: Der Client braucht regelmäßigen Zugriff auf die Daten eines Spieler:in(zum Beispiel, um die Benutzeroberfläche zu aktualisieren).Ein generischer Ansatz zur Replikation von Spielerdaten auf den Client ermöglicht es Ihnen, diese Informationen zu übermitteln, ohne benutzerdefinierte Replikationssysteme für jeden Bestandteil der Daten zu erstellen.Entwickler wollen oft die Möglichkeit, auszuwählen, was und was nicht auf den Client repliziert wird.
Fehlerbehandlung: Wenn DataStores nicht zugänglich sind, implementieren die meisten Lösungen einen Wiederholungsmechanismus und einen Rückgriff auf "Standard"-Daten.Besondere Aufmerksamkeit ist erforderlich, um sicherzustellen, dass Fallback-Daten nicht später "echte" Daten überschreiben und dass dies dem Spieler angemessen mitgeteilt wird.
Wiederholte Versuche: Wenn Datenbanken nicht zugänglich sind, implementieren die meisten Lösungen einen Wiederholungsmechanismus und einen Rückgriff auf Standarddaten.Seien Sie besonders vorsichtig, um sicherzustellen, dass Fallback-Daten nicht später "echte" Daten überschreiben und die Situation dem Spieler angemessen mitteilen.
Sitzungsblockierung: Wenn die Daten eines einzelnen Spieler:inauf mehreren Servern geladen und in der Speicher sind, können Probleme auftreten, bei denen ein Server veraltete Informationen speichert.Dies kann zu Datenverlust und häufigen Duplikationslücken führen.
Atomare Kaufabwicklung: Überprüfen, vergeben und dokumentieren Sie Käufe atomar, um zu verhindern, dass Artikel mehrfach verloren gehen oder vergeben werden.
Codes
Roblox hat einen Referenzcode, der dir bei der Entwicklung und Erstellung von Spielerdatensystemen hilft.Der Rest dieser Seite untersucht Hintergrund, Implementierungsdetails und allgemeine Einschränkungen.
Nachdem du das Modell in Studio importiert hast, solltest du die folgende Ordnerstruktur sehen:

Architektur
Dieses hohebene-Diagramm illustriert die wichtigsten Systeme im Sample und wie sie mit Code im Rest der Erlebnisinteragieren.

Wiederholte Versuche
Klasse: DataStoreWrapper
Hintergrund
Da DataStoreService Webanfragen unter der Motorhaube stellt, sind seine Anfragen nicht garantiert erfolgreich.Wenn dies passiert, werfen die DataStore -Methoden Fehler, sodass du sie handhaben kannst.
Ein häufiges "gotcha" kann auftreten, wenn du versuchst, Datenlagerschäden wie diesen zu behandeln:
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
Während dies ein perfekt gültiger Wiederholungsmechanismus für eine generische Funktion ist, ist er für DataStoreService Anfragen nicht geeignet, weil er nicht die Reihenfolge garantiert, in der Anfragen gestellt werden.Die Reihenfolge der Anfragen zu erhalten ist wichtig für DataStoreService Anfragen, weil sie mit dem Zustand interagieren.Betrachten Sie das folgende Szenario:
- Anfrage A wird gemacht, um den Wert des Schlüssels K auf 1 zu setzen.
- Die Anfrage ist fehlgeschlagen, sodass eine Wiederholung in 2 Sekunden geplant ist.
- Bevor der erneute Versuch stattfindet, fordert B den Wert von K auf 2 an, aber der erneute Versuch von Anfrage A überschreibt diesen Wert und setzt K auf 1.
Obwohl UpdateAsync auf der neuesten Version des Werts des Schlüssels arbeitet, müssen UpdateAsync Anfragen immer noch verarbeitet werden, um ungültige transitorische Zustände zu vermeiden (zum Beispiel reduziert ein Kauf Münzen, bevor eine Münzvermehrung verarbeitet wird, was zu negativen Münzen führt).
Unser Spielerdatensystem verwendet eine neue Klasse, DataStoreWrapper, die verschiebende Wiederholungen bereitstellt, die garantiert werden, dass sie in der Reihenfolge der Schlüssel verarbeitet werden.
Ansatz

DataStoreWrapper bietet methoden, die entsprechen den methoden der DataStore -methode: DataStore:GetAsync() , DataStore:SetAsync() , DataStore:UpdateAsync() und DataStore:RemoveAsync() .
Diese Methoden, wenn sie aufgerufen werden:
Füge die Anfrage in eine Warteschlange ein.Jeder Schlüssel hat seine eigene Warteschlange, in der Anfragen in der Reihenfolge und in Serie bearbeitet werden.Der anfordernde Thread gibt bis die Anfrage abgeschlossen ist, nach.
Diese Funktionalität basiert auf der ThreadQueue-Klasse, die ein koroutinebasierter Aufgabenplaner und Geschwindigkeitsbegrenzer ist.Anstatt eine Versprechen zurückzugeben, gibt ThreadQueue den aktuellen Thread aus, bis die Operation abgeschlossen ist und einen Fehler wirft, wenn sie fehlschlägt.Dies ist konsistenter mit idiomatischen asynchronen Luau-Mustern.
Wenn eine Anfrage fehlschlägt, versucht sie es mit einem konfigurierbaren exponentiellen Rückgang.Diese Wiederholungsversuche sind Teil des Rückrufs, der an den ThreadQueue gesendet wurde, so dass sie garantiert abgeschlossen sind, bevor die nächste Anfrage in der Warteschlange für diesen Schlüssel beginnt.
Wenn eine Anfrage abgeschlossen ist, kehrt die Anforderungsmethode mit dem success, result-Muster zurück
DataStoreWrapper zeigt auch Methoden auf, um die Warteschlangenlänge für einen bestimmten Schlüssel zu erhalten und alte Anfragen zu löschen.Die letztere Option ist besonders nützlich in Szenarien, in denen der Server heruntergefahren wird und es keine Zeit gibt, um alle, aber die neuesten Anfragen zu verarbeiten.
Bedenken
DataStoreWrapper folgt dem Prinzip, dass jeder Datenlagersuchanfrage außerhalb extremer Szenarien erlaubt werden sollte, abgeschlossen zu werden (mit Erfolg oder auf andere Weise), selbst wenn eine jüngere Anfrage sie redundant macht.Wenn eine neue Anfrage auftritt, werden alte Anfragen nicht aus der Warteschlange entfernt, sondern können stattdessen abgeschlossen werden, bevor die neue Anfrage gestartet wird.Die Rationale dafür liegt in der Anwendbarkeit dieses Moduls als generische Datenlagern-Utility und nicht als spezifisches Werkzeug für Spielerdaten und ist wie folgt:
Es ist schwierig, sich für eine intuitive Reihe von Regeln zu entscheiden, wann eine Anfrage aus der Warteschlange entfernt werden kann. Betrachte die folgende Warteschlange:
Value=0, SetAsync(1), GetAsync(), SetAsync(2)
Das erwartete Verhalten ist, dass GetAsync() würde 1 zurückgeben, aber wenn wir die SetAsync() Anfrage aus der Warteschlange entfernen, weil sie durch die neueste Anfrage redundant gemacht wird, würde sie 0 zurückgeben.
Die logische Fortschrittsweise ist, dass, wenn eine neue Schreibanfrage hinzugefügt wird, nur veraltete Anfragen so weit zurück wie die jüngste Lesanfrage gekappt werden.UpdateAsync(), die mit Abstand häufigste Operation (und die einzige, die von diesem System verwendet wird), kann sowohl lesen als auch schreiben, so wäre es schwierig, innerhalb dieses Designs ohne zusätzliche Komplexität zu vereinbaren.
DataStoreWrapper könnte erfordern, dass du angeben musst, ob eine UpdateAsync() anforderung erlaubt war, zu lesen und/oder zu schreiben, aber sie hätte keine anwendung auf unser spielerdatensystem, wo dies vor der zeit nicht bestimmt werden kann, wegen des sitzungsblockierungsmechanismus (in mehr einzelheiten später).
Sobald er aus der Warteschlange entfernt wurde, ist es schwierig, eine intuitive Regel für wie dies zu handhaben, zu entscheiden.Wenn eine DataStoreWrapper Anfrage gestellt wird, wird der aktuelle Thread freigegeben, bis er abgeschlossen ist.Wenn wir alte Anfragen aus der Warteschlange entfernen, müssten wir entscheiden, ob wir false, "Removed from queue" oder niemals zurückgeben und den aktiven Thread verwerfen sollen.Beide Ansätze kommen mit ihren eigenen Nachteilen und übertragen zusätzliche Komplexität auf den Verbraucher.
Letztendlich ist es unsere Ansicht, dass der einfache Ansatz (Verarbeitung jeder Anfrage) hier vorzuziehen ist und ein klares Umfeld schafft, in dem man sich bei der Annäherung an komplexe Probleme wie das Sperren von Sitzungen bewegt.Die einzige Ausnahme davon ist während DataModel:BindToClose(), wo die Löschung der Warteschlange notwendig wird, um alle Daten der Benutzer in der Zeit zu speichern, und der Wert der einzelnen Funktionsaufrufe ist nicht mehr eine laufende Sorge.Um dies zu berücksichtigen, stellen wir eine skipAllQueuesToLastEnqueued -Methode bereit.Für mehr Kontext siehe Spielerdaten.
Sitzungsblockierung
Klasse: SessionLockedDataStoreWrapper
Hintergrund
Spieldaten werden in der Erinnerung auf dem Server gespeichert und werden nur dann gelesen und geschrieben in die zugrunde liegenden Datenstores, wenn dies erforderlich ist.Sie können sofort in-Memory-Spielerdaten lesen und aktualisieren, ohne Web-Anfragen benötigen zu müssen, und die DataStoreService Limitüberschreitung vermeiden.
Damit dieses Modell wie beabsichtigt funktioniert, ist es unerlässlich, dass nicht mehr als ein Server in der Lage ist, die Daten eines Spieler:ingleichzeitig aus der DataStore in den Speicher zu laden.
Wenn zum Beispiel Server A die Daten eines Spieler:inladen, kann Server B diese Daten nicht laden, bis Server A sein Schloss während einer endgültigen Speicherung freigibt.Ohne einen Sperrm机制, könnte Server B Daten des veralteten Spielers aus dem Datenstore laden, bevor Server A die jüngere Version, die es in der Erinnerung hat, speichern kann.Dann, wenn Server A seine neuere Daten speichert, nachdem Server B die veralteten Daten geladen hat, würde Server B diese neuere Daten während seiner nächsten Speicherung überschreiben.
Obwohl Roblox nur einen Client gleichzeitig mit einem Server verbinden lässt, kannst du nicht davon ausgehen, dass Daten aus einer Sitzung immer gespeichert werden, bevor die nächste Sitzung beginnt.Betrachte die folgenden Szenarien, die auftreten können, wenn ein Spieler den Server A verlässt:
- Server A sendet eine DataStore Anfrage zum Speichern seiner Daten, aber die Anfrage scheitert und erfordert mehrere Versuche, um erfolgreich abgeschlossen zu werden.Während des Wiederholungszeitraums schließt sich der Spieler dem Server B an.
- Server A macht zu viele UpdateAsync() Anrufe auf denselben Schlüssel und wird gedrosselt.Die endgültige Speicheranfrage wird in einer Warteschlange platziert.Während die Anfrage in der Warteschlange ist, schließt sich der Spieler dem Server B an.
- Auf Server A wird ein Teil des Codes, der mit dem Ereignis PlayerRemoving verbunden ist, ausgeführt, bevor die Daten des Spieler:ingespeichert werden.Bevor diese Operation abgeschlossen ist, schließt sich der Spieler dem Server B an.
- Die Leistung des Servers A ist auf den Punkt heruntergekommen, dass die endgültige Speicherung bis zum Beitritt des Spielers zum Server B verzögert wird.
Diese Szenarien sollten selten auftreten, aber sie treten auf, insbesondere in Situationen, in denen ein Spieler von einem Server abtritt und sich schnell hintereinander mit einem anderen verbindet (zum Beispiel, während des Teleportierens).Einige böswillige Benutzer könnten sogar versuchen, dieses Verhalten zu missbrauchen, um Aktionen abzuschließen, ohne dass sie fortbestehen.Dies kann besonders in Spielen wirksam sein, in denen Spieler handeln können, und ist eine häufige Quelle für Gegenstandsduplikations-Exploits.
Session-Sperranschriften adressieren diese Schwachstelle, indem sie sicherstellen, dass, wenn der DataStore -Schlüssel eines Spieler:inzuerst vom Server gelesen wird, der Server atomar einen Sperrschlüssel in die Metadaten der Schlüssel innerhalb derselben UpdateAsync() -Aufrufe schreibt.Wenn dieser Schlosswert vorhanden ist, wenn ein anderer Server versucht, den Schlüssel zu lesen oder zu schreiben, führt der Server nicht fort.
Ansatz

SessionLockedDataStoreWrapper ist ein meta-wrapper um die DataStoreWrapper klasse herum. bietet funktionen zur warteschlange und wiederholung, die mit sitzungsblockierung ergänzt werden.
SessionLockedDataStoreWrapper passt jede DataStore Anfrage durch - unabhängig davon, ob es sich um GetAsync , SetAsync oder UpdateAsync handelt - durch UpdateAsync .Das liegt daran, dass UpdateAsync einen Schlüssel erlaubt, sowohl gelesen als auch geschrieben zu werden, um atomar zu sein.Es ist auch möglich, die Schrift auf der Grundlage des Werts aufzugeben, indem du nil in der Transformations-Callback zurückgibst.
Die Transformationsfunktion, die in UpdateAsync für jede Anfrage übergeben wird, führt die folgenden Operationen aus:
Überprüft, dass der Schlüssel sicher zum Zugriff ist, und gibt die Operation auf, wenn er nicht ist. "Sicher zum Zugriff" bedeutet:
Das Metadatenobjekt der Schlüssel enthält keinen unerkannten LockId, der vor weniger als der Ablaufzeit des Schließens aktualisiert wurde.Dies ergibt sich aus dem Respekt einer von einem anderen Server platzierten Sperre und der Ignorierung dieser Sperre, wenn sie abgelaufen ist.
Wenn dieser Server zuvor seinen eigenen LockId Wert in den Metadaten der Schlüssel platziert hat, dann ist dieser Wert immer noch in den Metadaten des Schlüssels.Dies erklärt die Situation, in der ein anderer Server die Sperre dieses Servers übernommen hat (durch Ablauf oder durch Gewalt) und sie später freigegeben hat.Alternativ formuliert, auch wenn LockId ist nil, könnte ein anderer Server immer noch einen Schloss ersetzt und entfernt haben, seit du den Schlüssel gesperrt hast.
UpdateAsync führt die DataStore Operation aus, die vom Verbraucher von SessionLockedDataStoreWrapper angefordert wurde. Zum Beispiel übersetzt GetAsync() in function(value) return value end.
Abhängig von den übergebenen Parametern in die Anfrage, UpdateAsync sperrt oder entsperrt der Schlüssel:
Wenn der Schlüssel gesperrt werden soll, UpdateAsync setzt den LockId in den Metadaten des Schlüssels auf eine GUID.Diese GUID wird im Speicher des Servers gespeichert, so dass sie beim nächsten Zugriff auf den Schlüssel überprüft werden kann.Wenn der Server bereits ein Schloss auf diesem Schlüssel hat, macht er keine Änderungen.Es plant auch eine Aufgabe, um Sie zu warnen, wenn Sie den Schlüssel nicht erneut zugreifen, um die Sperre innerhalb der Verfallszeit des Schlüssels aufrechtzuerhalten.
Wenn der Schlüssel freigeschaltet werden soll, entfernt UpdateAsync den LockId in den Metadaten des Schlüssels.
Ein benutzerdefinierter Wiederholungsverarbeiter wird in den unterliegenden DataStoreWrapper übergeben, so dass die Operation erneut versucht wird, wenn sie aufgrund der Sperrung der Sitzung im Schritt 1 abgebrochen wurde.
Eine benutzerdefinierte Fehlermeldung wird auch an den Verbraucher zurückgegeben, so dass das Spielerdatensystem einen alternativen Fehler im Fall der Sitzungsblockierung beim Client melden kann.
Bedenken
Das Sitzungssperrregime basiert auf einem Server, der immer seine Sperre auf einen Schlüssel freigibt, wenn er damit fertig ist.Dies sollte immer durch eine Anweisung passieren, den Schlüssel als Teil der endgültigen Schrift in PlayerRemoving oder BindToClose() freizuschalten.
Die Freischaltung kann jedoch in bestimmten Situationen fehlschlagen. Zum Beispiel:
- Der Server ist abgestürzt oder DataStoreService war für alle Versuche, auf den Schlüssel zuzugreifen, nicht funktionsfähig.
- Aufgrund eines Fehlers in der Logik oder eines ähnlichen Fehlers wurde die Anweisung zum Freischalten des Schlüssels nicht ausgeführt.
Um das Schloss auf einem Schlüssel zu halten, musst du ihn regelmäßig zugreifen, solange er in der Erinnerung geladen ist.Dies würde normalerweise als Teil der automatischen Speicherlaufschleife erfolgen, die im Hintergrund in den meisten Spielerdatensystemen läuft, aber dieses System zeigt auch eine refreshLockAsync -Methode an, wenn Sie es manuell tun müssen.
Wenn die Sperrdauer überschritten wurde, ohne dass der Sperrblock aktualisiert wurde, kann jeder Server die Sperre übernehmen.Wenn ein anderer Server das Schloss übernimmt, scheitern Versuche des aktuellen Servers, den Schlüssel zu lesen oder zu schreiben, es sei denn, er etabliert ein neues Schloss.
Entwicklerproduktverarbeitung
Singleton: ReceiptHandler
Hintergrund
Der ProcessReceipt Callback führt die kritische Aufgabe aus, zu bestimmen, wann ein kaufenabgeschlossen werden soll.ProcessReceipt wird in sehr spezifischen Szenarien aufgerufen.Für sein Set von Garantien siehe MarketplaceService.ProcessReceipt.
Obwohl die Definition von "Handling" eines Kaufs zwischen Erlebnissen unterschiedlich sein kann, verwenden wir die folgenden Kriterien
Der Kauf wurde zuvor nicht bearbeitet.
Der Kauf wird in der aktuellen Sitzung widergespiegelt.
Dies erfordert die Durchführung der folgenden Operationen, bevor Sie PurchaseGranted zurückkehren:
- Überprüfen Sie, dass die PurchaseId noch nicht als behandelt aufgezeichnet wurde.
- Verleihe den Kauf in den In-Memory-Spielerdaten des Spieler:in.
- Notiere die PurchaseId als in der Speicher des Spielers verwaltet in den Spielerdaten des Spielers.
- Schreiben Sie die Spielerdaten des Spieler:inin der Speicher des Spielers auf die DataStore.
Die Sitzungssperre vereinfacht diesen Prozess, da Sie sich nicht mehr um die folgenden Szenarien sorgen müssen:
- Die im Speicher befindlichen Spielerdaten auf dem aktuellen Server möglicherweise veraltet sein, wodurch Sie den neuesten Wert aus der DataStore vor der Überprüfung der PurchaseId Geschichte abrufen müssen
- Der Rückruf für denselben Kauf, der auf einem anderen Server ausgeführt wird, erfordert, dass Sie sowohl die PurchaseId Geschichte lesen und schreiben und die aktualisierten Spielerdaten mit dem Kauf atomar gespeichert werden, um Rennenbedingungen zu verhindern
Sitzungssperren garantieren, dass, wenn ein Versuch, auf den Spieler:inzu schreiben DataStore erfolgreich ist, kein anderer Server den Spieler:inDataStore zwischen den Daten gelesen oder geschrieben hat, die auf diesem Server geladen und gespeichert werden.Kurz gesagt ist die im Speicher befindliche Spielerdaten auf diesem Server die neueste verfügbare Version.Es gibt einige Einschränkungen, aber sie wirken sich nicht auf dieses Verhalten aus.
Ansatz
Die Kommentare in ReceiptProcessor umreißen den Ansatz:
Überprüfe, ob die Daten des Spieler:inauf diesem Server derzeit geladen sind und dass sie ohne Fehler geladen wurden.
Da dieses System Sitzungsblockierung verwendet, überprüft dieser Check auch, dass die in der Speicherdaten die neueste Version ist.
Wenn die Daten des Spieler:innoch nicht geladen wurden (was erwartet wird, wenn ein Spieler einem Spiel beitritt), warten Sie, bis die Daten des Spieler:ingeladen sind.Das System hört auch auf den Spieler, der das Spiel verlässt, bevor seine Daten geladen werden, da es nicht unendlich sein sollte und diesen Rückruf auf diesem Server für diesen Kauf erneut nicht aufrufen sollte, wenn der Spieler wieder beitritt.
Überprüfen Sie, dass die PurchaseId nicht bereits als verarbeitet in den Spielerdaten aufgezeichnet ist.
Aufgrund der Sitzungssperre ist das Array von PurchaseIds, das das System in der Speicher hat, die neueste Version.Wenn das PurchaseId als verarbeitet und in einem Wert aufgezeichnet wird, der auf oder in den DataStore geladen oder gespeichert wurde, geben Sie PurchaseGranted zurück.Wenn es als verarbeitet aufgezeichnet wird, aber nicht in den DataStore widergespiegelt wird, geben Sie NotProcessedYet.
Aktualisieren Sie die Spielerdaten lokal auf diesem Server, um die kaufen"zu vergeben".
ReceiptProcessor nimmt einen generischen rückrufansatz und weist für jeden DeveloperProductId einen anderen rückruf zu.
Aktualisieren Sie die Spielerdaten lokal auf diesem Server, um die PurchaseId zu speichern.
Senden Sie eine Anfrage, um die in-Speicher-Daten auf die DataStore zu speichern, und geben Sie PurchaseGranted zurück, wenn die Anfrage erfolgreich ist. Wenn nicht, geben Sie NotProcessedYet zurück.
Wenn diese Speicheranfrage nicht erfolgreich ist, könnte eine spätere Anfrage, die Speicherdaten der in-Memory-Sitzung des Spieler:inzu speichern, immer noch erfolgreich sein.Während des nächsten ProcessReceipt Anrufs behandelt Schritt 2 diese Situation und gibt PurchaseGranted zurück.
Spielerdaten
Singletons: PlayerData.Server , PlayerData.Client
Hintergrund
Module, die ein Interface für den Spielcode bieten, um Sitzungsdaten des Spielers synchron zu lesen und zu schreiben, sind in Roblox-Erlebnissen häufig.Dieser Abschnitt deckt PlayerData.Server und PlayerData.Client ab.
Ansatz
PlayerData.Server und PlayerData.Client bearbeiten das gefolgte profile:
- Laden der Daten des Spieler:inin den Speicher, einschließlich der Verarbeitung von Fällen, in denen es nicht geladen wird
- Eine Schnittstelle bereitstellen, über die Servercode die Spielerdaten abrufen und ändern kann
- Änderungen in den Daten des Spieler:inauf den Client replizieren, damit der Client-Code darauf zugreifen kann
- Wiederholen von Lade- und/oder Speicherfehlern an den Client, damit er Fehlerdialoge anzeigen kann
- Speichern der Daten des Spieler:inperiodisch, wenn der Spieler geht, und wenn der Server heruntergefahren wird
Spielerdaten laden

SessionLockedDataStoreWrapper macht eine getAsync anfrage an den store.
Wenn diese Anfrage fehlschlägt, werden die Standarddaten verwendet und das Profil als "fehlerhaft" markiert, um sicherzustellen, dass es später nicht in den Datenstore geschrieben wird.
Eine alternative Option ist es, den Spieler:inzu kicken, aber wir empfehlen, den Spieler mit Standarddaten und einer klaren Kommunikation spielen zu lassen, was passiert ist, anstatt ihn aus dem Erlebnis zu entfernen.
Eine erste Ladung wird an PlayerDataClient gesendet, die die geladenen Daten und den Fehlerstatus enthält (falls vorhanden).
Alle Threads, die mit waitForDataLoadAsync für den Spieler verwendet wurden, werden fortgesetzt.
Biete ein Interface für Codes
- PlayerDataServer ist ein singleton, das von jedem servercode, der in derselben umgebung ausgeführt wird, erforderlich und zugänglich sein kann.
- Spielerdaten werden in einem Wörterbuch von Schlüsseln und Werten organisiert.Du kannst diese Werte auf dem Server mit den Methoden setValue, getValue, updateValue und removeValue manipulieren.Diese Methoden funktionieren alle synchron ohne Ausgabe.
- Die Methoden hasLoaded und waitForDataLoadAsync sind verfügbar, um sicherzustellen, dass die Daten vor dem Zugriff geladen wurden.Wir empfehlen, dies einmal während eines Ladebildschirms zu tun, bevor andere Systeme gestartet werden, um zu vermeiden, dass vor jeder Interaktion mit Daten auf dem Client Überprüfungsfehler ausgeführt werden müssen.
- Eine hasErrored-Methode kann abfragen, ob die ursprüngliche Belastung des Spieler:infehlgeschlagen ist, wodurch sie Standarddaten verwendet.Überprüfen Sie diese Methode, bevor Sie dem Spieler erlauben, Einkäufe zu tätigen, da Einkäufe ohne eine erfolgreiche ladennicht in Daten gespeichert werden können.
- Ein playerDataUpdated -Signal feuert mit den player , key und value aus, wann immer die Daten eines Spieler:ingeändert werden.Einzelne Systeme können sich diesem abonnieren.
Änderungen am Client replizieren
- Jede Änderung der Spielerdaten in PlayerDataServer wird auf PlayerDataClient repliziert, es sei denn, dieser Schlüssel wurde mit setValueAsPrivate als privat markiert
- setValueAsPrivate wird verwendet, um schlüssel anzuzeigen, die nicht an den client gesendet werden sollen
- PlayerDataClient enthält eine Methode, um den Wert eines Schlüssels zu erhalten (erhalten) und ein Signal, das abgefeuert wird, wenn es aktualisiert wird (aktualisiert).Eine hasLoaded-Methode und ein loaded-Signal sind ebenfalls enthalten, so dass der Client auf Daten warten kann, die geladen und repliziert werden, bevor er seine Systeme startet
- PlayerDataClient ist ein singleton, das von jedem client-code, der in derselben umgebung ausgeführt wird, erforderlich und zugänglich sein kann
Wiederhole Fehler an den Client
- Fehlerzustände, die beim Speichern oder Laden von Spielerdaten auftreten, werden auf PlayerDataClient repliziert.
- Greife auf diese Informationen mit den Methoden getLoadError und getSaveError und den Signalen loaded und saved zu.
- Es gibt zwei Arten von Fehlern: DataStoreError (die DataStoreService Anfrage fehlgeschlagen ist) und SessionLocked (siehe Sitzungssperre ).
- Verwende diese Ereignisse, um Client-Kaufaufforderungen zu deaktivieren und Warndialoge zu implementieren. Dieses Bild zeigt ein Beispieldialog:

Spielerdaten speichern

Wenn der Spieler das Spiel verlässt, führt das System die folgenden Schritte aus:
- Überprüfe, ob es sicher ist, die Daten des Spieler:inin den Storezu schreiben.Szenarien, in denen es nicht sicher wäre, umfassen die Daten des Spieler:in, die nicht geladen werden oder immer noch geladen werden.
- Stelle eine Anfrage durch die SessionLockedDataStoreWrapper an, um den aktuellen In-Memory-Datenwert in den Datenstore zu schreiben und die Sitzungssperre zu entfernen, sobald sie vollständig ist.
- Löscht die Daten des Spieler:in(und andere Variablen wie Metadaten und Fehlerstatus) aus dem Serverspeicher.
Auf einer periodischen Schleife schreibt der Server die Daten eines jeden Spieler:inin den Datenstore (sofern es sicher ist, sie zu speichern).Diese willkommene Redundanz mindert den Verlust im Falle eines Serverabsturzes und ist auch erforderlich, um die Sitzungssperre zu wahren.
Wenn eine Anfrage zum Herunterfahren des Servers empfangen wird, geschieht Folgendes in einem BindToClose Callback:
- Es wird eine Anfrage gestellt, die Daten jedes Spieler:inauf dem Server zu speichern, nach dem Prozess, durch den normalerweise gegangen wird, wenn ein Spieler den Server verlässt.Diese Anfragen werden parallel gestellt, da BindToClose Rückrufe nur 30 Sekunden Zeit haben, um abgeschlossen zu werden.
- Um die Speicherung zu beschleunigen, werden alle anderen Anfragen in der Warteschlange eines Schlüssels aus der unterliegenden DataStoreWrapper (siehe Wiederholungen).
- Der Rückruf wird nicht zurückgegeben, bis alle Anfragen abgeschlossen sind.