添加回合

*此内容使用人工智能(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 服务的默认行为会自动将每个玩家分组到他们的团队下,并将每个玩家的贡献加到他们的团队得分中,但在基于回合的游戏玩法中,将积分存储和监控在单独的位置是很重要的,因为如果玩家得分后在回合结束前离开,他们的贡献将在他们断开连接后立即从排行榜中扣除。

为了确保这种情况不会发生,并且每个对团队目标的贡献都被保留,ReplicatedStorageHUDGuiSetupStartSyncingTeamPoints 将所有积分单独存储在 Teams 服务中的 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 服务中的当前 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 是我们在美国及其他国家或地区的注册与未注册商标。