【Roblox】DataStoreのデータをランダムに取得する
はじめに
今回の記事は、こちらの記事の続きの内容となっておりますので、まだ読まれていない方はこちらを先にご覧ください。
前回はDataStoreのデータを順位付けしましたが、もう一つDataStoreのできない事として、ランダムなデータの取得があります。
というわけで、その方法を考えてみました。
今回は、「キーに対してIDを割り振ったインデックスリストを作成・保存しておき、取得する際、1~総数の間でランダムに取得した値と一致するIDのキーのデータを取得する」という方法を使用します。
バージョン:0.641.2.6410741
1. 準備
前回のサンプルコードに追記していきますので、サンプルを真似して制作されている方は必要に応じて前回の記事も参照してください。
まずは、一旦前回保存したデータを削除しておきます。
-- 前回保存したデータを削除しておく.
for i = 1, 10, 1 do
print(userDataStore:RemoveAsync("TestData_" .. i))
print(scoreOrderedDataStore:RemoveAsync("TestData_" .. i))
end
そして、ソースコードの先頭あたりに、インデックスリストの型とキーを定義しておきます。
-- インデックスリストの型を定義.
type IndexList = {[number] :string}
-- インデックスリスト用のキーを文字列リテラル型として定数のように定義.
local KEY_INDEX_LIST :"IndexList" = "IndexList"
2. データの保存
準備ができたら、前回作成したアップロード用の関数に追記し、UpdataAsync()
を使ってインデックスリストの情報も保存するようにします。
さらに、同じキーに対して複数回アップロードされると、インデックスリストと不整合が起きてしまうため、今回はGetAsync()
で対象のキーにデータが存在するかチェックし、存在している場合はインデックスリストに追加しないようにしておきます。
-- 前回作成したアップロード用関数.
local function Upload(key :string, data :UserData) :(boolean, string?)
------------------------------------ここから追記.
-- インデックスリストに追加すべきかどうかのフラグ.
local shouldAddToIndex = true
-- データが既に存在するかチェック.
do
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 data ~= nil then
shouldAddToIndex = false
end
end
if shouldAddToIndex then
-- インデックスリストの末尾にキー情報を追加し保存する.
local result :{} = {pcall(userDataStore.UpdateAsync, userDataStore, KEY_INDEX_LIST, function(indexList :IndexList)
if indexList then
table.insert(indexList, key)
return indexList
else
-- データが無かったら空扱いとして最初にkeyを入れて保存する.
return {key}
end
end)}
local success = result[1]::boolean
if not success then
local errorMassage = result[2]::string
warn(errorMassage)
return false, errorMassage
end
end
------------------------------------ここまで追記.
-- 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
3. データの読み込み
前回同様、適当なデータを10個保存しておきます。
-- 適当なデータをアップロードする.
for i = 1, 10, 1 do
local testData = {}::UserData
testData.Score = i
testData.PlayCount = 1
testData.PlayerComment = "test"
Upload("TestData_" .. i, testData)
end
データの読み込みでは、まずインデックスリストを取得した後、そのリストからランダムに一つキー情報を取得し、そのキー情報を用いてDataStoreからデータを取得します。
-- ランダムなデータを一つ取得.
local function GetRandomData() :(boolean, any)
local indexList :IndexList?
-- インデックスリストを取得.
do
local result :{} = {pcall(userDataStore.GetAsync, userDataStore, KEY_INDEX_LIST)}
local success = result[1]
if not success then
local errorMassage = result[2]::string
warn(errorMassage)
return false, errorMassage
end
indexList = result[2]::IndexList?
if not indexList then
warn("Index List is nil")
return false, "Index List is nil"
end
if #indexList <= 0 then
warn("No data in Index List")
return false, "No data in Index List"
end
end
-- インデックスリストからランダムなデータを一つ取得.
local returnData = {}::{key :string, data :UserData}
do
local id = math.random(#indexList)
local key = indexList[id]
-- 対象のキーのデータを取得.
local result :{} = {pcall(userDataStore.GetAsync, userDataStore, key)}
local success = result[1]
if not success then
local errorMassage = result[2]::string
warn(errorMassage)
return false, errorMassage
end
local userData = result[2]::UserData?
if not userData then
warn(key .. " User Data is nil")
return false, key .. " User Data is nil"
end
returnData.key = key
returnData.data = userData
end
return true, returnData
end
-- 試しに3つランダムに取得してみる.
local randomData = {}
for i = 1, 3, 1 do
local success, data = GetRandomData()
table.insert(randomData, data)
end
print(randomData)
▼ {
[1] = ▼ {
["data"] = ▼ {
["PlayCount"] = 1,
["PlayerComent"] = "test",
["Score"] = 5
},
["key"] = "TestData_5"
},
[2] = ▼ {
["data"] = ▼ {
["PlayCount"] = 1,
["PlayerComent"] = "test",
["Score"] = 9
},
["key"] = "TestData_9"
},
[3] = ▼ {
["data"] = ▼ {
["PlayCount"] = 1,
["PlayerComent"] = "test",
["Score"] = 4
},
["key"] = "TestData_4"
}
}
ランダムなデータを取得することができました!
普段から複数のランダムなデータを取得することがある場合は、毎回インデックスリストを取得しなおすのは無駄なので、引数から取得するデータの数を指定できるようにしておくと良いかもしれませんね。
4. 注意点
今回は簡易的にするため対応を省略しましたが、実際に使用する場合、いくつかの注意点があります。
データの削除
削除されうるデータである場合、追加した時と同じようにインデックスリストと整合性を取る必要があります。
削除する際にインデックスリストを逆引きできるよう、保存しておくデータにインデックス情報を追加しておく必要がありますね。
また、インデックスリストに空きができることになるので、空きを埋める処理も必要になります。
削除するキーの位置に末尾のキーを入れ、末尾を削除した後、末尾だったキーのデータのインデックス情報を更新すればよいかと思います。
データのサイズ上限
インデックスリストのデータサイズにも注意する必要があります。
DataStoreではキーの数に上限はありませんが、一つのデータとして保存できるサイズの上限は4MB(4,194,304バイト)のため、仮にIDに10文字分の文字列を使用した場合、インデックスリストは322,638件までしか保存できません。
ユーザー一人当たりのデータだったとしても、運よく人気のエクスペリエンスになったら十分に到達しそうな数字です。
必要に応じてインデックスリストを複数に分割し、各リストの総数を別で保存しておくなど、管理する方法も必要になるでしょう。
あるいは、ランダムに取得したいデータを保存するキー自体を通し番号にし、総数だけ保存しておいてインデックスリストは持たないという手段もあります。
こちらはランダムなデータの取得や空きデータを埋めるのは簡単ですが、「特定のデータを取り出したい」という場合に困らないような仕組みが必要です。
エラー処理
インデックスリストに追加はされたもののデータの保存には失敗した、といった事が起きるとやはり不整合が起きるため、保存時や読み込み時に適切にリトライする仕組みや、それでも不整合が出てしまった場合にメンテナンスで対応可能な仕組みにしておく必要もあります。
5. まとめ
- インデックスリストを作成しておくことで、ランダムにデータを取得することができる
- 対象のキーの初回のアップロードの際に、データと同時にインデックスリストに書き込んでおく
- インデックスリストからランダムに取得したキーを使って、DataStoreのデータを取得する
- 実際に利用するなら、データとインデックスリストの不整合やデータサイズ上限への対応が必須
DataStoreは、機能的には融通が利かない所もありつつも、開発・運用で追加の費用がかからないという利点があります。
うまく活用して、低コストで効果的な開発をめざしましょう!
6. 参考
当社ではRobloxを活用したゲームの開発、 また企業の商品やサービスの認知度拡大に寄与する3Dワールドの制作など、 Robloxにおける様々な活用支援を行っております。 Robloxのコンテンツ開発をご検討されている企業様は、お気軽にご相談ください。 landho.co.jp/
Discussion