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

ループを開始
ServerScriptService ⟩ Gameplay ⟩ Rounds はラウンドを実装するためのほとんどのロジックを処理し、startRoundLoopAsync()関数を呼び出してラウンドループの開始をマークします。プレイヤーがロビーに参加し、チームに振り分けられるのを待っている間、startRoundLoopAsync()はServerScriptService ⟩ Gameplay ⟩ Scoring内のresetScores()関数を呼び出して、リーダーボードとチームポイントの両方をリセットします。
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に設定し、スポーン位置のTeamColorプロパティと同じTeam.Colorプロパティを持つプレイヤーのみがそこにスポーンできるようにします。スポーン位置のTeamColorプロパティがサンプルのミントやカーネーションピンクのチームではなく白に設定されているため、この構成により、ラウンドがアクティブな間はすべてのプレイヤーがそこにスポーンまたはリスポーンすることを防ぎます。
ロビーにいるプレイヤーに対して、startRoundLoopAsync()は現在ゲーム内にいるすべてのプレイヤーをServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMap内のspawnPlayersInMap関数に渡し、ほぼ同じ数のプレイヤーでチームに振り分けてバランスを取ります。
ロビーグループがチームに振り分けられた後にゲームに参加する新しいプレイヤーに対して、startRoundLoopAsync()はPlayers.PlayerAdded:Connectイベントをリッスンし、再度spawnPlayersInMap関数を呼び出して、最も少ないプレイヤー数のチームに追加します。このプロセスの詳細については、前のスポーンとリスポーンセクションのスポーン位置の設定を参照してください。
-- マップ内のすべてのプレイヤーをスポーンさせる
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に設定すると、プロンプトとチームポイントカウンターがそれに応じて更新されます。
local TEAM_SCORE_LIMIT = 200 -- 更新された行、元に戻すことを忘れないでください
return TEAM_SCORE_LIMIT
この単純な変数の更新は、ラウンドが始まるときにReplicatedStorage ⟩ HUDGuiSetup ⟩ SetObjectiveがTEAM_SCORE_LIMITモジュールスクリプトを要求するため、UIのObjectiveのTextLabelオブジェクト内のプレースホルダー文字列を交換することができます。
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が増加すると、このモジュールスクリプトはstartSyncingTeamPoints関数を呼び出して、Objective UIコンポーネント内のチームカウンターClass.GuiObjectsを見つけます。
TeamACounterとTeamBCounterを見つけると、それぞれのteamColor属性を取得します。これはチームのスポーンゾーンに関連しています:TeamACounterは緑チームのポイントを表示し、TeamBCounterはピンクチームのポイントを追跡します。
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プロパティと一致することを確認します。一致する場合は、両方のチームを返します。
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値に設定し、プレイヤーが対戦相手のプレイヤーをタグ付けしてポイントを獲得するたびにそれらを更新し続けます。
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
team:GetAttributeChangedSignal(GuiAttribute.teamPoints):Connect(function()
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)これまでのセクションでは、プレイヤーの画面上でポイントを追跡する方法に焦点を当ててきましたが、サーバー上でポイントを追跡するロジックを確認することも重要です。これにより、チームが目的目標を達成し、ラウンドに勝利したかどうかを知ることができます。ServerScriptService ⟩ Gameplay ⟩ Scoringに戻ると、モジュールスクリプトは、プレイヤーがポイントを獲得するたびに発火するバインダブルイベントを作成することから始まります。
local teamScoreChangedBindable = Instance.new("BindableEvent")
local Scoring = {
teamScoreChanged = teamScoreChangedBindable.Event,
}次に、incrementScore関数を呼び出し、以下のアクションを実行します:
- プレイヤーのリーダーボード上の個別スコアを取得し、1を加算します。
- プレイヤーのチームとスコアの両方を持つ前述のバインダブルイベントを発火します。
このプロセスは、クライアントとサーバーの両方がプレイヤーの個別スコアとチームスコアに関して整合性を保つのに効果的です。
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イベントインスタンスを発火します。
-- 各スコアの後にラウンドが終了したかどうかを確認
local team: Team
local score: number = 0
while score < TEAM_SCORE_LIMIT do
team, score = Scoring.teamScoreChanged:Wait()
end
-- 勝利チームを表示
for _, player in Players:GetPlayers() do
-- ラウンドの終わりにプレイヤーがどのチームにいるかを送信
-- プレイヤーのチームが削除されるため、クライアントは自分のチームを確認できなくなる
roundWinnerRemote:FireClient(player, team, player.Team)
endReplicatedStorage ⟩ RoundResultsGuiSetupスクリプトは、各クライアントでこのroundWinnerRemoteイベントインスタンスをリッスンし、以下のことを行います:
- ラウンドの結果を発表し、プレイヤーが勝利チームにいたかどうかを示すユニークなStarterGui ⟩ RoundResultsGui UI画面を表示します。
- 勝利または敗北のオーディオクリップを再生します。
たとえば、プレイヤーが勝利ポイントを獲得したチームにいる場合、彼らは勝利テキストを表示するUI画面と、喜びの音を再生するオーディオクリップの形でラウンド結果に対する複数のフィードバックを受け取ります。逆に、プレイヤーが勝利ポイントを獲得したチームにいない場合、彼らは敗北テキストを表示するUI画面と、不吉な音を再生するオーディオクリップを受け取ります。
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に設定し、プレイヤーがチームのステータスに関係なくそこにスポーンできるようにします。これにより、プレイヤーがラウンドから切断された後、スポーンできる唯一の場所がロビーになります。
-- すべてのプレイヤーをロビーに送る
playerAddedConnection:Disconnect()
neutralSpawn.Neutral = true
spawnPlayersInLobby(Players:GetPlayers())10秒間のインターミッションを待った後、Roundsサーバースクリプトは、すべてのスコアをリセットし、新しいチームに振り分けることでループを再開します。このサンプルは、サーバー内にプレイヤーがいなくなるまでこの循環ラウンドプロセスを繰り返します。
プレイヤーが自分のチームでマップにスポーンし、フルラウンドをプレイできるようになったので、次のセクションでは各ブラスターの動作の背後にあるスクリプトについて学びます。