Thêm các vòng

*Nội dung này được dịch bằng AI (Beta) và có thể có lỗi. Để xem trang này bằng tiếng Anh, hãy nhấp vào đây.

Thêm các vòng cho phép bạn cấu trúc gameplay thành các giai đoạn với điểm bắt đầu và kết thúc rõ ràng để người chơi có thể đo lường tiến độ của họ và có cơ hội định kỳ để có một sân chơi công bằng. Điều này đặc biệt quan trọng đối với gameplay theo đội vì nó mang lại cho người chơi cơ hội thay đổi phong cách chơi của họ tùy thuộc vào ai trên đội của họ trong vòng chơi đó.

Sử dụng trải nghiệm trò chơi laser tag mẫu làm tài liệu tham khảo, phần này của hướng dẫn sẽ dạy bạn cách sử dụng và tùy chỉnh các tính năng tích hợp sẵn của Roblox để cấu trúc mỗi vòng, bao gồm hướng dẫn lập trình về:

  • Bắt đầu một vòng bằng cách thiết lập lại điểm cá nhân và điểm đội, sau đó gieo players vào khu vực sinh sản đội của họ.
  • Tùy chỉnh các biến đặt mục tiêu cho vòng ở trên cùng của màn hình mỗi player.
  • Theo dõi sự đóng góp điểm của players cho điểm số đội của họ.
  • Khởi động các màn hình giao diện người dùng độc đáo tùy thuộc vào việc đội của player thắng hay thua vòng.
  • Kết thúc một vòng bằng cách ngắt kết nối players và gieo họ vào sảnh trung lập.

Sau khi bạn hoàn thành phần này, bạn sẽ học cách triển khai hành vi của các khẩu súng bắn chính xác và thoải mái cho người chơi.

Bắt đầu vòng lặp

ServerScriptServiceGameplayRounds xử lý hầu hết logic để triển khai các vòng, và nó bắt đầu bằng cách gọi hàm startRoundLoopAsync() để đánh dấu sự bắt đầu của một vòng lặp. Trong khi các player tham gia sảnh và chờ được phân loại thành đội, startRoundLoopAsync() gọi hàm resetScores() trong ServerScriptServiceGameplayScoring để thiết lập lại cả bảng điểm và điểm đội.

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

Bây giờ mọi người đều bắt đầu ở điểm số bằng không, startRoundLoopAsync() sau đó thiết lập thuộc tính Neutral của vị trí sinh sản Neutral thành false để chỉ những player có thuộc tính Team.Color giống như thuộc tính TeamColor của vị trí sinh sản mới có thể xuất hiện ở đó. Bởi vì thuộc tính TeamColor của vị trí sinh sản này được thiết lập thành trắng thay vì các đội màu bạc hà hoặc hồng, cấu hình này ngăn tất cả player sinh ra hoặc tái sinh ở đó trong khi một vòng đang hoạt động.

Đối với những player hiện đang ở trong sảnh, startRoundLoopAsync() chuyền tất cả player hiện tại trong trải nghiệm vào hàm spawnPlayersInMap trong ServerScriptServiceGameplayRoundsspawnPlayersInMap để phân loại và cân bằng mọi người thành một đội với số lượng player tương tự nhau.

Đối với bất kỳ player mới nào tham gia trải nghiệm sau khi nhóm sảnh được phân loại thành một đội, startRoundLoopAsync() lắng nghe sự kiện Players.PlayerAdded:Connect, sau đó gọi hàm spawnPlayersInMap lần nữa để thêm họ vào đội có số lượng player ít nhất. Để biết thêm thông tin về quá trình này, hãy xem Cấu hình Vị trí Sinh sản từ phần trước Gieo và Tái sinh của hướng dẫn.

Rounds

-- Gieo tất cả player vào bản đồ
neutralSpawn.Neutral = false
spawnPlayersInMap(Players:GetPlayers())
-- Gieo player mới trong bản đồ khi họ tham gia
local playerAddedConnection = Players.PlayerAdded:Connect(function(player: Player)
spawnPlayersInMap({ player })
end)

Đặt mục tiêu

Bây giờ mỗi player đã ở trong đấu trường với đồng đội của họ, trải nghiệm cần cung cấp hướng dẫn cho những gì cần làm để thành công trong vòng đấu. Trải nghiệm laser tag mẫu giải quyết yêu cầu này bằng cách cung cấp một thông điệp mục tiêu ở trên cùng màn hình của mỗi player với hướng dẫn rõ ràng về những gì đội cần làm để thắng.

Trong khi bạn có thể tìm hiểu thêm về cách cấu hình và hiển thị thành phần Giao diện Người dùng Mục tiêu trong chương trình UI, phần này tập trung vào cách triển khai mục tiêu khi vòng bắt đầu, bắt đầu với cách thiết lập điểm số mà mỗi đội cần hoàn thành vòng.

Mặc dù thông điệp mục tiêu tại thời gian chạy thông báo cho players rằng họ cần ghi ba điểm để thắng, nếu bạn kiểm tra thông điệp trong StarterGuiHUDGui, bạn có thể thấy rằng nó thực sự chứa một "%d" có thể cấu hình cho giá trị điểm.

"%d" là một chuỗi giữ chỗ mà bạn có thể tăng hoặc giảm bất kỳ lúc nào để đáp ứng yêu cầu gameplay của riêng mình bằng cách cập nhật biến TEAM_SCORE_LIMIT trong ReplicatedStorageTEAM_SCORE_LIMIT. Ví dụ, nếu bạn thiết lập con số này thành một 200 quá cao, thông điệp và bộ đếm điểm của đội sẽ cập nhật theo cách tương ứng.

TEAM_SCORE_LIMIT

local TEAM_SCORE_LIMIT = 200 -- dòng đã cập nhật, hãy chắc chắn thay đổi lại
return TEAM_SCORE_LIMIT

Việc cập nhật biến đơn giản này hoạt động trong runtime bởi vì khi vòng bắt đầu, ReplicatedStorageHUDGuiSetupSetObjective yêu cầu script module TEAM_SCORE_LIMIT để nó có thể hoán đổi chuỗi giữ chỗ trong đối tượng TextLabel của Mục tiêu UI.

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

Theo dõi điểm

Bây giờ các player đã có một mục tiêu cho vòng đấu, trải nghiệm cần theo dõi điểm của từng đội cho đến khi họ đạt được mục tiêu của mình. Trong khi hành vi mặc định của dịch vụ Teams tự động nhóm mỗi player dưới đội của họ và cộng dồn điểm của từng player vào điểm số đội của họ, điều quan trọng là lưu trữ và giám sát điểm ở một vị trí riêng biệt cho gameplay dựa trên vòng, vì nếu một player ghi điểm rồi rời đi trước khi vòng kết thúc, sự đóng góp của họ sẽ bị khấu trừ khỏi bảng xếp hạng ngay khi họ ngắt kết nối khỏi trải nghiệm.

Để đảm bảo điều này không xảy ra và mọi sự đóng góp cho mục tiêu đội được bảo tồn, ReplicatedStorageHUDGuiSetupStartSyncingTeamPoints lưu trữ tất cả điểm riêng biệt dưới thuộc tính teamPoints trong dịch vụ Teams. Khi teamPoints tăng lên, script module này gọi chức năng startSyncingTeamPoints để tìm bộ đếm đội Class.GuiObjects trong thành phần Giao diện Người dùng Mục tiêu.

Khi nó tìm thấy TeamACounterTeamBCounter, nó lấy thuộc tính teamColor của chúng, mà tương ứng với các khu vực sinh sản đội: TeamACounter hiển thị điểm của đội xanh, và TeamBCounter theo dõi điểm của đội hồng.

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)

Script module sau đó gọi chức năng getTeamFromTeamColor của nó để xác nhận rằng thuộc tính teamColor của TeamACounter là mint và thuộc tính teamColor của TeamBCounter là carnation pink đều khớp với các thuộc tính Team.Color tương ứng trong dịch vụ Teams. Nếu đúng như vậy, nó trả về cả hai đội.

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

Khi điều này xảy ra, startSyncingTeamPoints thiết lập thuộc tính TextLabel của cả hai bộ đếm đội thành giá trị teamPoints tương ứng của chúng, và tiếp tục cập nhật chúng mỗi khi một player ghi được điểm bằng cách loại bỏ một player khác trong đội đối thủ.

StartSyncingTeamPoints

teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
team:GetAttributeChangedSignal(GuiAttribute.teamPoints):Connect(function()
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)

Mọi thứ trong phần này cho đến giờ đã tập trung vào cách theo dõi điểm trên màn hình của player, nhưng điều quan trọng là xem lại logic xử lý theo dõi điểm trên máy chủ để nó biết khi nào một đội đạt được mục tiêu và thắng vòng. Nếu bạn quay lại ServerScriptServiceGameplayScoring, bạn có thể thấy rằng script module bắt đầu bằng cách tạo một sự kiện có thể liên kết, điều này sẽ được khởi động mỗi lần một player ghi được điểm.

Scoring

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

Sau đó nó gọi hàm incrementScore, thực hiện các hành động sau:

  • Lấy đội của player và giá trị teamPoints hiện tại của họ trong đối tượng Team trong dịch vụ Teams, rồi cộng thêm một.
  • Lấy điểm cá nhân của player trên bảng điểm, và cộng thêm một.
  • Khởi động sự kiện có thể liên kết đã đề cập với cả đội của player và điểm số của họ.

Quá trình này giữ cho cả client và máy chủ đồng bộ về điểm số cá nhân của cả player và điểm số đội của họ.

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

Hiển thị kết quả

Khi các player loại bỏ lẫn nhau và ghi điểm cho đội của họ, ServerScriptServiceGameplayRounds kiểm tra xem đội ghi điểm có đạt được mục tiêu của vòng hay không. Nếu điểm số của đội họ thấp hơn biến TEAM_SCORE_LIMIT trong ReplicatedStorageTEAM_SCORE_LIMIT, máy chủ sẽ tiếp tục chờ cho đến khi một trong các đội ghi điểm một lần nữa.

Tuy nhiên, khi điểm của một đội đạt đến biến TEAM_SCORE_LIMIT, script sẽ khởi động một sự kiện roundWinnerRemote với tên của player và đội của họ.

Rounds

-- Kiểm tra xem vòng đã kết thúc sau mỗi lần ghi điểm
local team: Team
local score: number = 0
while score < TEAM_SCORE_LIMIT do
team, score = Scoring.teamScoreChanged:Wait()
end
-- Hiển thị đội thắng
for _, player in Players:GetPlayers() do
-- Gửi thông tin về đội mà player đang thuộc về ở cuối vòng
-- vì đội của player sắp được xóa, vì vậy client
-- sẽ không thể kiểm tra đội của mình
roundWinnerRemote:FireClient(player, team, player.Team)
end

Script ReplicatedStorageRoundResultsGuiSetup trên mỗi client lắng nghe sự kiện roundWinnerRemote này để nó có thể:

  • Hiển thị một màn hình UI StarterGuiRoundResultsGui độc đáo thông báo kết quả vòng và nếu player ở trong đội thắng.
  • Phát một đoạn âm thanh thắng hoặc thua.

Ví dụ, nếu một player thuộc về đội ghi điểm thắng, họ sẽ nhận nhiều hình thức phản hồi về kết quả vòng dưới dạng một màn hình UI hiển thị văn bản chiến thắng và một đoạn âm thanh phát ra một âm thanh vui vẻ. Ngược lại, nếu một player không ở trong đội ghi điểm thắng, họ sẽ nhận một màn hình UI hiển thị văn bản thua và một đoạn âm thanh phát ra một âm thanh đe dọa.

Phản hồi chiến thắng
Phản hồi thua
RoundResultsGuiSetup

local function onRoundWinner(winner: Team, localTeam: Team?)
local victoryDefeatText = "Vòng đã kết thúc!"
if localTeam then
-- Nếu đội chúng ta thắng, chúng ta sẽ hiển thị Chiến thắng! Ngược lại hiển thị Thua...
local isVictory = winner == localTeam
if isVictory then
victorySound:Play()
victoryDefeatText = VICTORY_TEXT
else
defeatSound:Play()
victoryDefeatText = DEFEAT_TEXT
end
end

Đặt lại đội

Đồng thời, khi ServerScriptServiceGameplayRounds xác minh rằng một đội đã đạt được mục tiêu vòng và kích hoạt việc hiển thị UI phù hợp cho từng player, nó cũng vận chuyển tất cả player từ đấu trường về sảnh bằng cách ngắt kết nối họ khỏi vòng. Điều này bắt đầu quá trình kết thúc chính thức vòng và đặt lại cả hai đội.

Sử dụng cùng một logic trong Cấu hình Vị trí Sinh sản, Rounds sau đó thiết lập thuộc tính Neutral của vị trí sinh sản Neutral thành true để player có thể sinh ra ở đó bất kể trạng thái đội của họ. Điều này có nghĩa là sảnh trở thành vị trí duy nhất mà player có thể sinh ra sau khi họ bị ngắt kết nối khỏi vòng.

Rounds

-- Gửi tất cả về sảnh
playerAddedConnection:Disconnect()
neutralSpawn.Neutral = true
spawnPlayersInLobby(Players:GetPlayers())

Sau khi chờ mười giây cho một khoảng thời gian tạm dừng, script Rounds sau đó bắt đầu vòng lặp lại bằng cách đặt lại điểm số của mọi người và phân loại họ thành các đội mới. Mẫu sẽ lặp lại quy trình vòng lặp này cho đến khi không còn player nào trong máy chủ.

Giờ đây, player có thể sinh ra vào bản đồ với đội của họ và chơi một vòng đầy đủ, phần tiếp theo sẽ dạy bạn về các script đằng sau hành vi của từng khẩu súng.

©2026 Roblox Corporation. Roblox, logo Roblox và Powering Imagination là các nhãn hiệu đã đăng ký và chưa đăng ký của chúng tôi tại Hoa Kỳ và các quốc gia khác.