Robloxでゴール到達バッジを作る:BadgeServiceの使い方
Creator Dashboardでゴール到達バッジを作り、GoalPlatformへ触れた未取得プレイヤーへサーバーから安全に付与しよう。

黄色いGOALへ乗ると「ゴール到達」Badgeが付与され、プレイヤーのInventoryへ達成記録が残るようにする。完成までの目安は10分だ。Publish済みの自分のゲームと、Creator Dashboardへ入れる無料アカウントを使う。

この記事は、黄色いGoalPlatformを作る記事とゲームをPrivateでPublishする記事の続きだ。Course > GoalPlatformがあり、自分が所有するPublish済みゲームから始めよう。
Tip — Badgeは達成記録として残るメダル
Badgeは、ゴールや特別な条件を達成したプレイヤーへ贈るメダルだ。取得するとInventoryのBadgesへ残り、同じBadgeをもう一度取っても2個には増えない。
1. Creator DashboardでBadgeを作る
Creator Dashboardで対象ゲームを開き、左側のエンゲージメント > バッジへ進む。バッジを作成を押そう。
512×512ピクセルの正方形画像をアップロードし、次を入力する。
| 項目 | 入力する内容 |
|---|---|
| 名前 | ゴール到達 |
| 詳細 | 3ステージのアスレチックで黄色いGOALへ到達した! |
| バッジが有効です | オン |

Roblox公式のBadge解説では、所有するゲームごとにGMT基準の24時間で最初の5個まで無料、超過分は1個100 Robuxだ。作成ボタンの近くに100 Robuxと表示された場合は押さず、次の無料枠まで待とう。この記事の検証では無料表示を確認して作成した。
画像は円形に切り抜かれるため、文字やトロフィーなど大切な部分は中央へ置く。バッジが有効ですをオフにするとプレイヤーが取得できないので、オンのままバッジを作成を押そう。
アップロード後のプレビューでは、トロフィーの左右や上下が円の外へ切れていないかも確認する。元画像が正方形でも、一覧やInventoryでは円形で表示される。外周ぎりぎりに細い文字を置くと読めなくなるため、主役を中央へ寄せ、背景は切れても困らない模様にしよう。
名前と詳細は、プレイヤーが「なぜ取れたか」をあとから思い出せる内容にする。クリアだけでは条件が分かりにくいが、3ステージと黄色いGOALまで書けば、別のBadgeが増えても区別できる。作成後は内容審査が終わるまで、画像がほかの人へすぐ表示されない場合がある。
2. Badge IDをコピーする
Badge一覧へ戻り、作ったゴール到達へマウスを重ねる。…を開き、Asset IDをコピーを押そう。

Tip — Badge IDはScriptからBadgeを指定する番号
Badge IDとAsset IDは、ここでは同じ固有番号を指す。ゲームのExperience IDやPlace IDとは別物だ。今回コピーした
77529583068327を次のBADGE_IDへ入れる。
番号をメモ帳から手で写すと、途中の数字を落としやすい。メニューからコピーし、local BADGE_ID =の右へそのまま貼り付けよう。前後へ引用符は付けず、数字として書く。別のゲームで作ったBadge IDを使うと、対象ゲームとの組み合わせが違うため付与できない。
3. GoalBadge Scriptを追加する
Studioへ戻り、ServerScriptServiceへScriptを追加してGoalBadgeへ名前を変える。中身をすべて消し、次へ置き換えよう。
local BadgeService = game:GetService("BadgeService")
local Players = game:GetService("Players")
local BADGE_ID = 77529583068327
local goal = workspace:WaitForChild("Course"):WaitForChild("GoalPlatform")
local processing = {}
local function awardGoalBadge(player)
if processing[player] then
return
end
processing[player] = true
local infoSuccess, badgeInfo = pcall(
BadgeService.GetBadgeInfoAsync,
BadgeService,
BADGE_ID
)
if not infoSuccess then
warn("Goal badge info failed:", badgeInfo)
processing[player] = nil
return
end
if not badgeInfo.IsEnabled then
warn("Goal badge is disabled")
processing[player] = nil
return
end
local checkSuccess, hasBadge = pcall(
BadgeService.UserHasBadgeAsync,
BadgeService,
player.UserId,
BADGE_ID
)
if not checkSuccess then
warn("Goal badge check failed:", hasBadge)
elseif hasBadge then
print("GOAL_BADGE_ALREADY_OWNED", player.Name)
else
local callSuccess, awarded = pcall(
BadgeService.AwardBadgeAsync,
BadgeService,
player.UserId,
BADGE_ID
)
if callSuccess and awarded then
print("GOAL_BADGE_AWARDED", player.Name)
else
warn("Goal badge award failed:", awarded)
end
end
processing[player] = nil
end
local function onGoalTouched(hit)
local character = hit:FindFirstAncestorOfClass("Model")
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
local player = humanoid and Players:GetPlayerFromCharacter(character)
if player then
awardGoalBadge(player)
end
end
goal.Touched:Connect(onGoalTouched)
Players.PlayerRemoving:Connect(function(player)
processing[player] = nil
end)

GoalPlatform.Touchedは足や体の複数Partで続けて発生する場合がある。processing[player]がある間は同じPlayerの通信を重ねず、完了後にだけ次を受け付ける。
3つのBadgeServiceメソッドはRoblox側と通信するため、失敗する可能性がある。pcall()で受け止め、まずGetBadgeInfoAsync()で有効状態、次にUserHasBadgeAsync()で取得済みかを確認し、未取得の場合だけAwardBadgeAsync()を呼ぶ。公式もBadgeの付与はサーバー側Scriptから行うよう案内している。
順番を省かないのが大切だ。最初に有効状態を見ることで、Dashboard側で停止したBadgeを無理に付与しない。次に所有状態を見ることで、取得済みプレイヤーへ不要な付与通信を繰り返さない。最後のAwardBadgeAsync()がtrueを返した場合だけ、成功ログを出す。
processing[player]は保存データではなく、いま処理中かだけを覚える短い表だ。通信を待つ間に同じキャラクターの右足や左足が触れても2本目の処理を始めない。Playerが退出したら行を消すため、長時間serverが動いても退出済みPlayerの記録を残さない。
Badge付与をLocalScriptへ置かない理由もここにある。画面側のLocalScriptはプレイヤー自身の端末で動くため、達成していないのに付与を要求する処理へ書き換えられる可能性がある。ゴールの実物へ触れたかをserver側で確かめ、そのserverだけがBadgeServiceを呼ぶ形にしよう。
4. 未取得と取得済みを確認する
Playを押し、黄色いGOALへ歩いて乗ろう。初回はBadge取得通知が表示され、Outputへ次が出る。
GOAL_BADGE_AWARDED robtsuku
Stop後にRobloxのInventoryでバッジを開き、ゴール到達が追加されていることを確認する。

もう一度Playして同じGOALへ乗る。今度は新しいBadgeを増やさず、Outputへ次が出れば取得済み分岐も成功だ。
GOAL_BADGE_ALREADY_OWNED robtsuku
確認するのは、初回の付与、Inventoryへの追加、2回目の取得済み判定、赤いエラーなしの4点だ。この記事の実機確認では、初回に取得通知とGOAL_BADGE_AWARDED、Dashboardの獲得者数1、Inventoryのゴール到達を確認した。取得通知だけでは一時的に見逃すことがあるため、InventoryとOutputも合わせて見よう。
初回テストでは、Play開始直後にOutputを開き、GOALへ触れる前に赤い行がないことを確認する。そのままキャラクターを実際に歩かせよう。Command Barから関数を直接呼ぶだけでは、TouchedからPlayerを探す部分まで通った証拠にならない。
Inventoryへ反映されるまで少し待つ場合は、何度もGOALへ乗り直さず、まずOutputを見る。GOAL_BADGE_AWARDEDがあれば付与APIは成功している。ページを再読み込みしてBadgeを確認しよう。逆に警告が出ていれば、先にその内容を直してから新しいPlayを始める。
2回目は、同じアカウントで新しいPlayを開始する。取得済みのため通知は増えないが、GOAL_BADGE_ALREADY_OWNEDが1回出れば正常だ。GOALの上で体が動いても赤いエラーが連続せず、ゲームをそのまま操作できることまで確かめよう。
うまく動かないとき
- Badgeを作成できない: Publish済みで自分が所有するゲームを開いているか確認する
- 100 Robuxと表示される: 作成せず、GMT基準の次の無料枠まで待つ
Goal badge info failedが出る:BADGE_IDへExperience IDではなくAsset IDをコピーで得た番号を入れる- Badgeが無効と出る: Creator Dashboardで対象Badgeの有効設定をオンにする
- GOALへ乗っても反応しない:
Course > GoalPlatformの名前と親子関係を確認する - 取得済みなのに毎回通信する:
UserHasBadgeAsync()とhasBadgeの分岐を消していないか確認する
完成
これで、未取得プレイヤーがGOALへ触れたときだけBadgeを付与し、取得済みや通信失敗でもゲームを止めないゴール処理が完成した。別のBadgeを増やすときも、Dashboardで作ったAsset IDと達成条件を対応させよう。
