Hình nền
Roblox cung cấp một loạt các API để kết nối với các nhà lưu trữ dữ liệu thông qua DataStoreService . Các trường hợp sử dụng phổ biến nhất cho các API này là để lưu, tải và sao chép dữ liệu người chơi . Đó là, dữ liệu li
Hầu hết các trải nghiệm trên Roblox sử dụng những API này để thực hiện một số hình thức của hệ thống dữ liệu người chơi. Những implementation này khác nhau trong phương pháp của họ, nhưng nói chung là tìm kiếm giải quyết cùng một bộ sưu tập các vấn đề.
Những vấn đề thông thường
Dưới đây là một số vấn đề phổ biến nhất hệ thống dữ liệu người chơi cố gắng giải quyết:
Trong quản trị trong khải tượng: DataStoreService các yêu cầu làm cho web yêu cầu hoạt động nhưng bị giới hạn bởi giới hạn tỷ lệ. Điều này thích hợp cho một lượng tải
- Đọc ban đầu ở đầu của một phiên
- Lưu cuối cùng ở cuối phiên
- Các biểu tượng thời gian thực hiện theo một lịch trình để giảm thiểu tình trạng trượt vòng đua khi ghi lại
- Viết để đảm bảo dữ liệu được lưu khi xử lý một mua hàng
Lưu trữ hiệu quả: Lưu tất cả dữ liệu phiên của một người chơi trong một bảng duy nhất cho phép bạn cập nhật nhiều giá trị hạt nhân và xử lý cùng một lúc dữ liệu trong ít yêu cầu hơn. Nó cũng loại bỏ nguy cơ bị desynchron hóa giữa các giá trị và làm cho các cuộc
Một số nhà phát triển cũng tích hợp hóa serial hóa tùy chỉnh để nén các cấu trúc dữ liệu lớn (thường được lưu vào trong nội dung game người dùng tạo).
Replication: Cliente cần thường xuyên truy cập vào dữ liệu của một người chơi (ví dụ, để cập nhật UI). Một phương pháp chung để sao chép dữ liệu người chơi cho client là bạn có thể gửi thông tin này mà không cần phải tạo các hệ thống sao chép ri
Xử lý lỗi: Khi DataStores không thể được truy cập, hầu hết các giải pháp sẽ thực hiện một cơ chế chuyển đổi thử lại và một cơ chế để đổi đến 'nguyên tắc' dữ liệu. Đặc biệt cần thiết để đảm bảo rằng các dữ liệu đổi đều k
Lần thử lại: Khi lưu trữ dữ liệu không khả dụng, hầu hết các giải pháp thực hiện cơ chế thử lại và một cơ chế dự phòng cho dữ liệu mặc định. Hãy đặc biệt cẩn thận để đảm bảo rằng dữ liệu dự phòng không bao giờ đổi trên "thực" dữ liệu, và truyền
Khóa phiên cuộc: Nếu dữ liệu của một người chơi được tải và trong khoản nhớ trên nhiều máy chủ, vấn đề có thể xảy ra trong đó một máy chủ lưu thông tin lỗi thời. Điều này có thể dẫn đến thiệt hại dữ liệu và lỗi dupliquản dữ liệu phổ biến.
Xử lý mua hàng nguyên tử: Xác minh, trao tặng và ghi nhận mua hàng nguyên tử để ngăn chặn các mục bị mất hoặc được trao tặng nhiều lần.
Mã mẫu
Roblox có mã tham khảo để giúp bạn thiết kế và xây dựng các hệ thống dữ liệu người chơi. Phần còn lại của trang này xem xét hình nền, chi tiết thực hiện và những lưu ý chung.
Sau khi nhập mô hình vào Studio, bạn nên thấy cấu trúc thư mục sau đây:

Kiến trúc
Hình ảnh cấp cao này minh họa các hệ thống chính trong mẫu và cách chúng giao tiếp với mã trong phần còn lại của trải nghiệm.

Lần thử lại
Lớp: DataStoreWrap
Hình nền
Khi DataStoreService làm mẹo web dưới máy chủ, các yêu cầu của nó không được đảm bảo thành công. Khi điều này xảy ra, các phương thức DataStore thả lỗi, cho phép bạn xử lý chúng.
Một "nhận được" thông thường có thể xảy ra nếu bạn cố gắng xử lý các sự cố lỗi dữ liệu như thế này:
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
Mặc dù đây là một cơ chế thử lại hoàn toàn hợp lý cho một chức năng genéric, nó không phù hợp với các yêu cầu DataStoreService vì nó không đảm bảo thứ tự mà các yêu cầu được thực hiện. Giữ trật tự các yêu cầu là quan trọng
- Yêu cầu A để đặt giá trị của chìa khóa K để 1.
- Yêu cầu thất bại, vì vậy một lần thử lại đã được lịch sẵn để chạy trong 2 giây.
- Trước khi thử lại xảy ra, yêu cầu B đặt giá trị của K đến 2, nhưng yêu cầu thử lại yêu cầu A ngay lập tức đặt giá trị này và đặt K để 1.
Mặc dù UpdateAsync hoạt động trên phiên bản mới nhất của giá trị chìa khóa, yêu cầu UpdateAsync vẫn phải được xử lý để đảm bảo các trạng thái không hợp lệ (
Hệ thống dữ liệu người chơi của chúng tôi sử dụng một lớp mới, DataStoreWrapper , which provides yielding retries that are guaranteed to be processed in order per chìa khóa.
Pendekatan

DataStoreWrapper cung cấp các phương thức tương ứng với các phương thức DataStore : Class.GlobalDataStore:GetAsync()|DataStore:GetAsync()</
Các phương thức này, khi được gọi:
Thêm yêu cầu vào hàng đợi. Mỗi chìa khóa có chính hàng đợi của nó, nơi các yêu cầu được xử lý theo thứ tự và theo dòng. Cây chủ đề yêu cầu cho đến khi yêu cầu đã hoàn tất.
Điều này dựa trên lớp ThreadQueue, một người lập lịch nhiệm vụ và giới hạn tỷ lệ dựa trên các hàm coroutine. Thay vì trả lời một hứa hẹn, ThreadQueue tạo ra một người lập lịch nhiệm vụ hiện tại cho đến khi hoạt động hoà
Nếu một yêu cầu thất bại, nó sẽ thử lại với một backoff phương trình có thể tùy chỉnh. Các lần thử lại này là một phần của hồ sơ gọi được gửi đến ThreadQueue, vì vậy chúng được đảm bảo hoàn tất trước khi yêu cầu tiếp theo trong hàng đợi cho chìa khóa này bắ
Khi một yêu cầu được hoàn thành, phương thức yêu cầu trả về mẫu success, result
DataStoreWrapper cũng tiết lộ các phương thức để nhận chiều dài hàng đợi cho một chìa khóa cụ thể và xóa các yêu cầu lỗi. Tùy chọn này đặc biệt hữu ích trong các tình huống khi máy chủ đang kết thúc và không có thời gian để xử lý bất kỳ yêu cầu nào ngoài y
Hang động
DataStoreWrapper 따라 the principle that, outside of extreme scenarios, every data store request should be allowed to complete (successfully or otherwise), even if a more recent request makes it redundant. When a new request occurs, stale requests aren't removed from the queue, but are instead allowed to complete before the new request is started. The rationale for this is rooted in this module's applicability as a generic data store utility rather than a specific tool for player data, and is as follows
Thật khó để quyết định về một bộ quy tắc thông minh cho khi một yêu cầu an toàn để loại bỏ khỏi hàng đợi. Hãy xem xét bộ đợi này:
Value=0, SetAsync(1), GetAsync(), SetAsync(2)
Hành vi mong đợi là GetAsync() sẽ trả lại 1 , nhưng nếu chúng tôi xóa yêu cầu SetAsync() từ hàng đợi do nó được thực hiện bằng cách giảm giá nó đến
Cuộc tiến hành logic là khi một yêu cầu viết mới được thêm vào, chỉ xử lý các yêu cầu mới nhất của yêu cầu đọc gần nhất. UpdateAsync() , bởi far những hành động phổ biến nhất (và là một trong n
DataStoreWrapper có thể yêu cầu bạn spécify whether an UpdateAsync() request was allowed to read and/or write, but it would have no applicability to our player data system, where this cannot be determined ahead of time do to the session locking mechanism (được bao gồm trong thông tin chi tiết hơn sau đó).
Một khi đã được loại bỏ khỏi hàng đợi, thì rất khó để quyết định về một quy tắc thông minh cho làm thế nào điều này nên được xử lý. Khi mộ
Cuối cùng, quan điểm của chúng tôi là rằng phương pháp đơn giản (xử lý mọi yêu cầu) là đẹp hơn ở đây và tạo một môi trường rõ ràng đ
Đăm đăm nhận
Lớp: SessionLockedDataStoreWrapper>
Hình nền
Dữ liệu người chơi được lưu trong bộ nhớ trên máy chủ và chỉ được đọc và viết vào những dữ liệu cơ bản khi cần thiết. Bạn có thể đọc và cập nhật dữ liệu người chơi trong máy chủ ngay mà không cần yêu cầu web và đảm bảo tốc độ DataStoreService .
Để model này hoạt động như ý muốn, là rất quan trọng không thể có hơn một máy chủ có thể tải dữ liệu của một người chơi vào bộ nhớ từ DataStore cùng một lúc.
Ví dụ, nếu máy chủ A tải dữ liệu của một người chơi, máy chủ B không thể tải dữ liệu đó cho đến khi máy chủ A phát hành khóa trên nó trong một lần lưu cuối cùng. Nếu không có cơ chế khóa, máy chủ A có
Mặc dù Roblox chỉ cho phép một kết nối client đến một máy chủ cùng một lúc, bạn không thể đoán rằng dữ liệu từ một phiên được lưu trước khi phiên kế tiếp bắt đầu. Hãy xem xét các tình huống sau đây có thể xảy ra khi một người chơi rời khỏi máy chủ A:
- Máy chủ A thực hiện một yêu cầu DataStore để lưu dữ liệu của họ, nhưng yêu cầu thất bại và yêu cầu một loạt các lần thử lại để hoàn tất. Trong khoảng thời gian truy cập lại, người chơi tham gia vào máy chủ B.
- Máy chủ A thực hiện quá nhiều UpdateAsync() gọi đến cùng một chìa khóa và bị giảm giá. Yêu cầu cuối cùng được đặt trong hàng đợi. Khi yêu cầu được đặt trong hàng đợi, người chơi tham gia vào máy chủ B.
- Trên máy chủ A, một số mã kết nối với sự kiện PlayerRemoving được lưu trước khi dữ liệu của người chơi được lưu. Trước khi hoàn thành hoạt động này, người chơi tham gia máy chủ B.
- Thông suất của máy chủ A đã bị đổi xuống đến mức mà lần lượt lưu cuối cùng được hoàn thành cho đến sau khi người chơi tham gia vào máy chủ B.
Các tình huống này nên hiếm, nhưng chúng vẫn xảy ra, đặc biệt là trong những tình huống mà một người chơi kết nối từ một máy chủ và kết nối với một máy chủ khác trong một hồi phục nhanh chóng (ví dụ, trong khi dịch chuyển). Một
Lỗi khóa này khiến cho lỗ hổng này bằng cách đảm bảo rằng khi một chìa khóa DataStore của người chơi đầu tiên được đọc bởi máy chủ, máy chủ sẽ viết một cái khóa vào metadữ
Pendekatan

SessionLockedDataStoreWrapper là một người kết hợp meta xung quanh lớp DataStoreWrapper . DataStoreWrapper cung cấp các chức năng que hồi và thử lại, which 0> SessionLockedDataStoreWrap0> bổ sung với khóa phiên đợi.
SessionLockedDataStoreWrapper đượ
Hàm chuyển hóa đã được gửi vào UpdateAsync cho mỗi yêu cầu thực hiện các hành động sau:
Xác minh rằng chìa khóa an toàn để truy cập, bỏ rơi hoạt động nếu không. "An toàn để truy cập" có nghĩa là:
Các dữ liệu khóa không bao gồm giá trị LockId không xác nhận đã được cập nhật lần cuối dưới thời gian hết hạn của khóa. Điều này đại diện cho việc tôn trọng một khóa đã được đặt bởi một máy chủ khác và bỏ qua việc xác nhận nếu n
Nếu máy chủ này đã đặt giá trị LockId của riêng mình trong cấu hình của chìa khóa trước đó, thì giá trị này vẫn ở trong cấu hình của chìa khóa. Điều này
UpdateAsync thực hiện hoạt động DataStore mà người tiêu dùng đã yêu cầu. Ví dụ, SessionLockedDataStoreWrapper dịch sang 0> function(value) return value kết thúc0> .
Tùy thuộc vào các tham số được truyền vào yêu cầu, UpdateAsync có thể mở khóa hoặc mở khóa chìa khóa:
Nếu chìa khóa được khóa, UpdateAsync đặt LockId trong metadữ của chìa khóa vào một GUID. GUID này được lưu trong bộ nhớ trên máy chủ để
Nếu chìa khóa được mở khóa, UpdateAsync thì điều đó sẽ xóa LockId trong métadữ của chìa khóa.
Một thẻ cố gắng tùy chỉnh được truyền vào DataStoreWrapper nền tảng để cho phép hoạt động được thực hiện nếu nó bị ngừng ở bước 1 do phiên đấu được khóa.
Một thông điện lỗi tùy chỉnh cũng được trả về người tiêu dùng, cho phép hệ thống dữ liệu người chơi báo cáo một lỗi alternatif trong trường hợp khóa phiên đấu cho client.
Hang động
Chế độ khóa phiên đại diện dựa trên một máy chủ luôn luôn phát hành khóa của nó khi nó đã làm xong với nó. Điều này luôn xảy ra thông qua một hướng dẫn để mở khóa khóa như một phần của cuộc viết cuối cùng ở <
Tuy nhiên, việc mở khóa có thể bị thất bại ở một số tình huống nhất định. Ví dụ:
- Máy chủ đã phá vỡ hoặc DataStoreService không thể hoạt động cho tất cả các lần thử truy cập chìa khóa.
- Do một lỗi trong logic hoặc lỗi tương tự, hướng dẫn để mở khóa chìa khóa đã không được thực hiện.
Để duy trì khóa trên một chìa khóa, bạn phải truy cập nó thường xuyên như một phần của chuỗi tự động lưu được thi hành trong nhớ đệm. Điều này thường được thực hiện như một phần của phương thức tự-tiết kiệm trong hầu hết các hệ thố
Nếu thời gian hết hạn khóa không được cập nhật mà không có sự cập nhật của khóa, thì bất kỳ máy chủ nào cũng có thể thuộc về nó. Nếu một máy chủ khác thuộc về nó, những lần thử của máy chủ hiện tại để đọc hoặc viết chìa khóa không thành công trừ khi nó thiết l
Quản lý sản phẩm nhà cung cấp
Singleton: ReceiptHandler >
Hình nền
Cuộc gọi ProcessReceipt đã làm công việc quan trọng của xác định khi nào hoàn tất một mua hàng. ProcessReceipt được gọi trong những tình huống rất cụ thể. Đối với bộ hứa hẹn của mình, xem MarketplaceService.ProcessReceipt .
Mặc dù định nghĩa "xử lý" một mua hàng có thể khác nhau giữa các trải nghiệm, chúng tôi sử dụng các tiêu chuẩn sau đây
Mua đã không được xử lý trước đó.
Mua được phản ánh trong phiên đấu hiện tại.
Điều này yêu cầu thực hiện các hoạt động sau đây trước khi trả lại PurchaseGranted :
- Xác minh rằng PurchaseId đã không được ghi nhận là đã được xử lý.
- Tặng mua trong dữ liệu người chơi trong khoảng thời gian.
- Lưu PurchaseId như đã xử lý trong dữ liệu người chơi trong ký thuật số.
- Viết dữ liệu người chơi trong khoảng thời gian DataStore .
Đăng nhập phiên đa giản lược dòng chảy này, vì bạn không cần phải lo lắng về các tình huống sau đây nữa:
- Dữ liệu người chơi trong máy chủ hiện tại có thể bị lỗi thời gian, yêu cầu bạn phải lấy giá trị mới nhất từ DataStore trước khi xác minh lịch sử PurchaseId của bạn
- Cú động cho cùng một mua hàng đang diễn ra trên một máy chủ khác, yêu cầu bạn đọc và viết lịch sử PurchaseId và lưu dữ liệu người chơi đã cập nhật với mua hàng phản ánh hạt nhân để ngăn chặn các điều kiện chạy đua
Đảm bảo phiên bản được lưu để bảo đảm rằng, nếu một lần viết vào DataStore của người chơi thành công, không có máy chủ nào khác đọc hoặc viết thành cô
Pendekatan
Các bình luận trong ReceiptProcessor đường dẫn đến phương pháp:
Xác minh dữ liệu của người chơi đã tải trên máy chủ này và đã tải mà không có lỗi.
Vì hệ thống này sử dụng khóa phiên bản, check này cũng xác nhận rằng dữ liệu trong bộ nhớ là phiên bản mới nhất.
Nếu dữ liệu của người chơi chưa được tải (điều này được mong đợi khi một người chơi tham gia một trò chơi), hãy đợi dữ liệu của người chơi được tải. Hệ thống cũng lắng nghe người chơi rời khỏi trò chơi trước khi dữ liệu của họ
Xác minh rằng PurchaseId đã không được ghi nhận là đã xử lý trong dữ liệu người chơi.
Do đó, hệ thống đã được khóa, hệ số nhân của PurchaseIds trong bộ nhớ là phiên bản mới n
Cập nhật Dữ liệu Người chơi ở đây trên máy chủ để "thưởng" cho mua hàng.
ReceiptProcessor sử dụng một phương tiện gọi xe genéric và giao một phương tiện gọi xe khác cho mỗi DeveloperProductId .
Cập nhật dữ liệu người chơi ở đây trên máy chủ để lưu PurchaseId .
Gửi một yêu cầu để lưu dữ liệu trong khoảng thời gian để lưu dữ liệu vào DataStore, trả lại PurchaseGranted nếu yêu cầu thành công. Nếu không, trả lại NotProcessedYet .
Nếu yêu cầu lưu này không thành công, một lần gọi sau đó để lưu dữ liệu hành vi của người chơi trong bộ nhớ có thể vẫn thành công. Trong cuộc gọi tiếp theo ProcessReceipt, hai bước đầu tiên xử lý tình huống này và trả lại PurchaseGranted .
Dữ liệu người chơi
Singletons:: PlayerData.Server PlayerData.Client
Hình nền
Các module cung cấp giao diện cho game code để đồng bộ dữ liệu phiên bản người chơi để đọc và viết đều nhau là thông thường trong các trải nghiệm Roblox. This section covers PlayerData.Server and PlayerData.Client .
Pendekatan
PlayerData.Server và PlayerData.Client xử lý những điều theo dõi:
- Tải dữ liệu người chơi vào bộ nhớ, bao gồm cả trường hợp xử lý nếu nó không tải được
- Cung cấp một giao diện cho mã của máy chủ để tìm kiếm và thay đổi dữ liệu người chơi
- Replicating changes in the người chơi's data to the client so that client code can access it
- Tái tạo lỗi tải và/hoặc lỗi lưu cho client để nó có thể hiển thị các thông báo lỗi
- Lưu dữ liệu người chơi theo thời gian, khi người chơi rời đi và khi máy chủ kết thúc
Đang tải Dữ liệu Người chơi

SessionLockedDataStoreWrapper tạo một yêu cầu getAsync đến cửa hàng dữ liệu.
Nếu yêu cầu này thất bại, dữ liệu mặc định được sử dụng và hồ sơ được đánh dấu là "bị lỗi" để đảm bảo nó không được viết vào cửa hàng dữ liệu sau đó.
Một lựa chọn khác là kick người chơi, nhưng chúng tôi khuyến nghị để người chơi chơi với dữ liệu mặc định và xóa tất cả các thông báo như vậy nhưng không bỏ chúng khỏi trải nghiệm.
Một lần đầu tiên hành động tải dữ liệu và trạng thái lỗi (nếu có) được gửi đến PlayerDataClient chứa dữ liệu đã tải và trạng thái lỗi (nếu có).
Bất kỳ chủ đề nào đã được tạo bằng cách sử dụng waitForDataLoadAsync cho người chơi đều đã được hoàn thành.
Cung cấp một giao diện cho mã máy chủ
- PlayerDataServer là một singleton có thể được yêu cầu và truy cập bởi bất kỳ mã máy chủ đang chạy trong cùng một môi trường.
- Dữ liệu người chơi được tổ chức thành một từ điển các chìa khóa và giá trị. Bạn có thể xử lý các giá trị này trên máy chủ bằng cách sử dụng các phương thức setValue, getValue và updateValue và 1> RemoveValue1>. Tất c
- Các phương thức hasLoaded và waitForDataLoadAsync được cung cấp để đảm bảo dữ liệu đã được tải trước khi bạn truy cập nó. Chúng tôi khuyến nghị làm điều này một lần trong một màn hình tải trước khi các hệ thống khác bắt đ
- Một phương thức hasErrored có thể xin if the người chơi's initial load failed, causing them to use default data. Check this method before允许 the player make any purchases, as purchases cannot be saved to data without a successful tải.
- Một tín hiệu playerDataUpdated với các player , key và 1> value1> mỗi khi dữ liệu của một người chơiđược thay đổi. Các hệ thống cá nhân có thể subscribe đến điều này.
Replicating thay đổi cho client
- Bất kỳ thay đổi nào đối với dữ liệu người chơi trong PlayerDataServer đều được sao chép đến PlayerDataClient, trừ khi chìa khóa đó được gắn nhãn là riêng tư bằng cách sử dụng setValueAsPrivate
- setValueAsPrivate được sử dụng để chỉ định các khóa không nên gửi cho khách hàng
- PlayerDataClient bao gồm một phương thức để nhận giá trị của một chìa khóa (nhận) và một tín hiệu khi nó được cập nhật (cập nhật). Một phương thức hasLoaded và một tín hiệu loaded cũng được bao gồm, vì v
- PlayerDataClient là một singleton có thể được yêu cầu và truy cập bởi bất kỳ mã client chạy trong cùng một môi trường
Đang sao chép lỗi về khách hàng
- Các lỗi trạng thái đã được gặp khi lưu hoặc tải dữ liệu người chơi được sao chép đến PlayerDataClient .
- Truy cập thông tin này với các phương pháp getLoadError và getSaveError cùng với các tín hiệu loaded và 1> Saved1>.
- Có hai loại lỗi: DataStoreError (yêu cầu DataStoreService ) và SessionLocked (xem 1> Khoá họp session1>).
- Sử dụng các sự kiện này để vô hiệu hóa yêu cầu mua client và thực hiện các thông báo cảnh báo. Hình ảnh này cho thấy một hộp thoại ví dụ:

Lưu Thông Tin Người Chơi

Khi người chơi rời khỏi trò chơi, hệ thống sẽ thực hiện các bước sau đây:
- Kiểm tra xem có an toàn khi viết dữ liệu người chơi vào cửa hàng dữ liệu. Tình huống nơi nó sẽ không an toàn bao gồm dữ liệu người chơi không tải được hoặc vẫn đang tải.
- Làm theo yêu cầu thông qua SessionLockedDataStoreWrapper để viết giá trị dữ liệu trong khoản vào nhớ hiện tại vào nhà máy dữ liệu và loại bỏ khóa nhà máy sau khi hoàn thành.
- Xóa dữ liệu người chơi (và các biến khác như metadữ liệu và trạng thái lỗi) khỏi bộ nhớ máy chủ.
Trên một vòng lặp thường xuyên, máy chủ viết dữ liệu của mỗi người chơi vào bộ nhớ dữ liệu (cung cấp nó là an toàn để lưu). Điều này giảm thiểu sự mất mát trong trường hợp máy chủ bị rơi và cũng là cần thiết để duy trì khóa phiên.
Khi một yêu cầu để kết thúc máy chủ được nhận, hành động sau đây xảy ra trong một BindToClose lễ kêu gọi:
- Một yêu cầu được thực hiện để lưu dữ liệu mỗi người chơi trong máy chủ, theo quá trình thường được thực hiện khi một người chơi rời khỏi máy chủ. Các yêu cầu này được thực hiện song song, vì BindToClose các hồ sơ chỉ có 30 giâ
- Để tối ưu hóa dữ liệu, tất cả các yêu cầu khác trong hàng đợi của mỗi chìa khóa được xóa bỏ từ DataStoreWrapper (xem Lần thử lại).
- Hàm này không trả về cho đến khi tất cả các yêu cầu đã hoàn tất.