収益化初級プラスRobux不要

Roblox Studioにゲームパス購入ボタンを置く方法

ゲーム内UIへPass名と現在価格を表示し、未所有者だけ購入画面を開き、購入後は所有済み表示へ切り替えよう。

公開日
VIP Pass名と現在価格をAPIから表示したゲーム内購入カード

ゲーム画面の下にVIP Passの名前、現在価格、購入ボタンを表示する。次の画像が、公式APIから現在価格を読み込んだ購入前の完成状態だ。完成までの目安は10分になる。

VIP Pass名と現在価格をAPIから表示したゲーム内購入カード

手順5では、ボタンからRobloxの購入画面が開くことを確認する。Studioだけの安全な完了テストを行うと所有済みへ変わり、そのPlay中はVIPドアを通れる。変化の前後は手順5の画像とGIFで確かめられる。

この記事は、ゲームパスでVIPドアを作る記事の続きだ。ServerScriptService > VipAccessがあり、サーバーがplayer.HasVipPassというBoolValueを作っている状態から始めよう。Passをまだ作成していない場合は、先に前の記事を完了する。

この記事では実際の購入を完了しない。購入ボタンから開く画面の確認までと、Studioでだけ作られるサーバー側のテストイベントを使う。本番の購入履歴やPass所有権は作られず、StopしてPlayし直すとテスト状態も消える。Roblox公式のMock Purchase案内でも、Studio内の購入シミュレーションはセッションをまたいで残らないと説明されている。

重要 — テスト表示を見てから押そう

購入画面にテスト購入であることと、Robuxを消費しないことが表示された場合だけ購入操作を進めよう。その表示がなければ画面を閉じる。この記事では、表示の有無に左右されないStudio限定のテスト手順も用意する。

1. Passを10 Robuxで販売状態にする

Creator Dashboardで対象ゲームを開き、収益化 > パスへ進む。前の記事で作ったPassを選び、販売を開こう。販売中のアイテムをオンにし、価格10を入力して変更を保存を押す。

Creator DashboardでVIP Passを販売中にして価格を10 Robuxへ設定する画面

保存できると、Pass一覧の価格に10が表示される。販売設定の保存ではRobuxを使わない。ただし、本番でプレイヤーが購入を確定すると、その画面に表示されたRobuxを使う。この記事の完成確認では購入を確定しない。

Tip — 設定した10を画面へ直接書かない

価格は地域などによって変わる場合がある。Dashboardの10をTextLabelへ固定せず、次の手順でそのプレイヤー向けの現在価格を読み込む。

2. VIP Passのカードを作る

ExplorerでStarterGuiScreenGuiを追加してVipPassGuiにする。その中へFrameVipPassCardを追加し、さらに次の4つを入れよう。

VipPassGui
└─ VipPassCard
   ├─ PassName       (TextLabel)
   ├─ PriceLabel     (TextLabel)
   ├─ PurchaseButton (TextButton)
   └─ PassStore      (LocalScript)

VipPassGuiへ追加したラベル、購入ボタン、LocalScript

VipPassCardは画面下へ置き、PassNameVIP PassPriceLabel価格を確認中...PurchaseButton読み込み中...にする。ボタンはマウス、タッチ、ゲームパッドで使えるActivatedイベントへつなぐ。公式のボタン解説でも、複数の入力方法に対応する操作としてActivatedを使っている。

カードが画面外へ出ないよう、VipPassGui.ResetOnSpawnをオフにする。VipPassCardは幅360、高さ180ほどにし、下中央へ寄せよう。上からPass名、価格、購入ボタンの順に並べ、3つの文字が重ならないことをPlay前に確認する。細かな色や角丸は自由だが、価格と購入ボタンは背景から読める色にしよう。

Tip — LocalScriptは自分の画面を動かすScript

LocalScriptは各プレイヤーの端末で動く。今回は、その人向けの価格を取得して、その人の購入画面を開く。VIPドアを通れるかは決めず、serverが作ったHasVipPassを表示に使うだけだ。

3. Pass名と現在価格を読み込む

PassStoreを開き、すべてを次のコードへ置き換えよう。PASS_ID = 00だけを、前の記事でコピーしたPass IDへ置き換える。

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

local PASS_ID = 0 -- 作成したPass IDへ置き換える

local player = Players.LocalPlayer
local hasVipPass = player:WaitForChild("HasVipPass")
local card = script.Parent
local passName = card:WaitForChild("PassName")
local priceLabel = card:WaitForChild("PriceLabel")
local purchaseButton = card:WaitForChild("PurchaseButton")

local productReady = false
local canRetry = false
local promptOpen = false
local currentPrice = 0

local function updateDisplay()
	if hasVipPass.Value then
		priceLabel.Text = "このPassを所有しています"
		purchaseButton.Text = "所有済み"
		purchaseButton.Active = false
		purchaseButton.AutoButtonColor = false
		return
	end

	purchaseButton.AutoButtonColor = true
	if productReady then
		priceLabel.Text = tostring(currentPrice) .. " Robux"
		purchaseButton.Text = tostring(currentPrice) .. " Robuxで購入"
		purchaseButton.Active = not promptOpen
	else
		purchaseButton.Active = canRetry
	end
end

local function loadProduct()
	productReady = false
	canRetry = false
	priceLabel.Text = "価格を確認中..."
	purchaseButton.Text = "読み込み中..."
	purchaseButton.Active = false
	updateDisplay()

	local success, productInfo = pcall(function()
		return MarketplaceService:GetProductInfoAsync(
			PASS_ID,
			Enum.InfoType.GamePass
		)
	end)

	if not success then
		warn("Pass information failed:", productInfo)
		priceLabel.Text = "価格を取得できない"
		purchaseButton.Text = "価格を再読み込み"
		canRetry = true
		updateDisplay()
		return
	end

	passName.Text = productInfo.Name
	if productInfo.IsForSale and typeof(productInfo.PriceInRobux) == "number" then
		currentPrice = productInfo.PriceInRobux
		productReady = true
	else
		priceLabel.Text = "現在は販売していない"
		purchaseButton.Text = "購入できない"
	end
	updateDisplay()
end

purchaseButton.Activated:Connect(function()
	if hasVipPass.Value or promptOpen then
		return
	end
	if not productReady then
		if canRetry then
			loadProduct()
		end
		return
	end

	promptOpen = true
	updateDisplay()
	MarketplaceService:PromptGamePassPurchase(player, PASS_ID)
end)

MarketplaceService.PromptGamePassPurchaseFinished:Connect(
	function(finishedPlayer, purchasedPassId, wasPurchased)
		if finishedPlayer ~= player or purchasedPassId ~= PASS_ID then
			return
		end

		promptOpen = false
		if wasPurchased and not hasVipPass.Value then
			purchaseButton.Text = "所有状態を反映中..."
			purchaseButton.Active = false
			purchaseButton.AutoButtonColor = false
			return
		end
		updateDisplay()
	end
)

hasVipPass.Changed:Connect(updateDisplay)
updateDisplay()
loadProduct()

Regional Pricingの公式説明によると、Passの価格はユーザーによって変わる場合がある。そのため数字をコードへ直接書かず、GetProductInfoAsync()をLocalScriptから呼び、PriceInRobuxを表示する。Nameも同じ結果から取得する。

公式APIから読み込んだVIP Pass名と現在価格

productReadyは、販売中のPassから数字の価格を受け取れたときだけtrueになる。取得中や失敗中にボタンを押しても購入画面を開かないための安全札だ。promptOpenは購入画面を開いている間の連打を止め、canRetryは通信に失敗した場合だけ同じボタンを再読み込みへ使う。

Dashboardで設定した基本価格と表示価格が違っても、すぐにコードを書き換えない。Roblox Plusの公式説明にある割引や地域価格が反映される場合がある。PriceInRobuxと実際のpromptが同じ金額なら、APIから現在価格を読めている。

4. 購入完了をサーバーで受け取る

画面のLocalScriptだけでHasVipPassを変えてはいけない。ServerScriptService > VipAccessを開き、サービスを取得している上部へ次の1行を追加する。

local ServerStorage = game:GetService("ServerStorage")

次に、setVipAccess()より下へ次のコードを追加しよう。PASS_IDは前の記事と同じ値を使う。

local function onPromptGamePassPurchaseFinished(
	player,
	purchasedPassId,
	wasPurchased
)
	if not wasPurchased or purchasedPassId ~= PASS_ID then
		return
	end

	setVipAccess(player, true)
end

MarketplaceService.PromptGamePassPurchaseFinished:Connect(
	onPromptGamePassPurchaseFinished
)

if RunService:IsStudio() then
	local studioMockPurchase = Instance.new("BindableEvent")
	studioMockPurchase.Name = "StudioMockVipPurchase"
	studioMockPurchase.Parent = ServerStorage

	studioMockPurchase.Event:Connect(function(player)
		onPromptGamePassPurchaseFinished(player, PASS_ID, true)
	end)
end

Passesの公式実装例も、PromptGamePassPurchaseFinishedをサーバー側で処理して特典を反映する形だ。ここではsetVipAccess()がサーバーのBoolValueとCollision Groupを同時に更新する。LocalScriptが表示を書き換えても、サーバーのVIP権限は作れない。

onPromptGamePassPurchaseFinished()は、閉じただけの結果と別商品のIDを無視する。正しいPassの成功だけをsetVipAccess()へ渡すため、別の商品画面が閉じたときにVIPになることはない。

StudioMockVipPurchaseは、現在のStudio Play中にだけServerStorageへ作るテスト用イベントだ。クライアントからは見えず、本番サーバーでは作られない。実際のRobux購入をせず、同じ完了処理へ安全にテスト結果を渡せる。

5. 購入画面とStudio限定の完了テストを確かめる

Playを押す。前の記事のSTUDIO_TEST_OWNED = falseなら、カードにPass名と現在価格が表示される。今回のテストPassでは、Dashboardへ保存した販売状態をAPIから読み込み、VIP Access10 Robux10 Robuxで購入が表示された。

この表示はチュートリアル用のHasVipPass = falseを見ており、Roblox上のインベントリが本当に未所有だと証明するものではない。今回の制作者アカウントはPassをすでに所有していたため、購入画面では未所有者向けの購入確定やキャンセル分岐を検証していない。

購入ボタンを押し、購入画面が開くことを確認する。制作アカウントなど、すでにPassを所有しているアカウントでは「すでに所有しているため請求されない」という画面になる。この場合も購入は行われないので、画面を閉じよう。

すでにPassを所有しているためアカウントへ請求されないStudioの購入画面

本当に未所有のアカウントを使う場合も、画面にStudioのテスト購入であることとRobuxを消費しないことが明記された場合だけ完了処理を確認してよい。どちらの表示もない場合は購入を進めず、次のStudio限定テストを使う。

Command Barからサーバーのテストイベントを動かす

PlayしたままWindow > Script > Command Barを開く。Testメニューのクライアント/サーバー表示切替を使い、ウィンドウ枠が緑色になるServer表示へ切り替える。Command Barへ次の1行を貼り付けて実行しよう。

緑色のServer表示でCommand Barへテストコードを入力する場所

local player = game.Players:GetPlayers()[1]
game.ServerStorage.StudioMockVipPurchase:Fire(player)

Testメニューからクライアント表示へ戻ると、カードが所有済みへ変わる。同じPlay中にVIPドアを通れ、所有済みボタンを押しても購入画面が開かなければ成功だ。

Studio限定のサーバーテストで購入ボタンが所有済みへ変わる動き

最後にStopして、もう一度Playしよう。テスト状態は残らないため、現在価格と購入ボタンへ戻る。本番では参加時にサーバーのUserOwnsGamePassAsync()が実際の所有権を確認する。Studioの現在セッション用テストと本番の所有権を混ぜないことが大切だ。

Stop後の新しいPlayで10 Robuxの購入ボタンへ戻った画面

次の順番で見えれば、購入前、購入画面、完了直後、次のセッションを分けて確認できたことになる。

  1. 最初のPlayではStudioテスト設定による現在価格と購入ボタン
  2. 所有済み・請求なしの画面を閉じた後も、テスト状態はfalseでドアは閉じたまま
  3. Studio限定テスト後は所有済みでドアを通れる
  4. 所有済みボタンから購入画面は開かない
  5. Stop後の新しいPlayではテスト状態が消える

うまく動かないとき

  • ずっと価格を確認中...: PassStoreが通常のScriptではなくLocalScriptか確認する
  • 価格を取得できない: PASS_ID0のままではないか確認し、Outputの警告を見てから価格を再読み込みを押す
  • 現在は販売していない: Creator Dashboardの収益化 > パス > 販売販売中のアイテムがオンか確認する
  • 価格がDashboardの数字と違う: 地域価格やRoblox Plusの割引が反映される場合がある。画面へ固定価格を書かず、PriceInRobuxを使う
  • テスト後もドアを通れない: サーバー表示のCommand Barで実行したか、2か所のPASS_IDが同じか確認する
  • StudioMockVipPurchaseが見つからない: Stop中やクライアント表示ではなく、Play中のサーバー表示でCommand Barを実行する
  • Stop後に未所有へ戻った: Studio限定テストの正常な境界だ。本番の購入履歴や所有権は作られない
  • テスト購入の表示がない: 購入を進めず画面を閉じ、Studio限定のサーバーテストを使う

完成

これで、未所有者にはAPIから取得したPass名と現在価格が表示され、購入画面を開けるようになった。購入完了時はサーバーが現在のセッションへVIP権限を付け、所有済みの人へ再購入を求めない。Studio限定テストでも、Robuxを使わず同じ完了処理とStop後のリセットを確認できた。

次は、何度でも購入できるDeveloper Productを作り、購入1回につき100コインを増やす仕組みへ進む。

公式リファレンス

画像の拡大表示