Hintergrund
Roblox bietet eine Reihe von APIs, mit denen Sie sich mit Daten-Stores über DataStoreService verbinden. Die häufigste Verwendungsfall für diese APIs ist das Speichern, Laden und Replizieren von Spielerdaten. Das heißt, Daten, die mit dem Fortschritt, den Kaufen und anderen Sitzung Merkmalen des Spieler:inverbunden sind, die zwischen einzelnen Spielsitzungen bestehen bleiben.
Die meisten Erlebnisse auf Roblox verwenden diese APIs, um einige Formen eines Spielers-Datensystems zu implementieren. Diese Umsetzungen unterscheiden sich in ihrem Ansatz, aber im Allgemeinen versuchen, die gleichen Probleme zu lösen.
Gewöhnliche Probleme
Das sind einige der häufigsten Probleme, die Player-Daten-Systeme versuchen, zu lösen:
In Memory Access: DataStoreService Anfragen machen Web-Anfragen, die alsynchron sind und unter Rate-Limits operieren. Dies ist für einen anfänglichen Laden am Beginn der Sitzung geeignet, aber nicht für hohe Frequenz-Lesungen und Schreibungen während des normalen Gameplay. Die meisten Entwickler-Player-Datensysteme speichern diese Daten in-Memory auf dem
- Zu Beginn der Sitzung initialer Lesen
- Schließe den Schreiben am Ende der Sitzung
- Periodic schreibt zu einem Zeitintervall, um das Szenario zu verringern, bei dem das endgültige Schreiben fehlschlägt
- Schreibt, um sicherzustellen, dass die Daten beim Verarbeiten eines kaufengespeichert werden
Effiziente Speicherung: Wenn Sie alle Daten einer Spieler:inin einer einzigen Tabelle speichern, können Sie mehrere Werte atomar aktualisieren und die gleichen Daten in weniger Anfragen verarbeiten. Es entfernt auch das Risiko der Inter-Werte-Desynchronisierung und macht Rollbacks einfacher zu verstehen.
Einige Entwickler implementieren auch benutzerdefinierte SerIALisierung, um große Datenstrukturen zu komprimieren (z. B. um Inhalte im Spiel zu speichern, die vom Benutzer erstellt wurden).
Replikation: Der Client benötigt regelmäßigen Zugriff auf die Daten eines Spieler:in(z. B. zum Aktualisieren der Benutzeroberfläche). Ein generischer Ansatz zur Replikation von Spieldaten auf den Client ermöglicht es Ihnen, diese Informationen zu übermitteln, ohne benutzerdefinierte Replikationssysteme für jeden Teil der Daten zu erstellen. Entwickler möchten oft die Option, wählerisch darüber zu sein, was kopiert und nicht kopiert wird.
Fehlerbehandlung: Wenn DataStores nicht zugänglich ist, implementieren die meisten Lösungen einen Wiederholmechanismus und einen Fallback auf 'Standard'-Daten. Spezielle Aufmerksamkeit ist erforderlich, um sicherzustellen, dass das Fallback-Daten nicht 'reale' Daten überschreiben und dass dies dem Spieler angemessen kommuniziert wird.
Versuche es noch einmal: Wenn Daten nicht verfügbar sind, implementieren die meisten Lösungen einen Versuchsmechanismus und einen Standard-Fallback. Beachten Sie besonders, dass der Standard-Fallback-Daten nicht später „echte“ Daten überschreiben und die Situation dem Spieler angemessen kommunizieren.
Sitzungssperre: Wenn die Daten eines einzelnen Spieler:inauf mehreren Servern in seinem Speicher sind, können auf mehreren Servern außerhalb der Erinnerung verlorene Daten auftreten. Dies kann zu Datenverlust und häufigem Item-Duplikationsloophole führen.
Atom-Kauf-Verarbeitung: Überprüfen Sie, vergeben Sie und dokumentieren Sie Käufe atomar, um Artikel nicht zu verlieren oder mehrfach zu vergeben.
Beispielcode
Roblox hat einen Referenzcode, der dir hilft, deine Spielerdatensysteme zu entwerfen und zu erstellen. Der Rest dieser Seite untersucht Hintergrund, Umsetzung und allgemeine Hinweise.
Nachdem du das Modell in Studio importiert hast, solltest du die folgende Ordnerstruktur sehen:
Architektur
Dieses Diagramm auf hohem Niveau zeigt die Schlüsselsysteme im Beispiel und wie sie mit Code im Rest der Erlebnisinteragieren.
Neuer Versuche
Klasse: DataStoreWrap
Hintergrund
Da DataStoreService Web-Anfragen unter der Haube erstellt, garantiert seine Anfragen nicht den Erfolg. Wenn dies geschieht, werden die DataStore - Methoden Fehler anzeigen, so dass Sie sie behandeln können.
Ein häufiges "Gotcha" kann auftritt, wenn Sie versuchen, Fehler bei der Verarbeitung von Daten wie diesen zu beheben:
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 Wiederholmechanismus für eine generische Funktion ist, ist er nicht für DataStoreService-Anfragen geeignet, da er die Reihenfolge nicht garantiert, in der Anfragen gemacht werden. Das Erhalt der Reihenfolge der Anfragen ist wichtig für DataStoreService-Anfragen, da sie mit dem Zustand interagieren. Berücksichtigen Sie das folgende Szenario:
- Anfrage A wird verwendet, um den Wert von Schlüssel K auf 1 zu setzen.
- Die Anfrage ist fehlgeschlagen, so dass ein erneuter Versuch in 2 Sekunden ausgeführt wird.
- Before the retry occurs, request B sets the value of K to 2, but the retry of request A immediately overwrites this value and sets K to 1.
Obwohl UpdateAsync auf der neuesten Version des Wertes des Schlüssels operiert, müssen Anfragen, um UpdateAsync zu verarbeiten, immer noch verarbeitet werden, um ungültige transiente Zustände zu vermeiden (z. B. ein Kauf subtrahiert Münzen, bevor ein Münzen-Hinzufügungsprozess abgeschlossen ist, was zu negat
Unser Player-Datensystem verwendet eine neue Klasse, DataStoreWrapper, die Ansätze bietet, die garantiert sind, dass sie in der Reihenfolge pro Schlüssel verarbeitet werden.
Ansatz
DataStoreWrapper bietet Methoden, die den Methoden entsprechen Class.GlobalDataStore|DataStore : DataStore , DataStore:GetAsync() ,
Diese Methoden, wenn sie aufgerufen werden:
Fügen Sie die Anfrage einer Warteschlange hinzu. Jeder Schlüssel hat seine eigene Warteschlange, in der Anfragen in Reihenfolge verarbeitet werden. Der Anfrage-Thread gibt zurück, bis die Anfrage abgeschlossen ist.
Diese Funktionalität basiert auf der ThreadQueue -Klasse, die ein Coroutine-basierter Task-Scheduler und Rate-Limit ist. Statt einer Versprechen zurückzugeben, gibt ThreadQueue den aktuellen Thread zurück, bis die Operation abgeschlossen ist und ein Fehler wird geworfen, wenn sie fehlschlägt. Dies ist mit idiomischen asynchronen Lua-Mustern konsistenter.
Wenn eine Anfrage fehlscht, versucht sie es erneut mit einem konfigurierbaren Exponential-Backoff. Diese Anfragen werden Teil des Rückrufs sein, der an den ThreadQueue gesendet wurde, so dass sie vor der nächsten Anfrage in der Warteschlange für diesen Schlüssel abgeschlossen sind.
Wenn eine Anfrage abgeschlossen ist, kehrt die Anforderungsmethode mit dem success, result Muster zurück
DataStoreWrapper zeigt auch Methoden, um die Warteschlange für einen bestimmten Schlüssel zu erhalten und alte Anfragen zu löschen. Die letzere Option ist besonders nützlich in Szenarien, in denen der Server heruntergefahren wird und keine Zeit zum Verarbeiten von Anfragen, aber die neuesten Anfragen, ist.
Bewegungen
DataStoreWrapper folgt dem Prinzip, dass alle Datenstoresanfragen, die außerhalb von extremen Szenarien erfolgen, erlaubt sein sollten, abgeschlossen zu werden (erfolgreich oder anders), auch wenn eine kürzere Anfrage sie redundant macht. Wenn eine neue Anfrage auftritt, werden nicht veraltete Anfragen aus der Warteschlange entfernt, sondern stattd
Es ist schwer zu entscheiden, auf ein intuitives Set von Regeln für den Fall zu entscheiden, wenn eine Anfrage sicher aus der Warteschlange entfernt werden kann. Betrachten Sie die folgende Warteschlange:
Value=0, SetAsync(1), GetAsync(), SetAsync(2)
Das erwartete Verhalten ist, dass GetAsync()``1 zurückgibt, aber wenn wir die Class.GlobalDataStore:SetAsync()|SetAsync() -Anfrage aus der Warteschlange entfernen, weil sie durch den neuesten ersetzt wird, wird 1> 0 1> zurückgegeben.
Die logische Fortschrittsfolge ist, dass, wenn eine neue Schreibanfrage hinzugefügt wird, nur prune alte Anfragen, solange der neueste Leseranfrage zurückreicht, als weit zurück wie der neueste Leseranfrage. UpdateAsync() , ist die häufigste Operation (und die einzige, die von diesem System verwendet wird), kann sowohl lesen als auch schreiben, so dass es schwierig sein würde, innerhalb dieses Designs dies ohne
DataStoreWrapper könnte Sie auffordern, anzugeben, ob eine Class.GlobalDataStore:UpdateAsync()|UpdateAsync() -Anfrage gelesen und/oder geschrieben werden soll, aber sie hätte keine Anwendbarkeit auf unserer Spieler-Daten-System, wo dies vor dem Zeitpunkt der Sitzungssperre (abgedeckt in mehr Details später) nicht vorhersehbar ist.
Nachdem er aus der Warteschlange entfernt wurde, ist es schwer zu entscheiden, auf eine intuitive Regel für wie dies zu handhaben. Wenn eine DataStoreWrapper-Anfrage gemacht wird, wird der aktuelle Thread erzeugt, bis sie abgeschlossen ist. Wenn wir alte Anfragen aus der Warteschlange entfernt, müssen
Letztlich ist unser Ansatz, dass der einfache Ansatz (jedem Anfrage zu verarbeiten) hier bevorzugt ist und ein klareres Umfeld schafft, um in einem komplexen Problem wie der Sitzungssperre zu navigieren. Die einzige Ausnahme ist während DataModel:BindToClose(), wo das Löschen
Session-Sperrung
Klasse: SessionLockedDataStoreWrapper
Hintergrund
Spieldaten werden in den Speicher auf dem Server gespeichert und werden nur gelesen und geschrieben, wenn nötig. Sie können sofort in-Memory-Spielerdaten aufrufen und aktualisieren, ohne Web-Anfragen zu benötigen und die DataStoreService -Grenzen zu überschreiten.
Damit dieses Modell wie vorgesehen funktioniert, ist es unerlässlich, dass nicht mehr als ein Server die Daten eines Spieler:inin den DataStore laden kann, während dieselbe Zeit.
Zum Beispiel, wenn Server A die Daten eines Spieler:inlädt, kann Server B diese Daten nicht laden, bis Server A seine Verriegelung aufhebt, während ein Server A seine letzte Speicherversion veröffentlicht. Ohne eine Verriegelungsmechanismus kann Server B aus-of-Date-Spieler-Daten aus dem Daten-Store vor Server A speichern, bevor Server A die Chance hat, die neuere Version zu speichern, die sie in der Erinnerung hat. Dann wenn Server A seine ne
Obwohl Roblox nur eine Verbindung mit einem Server auf einmal zulässt, kannst du nicht darauf hinweisen, dass Daten aus einer Sitzung immer gespeichert werden, bevor die nächste Sitzung beginnt. Überlegen Sie die folgenden Szenarien, die passieren können, wenn ein Spieler Server A verlässt:
- Server A macht eine DataStore Anfrage, um seine Daten zu speichern, aber die Anfrage fehlschlägt und erfordert mehrere Wiederholungen, um sie erfolgreich abzuschließen. Während des Wiederholungszeitraums tritt der Spieler auf Server B bei.
- Server A macht zu viele UpdateAsync() Anrufe auf dieselbe Schlüssel und wird gedrosselt. Die endgültige Speicheranfrage wird in einer Warteschlange platziert. Während die Anfrage in der Warteschlange ist, wird der Spieler auf Server B beigetreten.
- Auf Server A, einige Code mit dem PlayerRemoving Ereignis liefert vor dem Speichern der Spieler:in. Bevor diese Operation abgeschlossen ist, tritt der Spieler dem Server B bei.
- Die Leistung von Server A hat sich auf den Punkt degradiert, dass der finale Speicher bis zum Beitritt des Spielers zu Server B verzögert wird.
Diese Szenarien sollten selten sein, aber sie auftretenauf, insbesondere in Situationen, in denen ein Spieler von einem Server abbindet und sich mit einem anderen in rascher Abfolge verbindet (z. B. während des Teleпорierens). Einige bösartige Benutzer versuchen sogar, dieses Verhalten zu missbrauchen, um Aktionen auszuführen, ohne dass sie bestehen. Dies kann in Spielen, die die Erlaubnis des Spielers erlauben, und ist ein häufiger Quelle für Artikel-Duplikatschm
Session-Schlüssel-Sperrung adressiert diese Schwachigkeit, indem sie sicherstellt, dass wenn der Spieler:inDataStore первый von dem Server gelesen wird, der Server atomar auf den Schlüssel-Metadaten innerhalb der gleichen UpdateAsync()-Anruf gespeichert wird. Wenn dieser Schlüsselwert vorhanden ist, wenn ein ander
Ansatz
SessionLockedDataStoreWrapper ist ein Meta-Wrap um die DataStoreWrapper -Klasse. DataStoreWrapper bietet Queue- und Wiederholungsfunktionen, die 0> SessionLockedDataStore0> mit Sitzungssperrung ergänzt.
SessionLockedDataStoreWrapper passt auf jede Callback
Die Transformationsfunktion überträgt in UpdateAsync für jede Anfrage die folgenden Operationen aus:
Überprüft, ob der Schlüssel sicher ist, um Zugriff darauf zu erhalten, und verzichtet auf die Operation, wenn er nicht ist. "Sicher zuzugreifen" bedeutet:
Das Metadaten-Objekt der Schlüssel enthält keinen unerkannten Wert LockId, der vor weniger als der Verfallszeit des Servers aktualisiert wurde. Dies berücksichtigt das Einhalten eines von einem anderen Server platzierten Schlüssels und die Ignorierung dieses Schlüssels, wenn er abgelaufen ist.
Wenn dieser Server seine eigene LockId -Wert in die Schlüssel-Metadaten vorher eingefügt hat, dann ist dieser Wert immer noch in der Schlüssel-Metadaten. Dies berücksichtigt die Situation, in der ein anderer Server das Schloss (per Erlöschung oder per Kraft) übernommen hat und dann veröffentlicht hat. Alternativ formuliert, auch wenn LockId LockId
Class.GlobalDataStore:UpdateAsync()|UpdateAsync führt die DataStore Operation, die von der benötigten Consumer-SessionLockedDataStoreWrapper angeforderte, aus. Zum Beispiel übersetzt 0> Class.GlobalDataStore:GetAsync()|GetAsync()0> in UpdateAsync3> .
Abhängig von den übergebenen Parametern in der Anfrage, UpdateAsync sperrt oder schaltet den Schlüssel:
Wenn der Schlüssel gesperrt ist, setzt UpdateAsync den LockId im Schlüssel-Metadaten auf einen GUID. Dieser GUID wird im Speicher des Servers gespeichert, sodass er beim nächsten Zugriff auf den Schlüssel wieder verifiziert werden kann. Wenn der Server bereits ein Schl
Wenn der Schlüssel freigeschaltet werden soll, UpdateAsync entfernt die LockId in der Schlüsselmetadaten.
Ein ein benutzerdefinierter Wiederholungs-DataStoreWrapper -Handler wird in das unterliegende DataStore übergegeben, damit die Operation erneut versucht wird, wenn sie auf Schritt 1 aufgrund der Sitzung gesperrt wird.
Eine benutzerdefinierte Fehler-Nachricht wird auch an den Verbraucher zurückgegeben, so dass das Spieler-Datensystem im Falle einer Sitzungssperre für den Client einen alternativen Fehler melden kann.
Bewegungen
Das Sitzungssperrungsregime basiert auf einem Server, der immer seinen Sperr auf einem Schlüssel freigibt, wenn er mit ihm fertig ist. Dies sollte immer durch eine Anweisung passieren, um den Schlüssel als Teil des finalen Schreibens in PlayerRemoving oder BindToClose() zu entfernen.
Die Freischaltung kann jedoch in bestimmten Situationen feit fehlen. Zum Beispiel:
- Der Server ist abgestürzt oder DataStoreService war für alle Anforderungen an den Schlüssel nicht verfügbar.
- Aufgrund eines Fehlers in der Logik oder eines ähnlichen Bugs wurde die Anleitung zum Freischalten des Schlüssels nicht ausgeführt.
Um das Limit auf einem Schlüssel zu verwalten, musst du ihn regelmäßig für so lange zugreifen, wie er in der Erinnerung geladen ist. Dies würde normalerweise als Teil des automatischen Speichers geschehen, wenn du dies manuell tun musst. Dieses System zeigt auch einen refreshLockAsync-Methode an, wenn du es manuell tun musst.
Wenn die Locks-Verfallszeit überschritten wurde, ohne dass die Locks aktualisiert wurden, dann ist jeder Server kostenlos, um die Locks zu übernehmen. Wenn ein anderer Server die Locks übernimmt, versuchen derzeitige Server, den Schlüssel zu lesen oder zu schreiben, fehlgeschlagen, es sei denn, er etabliert einen neuen Schlüssel.
Entwicklerproduktverarbeitung
Singleton: ReceiptHandler >
Hintergrund
Der ProcessReceipt -Callback führt die kritische Arbeit aus, um festzustellen, wann ein kaufenfinalisiert werden soll. ProcessReceipt wird in sehr spezifischen Szenarien aufgerufen. Für seine Reihe von Garantien siehe MarketplaceService.ProcessReceipt.
Obwohl die Definition von "Handling" ein Kauf zwischen Erlebnissen unterschiedlich sein kann, verwenden wir die folgenden Kriterien
Der Kauf wurde nicht zuvor behandelt.
Der Kauf wird in der aktuellen Sitzung widerspiegelt.
Dies erfordert die folgenden Operationen auszuführen, bevor Sie PurchaseGranted zurückkehren:
- Überprüfen Sie, dass PurchaseId nicht bereits als "behandelt" eingetragen wurde.
- Belohnen Sie den Kauf in den In-Memory-Spielerdaten des Spieler:in.
- Erstellen Sie das PurchaseId als verarbeitete ID in den Spielernamen-Daten des Spieler:in.
- Schreiben Sie die in-Memory-Spielerdaten des Spieler:inin den DataStore .
Die Sitzungssperre erleichtert diesen Flow, da Sie sich nicht mehr um die folgenden Szenarien sorgen müssen:
- Die In-Memory-Spielerdaten auf dem aktuellen Server potenziell veraltet, so dass Sie den neuesten Wert aus dem DataStore abrufen müssen, bevor Sie die PurchaseId -History verifizieren
- Der Rückruf für dieselben Käufe, die auf einem anderen Server ausgeführt werden, bei denen Sie sowohl die PurchaseId-Historie lesen als auch schreiben müssen, und die aktualisierten Spielerdaten mit dem Kauf atomar speichern, um Verluste zu vermeiden
Session-Locking-Garantien, dass, wenn ein Versuch, auf den Spieler:in's DataStore zu schreiben, erfolgreich ist, kein anderes Server hat erfolgreich gelesen oder geschrieben auf den Spieler:in's DataStore zwischen der Datenbeladung und Speicherung in diesem Server. Kurz gesagt, die in-Memory-Player-Dat
Ansatz
Die Kommentare in ReceiptProcessor umreißen den Ansatz:
Stellen Sie sicher, dass die Daten des Spieler:inauf diesem Server aktuell geladen sind und dass sie ohne Fehler geladen wurden.
Da dieses System Sitzungssperrung verwendet, überprüft dieser Check auch, dass die in-Memory-Daten die neueste Version sind.
Wenn die Daten des Spieler:innoch nicht geladen wurden (was erwartet wird, wenn ein Spieler dem Spiel beitritt), warten Sie auf die Daten des Spieler:in, um sie zu laden. Das System hört auch auf den Spieler, der das Spiel verlässt, bevor seine Daten geladen sind, da er nicht unbestimmt bleiben und diesen Rückruf nicht auf diesem Server erneut aufgerufen wird, wenn der Spieler das Spiel verlässt.
Überprüfen Sie, dass die PurchaseId nicht bereits als verarbeitetes Objekt in den Spielerdaten aufgezeichnet ist.
Aufgrund der Sitzungssperre ist die Matrix von PurchaseIds , die der System in der Erinnerung hat, die most up-to-Date-Version. Wenn die PurchaseId als verarbeitet und in einem W
Aktualisieren Sie die Spielerdaten lokal auf diesem Server, um die kaufenzu "belohnen".
ReceiptProcessor nimmt einen generischen Rückrufansatz und weist für jedes 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-Memory-Daten in den DataStore zu speichern, wobei PurchaseGranted zurückgegeben wird, wenn die Anfrage erfolgreich ist. Wenn nicht, kehre NotProcessedYet zurück.
Wenn diese Speicheranfrage nicht erfolgreich ist, kann eine spätere Anfrage, die Spieler:inzu speichern, immer noch erfolgreich sein. Während der nächsten ProcessReceipt -Anruf, Stufe 2 behandelt diese Situation und gibt PurchaseGranted zurück.
Spielerdaten
Singletons: PlayerData.Server PlayerData.Client
Hintergrund
Module, die eine Schnittstelle für Spielcode bieten, um Player-Sitzungsdaten synchron zu lesen und zu schreiben, sind in Roblox-Erlebnissen häufig. Dieser Abschnitt umfasst PlayerData.Server und PlayerData.Client.
Ansatz
PlayerData.Server und PlayerData.Client verarbeiten gefolgte Profile:
- Laden der Daten des Spieler:inin den Speicher, einschließlich der Fälle, in denen es nicht geladen werden kann
- Bereitstellen einer Oberfläche für Server-Code, um die Spielerdaten zu erfragen und zu ändern
- Änderungen in den Daten des Spieler:inauf dem Client replizieren, damit der Client-Code darauf zugreifen kann
- Wiederholen von Lade- und/oder Speicherfehlern auf dem Client, damit er Fehler-Dialoge anzeigen kann
- Speichern der Daten des Spieler:inperiodisch, wenn der Spieler geht, und wenn der Server heruntergefahren wird
Laden von Spielerdaten
SessionLockedDataStoreWrapper macht eine getAsync Anfrage an den Datenstores.
Wenn diese Anfrage fehlschlägt, werden die Standarddaten verwendet und das Profil als "errored" markiert, um sicherzustellen, dass es nicht später in den Daten-Store geschrieben wird.
Eine alternative Option ist es, den Spieler:inzu kicken, aber wir empfehlen, den Spieler mit Standarddaten zu spielen und die Nachrichten zu löschen, was passiert ist, anstatt sie aus dem Erlebnis zu entfernen.
Ein ursprüngliches Ladebündel wird an PlayerDataClient gesendet, das die geladenen Daten und den Fehlerstatus enthält (wenn vorhanden).
Alle Threads, die mit waitForDataLoadAsync für den Spieler ausgeführt wurden, werden wieder aufgenommen.
Bereitstellen einer Interface für Server-Code
- PlayerDataServer ist ein Singleton, das von jedem Server-Code, der in derselben Umgebung ausgeführt wird, benötigt und aufgerufen werden kann.
- Spielerdaten werden in ein Wörterbuch von Schlüsseln und Werten organisiert. Sie können diese Werte auf dem Server mit den setValue, getValue, updateValue und 2>PlayerRemoveValue2> Methoden manipulieren. Alle diese Methoden arbeiten synchron ohne Ausgabe.
- Die Methoden hasLoaded und waitForDataLoadAsync sind verfügbar, um sicherzustellen, dass die Daten geladen sind, bevor Sie auf sie zugreifen. Wir empfehlen, dies einmal während eines Ladebildschirms zu tun, bevor andere Systeme beginnen, um zu überprüfen, ob Sie beim Zugriff auf die Daten auf dem Client Fehler beim Laden haben.
- Eine hasErrored Methode kann abrufen, ob die ursprüngliche Lade des Spieler:infehlgeschlagen ist, wodurch die verwendeten Standarddaten sind. Überprüfen Sie diese Methode, bevor Sie dem Spieler erlauben, irgendwelche Käufe vorzunehmen, da Käufe nicht in Daten ohne einen erfolgreichen ladengespeichert werden können.
- Ein playerDataUpdated-Signal feuert mit dem player , key und 2>value2> ab, wann immer die Daten eines Spieler:ingeändert werden. Einzelные Systeme können sich daran abmelden.
Änderungen des Clients replizieren
- Jede Änderung der Spielerdaten in PlayerDataServer wird auf PlayerDataClient repliziert, es sei denn, dieser Schlüssel wurde mit setValueAsPrivate markiert
- setValueAsPrivate wird verwendet, um Schlüssel zu beschreiben, die nicht an den Client gesendet werden sollten
- PlayerDataClient enthält eine Methode, um den Wert eines Schlüssels zu erhalten (erhalten) und ein Signal, das abgerufen wird, wenn es aktualisiert wird (aktualisiert). Ein hasLoaded -Methode und ein loaded -Signal sind auch enthalten, sodass der Client auf die Daten warten kann, bevor seine Systeme starten, und die Daten kopieren und replizieren, bevor sie aktualisiert werden.
- PlayerDataClient ist ein Singleton, das von jedem Client-Code, der in derselben Umgebung ausgeführt wird, benötigt und aufgerufen werden kann
Fehler auf dem Client reproduzieren
- Fehlerstatuses werden beim Speichern oder Laden von Spielerdaten auf PlayerDataClient festgestellt.
- Zugreifen Sie auf diese Informationen mit den getLoadError und getSaveError Methoden und den loaded und 2>saved2> Signalen.
- Es gibt zwei Arten von Fehlern: DataStoreError (die Anfrage DataStoreService ist fehlgeschlagen) und SessionLocked (siehe 1> Sitzungssperrung1>).
- Verwenden Sie diese Ereignisse, um die Kaufanfragen des Clients zu deaktivieren und Warnungsdialogen zu implementieren. Dieses Bild zeigt ein Beispiel-Dialog:
Spielerdaten speichern
Wenn der Spieler das Spiel verlässt, ergreift das System die folgenden Schritte:
- Überprüfen Sie, ob es sicher ist, die Daten des Spieler:inin den Storezu schreiben. Szenarien, in denen es sicher nicht sicher wäre, enthalten die Daten des Spieler:in, die nicht geladen werden oder immer noch geladen werden.
- Mit der SessionLockedDataStoreWrapper Anfrage wird der aktuelle In-Memory-Datenwert in den Datenstores geschrieben und das Sitzungsschloss einmal abgeschlossen, sobald die Sitzung abgeschlossen ist.
- Löscht die Daten des Spieler:in(und andere Variablen wie Metadaten und Fehlerstatuses) aus der Server-Speicherung.
Auf einer periodischen Schleife schreibt der Server die Daten jedes Spieler:inin den Datenstores (je nach Sicherheitsbedenken). Dies verringert die Verluste im Falle eines Servercrashs und ist auch erforderlich, um die Sitzungssperre zu verwalten.
Wenn eine Anfrage zum Server-Shutdown erhalten wird, wird das Folgende in einem BindToClose-Callback geschehen:
- Eine Anfrage wird gemacht, um die Daten jedes Spieler:inauf dem Server zu speichern, nach dem Prozess, den ein Spieler normalerweise verlässt, wenn er den Server verlässt. Diese Anfragen werden parallel ausgeführt, da BindToClose-Rückrufe nur 30 Sekunden lang abgeschlossen werden können.
- Um die Speicherungen zu beschleunigen, werden alle anderen Anfragen in der Warteschlange jedes Schlüssels von dem unterliegenden DataStoreWrapper (siehe Versuche erneut ) entfernt.
- Der Rückruf wird nicht ausgeführt, bis alle Anfragen abgeschlossen sind.