添加轮次

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

添加轮次 让你将游戏结构化为具有明确起点和终点的阶段,以便玩家能够衡量他们的进度,并有周期性机会获得公平的竞争环境。这对团队游戏尤其重要,因为它为玩家提供了根据他们在该轮次的队伍情况切换游戏风格的机会。

示例激光标记体验 作为参考,本教程的这一部分教你如何使用和自定义 Roblox 内置功能来结构化每个轮次,包括有关脚本的指导:

  • 通过重置个人和团队积分开始轮次,然后将玩家生成到他们的团队生成区。
  • 自定义变量,在每个玩家屏幕顶部设置轮次的目标。
  • 跟踪玩家对团队得分的贡献。
  • 根据玩家的队伍是否赢得了轮次触发独特的 UI 屏幕。
  • 通过断开玩家连接并将他们生成到中立大厅结束轮次。

完成本节后,你将学习如何实现既准确又让玩家满意的标枪行为。

开始循环

ServerScriptServiceGameplayRounds 处理实现轮次的大部分逻辑,它通过调用 startRoundLoopAsync() 函数来标记轮次循环的开始。当玩家加入大厅并等待分配到团队时, startRoundLoopAsync()ServerScriptServiceGameplayScoring 中调用 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,以便只有与生成位置 TeamColor 属性相同 Team.Color 属性的玩家才能在此处生成。由于生成位置的 TeamColor 属性设置为 white 而不是示例中的薄荷绿色或肉色粉色团队,这种配置防止所有玩家在轮次进行时在此处生成或重生。

对于当前在大厅中的玩家, startRoundLoopAsync() 将所有当前在体验中的玩家传递给 ServerScriptServiceGameplayRoundsspawnPlayersInMap 中的 spawnPlayersInMap 函数,以大致相同数量的玩家将所有人排序和均衡到一个团队中。

对于在大厅组排序为团队后加入体验的任何新玩家, startRoundLoopAsync() 将监听 Players.PlayerAdded:Connect 事件,然后再次调用 spawnPlayersInMap 函数将他们添加到人数最少的团队。有关此过程的更多信息,请参阅之前的 生成和重生 部分中的 配置生成位置

Rounds

-- 将所有玩家生成到地图中
neutralSpawn.Neutral = false
spawnPlayersInMap(Players:GetPlayers())
-- 当新玩家加入时将他们生成到地图中
local playerAddedConnection = Players.PlayerAdded:Connect(function(player: Player)
spawnPlayersInMap({ player })
end)

设置目标

现在每个玩家都与他们的队友在竞技场中,体验需要提供成功完成轮次的说明。示例激光标记体验通过在每个玩家屏幕顶部提供目标提示来解决这个需求,明确指导团队需要做什么才能获胜。

虽然你可以更详细地了解如何在 UI 课程中配置和显示 目标 UI 组件,但这一部分专注于如何在轮次开始时实现目标,首先是设置每个团队需要完成轮次的积分数量。

即使在运行时目标提示通知玩家他们需要得分三点才能获胜,如果你检查 StarterGuiHUDGui 中的提示,你会看到它实际上包含一个可配置的 "%d" 用于积分值。

"%d" 是一个占位符字符串,你可以随时增加或减少以满足你自己的游戏需求,通过更新 ReplicatedStorageTEAM_SCORE_LIMIT 中的 TEAM_SCORE_LIMIT 变量。例如,如果你将此数字设置为过高的 200,则提示和团队积分计数器会相应更新。

TEAM_SCORE_LIMIT

local TEAM_SCORE_LIMIT = 200 -- 更新行,请确保恢复
return TEAM_SCORE_LIMIT

这个简单的变量更新在运行时有效,因为当轮次开始时, ReplicatedStorageHUDGuiSetupSetObjective 需要 TEAM_SCORE_LIMIT 模块脚本,以便在 UI 目标的 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 服务的默认行为会自动将每个玩家分组到他们的团队下,并将每个玩家贡献的分数汇总到他们的团队得分,但对基于轮次的游戏玩法而言,在单独的位置存储和监控积分很重要,因为如果一个玩家得分后在轮次结束前离开,他们的贡献将在他们断开与体验连接后立即从排行榜中扣除。

为了确保不发生这种情况,并且每一项对团队目标的贡献得以保留, ReplicatedStorageHUDGuiSetupStartSyncingTeamPointsTeams 服务中的 teamPoints 属性下单独存储所有积分。随着 teamPoints 的递增,该模块脚本调用 startSyncingTeamPoints 函数,以便在目标 UI 组件中找到团队计数器 Class.GuiObjects

当它找到 TeamACounterTeamBCounter 时,它获取它们的 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 的 mint teamColor 属性和 TeamBCounter 的 carnation pink 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)

到目前为止,本部分的所有内容都集中在如何跟踪玩家屏幕上的积分,但审查服务器上处理积分跟踪的逻辑同样重要,这样它才能知道何时一个团队满足目标并获胜。如果你回到 ServerScriptServiceGameplayScoring,你会看到模块脚本首先创建一个可绑定事件,在玩家得分时将触发该事件。

Scoring

local teamScoreChangedBindable = Instance.new("BindableEvent")
local Scoring = {
teamScoreChanged = teamScoreChangedBindable.Event,
}

然后它调用 incrementScore 函数,该函数执行以下操作:

  • 抓取玩家的团队及其在 Teams 服务中的 Team 对象的当前 teamPoints 值,并加一。
  • 抓取玩家在排行榜上的个人得分,并加一。
  • 触发之前提到的绑定事件,传递玩家的团队及其得分。

此过程有效地保持客户端和服务器关于玩家的个人得分和他们的团队得分一致。

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

显示结果

当玩家相互标记并为他们的团队得分时, ServerScriptServiceGameplayRounds 会检查得分的团队是否满足轮次目标。如果他们的团队得分低于 ReplicatedStorageTEAM_SCORE_LIMIT 中的 TEAM_SCORE_LIMIT 变量,服务器将继续等待,直到其中一个团队再次得分。

然而,一旦一个团队的得分达到 TEAM_SCORE_LIMIT 变量,脚本将触发一个 roundWinnerRemote 事件实例,并带上玩家的名字和他们的团队。

Rounds

-- 检查每次得分后轮次是否结束
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)
end

每个客户端上的 ReplicatedStorageRoundResultsGuiSetup 脚本监听此 roundWinnerRemote 事件实例,以便它能够:

  • 显示一个独特的 StarterGuiRoundResultsGui UI 屏幕,宣布轮次的结果以及玩家是否在获胜团队上。
  • 播放胜利或失败的音频剪辑。

例如,如果玩家在得分获胜的团队中,他们会收到多种形式的反馈,轮次结果以显示胜利文本的 UI 屏幕和播放欢快声音的音频剪辑的形式呈现。反之,如果玩家不在得分获胜的团队中,他们会收到显示失败文本的 UI 屏幕和播放凶险声音的音频剪辑。

胜利反馈
失败反馈
RoundResultsGuiSetup

local function onRoundWinner(winner: Team, localTeam: Team?)
local victoryDefeatText = "轮次结束!"
if localTeam then
-- 如果我们的团队获胜,我们会显示胜利!否则显示失败...
local isVictory = winner == localTeam
if isVictory then
victorySound:Play()
victoryDefeatText = VICTORY_TEXT
else
defeatSound:Play()
victoryDefeatText = DEFEAT_TEXT
end
end

重置团队

ServerScriptServiceGameplayRounds 验证某个团队满足轮次目标并触发适当的 UI 显示给每个玩家的同时,它还通过将所有玩家从竞技场传送到大厅,断开他们与轮次的连接。这开始了正式结束轮次和重置两个团队的过程。

使用 配置生成位置 中相同的逻辑, Rounds 接着将 Neutral 生成位置的 Neutral 属性设置为 true,以便玩家可以在此生成,而不考虑他们的团队状态。这意味着大厅将成为玩家在断开与轮次连接后生成的唯一位置。

Rounds

-- 将每个人发送到大厅
playerAddedConnection:Disconnect()
neutralSpawn.Neutral = true
spawnPlayersInLobby(Players:GetPlayers())

在等待十秒钟的间歇后,Rounds 服务器脚本恢复这一循环,通过重置每个人的得分并将他们排序为新团队。该示例重复这一周期轮次过程,直到服务器中没有任何玩家。

现在玩家可以与自己的团队一起生成到地图中,并进行完整的轮次,下一部分将教你关于每个标枪行为背后的脚本。

©2026 Roblox Corporation、Roblox、Roblox 标志及 Powering Imagination 是我们在美国及其他国家或地区的注册与未注册商标。