ラウンドの追加は、ゲームプレイをフェーズに構造化し、明確な開始および終了ポイントを設けることで、プレイヤーが自分の進捗を測り、均等な競技の機会を定期的に得ることを可能にします。特にチームベースのゲームプレイにおいては、ラウンド中に誰がチームにいるかに応じてプレイスタイルを変更する機会を提供するため、重要です。
サンプルのレーザータグエクスペリエンスを参考に、このチュートリアルのセクションでは、各ラウンドを構造化するためにRobloxの組み込み機能を使用およびカスタマイズする方法を学びます。これには以下のスクリプト関連のガイダンスが含まれます。
- 各プレイヤーとチームのポイントをリセットし、プレイヤーをチームのスポーンゾーンにスポーンさせることでラウンドを開始します。
- 各プレイヤーの画面上部にラウンドの目的を設定するための変数をカスタマイズします。
- プレイヤーがチームスコアに寄与したポイントを追跡します。
- プレイヤーのチームがラウンドに勝ったか負けたかに応じてユニークなUI画面をトリガーします。
- プレイヤーを切断して、中立的なロビーにスポーンさせることでラウンドを終了させます。
このセクションを完了すると、プレイヤーにとって正確で満足のいくブラスター動作を実装する方法を学ぶことができます。

ループの開始
ServerScriptService ⟩ Gameplay ⟩ Roundsは、ラウンドを実装するためのロジックのほとんどを処理し、startRoundLoopAsync()関数を呼び出してラウンドループの開始を示します。プレイヤーがロビーに参加してチームに割り振られるのを待っている間に、startRoundLoopAsync()はServerScriptService ⟩ Gameplay ⟩ ScoringにあるresetScores()関数を呼び出して、リーダーボードおよびチームポイントの両方をリセットします。
Scoring
function Scoring.resetScores()
for _, player in Players:GetPlayers() do
player.leaderstats.Points.Value = 0
end
for _, team in Teams:GetTeams() do
team:SetAttribute(GuiAttribute.teamPoints, 0)
end
end
全員がゼロポイントからスタートすることになったので、startRoundLoopAsync()は次にNeutralスポーン位置のNeutralプロパティをfalseに設定し、同じTeam.Colorプロパティを持つプレイヤーだけがそのスポーン位置にスポーンできるようにします。スポーン位置のTeamColorプロパティがサンプルのミントやカーネーションピンクのチームの代わりに白に設定されているため、この構成によりすべてのプレイヤーはラウンドがアクティブな間、その場所にスポーンまたはリスポーンできなくなります。
ロビーにいるプレイヤーについては、startRoundLoopAsync()は現在ロビー内にいるすべてのプレイヤーをServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMapのspawnPlayersInMap関数に渡し、全員をほぼ同数のプレイヤーのチームに振り分けてバランスを取ります。
ロビーグループがチームに振り分けられた後に新しいプレイヤーがエクスペリエンスに参加した場合、startRoundLoopAsync()はPlayers.PlayerAdded:Connectイベントを監視し、再びspawnPlayersInMap関数を呼び出してプレイヤーを最も少ないプレイヤーのチームに追加します。このプロセスの詳細については、前回のスポーンおよびリスポーンセクションのスポーン場所の設定を参照してください。
Rounds
-- マップ内のすべてのプレイヤーをスポーン
neutralSpawn.Neutral = false
spawnPlayersInMap(Players:GetPlayers())
-- 新しいプレイヤーが参加したときにマップ内にスポーン
local playerAddedConnection = Players.PlayerAdded:Connect(function(player: Player)
spawnPlayersInMap({ player })
end)
目標の設定
各プレイヤーが仲間と共にアリーナにいる今、エクスペリエンスはラウンド内で成功するために何をすべきかの指示を提供する必要があります。サンプルのレーザータグエクスペリエンスは、この要件に対処するために、勝つためにチームが何をする必要があるのかを明確に指示する目的プロンプトを各プレイヤーの画面の上部に提供します。

Objective UIコンポーネントを設定し表示する方法についてはUIカリキュラムで詳しく学べますが、このセクションではラウンドが開始されるときにどのように目標を設定するか、特に各チームがラウンドを完了するのに必要なポイント数を設定する方法に焦点を当てています。
ランタイム時の目的プロンプトでは、プレイヤーに勝つために3ポイントをスコアする必要があることを通知しますが、StarterGui ⟩ HUDGuiでプロンプトを確認すると、実際にはポイント値のための設定可能な"%d"が含まれていることがわかります。

"%d"はプレイヤーが自身のゲームプレイ要件を満たすために、いつでも増減できるプレースホルダ文字列です。これを行うには、ReplicatedStorage ⟩ TEAM_SCORE_LIMIT内のTEAM_SCORE_LIMIT変数を更新します。例えば、この数を大幅に高い200に設定すると、プロンプトとチームポイントカウンターはそれに応じて更新されます。
TEAM_SCORE_LIMITlocal TEAM_SCORE_LIMIT = 200 -- updated line, be sure to change backreturn TEAM_SCORE_LIMIT

この単純な変数の更新は、ラウンド開始時にReplicatedStorage ⟩ HUDGuiSetup ⟩ SetObjectiveがTEAM_SCORE_LIMITモジュールスクリプトを必要とするため、UI ObjectiveのTextLabelオブジェクト内のプレースホルダ文字列を交換できるため、ランタイムで機能します。
TEAM_SCORE_LIMIT
local TEAM_SCORE_LIMIT = require(ReplicatedStorage.TEAM_SCORE_LIMIT)
local function setObjective(gui: ScreenGui)
local bodyTextLabel = gui.Objective.ObjectiveDisplay.Body.BodyTextLabel
bodyTextLabel.Text = bodyTextLabel.Text:format(TEAM_SCORE_LIMIT)
end
ポイントの追跡
プレイヤーがラウンドの目標を持つことになったので、エクスペリエンスは各チームのポイントを目標に達するまで追跡する必要があります。Teamsサービスのデフォルトの動作は、各プレイヤーを自動的に自分のチームの下にグループ化し、各プレイヤーの貢献をチームスコアに加算しますが、ラウンドベースのゲームプレイではポイントを別の場所に保存・監視することが重要です。というのも、プレイヤーが得点した後にラウンドが終わる前に離脱すると、彼らの貢献は、エクスペリエンスから切断されるとすぐにリーダーボードから差し引かれるからです。
これを防ぎ、チーム目標へのすべての貢献を保持するために、ReplicatedStorage ⟩ HUDGuiSetup ⟩ StartSyncingTeamPointsは、すべてのポイントをTeamsサービスのteamPoints属性の下に別々に保存します。teamPointsがインクリメントされると、このモジュールスクリプトは、目的UIコンポーネント内のチームカウンターClass.GuiObjectsを見つけるためにstartSyncingTeamPoints関数を呼び出します。
TeamACounterとTeamBCounterを見つけると、ラウンドのスポーンゾーンに関連するteamColor属性を取得します:TeamACounterは緑チームのポイントを表示し、TeamBCounterはピンクチームのポイントを追跡します。
StartSyncingTeamPoints
local function startSyncingTeamPoints(gui: ScreenGui)
for _, teamPointCounter in gui.Objective.TeamPointCounter:GetChildren() do
if not teamPointCounter:IsA("GuiObject") then
continue
end
local iconTeamColor = teamPointCounter:GetAttribute(GuiAttribute.teamColor)
モジュールスクリプトは次にgetTeamFromTeamColor関数を呼び出して、TeamACounterのミントのteamColor属性とTeamBCounterのカーネーションピンクのteamColor属性が、Teamsサービスの下にある対応するTeam.Colorプロパティと一致することを確認します。一致すれば、両方のチームを返します。
StartSyncingTeamPoints
local function getTeamFromTeamColor(teamColor: Color3): Team?
for _, team in Teams:GetTeams() do
if team.TeamColor == teamColor then
return team
end
end
return nil
end
これが発生すると、startSyncingTeamPointsは両方のチームカウンターのTextLabelオブジェクトを対応するteamPoints値に設定し、プレイヤーが対戦チームの他のプレイヤーをタグ付けしてポイントを獲得するたびに、それらを更新し続けます。
StartSyncingTeamPoints
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
team:GetAttributeChangedSignal(GuiAttribute.teamPoints):Connect(function()
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
これまでのセクションでは、プレイヤーの画面上でポイントを追跡する方法に焦点を当ててきましたが、サーバー上でポイントを追跡するロジックも確認することが重要です。チームが目標に達し、そのラウンドに勝ったと知る必要があるからです。ServerScriptService ⟩ Gameplay ⟩ Scoringを再訪すると、モジュールスクリプトがバインダブルイベントを作成しており、プレイヤーがポイントをスコアするたびに発火します。
Scoringlocal teamScoreChangedBindable = Instance.new("BindableEvent")local Scoring = {teamScoreChanged = teamScoreChangedBindable.Event,}
次に、incrementScore関数を呼び出します。この関数は次のアクションを実行します:
- プレイヤーのリーダーボード上での個々のスコアを取得し、1を加算します。
- プレイヤーのチームと彼らのスコアを持って、前述のバインダブルイベントを発火させます。
このプロセスは、クライアントとサーバーの両方が、プレイヤーの個々のスコアとチームスコアについて整合を保つのに効果的です。
Scoring
function Scoring.incrementScore(player: Player, amount: number)
local team = player.Team
assert(team, `Player {player.Name} must be on a team to score a point, but has no team`)
local teamPoints = team:GetAttribute(GuiAttribute.teamPoints)
teamPoints += amount
team:SetAttribute(GuiAttribute.teamPoints, teamPoints)
local leaderstat = player.leaderstats.Points
leaderstat.Value += amount
teamScoreChangedBindable:Fire(team, teamPoints)
end
結果の表示
プレイヤーが互いにタグ付けを行い、チームのためにポイントをスコアするにつれて、ServerScriptService ⟩ Gameplay ⟩ Roundsは、スコアを記録したチームがラウンドの目標を達成したかどうかを確認します。もしそのチームのスコアが、ReplicatedStorage ⟩ TEAM_SCORE_LIMITにあるTEAM_SCORE_LIMIT変数よりも低い場合、サーバーは、再度いずれかのチームが得点するのを待ち続けます。
しかし、チームのスコアがTEAM_SCORE_LIMIT変数に達すると、スクリプトはプレイヤーの名前とそのチームでroundWinnerRemoteイベントインスタンスを発火させます。
Rounds-- 各スコアの後にラウンドが終了したか確認local team: Teamlocal score: number = 0while score < TEAM_SCORE_LIMIT doteam, score = Scoring.teamScoreChanged:Wait()end-- 勝利したチームを表示for _, player in Players:GetPlayers() do-- ラウンドの終わりにプレイヤーが属しているチームを送信-- プレイヤーのチームが削除されるため、クライアントは自チームを確認できなくなるroundWinnerRemote:FireClient(player, team, player.Team)end
各クライアント上のReplicatedStorage ⟩ RoundResultsGuiSetupスクリプトは、このroundWinnerRemoteイベントインスタンスをリッスンして、次のアクションを実行します:
- ラウンドの結果とプレイヤーが勝利チームに所属していたかどうかを知らせるユニークなStarterGui ⟩ RoundResultsGui UIスクリーンを表示します。
- 勝利または敗北の音声クリップを再生します。
たとえば、プレイヤーが勝利ポイントをスコアしたチームに属している場合、彼らはラウンドの結果に対する複数のフィードバック(勝利テキストを表示するUIスクリーンや、楽しい音が流れる音声クリップ)を受け取ります。逆に、プレイヤーが勝利ポイントをスコアしたチームに所属していない場合、彼らは敗北テキストを示すUIスクリーンや、不気味な音が流れる音声クリップを受け取ります。
RoundResultsGuiSetup
local function onRoundWinner(winner: Team, localTeam: Team?)
local victoryDefeatText = "ラウンドが終了しました!"
if localTeam then
-- チームが勝った場合はVictoryを表示、それ以外はDefeatを表示...
local isVictory = winner == localTeam
if isVictory then
victorySound:Play()
victoryDefeatText = VICTORY_TEXT
else
defeatSound:Play()
victoryDefeatText = DEFEAT_TEXT
end
end
チームのリセット
同時にServerScriptService ⟩ Gameplay ⟩ Roundsは、チームがラウンドの目標を達成したか確認し、各プレイヤーに適切なUI表示をトリガーすると同時に、すべてのプレイヤーをアリーナからロビーに移動させ、ラウンドの正式な終了プロセスを開始します。これにより、チームのリセットも行われます。
スポーン場所の設定における同様のロジックを使用して、RoundsはNeutralスポーン位置のNeutralプロパティをtrueに設定し、プレイヤーはチームのステータスに関係なくそこにスポーンできるようにします。これにより、ラウンドから切断された後、プレイヤーがスポーンできるのはロビーだけになります。
Rounds-- みんなをロビーに送るplayerAddedConnection:Disconnect()neutralSpawn.Neutral = truespawnPlayersInLobby(Players:GetPlayers())
10秒のインターバルを待った後、Roundsサーバースクリプトは再びループを開始し、すべてのスコアをリセットして新しいチームに振り分けます。サンプルは、サーバー内にプレイヤーがいなくなるまで、この循環的なラウンドプロセスを繰り返します。
プレイヤーが自分のチームでマップにスポーンし、フルラウンドをプレイできるようになったので、次のセクションでは各ブラスターの動作に関するスクリプトについて教えます。