📊

【Roblox】DataStoreのデータを順位付けする

2024/10/14に公開

はじめに

Robloxは、サーバー上にユーザーデータなどを保存しておけるDataStoreという機能があります。
ただし、DataStoreはデータ内容によるソートができません。
OrderedDataStoreなら、ソートされた状態のリストを取得できるのですが、保存できるデータは整数値のみになります。

というわけで、これらを組み合わせてDataStoreのデータを順位付けしてみました。
やり方はごく単純で、「順位付けに用いたい整数値のパラメータをOrderedDataStoreに保存しておき、それ以外のデータを共通のキーでDataStoreに保存しておく」という方法です。

バージョン:0.641.2.6410741

1. データの保存

例えば、以下のようなデータを個別に保存し、Scoreで順位付けしたいとします。

type UserData = {
	Score :number,	
	PlayCount :number,
	PlayerComment :string,

	-- その他、色々なデータ.
}

その場合、保存の際にScoreをそのままOrderedDataStoreにも保存してしまえばOKです。

local DataStoreService = game:GetService("DataStoreService")
local userDataStore = DataStoreService:GetDataStore("UserData") -- UserDataを保存するDataStore
local scoreOrderedDataStore = DataStoreService:GetOrderedDataStore("Score") -- Scoreを保存し順位付けするOrderedDataStore

-- アップロード用の関数.
local function Upload(key :string, data :UserData) :(boolean, string?)
	-- dataStoreにdataを保存する.
	do
		local result :{} = {pcall(userDataStore.SetAsync, userDataStore, key, data)}

		local success = result[1]::boolean
		if not success then
			local errorMassage = result[2]::string
			warn(errorMassage)
			return false, errorMassage
		end
	end

	-- 同時にOrderedDataStoreにScoreを保存する.
	do
		local result :{} = {pcall(scoreOrderedDataStore.SetAsync, scoreOrderedDataStore, key, data.Score)}

		local success = result[1]::boolean
		if not success then
			local errorMassage = result[2]::string
			warn(errorMassage)
			return false, errorMassage
		end
	end

	return true
end

本来はもっと堅い作りにした方がいいのですが、サンプルコードなのでこれくらいで。

同じScoreのデータをDataStore側にも重複して持たせるべきかどうかは場合によるかと思いますが、ソートと関係なくDataStoreのデータを単独で取得した際にScoreのデータが必要になるようであれば、毎回OrderedDataStoreに取得しにいくのはリクエスト数的にも処理時間的にもよろしくないので、重複して持たせてしまってもよいのではないかと思います。

2. データの読み込み

では仮に、上位10位までのデータのリストを取得してみましょう。
まずは先ほどのUploadを使って、適当なデータを10個保存しておきます。

-- 適当なデータをアップロードする.
for i = 1, 10, 1 do
	local testData = {}::UserData
	testData.Score = i
	testData.PlayCount = 1
	testData.PlayerComment = "test"

	Upload("TestData_" .. i, testData)
end

データの読み込みでは、まずOrderedDataStoreのGetSortedAsync()で上位10位までのデータを取得した後、そのkey情報を用いてDataStoreからデータを取得していきます。

-- 個別のデータをロードする関数.
local function Load(key :string) :(boolean, any)
	local result :{} = {pcall(userDataStore.GetAsync, userDataStore, key)}

	local success = result[1]::boolean
	if not success then
		local errorMassage = result[2]::string
		warn(errorMassage)
		return false, errorMassage
	end

	local data = result[2]
	if not data then
		warn("Data is nil")
		return false, "Data is nil"
	end

	return true, data
end

-- ソートされたデータを取得する関数.
local function GetSortedData(ascending :boolean, pagesize :number, minValue :number?, maxValue :number?) :(boolean, any)
	-- OrderedDataStoreからソートされたキー情報を取得する.
	local result :{} = {pcall(scoreOrderedDataStore.GetSortedAsync, scoreOrderedDataStore, ascending, pagesize, minValue, maxValue)}

	local success :boolean = result[1]::boolean
	if not success then
		local errorMassage = result[2]::string
		warn(errorMassage)
		return false
	end

	local dataStorePages = result[2]::DataStorePages
	local ranking = {}::{[number] :{key :string, data :UserData}}
	local page :{[number] :{key :string, value :number}} = dataStorePages:GetCurrentPage()

	-- 取得した情報を元にDataStoreから個別のデータを取得する.
	for i = 1, #page, 1 do
		local key :string = page[i].key

		local loadResult :{} = {Load(key)}

		success = loadResult[1]
		if not success then
			local errorMassage = loadResult[2]::string
			warn(errorMassage)
			i -= 1
			wait(1)
			continue
		end

		ranking[i] = {key = key, data = loadResult[2]::UserData}
	end

	return true, ranking
end

-- ソートされたデータを取得.
local success, ranking = GetSortedData(false, 10)
print(ranking)
実行結果
▼ {
	[1] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 10
		},
		["key"] = "TestData_10"
	},
	[2] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 9
		},
		["key"] = "TestData_9"
	},
	[3] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 8
		},
		["key"] = "TestData_8"
	},
	[4] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 7
		},
		["key"] = "TestData_7"
	},
	[5] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 6
		},
		["key"] = "TestData_6"
	},
	[6] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 5
		},
		["key"] = "TestData_5"
	},
	[7] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 4
		},
		["key"] = "TestData_4"
	},
	[8] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 3
		},
		["key"] = "TestData_3"
	},
	[9] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 2
		},
		["key"] = "TestData_2"
	},
	[10] =  ▼  {
		["data"] =  ▼  {
			["PlayCount"] = 1,
			["PlayerComment"] = "test",
			["Score"] = 1
		},
		["key"] = "TestData_1"
	}
} 

Score順にソートされたデータを取得することができました!
OrderedDataStoreはリスト化する際、昇順、降順のほか、値の最小値、最大値も指定できるため、色々なリストを作ることができます。
これを応用して、タグのような値を整数で設定しておき、最小値と最大値を同じ値に指定して特定のタグのデータだけ抜き出す、というようなこともできるかもしれません。
ただし、取得したいデータが大量だったり、取得する頻度が高かったりする場合、リクエスト数の制限には注意が必要です。

3. まとめ

  • DataStoreとOrderedDataStoreを組み合わせて、ソートされたデータリストを取得できる
  • アップロードの際に、DataStoreとOrderedDataStoreに同時に書き込んでおく
  • OrderedDataStoreから取得したキーリストを使って、DataStoreのデータを取得する
  • 工夫次第で、いろんなリストが作れるかも

単純に、上位○○位までのデータが欲しい、というだけだったら、OrderedDataStoreは使わず、自分でランキングの仕組みを作り、データ保存の際にランキングを更新して、そのランキングデータをDataStoreに保存する、という方法でも良いかと思います。
やりたい事に応じて、方法を選択してみてください!

4. 参考

https://create.roblox.com/docs/cloud-services/data-stores

ランド・ホー Roblox開発チーム

Discussion