라운드 추가

*이 콘텐츠는 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)

목표 설정

이제 각 플레이어가 팀원과 함께 경기장에 있으므로, 경험은 라운드 내에서 성공하기 위해 무엇을 해야 하는지에 대한 지침을 제공해야 합니다. 샘플 레이저 태그 경험은 각 플레이어 화면 상단에 팀이 승리하기 위해 무엇을 해야 하는지에 대한 명확한 안내와 함께 객관적인 프롬프트를 제공하여 이 요구 사항을 처리합니다.

Objective UI 컴포넌트를 구성하고 표시하는 방법에 대해서는 UI 커리큘럼에서 더 많은 정보를 배울 수 있지만, 이 섹션에서는 라운드가 시작될 때 목표를 구현하는 방법에 초점을 맞추며, 각 팀이 라운드를 완료하기 위해 필요한 점수의 양을 설정하는 방법으로 시작합니다.

런타임에서 목표 프롬프트가 플레이어에게 승리하기 위해 세 점을 득점해야 한다고 알려주지만, StarterGuiHUDGui에서 프롬프트를 살펴보면 대신 구성 가능한 "%d"가 점수 값으로 포함되어 있음을 알 수 있습니다.

"%d"는 언제든지 TEAM_SCORE_LIMIT 변수를 ReplicatedStorageTEAM_SCORE_LIMIT에서 업데이트하여 자신의 게임 플레이 요구 사항에 맞게 늘리거나 줄일 수 있는 자리 표시자 문자열입니다. 예를 들어, 이 숫자를 지나치게 높은 200으로 설정하면 프롬프트와 팀 점수 카운터가 해당 사항에 따라 업데이트됩니다.

TEAM_SCORE_LIMIT

local TEAM_SCORE_LIMIT = 200 -- 업데이트된 줄, 반드시 되돌리기
return TEAM_SCORE_LIMIT

이 간단한 변수 업데이트는 라운드가 시작되면서 ReplicatedStorageHUDGuiSetupSetObjective가 프롬프트 문자열을 UI Objective의 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 서비스의 기본 동작은 각 플레이어를 팀 아래에 자동으로 그룹화하고 각 플레이어의 기여도를 팀 점수에 더하는 반면, 플레이어가 점수를 올리고 라운드가 끝나기 전에 떠나는 경우 리더보드에서 기여도가 즉시 차감되기 때문에, 라운드 기반 게임 플레이에서는 별도의 위치에 점수를 저장하고 ​​모니터링하는 것이 중요합니다.

이런 일이 발생하지 않도록 하고 팀 목표에 대한 모든 기여가 보존되도록 하기 위해 ReplicatedStorageHUDGuiSetupStartSyncingTeamPointsTeams 서비스의 teamPoints 속성에 따라 점수를 별도로 저장합니다. teamPoints가 증가하면 이 모듈 스크립트는 Objective 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)

지금까지 이 섹션의 모든 내용은 플레이어 화면에서 점수를 추적하는 방법에 초점을 맞추었지만, 서버에서 팀 점수가 목표를 달성하고 라운드를 이길 때까지 점수를 추적하는 로직도 검토하는 것이 중요합니다. ServerScriptServiceGameplayScoring를 다시 방문하면 모듈 스크립트가 점수를 얻을 때마다 발사될 바인더블 이벤트를 생성하는 것을 볼 수 있습니다.

Scoring

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

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

  • 플레이어의 팀과 Teams 서비스의 Team 객체에서 현재 팀 점수를 가져온 후 하나를 추가합니다.
  • 리더보드에서 플레이어의 개별 점수를 가져와서 하나를 추가합니다.
  • 이전에 언급한 바인더블 이벤트를 플레이어의 팀과 그 팀 점수로 발사합니다.

이 과정은 클라이언트와 서버가 플레이어의 개별 점수와 팀 점수에 대해 정렬되게 유지합니다.

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
-- 만약 우리의 팀이 이겼다면 Victory!를 표시하고, 그렇지 않으면 Defeat를 표시합니다...
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'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.