生成與重生

*此內容是使用 AI(Beta 測試版)翻譯,可能含有錯誤。若要以英文檢視此頁面,請按一下這裡

生成是指在遊戲中創建物件或角色的過程,而重生是指在滿足移除條件後,將物件或角色重新加入遊戲的過程,例如角色的生命值降至零或掉落地圖。這兩個過程都很重要,因為它們確保玩家能夠加入你的遊戲,並能繼續遊玩以提升他們的技能。

範例激光標籤遊戲為參考,本教程的這一部分教你如何使用和自定義Roblox的內建功能來處理生成和重生,包括腳本指導:

  • 配置生成位置,以便玩家只能在他們隊伍的生成區域生成。
  • 當新玩家加入遊戲時,將他們和他們的角色添加到回合中。
  • 自定義防護場,以防止玩家在生成和重生時受到傷害。
  • 處理客戶端狀態,以便在適當的時間正確運行遊戲。
  • 在角色被標記出局後重生角色。
  • 執行一些小的雜項操作,這些操作對於設置遊戲和角色參數至關重要。

這一部分包含大量的腳本內容,但在創建遊戲時,鼓勵你利用現有的組件,快速迭代,並找出哪些系統需要自定義實現以符合你的願景。完成這一部分後,你將學會如何實現基於回合的遊戲玩法,跟蹤分數,監控玩家狀態,並顯示回合結果。

配置生成位置

如果你現在進行遊戲測試,所有玩家將隨機在綠隊的SpawnLocation物件或粉隊的SpawnLocation物件中生成。這會造成一個遊戲問題,因為玩家可以在對方的生成區域內互相標記,當對手的防護場消失時。

為了解決這個問題,範例激光標籤遊戲將兩個生成位置的Neutral屬性設置為false,以限制對方隊伍的玩家在錯誤的生成區域生成,並將TeamColor屬性設置為來自分配隊伍顏色的相應Team.Color值:

  • TeamASpawn – 綠隊生成區域的生成位置,TeamColor屬性設置為薄荷綠
  • TeamBSpawn – 粉隊生成區域的生成位置,TeamColor屬性設置為康乃馨粉
TeamASpawn
TeamBSpawn

當玩家加入遊戲時,ServerScriptServiceGameplayRoundsspawnPlayersInMap會檢查每個隊伍中已經有多少玩家,然後返回玩家最少的隊伍。

spawnPlayersInMap
local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- 按從小到大的順序對隊伍進行排序
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- 返回最小的隊伍
return teams[1]
end

一旦知道了玩家最少的隊伍,它就會將玩家分配到該隊伍,將他們的Player.Neutral屬性設置為false,以便玩家只能在他們隊伍的生成位置生成和重生,然後將他們的PlayerState設置為SelectingBlaster,你將在本教程的後面部分了解更多。

spawnPlayersInMap
local function spawnPlayersInMap(players: { Player })
for _, player in players do
player.Team = getSmallestTeam()
player.Neutral = false
player:SetAttribute(PlayerAttribute.playerState, PlayerState.SelectingBlaster)
task.spawn(function()
player:LoadCharacter()
end)
end
end

如果你檢查WorkspaceWorldMapSpawns,你會看到地圖中還有一個生成位置:NeutralSpawn。這個生成位置與其他位置不同,因為它沒有將TeamColor屬性設置為遊戲中的兩個隊伍之一;相反,這個生成位置的Neutral屬性會根據回合是否活躍而改變。

例如,如果回合是活躍的,Neutral屬性設置為false,以便spawnPlayersInMap可以將玩家分配到隊伍並將他們生成到競技場中。然而,如果回合不活躍,例如在一回合與下一回合之間的時間,Neutral屬性設置為true,以便玩家可以在那裡生成,而不管他們的隊伍狀態如何。這個過程使得Neutral生成位置成為一個功能性大廳。

Neutral

為了演示,如果你檢查ServerScriptServiceGameplayRoundsSpawnPlayersInLobby,該腳本在回合結束時運行,你會看到對於傳遞到players: { Player }表中的每個玩家,腳本:

  • 將他們的Player.Neutral屬性設置為true,以自動重置他們的Player.Teamnil,允許玩家在回合不活躍時在大廳中重生,因為生成位置的Neutral屬性也設置為true
  • 將他們的PlayerState更改為InLobby,以移除玩家的槍和第一人稱UI視覺效果。

有關中立生成區及其在每回合中的功能的更多信息,請參見本教程下一部分的添加回合

spawnPlayersInLobby
local function spawnPlayersInLobby(players: { Player })
for _, player in players do
player.Neutral = true
player:SetAttribute(PlayerAttribute.playerState, PlayerState.InLobby)
task.spawn(function()
player:LoadCharacter()
end)
end
end

連接新玩家

Studio中的Luau代碼通常是事件驅動的,這意味著腳本會監聽來自Roblox服務的事件,然後根據事件調用函數。例如,在將新玩家添加到多人遊戲時,必須有一個事件來處理所有必要的操作,以便玩家能夠成功連接。在範例激光標籤遊戲中,這個對應的事件是Players.PlayerAdded:Connect

Players.PlayerAdded:Connect是遊戲中多個腳本的一部分。如果你使用Ctrl/Cmd+Shift+F快捷鍵並搜索Players.PlayerAdded:Connect,結果將為理解遊戲的初始設置提供一個良好的起點。

Studio的查找所有窗口,突出顯示Players.PlayerAdded的結果。

為了演示,打開ServerScriptServiceSetupHumanoidPlayerCharacter之間的區別是理解這個腳本的關鍵:

  • 玩家是連接的客戶端,而角色Humanoid模型。
  • 玩家需要選擇一把槍並被添加到排行榜中。角色需要生成並獲得一把槍。

SetupHumanoid立即檢查玩家是否有角色(剛加入)或沒有(正在重生)。在找到一個角色後,它調用onCharacterAdded(),從角色中獲取Humanoid模型,並將其傳遞到ServerScriptServiceSetupHumanoidsetupHumanoidAsync進行自定義。在設置這些值後,腳本會等待角色的生命值降至零。你將在本教程的後面部分了解更多有關重生的內容。

setupHumanoidAsync
local function setupHumanoidAsync(player: Player, humanoid: Humanoid)
humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.Subject
humanoid.NameDisplayDistance = 1000
humanoid.HealthDisplayDistance = 1000
humanoid.NameOcclusion = Enum.NameOcclusion.OccludeAll
humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOn
humanoid.BreakJointsOnDeath = false
humanoid.Died:Wait()
onHumanoidDied(player, humanoid)
end

這個腳本的重要說明是,這些屬性都是完全可選的,這意味著如果你刪除函數的前六行,遊戲仍然能正常運行。這些屬性並不是功能要求,而是允許你做出符合遊戲目標的設計決策。例如:

如果你更改這些屬性的值,進行遊戲測試是很重要的,以便你能看到新設置的影響。你可以通過在Server & Clients的遊戲測試中選擇至少兩個角色來重現玩家的體驗,進行多客戶端模擬

Studio的夾層測試模式下的伺服器和客戶端選項。

Players.PlayerAdded:Connect事件的另一個示例在ServerScriptServicePlayerStateHandler中。與前面的示例一樣,PlayerStateHandler立即檢查角色。如果玩家不在大廳,腳本將玩家屬性設置為SelectingBlaster狀態,這是回合的初始狀態,玩家可以在進入競技場後從兩種不同的槍中選擇一種。這個狀態還包括一個防護場,防止玩家在選擇時受到傷害。

PlayerStateHandler
local function onPlayerAdded(player: Player)
player.CharacterAdded:Connect(function()
if not player.Neutral then
player:SetAttribute(PlayerAttribute.playerState, PlayerState.SelectingBlaster)
onPlayerStateChanged(player, PlayerState.SelectingBlaster)
end
end)

PlayerStateHandler中的一個特定變數值得討論:attributeChangedConnectionByPlayer。這個表存儲所有玩家及其ConnectionsGetAttributeChangedSignal。將這個連接存儲在表中的原因是,PlayerStateHandler可以在玩家離開遊戲時斷開它。這個過程作為一種記憶管理,以防止連接數量隨著時間的推移而不斷增長。

PlayerStateHandler
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- 處理所有未來的玩家狀態更新
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- 當玩家離開時斷開屬性變更連接
local function onPlayerRemoving(player: Player)
if attributeChangedConnectionByPlayer[player] then
attributeChangedConnectionByPlayer[player]:Disconnect()
attributeChangedConnectionByPlayer[player] = nil
end
end

你可以看到onPlayerAdded()中的兩個連接函數都調用了onPlayerStateChanged()。在玩家排序到隊伍後的初始設置中,onPlayerAdded()PlayerState設置為SelectingBlaster,因此第一個if語句的評估為false,並禁用BlasterState。在後面的實現槍械部分中,你將了解有關此過程的更多細節。

PlayerStateHandler
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- 只有當玩家狀態為'Playing'時,槍械狀態才為'Ready'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- 當玩家開始遊玩時安排銷毀防護場的邏輯
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
end

如果你添加斷點或甚至只是一個print()語句,你可以看到onPlayerStateChanged()在遊戲中經常被調用:例如在回合的初始設置期間,將其設置在主代碼路徑上,玩家選擇槍械後,以及當玩家返回大廳或Neutral生成位置時。此外,在玩家選擇槍械後,ServerScriptServiceBlasterSelectedHandlerPlayerState設置為Playing,而PlayerStateHandler最終可以通過調用scheduleDestroyForceField()來移除防護場。

自定義防護場

範例激光標籤遊戲使用Studio的內建ForceField類來防止玩家在選擇槍械時受到傷害,而不是使用自定義實現。這確保了玩家生成時只需包含SpawnLocation.Duration屬性大於0的生成位置即可啟用防護場。範例使用了一個任意值9,999來啟用防護場,然後在ReplicatedStorageForceFieldClientVisuals中以編程方式處理實際持續時間。

類似於setupHumanoidAsyncForceFieldClientVisuals中的大多數行都是可選的。例如,如果你像以下腳本那樣註釋掉函數的內容,遊戲將使用默認的閃爍防護場,而不是StarterGuiForceFieldGui中的六邊形腳本。

Commenting Out Properties in ForceFieldClientVisuals
local function onCharacterAddedAsync(character: Model)
-- local forceField = character:WaitForChild("ForceField", 3)
-- if not forceField then
-- return
-- end
-- forceField.Visible = false
-- localPlayer.PlayerGui:WaitForChild("ForceFieldGui").Enabled = true
-- forceField.Destroying:Wait()
-- localPlayer.PlayerGui.ForceFieldGui.Enabled = false
end

由於自定義防護場是一個GUI,而不是新的ParticleEmitter,因此ForceFieldClientVisuals腳本僅影響每個玩家的第一人稱視覺效果,而是玩家在查看其他玩家時的第三人稱視覺效果。第三人稱視覺效果保留了Roblox的默認外觀。要了解有關修改防護場的更多信息,請參見ForceField.Visible

第一人稱防護場視覺效果包括屏幕周圍的未來主義六邊形網格。
第一人稱防護場視覺效果
第三人稱防護場視覺效果包括在玩家生成到遊戲中的藍色閃爍球體。
第三人稱防護場視覺效果

防護場是有用的,因為它們為玩家在生成和重生之間提供了足夠的時間,而無需擔心敵方玩家,但最終它們需要消失以進行主要的激光標籤遊戲。處理防護場移除的腳本位於ReplicatedStoragescheduleDestroyForceField,並檢查三個獨特的條件:

  • 玩家選擇槍械後,防護場需要持續足夠長的時間,以便玩家適應周圍環境。
  • 在這段適應時間內,防護場不能成為優勢,因此它們需要在玩家開火的瞬間消失。
  • 當玩家重置角色時,無論是在開火之前還是防護場超時之前,防護場都需要消失。

scheduleDestroyForceField腳本中的每個檢查都調用endForceField()以滿足這些條件。

scheduleDestroyForceField
-- 如果玩家開火則結束防護場
local blasterStateAttribute = getBlasterStateAttribute()
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
if currentBlasterState == BlasterState.Blasting then
endForceField()
end
end)
-- 如果玩家重置則結束防護場
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- 在8秒後結束防護場
task.delay(MAX_FORCE_FIELD_TIME, endForceField)

endForceField()包含一個看似奇怪的if語句,圍繞forceFieldEnded布林值。由於檢查是按順序運行的,腳本可以多次調用endForceField()函數。forceFieldEnded布林值確保該函數僅嘗試銷毀防護場一次。

scheduleDestroyForceField
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
end

處理客戶端狀態

雖然本節的大部分內容集中在ServerScriptServicePlayerStateHandler上,但在ReplicatedStorage中還有一個同名的腳本。分開的原因是客戶端-伺服器架構:

  • 客戶端需要理解玩家狀態信息,以便能夠實時做出適當的反應,例如顯示正確的用戶界面元素,或使玩家能夠移動和開火。

  • 伺服器需要所有這些相同的信息,以便能夠防止利用漏洞。例如,伺服器還需要玩家狀態來執行像生成和裝備角色、禁用防護場和顯示排行榜等操作。這就是為什麼這個腳本在ReplicatedStorage中,而不是純粹的客戶端位置。

要查看這個核心邏輯,請檢查ReplicatedStoragePlayerStateHandler中的以下腳本,該腳本驗證用戶的當前狀態,然後調用相應的函數來處理該狀態的相應操作。

PlayerStateHandler
local function onPlayerStateChanged(newPlayerState: string)
if newPlayerState == PlayerState.SelectingBlaster then
onSelectingBlaster()
elseif newPlayerState == PlayerState.Playing then
onPlaying()
elseif newPlayerState == PlayerState.TaggedOut then
onTaggedOut()
elseif newPlayerState == PlayerState.InLobby then
onInLobby()
else
warn(`無效的玩家狀態 ({newPlayerState})`)
end
end

所有事件響應在這個腳本中邏輯上被分組在一起,因為它們需要類似的行為來啟用或禁用玩家控制、相機移動以及可見的UI層。例如,在槍械選擇期間,玩家需要同時無敵且無法移動。伺服器已經處理了防護場,但客戶端處理移動。為了演示,如果你檢查onSelectingBlaster()函數的邏輯,你會看到客戶端在玩家選擇槍械時禁用玩家移動。

PlayerStateHandler
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end

onPlaying()函數同樣簡單。它啟用移動,過渡到主顯示界面(HUD),啟用槍械,並調用與伺服器相同的防護場函數。

PlayerStateHandler
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
end

重生角色

範例激光標籤遊戲通過ReplicatedStoragePlayerStateHandler中的onTaggedOut()狀態處理角色的重生。與onSelectingBlaster()onPlaying()狀態一樣,onTaggedOut()根據playerState屬性的變化觸發獨特的行為。具體來說,它禁用玩家移動,顯示重生UI,並禁用槍械。

PlayerStateHandler
local function onTaggedOut()
-- 標記出局時禁用控制
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- 標記出局時禁用槍械
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end

如果你想測試這種行為,可以按Esc,導航到設置選項卡,然後單擊重置角色按鈕。注意當你觸發重生畫面時,你無法移動、旋轉相機或開火。

Roblox的設置菜單,突出顯示重置角色按鈕。
重置角色按鈕
重生畫面顯示玩家重生回合中的情況。
重生畫面

重要的是要注意,這個腳本實際上並不重生角色,它只是停止他們的行動並向玩家提供視覺反饋,告訴他們伺服器正在重生他們的角色。為了演示,如果你檢查ServerScriptServiceSetupHumanoidsetupHumanoidAsynconHumanoidDied,腳本將PlayerState設置為TaggedOut(本質上通知ReplicatedStoragePlayerStateHandler),並添加一些視覺指示。實際的重生邏輯是Roblox的內建行為。

當玩家重生回合時,他們會根據SpawnLocation.TeamColor屬性在他們隊伍的生成位置重生。要自定義重生時間,你可以在SetupHumanoid的頂部添加以下行。要了解有關此技術的更多信息,請參見Players.RespawnTime

SetupHumanoid
local Players = game:GetService("Players")
Players.RespawnTime = 10 -- 新行,單位為秒

雜項設置

作為初始設置的一部分,範例激光標籤遊戲還執行了一些小但關鍵的步驟:

  • 遊戲包括一個名為StarterPlayerStarterCharacterScriptsHealth的空腳本,該腳本禁用默認的Roblox生命值再生。要了解此屬性的行為,請參見Humanoid.Health

  • 遊戲通過設置StarterPlayer.CameraMode.LockFirstPerson屬性來使用第一人稱相機。請注意,如果你希望讓用戶在第一人稱和第三人稱相機之間切換,則必須以編程方式更改該屬性,而不僅僅是在Studio中設置一次,並修改控制和UI以補償視角的變化。

  • 遊戲使用內建的Roblox排行榜,單位為“分數”,玩家每次標記另一名玩家出局時都會獲得分數。你可以在ServerScriptServiceSetupLeaderboard中查看配置,但遊戲內排行榜提供了完整的概述。請注意,onPlayerTagged會將分數添加到排行榜中,這部分你將在添加回合檢測擊中中了解。

現在玩家可以生成、選擇槍械並從第一人稱視角瞄準,下一部分將教你有關創建基於回合的遊戲玩法背後的腳本。

©2026 Roblox Corporation、Roblox、Roblox 標誌及 Powering Imagination 是我們在美國及其他國家地區的部分註冊與未註冊商標。