Thực hiện dữ liệu người chơi và hệ thống mua hàng

*Nội dung này được dịch bằng AI (Beta) và có thể có lỗi. Để xem trang này bằng tiếng Anh, hãy nhấp vào đây.

Nền

Roblox cung cấp một bộ API để giao tiếp với các kho dữ liệu thông qua DataStoreService .Trường hợp sử dụng phổ biến nhất cho các API này là để lưu trữ, tải và sao chép dữ liệu người chơi.Đó là, dữ liệu liên quan đến tiến trình của người chơi, mua hàng và các đặc điểm phiên khác vẫn tồn tại giữa các phiên chơi riêng lẻ.

Hầu hết các trải nghiệm trên Roblox sử dụng các API này để triển khai một số hình thức hệ thống dữ liệu người chơi.Các triển khai này khác nhau về phương pháp tiếp cận, nhưng chúng thường tìm cách giải quyết cùng một bộ vấn đề.

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 truy cập bộ nhớ: DataStoreService các yêu cầu yêu cầu web hoạt động song song và chịu giới hạn tốc độ.Điều này thích hợp cho một lần tải ban đầu khi bắt đầu phiên, nhưng không phải cho các hoạt động đọc và ghi tần suất cao trong quá trình trải nghiệm trò chơibình thường.Hầu hết các hệ thống dữ liệu người chơi của các nhà phát triển lưu dữ liệu này trong bộ nhớ trên máy chủ Roblox, giới hạn DataStoreService yêu cầu cho các tình huống sau:

    • Đọc ban đầu tại mở đầu của phiên
    • Viết cuối cùng vào cuối phiên
    • Viết theo chu kỳ ở một khoảng thời gian để giảm thiểu tình huống mà viết cuối cùng thất bại
    • Viết để đảm bảo dữ liệu được lưu trong khi xử lý một mua
  • 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ị theo nguyên tử và xử lý lượng dữ liệu tương tự trong ít yêu cầu hơn.Nó cũng loại bỏ rủi ro về sự không đồng bộ giá trị và làm cho việc quay lại dễ dàng hơn để lý giải xung quanh.

    Một số nhà phát triển cũng triển khai serialization tùy chỉnh để nén các cấu trúc dữ liệu lớn (thường để lưu nội dung người dùng tạo ra trong trò chơi).

  • Replication: Khách hàng cần truy cập thường xuyên vào dữ liệu của người chơi ( ví dụ, để cập nhật UI).Một tiếp cận chung để sao lưu dữ liệu người chơi cho khách hàng cho phép bạn truyền đi thông tin này mà không cần phải tạo các hệ thống sao lưu riêng biệt cho mỗi thành phần dữ liệu.Các nhà phát triển thường muốn tùy chọn được lựa chọn về những gì được sao chép và không được sao chép cho khách hàng.

  • Xử lý lỗi: Khi DataStores không thể truy cập, hầu hết các giải pháp sẽ triển khai một cơ chế thử lại và một sự thay thế cho dữ liệu 'mặc định'.Cần chăm sóc đặc biệt để đảm bảo dữ liệu dự phòng không ghi đè lên dữ liệu "thực" sau này, và việc này được truyền đến người chơi thích hợp.

  • Thử lại: Khi các kho dữ liệu không thể truy cập, hầu hết các giải pháp triển khai một cơ chế thử lại và một lựa chọn dữ liệu mặc định.Hãy chú ý đặc biệt để đảm bảo rằng dữ liệu dự phòng không ghi đè lên dữ liệu "thực" sau này và truyền thông tình huống cho người chơi thích hợp.

  • Khóa phiên: Nếu dữ liệu của một người chơi được tải và trong bộ nhớ trên nhiều máy chủ, các 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 mất dữ liệu và kẽ hở sao chép vật phẩm phổ biến.

  • Xử lý mua hàng nguyên tử: Xác minh, trao giải và ghi lại việc mua hàng nguyên tử để ngăn chặn các mặt hàng bị mất hoặc được trao giải nhiều lần.

Mã mã

Roblox có mã tham chiếu để hỗ trợ bạn thiết kế và xây dựng hệ thống dữ liệu người chơi.Phần còn lại của trang này xem xét chi tiết nền, thông tin thực hiện và 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:

Explorer window showing the purchasing system model.

Kiến trúc

Sơ đồ cấp cao này minh họa các hệ thống chính trong mẫu và cách chúng kết nối với mã trong phần còn lại của trải nghiệm.

An architecture diagram for the code sample.

Thử lại

Lớp: DataStoreWrapper >

Nền

Khi DataStoreService thực hiện yêu cầu web dưới máy nắp, 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 pháp DataStore ném lỗi, cho phép bạn xử lý chúng.

Một "gotcha" phổ biến có thể xảy ra nếu bạn cố gắng xử lý sự cố lưu trữ 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ế tái thử hoàn toàn hợp lệ cho một chức năng chung, nó không phù hợp với yêu cầu DataStoreService vì nó không đảm bảo thứ tự mà yêu cầu được thực hiện.Giữ lệp trật tự của yêu cầu là quan trọng đối với DataStoreService yêu cầu vì chúng tương tác với trạng thái.Xem xét các tình huống sau:

  1. Yêu cầu A được thực hiện để đặt giá trị của chìa khóa K thành 1.
  2. Yêu cầu thất bại, vì vậy một lần thử lại được lên lịch chạy trong 2 giây.
  3. Trước khi thử lại xảy ra, yêu cầu B đặt giá trị của K thành 2, nhưng lần thử lại của yêu cầu A ngay lập tức ghi đè giá trị này và đặt K thành 1.

Mặc dù UpdateAsync hoạt động trên phiên bản mới nhất của giá trị của chìa khóa, UpdateAsync yêu cầu vẫn phải được xử lý để tránh các trạng thái chuyển đổi không hợp lệ ( ví dụ, một lệnh mua trừ bỏ tiền trước khi lệnh thêm tiền được xử lý, dẫn đến tiền âm ).

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, cung cấp các lần thử lại được đảm bảo sẽ được xử lý theo thứ tự theo chìa khóa.

Tiếp cận

An process diagram illustrating the retry system

DataStoreWrapper cung cấp các phương pháp tương ứng với các phương pháp DataStore : DataStore:GetAsync() , DataStore:SetAsync() , DataStore:UpdateAsync()DataStore:RemoveAsync() .

Các phương pháp này, khi gọi:

  1. Thêm yêu cầu vào hàng đợi.Mỗi chìa khóa có hàng chờ riêng, nơi các yêu cầu được xử lý theo thứ tự và theo chuỗi.Chuỗi yêu cầu cho đến khi yêu cầu được hoàn thành.

    Chức năng này dựa trên lớp ThreadQueue , đó là một lịch trình việc làm dựa trên coroutine và giới hạn tốc độ.Thay vì trả lại một lời hứa, ThreadQueue trả về luồng hiện tại cho đến khi hoạt động hoàn thành và ném lỗi nếu thất bại.Điều này phù hợp hơn với các mẫu Luau không đồng bộ theo ngữ cảnh.

  2. Nếu yêu cầu thất bại, nó sẽ thử lại với một sự lùi lại exponential có thể cấu hình.Các lần thử lại này là một phần của cuộc gọi trả lại được gửi đến ThreadQueue, vì vậy chúng được đảm bảo hoàn thành trước khi yêu cầu tiếp theo trong hàng đợi cho chìa khóa này bắt đầu.

  3. Khi yêu cầu được hoàn thành, phương thức yêu cầu trả về với mẫu success, result

DataStoreWrapper Cũng tiết lộ các phương pháp để lấy chiều dài hàng đợi cho một chìa khóa nhất định và xóa các yêu cầu cũ.Tùy chọn cuối cùng có ích đặc biệt trong các tình huống khi máy chủ đang tắt và không có thời gian để xử lý bất kỳ yêu cầu nào nhưng mới nhất.

Lưu ý

DataStoreWrapper tuân theo nguyên tắc rằng, ngoài các tình huống cực đoan, mọi yêu cầu lưu trữ dữ liệu nên được phép hoàn thành (thành công hoặc khác), ngay cả khi một yêu cầu gần đây hơn làm cho nó trở nên dư thừa.Khi một yêu cầu mới xảy ra, các yêu cầu cũ không bị xóa khỏi hàng đợi, mà thay vào đó cho phép hoàn thành trước khi yêu cầu mới bắt đầu.Lý do cho việc này bắt nguồn từ khả năng áp dụng của module này như một công cụ lưu trữ dữ liệu chung chứ không phải là một công cụ cụ thể cho dữ liệu người chơi, và nó như sau:

  1. Thật khó để quyết định về một bộ quy tắc trực quan khi nào yêu cầu có thể được xóa khỏi hàng đợi. Hãy xem xét hàng đợi sau:

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

    Hành vi mong đợi là rằng GetAsync() sẽ trả về 1 , nhưng nếu chúng ta xóa yêu cầu SetAsync() khỏi hàng đợi do nó bị làm trùng lặp bởi yêu cầu mới nhất, nó sẽ trả về 0 .

    Tiến trình logic là khi một yêu cầu viết mới được thêm vào, chỉ cắt bỏ các yêu cầu cũ khi xa nhất là yêu cầu đọc gần đây nhất. UpdateAsync() , là hoạt động phổ biến nhất (và là duy nhất được sử dụng bởi hệ thống này), có thể đọc và viết, vì vậy sẽ rất khó để hòa hợp trong thiết kế này mà không cần thêm sự phức tạp.

    DataStoreWrapper có thể yêu cầu bạn xác định xem một yêu cầu UpdateAsync() có được phép đọc và/hoặc viết hay không, nhưng nó sẽ không có tính ứng dụng đối với hệ thống dữ liệu người chơi của chúng tôi, nơi điều này không thể được xác định trước thời gian do cơ chế khóa phiên (được đề cập chi tiết hơn sau).

  2. Một khi đã bị xóa khỏi hàng đợi, thật khó để quyết định về một quy tắc trực quan cho cách xử lý này nên được thực hiện.Khi yêu cầu DataStoreWrapper được thực hiện, luồng hiện tại được trả cho đến khi hoàn thành.Nếu chúng tôi xóa các yêu cầu cũ khỏi hàng đợi, chúng tôi sẽ phải quyết định xem có nên trả lại false, "Removed from queue" hoặc không bao giờ trả lại và loại bỏ luồng hoạt động.Cả hai phương pháp đều có nhược điểm riêng và chuyển thêm phức tạp sang cho người tiêu dùng.

Cuối cùng, quan điểm của chúng tôi là phương pháp đơn giản (xử lý tất cả các yêu cầu) là ưu tiên ở đây và tạo ra một môi trường rõ ràng hơn để điều hướng khi tiếp cận các vấn đề phức tạp như khóa phiên.Ngoại lệ duy nhất đối với điều này là trong DataModel:BindToClose() , nơi xóa hàng đợi trở nên cần thiết để lưu tất cả dữ liệu của người dùng trong thời gian và giá trị hàm gọi cá nhân không còn là mối quan tâm tiếp tục.Để bù đắp cho điều này, chúng tôi tiết lộ một phương pháp skipAllQueuesToLastEnqueued .Đối với thêm bối cảnh, xem Dữ liệu người chơi.

Khóa phiên

Lớp: SessionLockedDataStoreWrapper >

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 các kho dữ liệu cơ sở khi cần thiết.Bạn có thể đọc và cập nhật dữ liệu người chơi trong bộ nhớ ngay lập tức mà không cần yêu cầu web và tránh vượt quá giới hạn DataStoreService .

Để mô hình này hoạt động như dự định, bắt buộc không quá một máy chủ có thể tải dữ liệu người chơi vào bộ nhớ từ DataStore tại cùng một thời điểm.

Ví dụ, nếu máy chủ A tải dữ liệu của 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 của nó trên nó trong lúc lưu cuối cùng.Không có cơ chế khóa, máy chủ B có thể tải dữ liệu người chơi lỗi thời từ kho dữ liệu trước khi máy chủ A có cơ hội lưu phiên bản mới nhất mà nó có trong bộ nhớ.Sau đó, nếu máy chủ A lưu dữ liệu mới hơn sau khi máy chủ B tải dữ liệu lỗi thời, máy chủ B sẽ ghi đè dữ liệu mới hơn trong lần lưu tiếp theo.

Mặc dù Roblox chỉ cho phép một khách hàng kết nối với một máy chủ tại một thời điểm, bạn không thể cho rằng dữ liệu từ một phiên luôn được lưu trước khi phiên tiếp theo bắt đầu.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:

  1. 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à cần nhiều lần thử lại để hoàn thành thành công.Trong khoảng thời gian thử lại, người chơi tham gia máy chủ B.
  2. Máy chủ A thực hiện quá nhiều cuộc gọi UpdateAsync() đến cùng một chìa khóa và bị giới hạn.Yêu cầu lưu cuối cùng được đặt trong hàng đợi.Trong khi yêu cầu đang ở trong hàng chờ, người chơi tham gia máy chủ B.
  3. Trên máy chủ A, một số mã kết nối với sự kiện PlayerRemoving sẽ được hiển thị trước khi dữ liệu của người chơi được lưu.Trước khi hoạt động này hoàn thành, người chơi tham gia máy chủ B.
  4. Hiệu suất của máy chủ A đã xuống cấp đến mức lưu cuối cùng bị trì hoãn cho đến khi người chơi tham gia 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 thoát 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 chuỗi nhanh chóng ( ví dụ, trong khi dịch chuyển ).Một số người dùng độc hại thậm chí có thể cố gắng lạm dụng hành vi này để hoàn thành hành động mà không tiếp tục.Điều này có thể đặc biệt ảnh hưởng trong các trò chơi cho phép người chơi giao dịch và là một nguồn phổ biến của các lỗ hổng sao chép vật phẩm.

Bộ khóa phiên xử lý lỗ hổng này bằng cách đảm bảo rằng khi chìa khóa DataStore của người chơi được đọc đầu tiên bởi máy chủ, máy chủ sẽ viết khóa toàn bộ vào métadữ liệu của chìa khóa trong cùng một cuộc gọi UpdateAsync().Nếu giá trị khóa này hiện diện khi bất kỳ máy chủ nào khác cố gắng đọc hoặc ghi chìa khóa, máy chủ không tiến hành.

Tiếp cận

An process diagram illustrating the session locking system

SessionLockedDataStoreWrapper là một meta-wrapper xung quanh lớp DataStoreWrapper.DataStoreWrapper cung cấp chức năng xếp hàng và thử lại, mà SessionLockedDataStoreWrapper bổ sung với việc khóa phiên.

SessionLockedDataStoreWrapper vượt qua mọi yêu cầu DataStore — bất kể nó là GetAsync , SetAsync hoặc UpdateAsync —thông qua UpdateAsync .Điều này là bởi vì UpdateAsync cho phép một chìa khóa được đọc và viết vào toàn bộ nguyên tử.Nó cũng có thể từ bỏ việc viết dựa trên giá trị đọc bằng cách trả về nil trong lời gọi lại biến đổi.

Chức năng biến đổi được chuyển vào UpdateAsync cho mỗi yêu cầu thực hiện các hoạt động sau:

  1. Xác minh chìa khóa an toàn để truy cập, từ bỏ hoạt động nếu nó không phải là như vậy. "An toàn để truy cập" có nghĩa là:

    • Đối tượng metadata của chìa khóa không bao gồm một giá trị không được công nhận LockId được cập nhật lần cuối cách đây ít hơn thời gian hết hạn khóa.Điều này giải thích việc tôn trọng một khóa được đặt bởi một máy chủ khác và bỏ qua khóa nếu nó hết hạn.

    • Nếu máy chủ này đã đặt giá trị riêng của nó LockId trong metadata của chìa khóa trước đó, thì giá trị này vẫn còn trong metadata của chìa khóa.Điều này giải thích tình huống mà máy chủ khác đã tiếp quản khóa của máy chủ này (bằng cách hết hạn hoặc bằng cách buộc) và sau đó phát hành nó.Thay thế lời nói khác, ngay cả khi LockIdnil, một máy chủ khác vẫn có thể đã thay thế và xóa khóa trong thời gian kể từ khi bạn khóa chìa khóa.

  2. UpdateAsync thực hiện hoạt động DataStore mà người tiêu dùng của SessionLockedDataStoreWrapper yêu cầu. Ví dụ, GetAsync() dịch sang function(value) return value end.

  3. Tùy thuộc vào các tham số được chuyển vào yêu cầu, UpdateAsync hoặc khóa hoặc mở khóa chìa khóa:

    1. Nếu chìa khóa phải được khóa, UpdateAsync đặt LockId trong metadata của chìa khóa thành một GUID.GUID này được lưu trong bộ nhớ trên máy chủ để có thể kiểm tra lần tiếp theo khi nó truy cập chìa khóa.Nếu máy chủ đã có khóa trên chìa khóa này, nó không thay đổi gì.Nó cũng lên lịch một nhiệm vụ để cảnh báo bạn nếu bạn không truy cập lại chìa khóa để duy trì chặn trong thời gian hết hạn của chặn.

    2. Nếu chìa khóa cần được mở khóa, UpdateAsync loại bỏ LockId trong metadata của chìa khóa.

Một xử lý lặp lại tùy chỉnh được chuyển vào đầu cuối DataStoreWrapper để thực hiện lại hoạt động nếu nó bị hủy ở bước 1 do phiên bị khóa.

Một tin nhắn lỗi tùy chỉnh cũng được trả lại cho 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 thay thế trong trường hợp khóa phiên cho khách hàng.

Lưu ý

Chế độ khóa phiên dựa vào một máy chủ luôn phát hành khóa vào một chìa khóa khi nó hoàn thành với nó.Điều này nên luôn xảy ra thông qua lệnh để mở khóa chìa khóa như một phần của viết cuối cùng trong PlayerRemoving hoặc BindToClose() .

Tuy nhiên, việc mở khóa có thể thất bại trong một số tình huống. Ví dụ:

  • Máy chủ bị lỗi hoặc DataStoreService không thể sử dụng được đối với tất cả các lần truy cập vào chìa khóa.
  • Do một lỗi trong logic hoặc lỗi tương tự, lệnh để 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 thường xuyên cho đến khi nó được tải vào bộ nhớ.Điều này thường được thực hiện như một phần của vòng lặp lưu tự động chạy trong nền trong hầu hết các hệ thống dữ liệu người chơi, nhưng hệ thống này cũng tiết lộ một phương pháp refreshLockAsync nếu bạn cần thực hiện thủ công.

Nếu thời gian hết hạn khóa đã bị vượt quá mà khóa không được cập nhật, thì bất kỳ máy chủ nào cũng có thể lấy lại khóa.Nếu một máy chủ khác nhận khóa, các lần thử đọc hoặc ghi chìa khóa bởi máy chủ hiện tại sẽ thất bại trừ khi nó thiết lập một khóa mới.

Xử lý sản phẩm nhà phát triển

Đơn nhiệm: ReceiptHandler >

Nền

Cuộc gọi trả lại ProcessReceipt thực hiện công việc quan trọng xác định khi nào kết thúc một lần mua hàng.ProcessReceipt được gọi trong các tình huống rất cụ thể.Đối với bộ đảm bảo của nó, xem MarketplaceService.ProcessReceipt .

Mặc dù định nghĩa về "xử lý" một lần mua có thể khác nhau giữa các trải nghiệm, chúng tôi sử dụng các tiêu chí sau

  1. Việc mua hàng chưa được xử lý trước đó.

  2. Việc mua hàng được phản ánh trong phiên hiện tại.

  3. Việc mua đã được lưu vào một DataStore .

    Mỗi lần mua, ngay cả tiêu dùng một lần, nên được phản ánh trong DataStore để lịch sử mua hàng của người dùng được bao gồm trong dữ liệu phiên của họ.

Điều này yêu cầu thực hiện các hoạt động sau trước khi trả về PurchaseGranted :

  1. Xác minh PurchaseId chưa được ghi nhận là đã được xử lý.
  2. Tặng mua trong dữ liệu người chơi trong bộ nhớ của người chơi.
  3. Ghi lại PurchaseId như được xử lý trong dữ liệu người chơi trong bộ nhớ của người chơi.
  4. Viết dữ liệu người chơi trong bộ nhớ của người chơi vào DataStore.

Khóa phiên đơn giản hóa dòng chảy này, vì bạn không còn cần phải lo lắng về các tình huống sau:

  • Dữ liệu người chơi trong bộ nhớ trong máy chủ hiện tại có thể bị lỗi thời, 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
  • Cuộc gọi trả lại cho cùng một lần mua hàng chạy trên máy chủ khác, yêu cầu bạn phải đọc và viết lịch sử PurchaseId và lưu dữ liệu người chơi được cập nhật với việc mua hàng được phản ánh toàn bộ để ngăn chặn các điều kiện cuộc đua

Khóa phiên đảm bảo rằng, nếu một lần cố gắng 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ông vào DataStore của người chơi giữa dữ liệu được tải và lưu trong máy chủ này.Tóm lại, dữ liệu người chơi trong bộ nhớ trong máy chủ này là phiên bản mới nhất có sẵn.Có một số lưu ý, nhưng chúng không ảnh hưởng đến hành vi này.

Tiếp cận

Các bình luận trong ReceiptProcessor vẽ ra cách tiếp cận:

  1. Xác minh rằng dữ liệu của người chơi hiện đang được tải trên máy chủ này và nó được tải mà không có lỗi.

    Bởi vì hệ thống này sử dụng khóa phiên, kiểm tra này cũng xác minh 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 mong đợi khi một người chơi tham gia vào một trò chơi), hãy chờ dữ liệu của người chơi 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ọ được tải, vì nó không nên cho phép vô cực và chặn lại cuộc gọi trả lại này trên máy chủ này cho mua hàng này nếu người chơi tham gia lại.

  2. Xác minh PurchaseId không đã được ghi lại như đã xử lý trong dữ liệu người chơi.

    Do chặn phiên, mảng của PurchaseIds hệ thống có trong bộ nhớ là phiên bản mới nhất.Nếu PurchaseId được ghi lại là đã xử lý và được phản ánh trong một giá trị đã được tải vào hoặc lưu vào DataStore , trả về PurchaseGranted .Nếu nó được ghi lại là đã xử lý, nhưng không được phản ánh trong DataStore , trả về NotProcessedYet .

  3. Cập nhật dữ liệu người chơi địa phương trên máy chủ này để "tặng" mua hàng.

    ReceiptProcessor sử dụng một tiếp cận trả lời chung và gán một trả lời khác cho mỗi DeveloperProductId .

  4. Cập nhật dữ liệu người chơi địa phương trên máy chủ này để lưu PurchaseId .

  5. Gửi yêu cầu để lưu dữ liệu trong bộ nhớ vào DataStore , trả về PurchaseGranted nếu yêu cầu thành công. Nếu không, trả về NotProcessedYet .

    Nếu yêu cầu lưu này không thành công, yêu cầu lưu dữ liệu phiên trong bộ nhớ của người chơi sau này vẫn có thể thành công.Trong cuộc gọi tiếp theo ProcessReceipt , bước 2 xử lý tình huống này và trả về PurchaseGranted .

Dữ liệu người chơi

Các đơn vị: PlayerData.Server , PlayerData.Client >

Nền

Các mô-đun cung cấp giao diện cho mã trò chơi để đọc và ghi dữ liệu phiên người chơi đồng bộ là phổ biến trong các trải nghiệm Roblox.Phần này bao gồm PlayerData.ServerPlayerData.Client .

Tiếp cận

PlayerData.ServerPlayerData.Client xử lý những điều theo dõi:

  1. Tải dữ liệu của người chơi vào bộ nhớ, bao gồm xử lý các trường hợp không thể tải
  2. Cung cấp một giao diện cho mã máy chủ để truy vấn và thay đổi dữ liệu người chơi
  3. Sao lưu các thay đổi trong dữ liệu của người chơi vào khách hàng để mã khách hàng có thể truy cập nó
  4. Lặp lại lỗi tải và/hoặc lưu vào khách hàng để có thể hiển thị các thông báo lỗi
  5. Lưu dữ liệu của người chơi theo kỳ, khi người chơi rời đi, và khi máy chủ tắt

Tải dữ liệu người chơi

An process diagram illustrating the loading system
  1. SessionLockedDataStoreWrapper tạo một yêu cầu getAsync đến kho dữ cửa hàng.

    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à "có lỗi" để đảm bảo nó không được viết vào kho dữ liệu sau này.

    Một lựa chọn thay thế là đuổi người chơi, nhưng chúng tôi khuyên bạn nên để người chơi chơi với dữ liệu mặc định và xóa tin nhắn như những gì đã xảy ra thay vì loại bỏ họ khỏi trải nghiệm.

  2. Một phần tải ban đầu được gửi đến PlayerDataClient chứa dữ liệu đã tải và tình trạng lỗi (nếu có).

  3. Bất kỳ luồng nào được sản xuất bằng cách sử dụng waitForDataLoadAsync cho người chơi được tiếp tục.

Cung cấp một giao diện cho mã máy chủ

  • PlayerDataServer là một đối tượng duy nhất có thể được yêu cầu và truy cập bởi bất kỳ mã máy chủ nào 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 bảng chỉ mục các chìa khóa và giá trị.Bạn có thể thao tác với các giá trị này trên máy chủ bằng cách sử dụng các phương pháp setValue, getValue, updateValueremoveValue.Tất cả các phương pháp này đều hoạt động song song mà không từ bỏ.
  • Các phương pháp hasLoadedwaitForDataLoadAsync có sẵn để đảm bảo dữ liệu đã được tải trước khi bạn truy cập nó.Chúng tôi khuyên bạn nên làm điều này một lần trong khi đang tải màn hình trước khi các hệ thống khác bắt đầu để tránh phải kiểm tra lỗi tải trước mỗi tương tác với dữ liệu trên máy khách.
  • Một phương pháp hasErrored có thể truy vấn nếu tải ban đầu của người chơi thất bại, khiến họ sử dụng dữ liệu mặc định.Kiểm tra phương pháp này trước khi cho phép người chơi thực hiện bất kỳ mua hàng nào, vì mua hàng không thể lưu vào dữ liệu nếu không có lượt tải thành công.
  • Một tín hiệu playerDataUpdated bắt lửa với player, keyvalue mỗi khi dữ liệu của một người chơi thay đổi.Các hệ thống cá nhân có thể đăng ký vào điều này.

Sao lưu các thay đổi đến khách hàng

  • Bất kỳ thay đổi nào về dữ liệu người chơi trong PlayerDataServer được sao chép sang PlayerDataClient , trừ khi chìa khóa được đánh dấu là riêng tư bằng cách sử dụng setValueAsPrivate
    • setValueAsPrivate được sử dụng để chỉ ra các chìa khóa không nên gửi cho khách hàng
  • PlayerDataClient bao gồm một phương pháp để lấy giá trị của một chìa khóa (nhận) và một tín hiệu bắt lửa khi nó được cập nhật (cập nhật).Một phương pháp hasLoaded và một tín hiệu loaded cũng được bao gồm, vì vậy khách hàng có thể chờ đợi dữ liệu tải và sao chép trước khi bắt đầu hệ thống của mình
  • PlayerDataClient là một đối tượng duy nhất có thể được yêu cầu và truy cập bởi bất kỳ mã khách hàng nào đang chạy trong cùng một môi trường

Lặp lại lỗi cho khách hàng

  • Các trạng thái lỗi xảy ra khi lưu hoặc tải dữ liệu người chơi được sao lưu lại PlayerDataClient .
  • Truy cập thông tin này với các phương pháp getLoadErrorgetSaveError , cùng với các tín hiệu loadedsaved .
  • Có hai loại lỗi: DataStoreError (yêu cầu DataStoreService không thành công) và SessionLocked (xem Khóa phiên ).
  • Sử dụng các sự kiện này để vô hiệu hóa lời nhắc mua khách hàng và triển khai các cuộc thoại cảnh báo. Hình ảnh này cho thấy một ví dụ cuộc thoại:
A screenshot of an example warning that could be shown when player data fails to load

Lưu dữ liệu người chơi

A process diagram illustrating the saving system
  1. Khi người chơi rời khỏi trò chơi, hệ thống thực hiện các bước sau:

    1. Kiểm tra xem có an toàn để viết dữ liệu của người chơi vào kho dữ cửa hàngkhông.Các tình huống có nguy cơ bị mất an toàn bao gồm dữ liệu người chơi không thể tải hoặc vẫn đang tải.
    2. Thực hiện yêu cầu thông qua SessionLockedDataStoreWrapper để viết giá trị dữ liệu hiện tại trong bộ nhớ vào kho dữ liệu và xóa khóa phiên một khi hoàn thành.
    3. Xóa dữ liệu của người chơi (và các biến khác như metadata và tình trạng lỗi) khỏi bộ nhớ máy chủ.
  2. Trong vòng lặp hàng tuần, máy chủ viết dữ liệu của mỗi người chơi vào kho dữ liệu (miễn là an toàn để lưu).Sự trùng lặp chào mừng này giảm thiểu thiệt hại trong trường hợp máy chủ bị lỗi và cũng cần thiết để duy trì khóa phiên.

  3. Khi nhận được yêu cầu đóng máy chủ, sau đây xảy ra trong một lời gọi lại BindToClose

    1. Một yêu cầu được thực hiện để lưu dữ liệu của mỗi người chơi trong máy chủ, sau quá trình thường đi qua 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 cuộc gọi trả lại chỉ có 30 giây để hoàn thành.
    2. Để rút ngắn thời gian lư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 khỏi DataStoreWrapper (xem Lần thử lại ).
    3. Callback không trả lại cho đến khi tất cả các yêu cầu đã hoàn thành.