スコア・データ初級プラスRobux不要

Roblox Studioで全サーバー共通ランキングを作る方法

Ordered Data StoreへScoreを保存し、ゲーム内のボードへ全サーバー共通の上位10人を高い順で表示しよう。

公開日
全サーバー共通ランキングに3人のScoreが高い順で表示されたStudio画面

ゲーム内のボードへ、すべてのサーバーを合わせたScore上位10人を表示する。完成すると、1300、900、500のように高い値から1位、2位、3位と並び、60秒ごとに更新される。完成までの目安は8分だ。Robuxは必要ない。

全サーバー共通ランキングへ3人のScoreが高い順で表示された完成状態

この記事は、ScoreをData Storeへ保存する記事の続きだ。Publish済みの専用テストゲームでleaderstats > ScoreSaveScoreが動き、Studio Access to API Servicesを有効にした状態から始めよう。

Roblox公式のData Store解説にあるとおり、Studioからは同じゲームのクラウドデータへ接続する。公開中の本番ゲームではなく、前の記事で用意したテスト用ゲームを使おう。

1. 順位を表示するボードを作る

Stopした編集画面で、WorkspaceへPartを追加し、GlobalLeaderboardBoardへ名前を変える。Anchoredをオン、CanCollideをオフにし、Size18, 12, 1へ設定しよう。

その右のからSurfaceGui、その中へFrameを追加してPanelにする。Panelの中へ3つのTextLabelを追加し、TitleStatusEntriesへ名前を変える。区切り線が欲しい場合はDividerという細いFrameも追加してよい。

Workspace
└─ GlobalLeaderboardBoard (Part)
   └─ SurfaceGui
      └─ Panel (Frame)
         ├─ Title (TextLabel)
         ├─ Status (TextLabel)
         ├─ Divider (Frame)
         └─ Entries (TextLabel)

GlobalLeaderboardBoardのSurfaceGui内へランキング表示を作った構成

SurfaceGuiCanvasSize = 900, 600AlwaysOnTopをオンにする。3つのTextLabelは背景を透明にし、文字を左寄せにして、次の値を入れよう。ボードの色やフォントは変えても、Scriptの動作には影響しない。

名前 Text Position Size
Title 全サーバー共通ランキング 40, 26 820, 64
Status 更新待ち... 42, 92 816, 34
Entries まだ記録がない 48, 150 804, 408

表のPositionSizeは、すべてOffsetのX、Yへ入れる。たとえばTitleのPositionはXへ40、Yへ26だ。Titleは黄色のGothamBoldStatusは水色、Entriesは白にすると、完成画像のように役割を見分けやすい。EntriesだけはTextYAlignment = Topにする。これで4人目以降も上から下へ追加される。

ボードへ10行が収まらない場合は、Entries.TextSizeを25前後へ下げよう。文字がボードの裏側に出た場合は、SurfaceGuiFaceFrontからBackへ変えるか、Partを180度回転する。SurfaceGuiが見えないままコードへ進むと、取得成功と表示失敗を区別できないため、まず更新待ち...が読めるところまで整えよう。

Tip — SurfaceGuiはPartの面へ貼るUI

SurfaceGuiは、画面に固定するScreenGuiと違い、Partの表面へ文字やボタンを表示する入れ物だ。今回はゲーム内に置いた1枚の掲示板として使う。

2. 退出時のScoreをランキングへ保存する

ServerScriptServiceへScriptを追加し、GlobalScoreWriterへ名前を変える。中身を次のコードへ置き換えよう。

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local rankingStore = DataStoreService:GetOrderedDataStore("GlobalScore_v1")
local lastSavedValue = {}

local function saveScore(player)
	local leaderstats = player:FindFirstChild("leaderstats")
	local score = leaderstats and leaderstats:FindFirstChild("Score")
	if not score then
		return
	end

	local userId = player.UserId
	local value = score.Value
	if lastSavedValue[userId] == value then
		return
	end

	local success, saveError = pcall(function()
		rankingStore:SetAsync(tostring(userId), value)
	end)

	if success then
		lastSavedValue[userId] = value
		print("Global score saved:", player.Name, value)
	else
		warn("Global score save failed:", player.Name, saveError)
	end
end

Players.PlayerRemoving:Connect(saveScore)

game:BindToClose(function()
	for _, player in Players:GetPlayers() do
		saveScore(player)
	end
end)

前の記事の標準Data Storeは、数値だけでなく文字やtableも保存できる。今回のGetOrderedDataStore("GlobalScore_v1")は、並び替え専用の別の保存場所だ。公式の保存方法と同じく、keyには文字列へ変えたUserId、valueには数値のScoreを保存する。

Tip — Ordered Data Storeは数値を並べられる

Ordered Data Storeは、保存した数値を大小の順で取り出せる保存場所だ。たとえば3人のScoreが500、1300、900なら、ランキング用に1300、900、500と並べられる。

lastSavedValueは、同じ終了処理で同じScoreを2回保存しないための記録だ。Data Storeへの通信は失敗することがあるため、SetAsync()pcall()の中で実行する。

このScriptはプレイヤーの画面から送られた順位やScoreを使わない。サーバーにあるleaderstats > Scoreを読み、その値だけを保存する。ランキングの正本をサーバーへ置いておけば、LocalScriptから見た目だけを書き換えられても、ほかのプレイヤーの順位には混ざらない。

3. 上位10人を高い順で表示する

ServerScriptServiceへもう1つScriptを追加し、GlobalLeaderboardへ名前を変える。中身を次のコードへ置き換えよう。

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local rankingStore = DataStoreService:GetOrderedDataStore("GlobalScore_v1")
local panel = workspace
	:WaitForChild("GlobalLeaderboardBoard")
	:WaitForChild("SurfaceGui")
	:WaitForChild("Panel")
local entriesLabel = panel:WaitForChild("Entries")
local statusLabel = panel:WaitForChild("Status")

local REFRESH_SECONDS = 60
local previousText = entriesLabel.Text

local function getDisplayName(userId)
	local success, name = pcall(function()
		return Players:GetNameFromUserIdAsync(userId)
	end)
	if success then
		return name
	end
	return "User " .. userId
end

local function getTopTen()
	local success, pagesOrError = pcall(function()
		return rankingStore:GetSortedAsync(false, 10)
	end)
	if not success then
		return nil, pagesOrError
	end

	local lines = {}
	for rank, entry in ipairs(pagesOrError:GetCurrentPage()) do
		local userId = tonumber(entry.key)
		local name = userId and getDisplayName(userId) or entry.key
		table.insert(lines, string.format(
			"%d位  %-18s %d",
			rank,
			name,
			entry.value
		))
	end

	if #lines == 0 then
		return "まだ記録がない"
	end
	return table.concat(lines, "\n")
end

local function refresh()
	statusLabel.Text = "更新中..."

	local newText, readError = getTopTen()
	if newText then
		previousText = newText
		entriesLabel.Text = newText
		statusLabel.Text = "全サーバー共通・60秒ごとに更新"
	else
		entriesLabel.Text = previousText
		statusLabel.Text = "更新に失敗。前回の順位を表示中"
		warn("Global leaderboard read failed:", readError)
	end
end

while true do
	refresh()
	task.wait(REFRESH_SECONDS)
end

GetSortedAsync(false, 10)falseは大きい値から並べる指定、10は最初に受け取る件数だ。OrderedDataStoreの公式リファレンスでも、この組み合わせで上位10件を取得している。

Tip — 降順は大きい値から小さい値の順

Scoreの降順は、1300、900、500の順だ。falsetrueへ変えると小さい値から並ぶため、Scoreランキングではfalseのまま使おう。

UserIdはGetNameFromUserIdAsync()で名前へ変える。名前の取得だけ失敗した場合も順位全体を止めず、User 12345のような表示へ戻す。

GlobalScoreWriterGlobalLeaderboardの両方で、保存場所の名前をGlobalScore_v1にそろえる。同じ名前を2つのScriptから開いてもデータが複製されるわけではなく、片方が保存し、もう片方が同じ保存場所を読む。どちらかをGlobalScores_v1のように変えると、ボード側には記録がない状態になる。

まだ10人が参加していないゲームでは、保存済みの人数だけ表示されればよい。3人なら1位から3位までで、空いている7行を作る必要はない。11人以上になったとき、最初のページに入った上位10人だけが残る。

4. 退出して順位を確かめる

Playし、コインを拾うなどしてScoreを増やしたらStopしよう。OutputにGlobal score saved:と名前、Scoreが出れば、Ordered Data Storeへ保存できている。

もう一度Playし、ボードを見よう。保存直後は反映まで数秒かかる場合があるため、最初の表示が古ければ60秒待つ。別のアカウントやフレンドにも参加、Score変更、退出を1回ずつ行ってもらい、全員が高いScoreから並べば完成だ。Data Storeはゲーム全体で共通なので、別サーバーで保存した値も同じランキングから読める。

複数人で確認するときは、全員が同じPublish済みゲームへ参加していることを確かめる。共有が必要なら、先にフレンド限定で共有する記事を完了しよう。1人目を1300、2人目を900、3人目を500に固定する必要はない。実際に保存された値が大きい順なら成功だ。

公式チュートリアルでも、クラウド側の反映に数秒かかる場合があると説明している。すぐ変わらなくてもScriptを連打せず、次の更新を待とう。

取得に失敗したときは、表示済みの順位を消さず、Statusだけが更新に失敗。前回の順位を表示中へ変わる。

データ取得に失敗しても前回の3人の順位を残した状態

Data Storeの制限では、短時間にGetSortedAsync()を繰り返すと失敗する場合がある。60秒更新を極端に短くせず、エラーが続く場合はOutputを確認しよう。

最初の取得から失敗した場合は、前回の順位がまだないためまだ記録がないのまま状態だけがエラーになる。通信が戻れば次の60秒更新で自動的に順位へ切り替わる。ボタン連打やScriptの複製で再試行回数を増やさないことが大切だ。

うまく動かないとき

  • ずっと「まだ記録がない」: Scoreを変えたあとにStopして保存し、次のPlayで60秒待つ
  • API accessのエラーが出る: Publish済みの専用テストゲームで、前の記事のStudio API設定を確認する
  • 名前ではなくUser番号になる: 名前取得だけが失敗している。順位とScoreが出ていれば次の更新を待つ
  • 小さいScoreが1位になる: GetSortedAsync(true, 10)ではなくfalse, 10へ戻す
  • ボードが真っ暗: SurfaceGui.Faceを見えている面へ変え、TitleStatusEntriesPanelの中にあるか確認する
  • 順位が消える: 失敗した分岐でentriesLabel.Textを空にせず、previousTextを戻しているか確認する

完成

これで、各サーバーから保存されたScoreをまとめ、上位10人を高い順で表示するランキングが完成した。次のガイドはまだ公開していないため、存在しないURLへはリンクしない。

公式リファレンス

画像の拡大表示