라운드 추가

*이 콘텐츠는 AI(베타)를 사용해 번역되었으며, 오류가 있을 수 있습니다. 이 페이지를 영어로 보려면 여기를 클릭하세요.

라운드 추가는 게임 플레이를 명확한 시작과 종료 지점이 있는 단계로 구조화하여 플레이어가 자신의 진행 상황을 측정하고 평등한 플레이 환경을 주기적으로 제공할 수 있도록 합니다. 이는 팀 기반 게임 플레이에서 특히 중요하며, 플레이어가 해당 라운드에서 자신의 팀에 따라 플레이 스타일을 전환할 수 있는 기회를 제공합니다.

샘플 레이저 태그 게임을 참조하여, 이 튜토리얼의 이 섹션에서는 각 라운드를 구조화하기 위해 Roblox의 내장 기능을 사용하고 사용자 정의하는 방법을 가르치며, 다음과 같은 스크립팅 지침을 포함합니다:

  • 개별 및 팀 점수를 초기화한 후 플레이어를 팀 스폰 존으로 스폰하여 라운드를 시작합니다.
  • 각 플레이어의 화면 상단에 라운드 목표를 설정하는 변수를 사용자 정의합니다.
  • 팀 점수를 위한 플레이어 점수 기여도를 추적합니다.
  • 플레이어의 팀이 라운드에서 승리했는지 또는 패배했는지에 따라 고유한 UI 화면을 트리거합니다.
  • 플레이어를 분리하고 중립 로비에 스폰하여 라운드를 종료합니다.

이 섹션을 완료한 후, 플레이어에게 정확하고 만족스러운 블래스터 행동을 구현하는 방법을 배우게 됩니다.

루프 시작

ServerScriptServiceGameplayRounds는 라운드를 구현하는 대부분의 논리를 처리하며, startRoundLoopAsync() 함수를 호출하여 라운드 루프의 시작을 표시합니다. 플레이어가 로비에 참여하고 팀으로 정렬되기를 기다리는 동안, startRoundLoopAsync()ServerScriptServiceGameplayScoringresetScores() 함수를 호출하여 리더보드와 팀 점수를 초기화합니다.

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

모든 플레이어가 0점에서 시작하게 되면, startRoundLoopAsync()Neutral 스폰 위치의 Neutral 속성을 false로 설정하여 스폰 위치의 TeamColor 속성과 동일한 Team.Color 속성을 가진 플레이어만 그곳에 스폰할 수 있도록 합니다. 스폰 위치의 TeamColor 속성이 샘플의 민트 또는 카네이션 핑크 팀 대신 흰색으로 설정되어 있기 때문에, 이 구성은 라운드가 활성화된 동안 모든 플레이어가 그곳에 스폰하거나 리스폰하는 것을 방지합니다.

로비에 현재 있는 플레이어의 경우, startRoundLoopAsync()는 현재 게임에 있는 모든 플레이어를 ServerScriptServiceGameplayRoundsspawnPlayersInMapspawnPlayersInMap 함수로 전달하여 모든 플레이어를 대략 동일한 수의 팀으로 정렬하고 균형을 맞춥니다.

로비 그룹이 팀으로 정렬된 후 게임에 새로 참여하는 플레이어의 경우, 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는 UI 목표의 TextLabel 객체에서 자리 표시자 문자열을 교체할 수 있도록 TEAM_SCORE_LIMIT 모듈 스크립트를 요구합니다.

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가 증가함에 따라 이 모듈 스크립트는 목표 UI 구성 내의 팀 카운터 Class.GuiObjects를 찾기 위해 startSyncingTeamPoints 함수를 호출합니다.

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의 민트 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)

지금까지 이 섹션에서는 플레이어 화면에서 점수를 추적하는 방법에 중점을 두었지만, 서버에서 점수를 추적하는 논리를 검토하는 것도 중요합니다. 서버는 팀이 목표를 달성하고 라운드를 이겼는지 알 수 있어야 합니다. ServerScriptServiceGameplayScoring으로 돌아가면, 모듈 스크립트가 플레이어가 점수를 얻을 때마다 발생할 바인더블 이벤트를 생성하는 것을 볼 수 있습니다.

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

그런 다음 incrementScore 함수를 호출하여 다음과 같은 작업을 수행합니다:

  • 플레이어의 팀과 Teams 서비스의 Team 객체에서 현재 teamPoints 값을 가져온 후 1을 추가합니다.
  • 플레이어의 리더보드에서 개별 점수를 가져오고 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

결과 표시

플레이어가 서로를 태그하고 팀을 위해 점수를 얻을 때, ServerScriptServiceGameplayRounds는 점수를 얻은 팀이 라운드 목표를 달성했는지 확인합니다. 팀 점수가 ReplicatedStorageTEAM_SCORE_LIMITTEAM_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 표시를 트리거하는 동시에, 모든 플레이어를 아레나에서 로비로 이동시켜 라운드에서 분리합니다. 이는 라운드를 공식적으로 종료하고 두 팀을 초기화하는 과정을 시작합니다.

스폰 위치 구성에서와 동일한 논리를 사용하여, RoundsNeutral 스폰 위치의 Neutral 속성을 true로 설정하여 플레이어가 팀 상태에 관계없이 그곳에 스폰할 수 있도록 합니다. 이는 로비가 플레이어가 라운드에서 분리된 후 스폰할 수 있는 유일한 위치가 됨을 의미합니다.

Rounds
-- 모든 플레이어를 로비로 보냅니다
playerAddedConnection:Disconnect()
neutralSpawn.Neutral = true
spawnPlayersInLobby(Players:GetPlayers())

10초의 인터미션을 기다린 후, Rounds 서버 스크립트는 모든 플레이어의 점수를 초기화하고 새로운 팀으로 정렬하여 루프를 다시 시작합니다. 샘플은 서버에 플레이어가 없을 때까지 이 순환 라운드 프로세스를 반복합니다.

이제 플레이어가 자신의 팀과 함께 맵에 스폰하고 전체 라운드를 플레이할 수 있게 되었으므로, 다음 섹션에서는 각 블래스터의 행동 뒤에 있는 스크립트에 대해 배웁니다.

©2026 Roblox Corporation. Roblox 및 Roblox 로고, 'Powering Imagination'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.