【Roblox】DataStoreを活用した期間限定ランキングシステムの構築
はじめに
RobloxのDataStoreでは保存されているデータの一括削除ができないため、すべてをリセットしようと思ったらストア名を変更するか、すべてのキーを取得して削除するしかありません。
今回はその両方を使用せずに期間指定のランキングシステムを構築する方法を紹介します。
Robloxバージョン:0.638.1.6380615
システム設計
今回はプレイヤー個人のスコアを管理するPlayerScoreStoreとランキングを管理するRankingStoreの2つのDataStoreを用意しました。サンプルとしてウィークリーランキングを作成した例を記載します。
週はUnixTimeStampを使用した1970年1月1日からの経過した週数を使用しています。
RankingStoreに保存するランキングのデータは100人分のプレイヤーネームとスコアを持つ配列です。これを週が切り替わったタイミングで配列をリセットすることでウィークリーのランキングを作成しています。ランキングに使用するスコアはPlayerScoreStoreに保存されていますが、週を一緒に保存することで保存されているデータが前週以前のものだった時に保存されているスコアをリセットしています。またプレイヤーのデータが更新されたタイミングでランキングの更新を行っています。
実装例(サンプルコード)
セーブデータ更新用のSaveDataManagerモジュール
local WeekSeconds = 604800
local RankingNum = 100
local WeeklyStoreKey = "WeeklyRanking"
local DateKey = "Date"
local DataStoreService = game:GetService("DataStoreService")
local RankingStore = DataStoreService:GetDataStore("RankingStore")
local PlayerScoreStore = DataStoreService:GetDataStore("PlayerScoreStore")
--ランキングのデータの初期値 ウィークリーなので週数のみ保存
local Date = {
Weeks = 0,
}
--プレイヤーのデータの初期値
local ScoreData = {
Week = 0,
Score = 0,
RankIn = false
}
--ランキングのデータを取得
local function GetRankingData()
local result
local Success, Error = pcall(function()
result = RankingStore:GetAsync(WeeklyStoreKey)
end)
if Success then
return result
else
print(Error)
GetRankingData()
end
end
--ランキングのデータを更新
local function SaveRankingData(RankingData)
while #RankingData > RankingNum do
table.remove(RankingData, RankingNum + 1)
end
local Success, Error = pcall(function()
RankingStore:SetAsync(WeeklyStoreKey, RankingData)
end)
if Success then
print("Save RankingData")
else
print(Error)
SaveRankingData(RankingData)
end
end
--ランキングを更新する際に比較する範囲をチェックする
local function CheckCompareRange(currentRanking, Score, Max, Min)
local range = Max - Min + 1
local nextMin = Min
local nextMax = Max
if Score > currentRanking[math.round(range / 2) + Min - 1].Score then
nextMin = Min
nextMax = math.round(range / 2) + Min - 1
else
nextMin = math.round(range / 2) + Min
nextMax = Max
end
--許容値(for文で回す回数)
if nextMax - nextMin < 10 then
return nextMax, nextMin
else
return CheckCompareRange(currentRanking, Score, nextMax, nextMin)
end
end
--ランキングを更新する必要があるかをチェックする
local function CompareWeeklyScore(Name, Data)
local currentRanking = GetRankingData()
local max = RankingNum
local min = 1
if Data.Score > currentRanking[RankingNum].Score then
max, min = CheckCompareRange(currentRanking, Data.Score, RankingNum, 1)
for i = max, min, -1 do
if Data.Score > currentRanking[i].Score then
else
--入れ替え
table.insert(currentRanking, i + 1, {Name = Name, Score = Data.Score})
if not Data.RankIn then
table.remove(currentRanking, RankingNum + 1)
else
for j = i + 2, RankingNum + 1 do
if currentRanking[j].Name == Name then
table.remove(currentRanking, j)
break
end
end
end
SaveRankingData(currentRanking)
return true
end
end
--入れ替え
table.insert(currentRanking, min, {Name = Name, Score = Data.Score})
if not Data.RankIn then
table.remove(currentRanking, RankingNum + 1)
else
for j = min + 1, RankingNum + 1 do
if currentRanking[j].Name == Name then
table.remove(currentRanking, j)
break
end
end
end
SaveRankingData(currentRanking)
return true
else
--入れ替えなし
return false
end
end
local SaveDataManager = {}
--ランキングデータに保存してある週数を更新(週が切り替わったかチェックする際に使用)
function SaveDataManager:UpdateStoreDate()
local Weeks = os.time() // WeekSeconds
local Success, Error = pcall(function()
RankingStore:SetAsync(DateKey, {Weeks = Weeks})
end)
if Success then
print("Save RankingData")
else
print(Error)
SaveDataManager:UpdateStoreDate()
end
end
--ランキングデータの週数を更新(週が切り替わった際に使用)
function SaveDataManager:GetStoreDate()
local result
local Success, Error = pcall(function()
result = RankingStore:GetAsync(DateKey)
end)
if Success then
print("Success")
return result
else
print(Error)
SaveDataManager:GetStoreDate(DateKey)
end
end
--ランキングデータを初期化(週が切り替わった際に使用)
function SaveDataManager:InitRankingData()
local data = {
Name = "Not Data",
Score = 0
}
local RankingData = {}
for i = 1, 100 do
RankingData[i] = table.clone(data)
end
SaveRankingData(RankingData)
end
--ランキングデータを取得
function SaveDataManager:GetRankingData()
return GetRankingData()
end
--プレイヤーのデータを取得
function SaveDataManager:GetData(player:Player)
local result
local Success, Error = pcall(function()
result = PlayerScoreStore:GetAsync(player.UserId)
end)
if Success then
print("Success")
return result
else
print("Error")
print(Error)
SaveDataManager:GetData(player)
end
end
--プレイヤーのデータを保存する
function SaveDataManager:SaveData(player:Player, Data)
local Success, Error = pcall(function()
local rankIn = CompareWeeklyScore(player.Name, Data)
Data.RankIn = rankIn
PlayerScoreStore:SetAsync(player.UserId, Data)
end)
if Success then
print("Saved "..player.Name..":"..Data.Score)
else
print(Error)
SaveDataManager:SaveData(player, Data)
end
end
--プレイヤーのデータを更新する(前週以前のデータかも確認する)
function SaveDataManager:Update(player:Player, AddScore)
local currentData = SaveDataManager:GetData(player)
if not currentData then
currentData = table.clone(ScoreData)
end
local WeekNum = os.time() // WeekSeconds
if currentData.Week == WeekNum then
currentData.Score += AddScore
else
currentData.Week = WeekNum
currentData.Score = AddScore
currentData.RankIn = false
end
SaveDataManager:SaveData(player, currentData)
end
return SaveDataManager
日付の更新などを確認するスクリプト
local WeekSeconds = 604800
local RunService = game:GetService("RunService")
local SaveDataManager = require(script.Parent)
local Weeks = SaveDataManager:GetStoreDate()
local currentDay = DateTime.now():ToUniversalTime().Day
local Timer = 0
RunService.Heartbeart:Connect(function(deltaTime)
Timer += deltaTime
if Timer >= 60 then --1分に一回確認
Timer = 0
local NowTime = DateTime.now():ToUniversalTime()
if NowTime.Day ~= currentDay then --日にちが変わったときの処理
local NowWeeks = os.time() // WeekSeconds
if NowWeeks ~= Weeks then --週が変わったときの処理
SaveDataManager:UpdateStoreDate()
SaveDataManager:InitRankingData()
Weeks = NowWeeks
end
end
end
end)
以上が今回作成したランキングのコードになります。
ランキングをテーブルで保存しているので更新の際に多少比較する必要がありますが、最低限の比較で済むようにしてあります。
まとめ
今回はできるだけ処理として重くならないようにウィークリーランキングのシステムを構築してみました。今回作成したマネージャーに変更を加えればマンスリーランキングも作ることができます。日付更新のスクリプトはほかにも時間を扱うところがあればまとめてしまうのがいいと思います。
お読みいただきありがとうございました。
当社ではRobloxを活用したゲームの開発、 また企業の商品やサービスの認知度拡大に寄与する3Dワールドの制作など、 Robloxにおける様々な活用支援を行っております。 Robloxのコンテンツ開発をご検討されている企業様は、お気軽にご相談ください。 landho.co.jp/
Discussion