生成 是创建对象或角色在体验中的过程, 重生 是在体验中添加对象或角色后,在满足移除条件后,可以将对象或角色重新添加到体验的过程。 两个过程都很重要,因为它们确保玩家可以加入您的体验,并且继续游玩以提升他们的技能。
使用示例激光标记体验作为参考,本教程的这个部分教你如何使用和自定义 Roblox 的内置功能来处理生成和重生,包括脚本指南:
- 配置生成地点,以便玩家只能在他们的团队生成区域中生成。
- 在新玩家加入体验时添加新角色。
- 自定义防止玩家生成和重生时造成伤害的力场。
- 处理客户端状态,以便游戏正常运行。
- 在标记出局后重生角色。
- 执行小型、杂项操作,对游戏玩法和角色参数的设置至关重要。
此部分包含大量脚本内容,但在创建体验时不再需要从零开始编写,而是鼓励您利用现有组件快速迭代并找出哪些系统需要自定义实现来匹配您的愿景。 完成此部分后,您将学习如何实现跟踪点的圆形游戏体验、显示回合结果和显示回合结果的圆形游戏体验。
配置生成地点
如果您现在玩测试体验,所有玩家都将随机在绿团队的生成区域或粉团队的生成区域中生成。这会在游戏中发生一个问题,当玩家在每个生成区域之间标记 друг друга时,如果玩家的对手的力场消失,玩家就可以在每个生成区域之间标记自己。
为了克服此问题,样本激光标记体验将两个生成位置配置为 Neutral 属性设置为 false 来限制对方团队伍的玩家在错误的生成区域生成,并且将
当玩家加入体验时, 服务器脚本服务 > 游戏体验 > 回合 > 1>生成玩家在地图1>检查要看到有多少玩家在每个队伍上,然后返回团队最小。
生成地图上的玩家
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 ,您将在后面的教程中学习更多。
生成地图上的玩家
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
如果您检查 工作区 > 世界 > 地图 > 1>生成1>,您可以看到地图上另一个生成地点:
例如,如果回合有效,Neutral 属性设置为false,因此spawnPlayersInMap可以将玩家分
要示例,如果您检查 ServerScriptService > 游戏玩法 > 回合 > 1> 玩家1> > 4> SpawnPlayersInLobby4>,在回合结束时,您可以看到每个玩家都被传到 7>players: player7> 表中的脚本:
- 将其 Player.Neutral 属性设置为 true,自动将其 Player.Team 重置为 nil,允许玩家在回合不激活时在大厅重生,因为该生成位置的 Neutral 属性也设置为 2> true2>。
- 将其PlayerState变更为InLobby以移除玩家的 Blaster 和第一人称 UI 视觉。
了解有关中立生成区域的功能和每个回合的功能,请参阅在下一部分的教程中添加回合。
生成玩家在大厅
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 ,结果为您提供了一个良好的起点来了解体验的初始设置。
要示例,打开 ServerScriptService > 设置人形 。 区别 Player 和 1> Class.Player.Character|Character1> 是理解此脚本的关键:
- 玩家 是一个连接的客户端,而 角色 是一个 Humanoid 模型。
- 玩家需要选择一个冲击波并被添加到排行榜。角色需要生成并接受一个冲击波。
SetupHumanoid 立即检查玩家是否有角色(刚刚加入)或不是(正在重生)。找到一个后,它会调用 onCharacterAdded(),从角色中获取
设置人形异步
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
脚本中的重要注释是,属性是完全可选的,这意味着如果您移除第六行功能的第一个六行,体验仍然会正常运行。 而不是被视为功能要求,每个属性都允许您做出设计决策,满足您的游戏体验目标。 例如:
- 如果您想要角色名称在更近的距离显示,请减少 Humanoid.NameDisplayDistance 的值。
- 如果您仅希望显示角色的生命值如果它低于 100%,请将 Humanoid.HealthDisplayType 设置为 显示受伤时 。
- 如果您想要角色在其生命值达到 0 时分解,设置 Humanoid.BreakJointsOnDeath 为 真实 。
如果您改变这些属性的值,请确保您玩测试,以便您可以看到新设置的影响。您可以在多人游戏环境中重现玩家体验,选择至少两个角色在 客户端和服务器 部分的 测试 选项卡中。
另一个示例 of the Players.PlayerAdded:Connect 事件在 ServerScriptService > PlayerStateHandler 中。与上一例相同,1> PlayerStatusHandler1> 立即检查到一个
玩家状态处理器
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 。 这个表存储所有玩家和其 Connections 到1>GetAttribute
玩家状态处理器
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() 将 1>
玩家状态处理器
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- 只有玩家状态为“游戏中”,才能使用 Blaster 状态
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
如果您在体验中添加生成地点或 even just a print()
自定义力场
而不是使用自定义实现,示例激光标记体验使用 Studio 的内置 ForceField 类来防止玩家在选择他们的激光器时受到伤害。这确保玩家生成时必须包含具有 Class.SpawnLocation
与 setupHumanoidAsync 类似, ForceFieldClientVisuals 的大多数线都是可选的。例如,如果您评论如下脚本中的内容,体验会使用默认的闪亮力场而不是六边形脚本在 StarterGui > 1>ForceFieldGui1> 中。
在 ForceFieldClientVisuals 中评论属性
local function onCharacterAddedAsync(character: Model)
-- 本地 forceField = 角色:WaitForChild("ForceField", 3)
-- 如果没有 forceField 则
-- 回传回
-- 结束
-- forceField.Visible = 否
-- localPlayer.PlayerGui:WaitForChild("ForceFieldGui")。启用 = 真
-- forceField.Destroying:等待()
-- 本地玩家。PlayerGui.ForceFieldGui.Enabled = false
end
因为自定义力场是一个 GUI 而不是一个新的 ParticleEmitter ,因此 ForceFieldClientVisuals 脚本仅影响每个玩家的第一人称视图,而不是第三人称视图。第三人称视图保留 Roblox 的默认外观。有关更改力场的更多信息,请参阅
力场有助于玩家,因为它们提供玩家足够的时间来在生成和重生之间进行,而无需为敌人玩家发愁,但最终它们需要消失以便启动主要激光标签游戏。处理力场移除的脚本在 ReplicatedStorage > scheduleDestroyForceField 检查三个独特条件:
- 当玩家选择一个激光器时,力场必须持续足够长时间,允许玩家适应其周围环境。
- 在这段时间内,力场不能是优势,因此它们需要在玩家发射他们的激光器时消失。
- 当玩家重置他们的角色时,力场需要消失,或者在力场时间到期之前。
在 scheduleDestroyForceField 脚本调用中,每个检查 endForceField() 为这些条件。
计划摧毁力场
-- 如果玩家爆炸,则结束力场
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 声明在 torn forceFieldEnded 周围。因为检查运行顺序,脚本可以调用 0> endForceField0> 函数两次或甚至三次。因为 endForceField()3> 函数只会试图在一个力场上破坏一次。
计划摧毁力场
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
end
处理客户端状态
虽然大部分此部分的目的是围绕 服务器脚本服务 > 玩家状态处理器 ,但在 复制存储 中有另一个名为相同的脚本。 理由是客户端-服务器架构:
客户端需要理解玩家状态信息,以便能够在实时时间内正确地回应,例如显示正确的用户界面元素或启用玩家移动和爆炸。
服务器需要所有这些相同的信息,以便它可以防止漏洞。 例如,服务器还需要玩家状态才能执行生成和装备角色、禁用力场和显示排行榜。 因此,此脚本位于 ReplicatedStorage 中,不是一个纯客户端位置。
要查看此核心逻辑,请查看 ReplicatedStorage > 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(`Invalid player state ({newPlayerState})`)
end
end
所有事件的响应都是在这个脚本中按照类似的逻辑组合在一起,因为它们需要启用或禁用玩家控制、相机移动和哪个 UI 层可以显示。 例如,在 blaster 选择器中,玩家需要同时不能被侵入,也不能移移动工具。 要示例,如果您检查了 onSelectingBlaster()</
玩家状态处理器
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
onPlaying() 函数与类似的简单。它启用移动,并且让主头显示器(HUD)、启用冲击波,并且调用服务器的相同的力场函数。
玩家状态处理器
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
end
重生角色
样本激光标记体验处理重生角色返回到onTaggedOut() 状态在 ReplicatedStorage > PlayerStateHandler 。 喜欢1> onSelectingBlaster()1> 和<
玩家状态处理器
local function onTaggedOut()
-- 在被标记为“禁用”控件时禁用控制
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- 在被标记出来时禁用冲击波
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
如果您想测试此行为,您可以按Esc,导航到设置选项卡,然后单击重置角色按钮。注意,当您触发重生屏幕时,您无法移动工具、旋转相镜头或爆炸您的冲击波。
重要的是,这个脚本不会实际重生角色,它只会停止他们的行为并提供视
当玩家重生回到回合中时,他们会在他们的团队的生成位置按照SpawnLocation.TeamColor属性重生。要自定义重生时间,您可以在SetupHumanoid的顶部添加以下行。要了解更多关于此技术的信息,请参阅Players.RespawnTime。
设置人形
local Players = game:GetService("Players")Players.RespawnTime = 10 -- new line, in seconds
其他设置
作为初始设置的一部分,样本激光标签体验还会执行一些小但重要的步骤:
体验包含一个名为 StarterPlayer 的空脚本 > StarterCharacterScripts > Health ,该禁用默认 Roblox 生命值恢复。为此属性的说明,请参阅 1>Class.Humanoid.Health1> 。
体验使用第一人称视角设置 StarterPlayer.CameraMode.LockFirstPerson 属性来实现用户之间的第一人称视角切换。 注意,如果您想要让用户在第一人称视角和第三人称视角之间切换,您必须通过在 Studio 中设置程序而不是只是设置它来调整视角变更。修改控件和用户界面以补偿视角变更。
体验使用内置的 Roblox 排行榜,其单位是“点”,玩家每次标记另一个玩家结束时可以看到配置在 服务器脚本服务 > 设置Leaderboard ,但 体验排行榜 提供
现在玩家可以生成,选择一个冲击波,并从第一人称视查看选择它,下一节教你 about 创建回合 based 游戏的脚本。