实现你的设计 是在Studio中使用内置和自定义UI元素以及脚本来创建你的线框模型的过程,这些脚本根据上下文触发你的用户界面。本教程的这一激动人心的步骤是你看到所有设计和辛勤工作的结合,形成一个完整并准备好与玩家互动的工作流程集。
使用 示例激光标签体验 .rbxl 文件作为参考,本节用户界面设计课程向你展示如何将你的UI规划变为现实,包括以下指导:
- 从UI资产库中检索资产ID,以便你可以重建示例激光标签体验的UI组件。
- 在Studio中直接模拟各种设备,以查看你的UI在不同屏幕和纵横比下的显示效果。
在你回顾本节中的技术后,你可以将它们应用到你自己的项目中,以创建令人兴奋的UI组件,帮助玩家导航他们在你的体验中可以做什么。
获取资产库
资产库是可以添加到你库存中的资产集合,以实现快速访问和重用。你将在 创作者商店 中使用的资产库包括九个2D独立UI元素资产,以及你在本节教程中创建的目标、光束选择器和玩家信息组件的最终版本。

rbxassetid://14309094777

rbxassetid://14309094641

rbxassetid://14309678581

rbxassetid://14304828203

rbxassetid://14304827304

rbxassetid://14309678701

rbxassetid://14304826876

rbxassetid://14304827147

rbxassetid://18308375067

rbxassetid://18308372597

rbxassetid://14400935532

rbxassetid://14401148777

rbxassetid://14462567943

rbxassetid://14309187282

rbxassetid://14309518632
要将资产库从你的库存中导入到你的体验中:
将资产库添加到你的库存。
- 导航到资产库的 详细页面 在创作者商店。
- 在右上角,点击 获取模型 按钮。资产库现在在你的库存中,你可以在平台上的任何项目中重用这些资产。
在Studio中,导航到 首页 选项卡,然后点击 工具箱 按钮。工具箱 窗口打开。

在 工具箱 窗口中,点击 库存 选项卡。我的模型 排序显示。

点击下拉菜单,然后选择 我的包 排序。
点击 最终屏幕UI组件 瓦片,然后在 资源管理器 窗口中,选择 已完成组件,然后将它们拖入 StarterGui 服务。现在你可以启用任何最终组件以引用它们的设计。
模拟设备
Studio的 设备模拟器 允许你测试玩家在各种设备上将如何查看和与UI交互。这个工具是实施过程中的一个重要部分,因为你在Studio中的视口纵横比并不一定反映玩家用来访问你的体验的屏幕纵横比,确保你的UI在每个设备上都是可读和可访问的非常重要。
例如,如果你不在各种屏幕尺寸上测试你的UI,使用大屏幕的玩家可能无法阅读你的文本或分辨你的图标,而使用小屏幕的玩家可能无法看到3D空间,因为你的UI元素占据了显示的太多空间。
要在各种屏幕尺寸上模拟你的UI:
从Studio的 测试 菜单中,启用 设备模拟器。
在分辨率下拉菜单中,选择 实际分辨率。这允许你查看在你正在模拟的设备上UI元素的真实分辨率。

在设备下拉菜单中,至少选择一个 电话、平板电脑、桌面 和 控制台 部分中的设备。

创建ScreenGui对象
要在每个玩家的屏幕上显示UI元素,你可以在 StarterGui 服务中创建一个 ScreenGui 对象。ScreenGui 对象是屏幕上UI的主要容器,StarterGui 服务将其内容复制到每个玩家的 PlayerGui 容器中,玩家进入体验时。
你可以创建多个 ScreenGui 对象,以在游戏过程中有上下文地组织和显示UI元素。例如,示例激光标签体验包括五个单独的 ScreenGui 对象,它们在玩家满足不同条件之前最初是禁用的,主要的 用户流程 中的体验:
- HUDGui - 当玩家在一轮中活动时,显示有关体验游戏玩法的关键信息,例如目标和每个团队的总点数。
- PickABlasterGui - 当玩家开始或重新加入一轮时,显示所有光束选择。
- ForceFieldGui - 当玩家选择光束并在暂时无敌时显示六边形网格。
- OutStateGui - 当玩家被标记出局时,在屏幕周围显示黑色边框。
- RoundResultsGui - 在屏幕顶部显示一个黑色覆盖图层,并提供有关哪支队伍赢得了这一轮的信息。
在你创建 ScreenGui 对象后,你可以根据每个容器的目的创建和自定义其子 GuiObjects。为了演示,在后续的直接部分中,你将学习如何为玩家需要了解的 三类信息 实施UI元素,以便在示例激光标签体验中取得成功。您可以调整流程的任何部分以满足自己体验的规格。
要创建 ScreenGui 对象:
在 资源管理器 窗口中,悬停在 StarterGui 服务上,然后点击 ⊕ 图标。一菜单显示。
插入一个 ScreenGui。

根据其子UI元素的上下文重命名 ScreenGui。
对于每组需要在每个玩家屏幕上显示的UI元素重复这个过程。

目标UI
根据 线框你的布局 的视觉层级最佳实践,本节教你如何实现与体验目标相关的所有屏幕上UI元素。这个UI元素组位于屏幕的顶部,因为目标和每个团队的分数对于如何赢得比赛具有最高的重要性。

例如,示例提供了一个目标UI组件,玩家可以参考以了解他们在一轮中需要做什么才能取得成功。当玩家标记出敌队成员并获得分数时,该组件会跟踪每个团队在头部提示内的总分数。有关所有客户端和服务器脚本之间的高级回顾,它们共同跟踪分数,请参阅 跟踪分数 。

要准确重建示例 激光标签 体验中的目标UI:
为整个组件创建一个容器。
在 HUDGui ScreenGui 对象中插入一个 Frame。
- 在 资源管理器 窗口中,导航到 StarterGui 服务。
- 悬停在其子 HUDGui 对象上,然后点击 ⊕ 图标。一菜单将显示。
- 从菜单中,插入一个 Frame。

选择新的 Frame,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 0,以将框架的原点设置在顶部中间位置(框架左侧到右侧50%,顶部到底部0%)。
- 将 BackgroundTransparency 设置为 1,使框架的背景完全透明。
- 将 Position 设置为 {0.5, 0},{0.03, 0},以将框架设置在接近屏幕顶部中间的位置(离左侧50%、离顶部3%)。
- 将 Size 设置为 {0.5, 0},{0.13, 0},使框架的元素占据屏幕顶部的大部分,以吸引玩家的注意(水平占50%,垂直占13%)。
- 将 Name 设置为 Objective。

(可选) 在 Objective 中插入一个 UIAspectRatioConstraint,以确保标签的纵横比在任何屏幕尺寸下保持一致。示例将其 UIAspectRatioConstraint.AspectRatio 属性设置为 7。
为目标提示对象创建一个容器。
在 Objective 中插入一个 Frame。
选择新的 Frame,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 0,以将框架的原点设置在顶部中间位置。
- 将 BackgroundTransparency 设置为 1,使框架的背景完全透明。
- 将 Position 设置为 {0.5, 0},{0, 0},将框架设置在容器的中间位置。
- 将 Size 设置为 {1, 0},{0.67, 0},以使选择UI组件占据容器的上半部分 (水平100%且垂直67%)。
- 将 Name 设置为 ObjectiveDisplay。

创建标题元素。
在 ObjectiveDisplay 中插入一个 ImageLabel。
选择 ImageLabel,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 1,以将标签的原点设置在底部中间(标签左侧到右侧50%,顶部到底部100%)。
- 将 BackgroundTransparency 设置为 1,使标签的背景完全透明。
- 设置 LayoutOrder 为 -1。
- 将 Position 设置为 {0.5, 0},{0.34, 0},以将标签设置在框架的顶部中间位置(离左侧50%、离顶部34%)。
- 将 Size 设置为 {0.46, 0},{0.34, 0},以使提示区域宽度接近框架的一半(水平46% 和 垂直34%)。
- 将 Name 设置为 Header。
- 将 Image 设置为 rbxassetid://14304828123 以显示梯形图像。
- 将 ImageTransparency 设置为 0.15,使标题半透明。

(可选) 在 ImageLabel 中插入一个 UIAspectRatioConstraint,以确保标签的纵横比在任何屏幕尺寸下保持一致。示例将其 UIAspectRatioConstraint.AspectRatio 属性设置为 13.781。
在 Header 中插入一个 TextLabel,以显示标题。
选择新的标签,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 0.5,以将新标签的原点设置在中间。
- 将 BackgroundTransparency 设置为 1,使标签的背景完全透明。
- 将 Position 设置为 {0.5, 0},{0.5, 0},将标签移动到其父标签的中间。
- 将 Size 设置为 {0.62, 0},{0.55, 0},使文本空间宽度超出父标签一半(水平62% 和 垂直55%)。
- 将 Name 设置为 HeaderTextLabel。
- 将 FontFace 设置为 Montserrat 以符合未来主义美学。
- 将 Weight 设置为 Medium。
- 将 Text 设置为 OBJECTIVE。
- 启用 TextScaled。

创建提示元素。
在 ObjectiveDisplay 中插入一个 ImageLabel。
选择 ImageLabel,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 1,以将标签的原点设置在底部中间。
- 将 BackgroundTransparency 设置为 1,使标签的背景完全透明。
- 将 Position 设置为 {0.5, 0},{1, 0},将标签移动到其父框架的底部中间。
- 将 Size 设置为 {0.89, 0},{0.66, 0},以使文本空间宽度接近其父框架的全部宽度(水平89%和垂直66%)。
- 将 Name 设置为 Body。
- 将 Image 设置为 rbxassetid://14304827265 以显示倒梯形。
- 将 ImageColor3 设置为 0, 0, 0 以将图像染成黑色。
- 将 ImageTransparency 设置为 0.3,使标题半透明。

(可选) 在 ImageLabel 中插入一个 UIAspectRatioConstraint,以确保标签的纵横比在任何屏幕尺寸下保持一致。示例将其 UIAspectRatioConstraint.AspectRatio 属性设置为 13.781。
在 Body 中插入一个 TextLabel 以显示提示。
选择新的标签,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 0.5,以将新标签的原点设置在中间。
- 将 BackgroundTransparency 设置为 1,使标签的背景完全透明。
- 将 Position 设置为 {0.5, 0},{0.5, 0},将标签移动到其父标签的中间。
- 将 Size 设置为 {0.85, 0},{0.39, 0},使文本空间宽度超出父标签一半(水平85% 和 垂直39%)。
- 将 Name 设置为 BodyTextLabel。
- 将 FontFace 设置为 Montserrat 以符合未来主义美学。
- 将 Weight 设置为 Medium。
- 将 TextColor3 设置为 255, 255, 255,以使文本在黑色背景上显得为白色。
- 将 Text 设置为 标记对方玩家以获得分数!第一支达到 %d 分的队伍获胜。。
- 启用 TextScaled。

创建一个容器用于目标的团队计分器。
在 Objective 中插入一个 Frame。
选择新的 Frame,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 1 以将标签的原点设置在底部中间。
- 将 BackgroundTransparency 设置为 1 使框架的背景完全透明。
- 将 Position 设置为 {0.5, 0},{1, 0},将框架设置在容器的底部中间。
- 将 Size 设置为 {0.44, 0},{0.27, 0},使选择UI组件占据容器的少于一半(水平44% 和 垂直27%)。
- 将 Name 设置为 TeamPointCounter。

为团队计分器创建填充。
- 在步骤5中的框架中插入一个 UIListLayout 对象。
- 选择 UIListLayout 对象,然后在 属性 窗口中,
- 将 Padding 设置为 0.025, 0,以提供未来团队计分器之间的空间。
- 将 FillDirection 设置为 Horizontal,以便每个团队计分器并排显示。
- 将 HorizontalAlignment 设置为 Center,以使每个团队计分器对齐在一起。
创建绿色团队计分器元素。
在 TeamPointCounter 中插入一个 ImageLabel。
选择ImageLabel,然后在 属性 窗口中,
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Position 设置为 {0.5, 0},{1, 0},将标签设置在父标签的底部中间。
- 将 Size 设置为 {0.5, 0},{1, 0},将标签宽度设置为父框架的一半(水平50% 和 垂直100%)。
- 将 Name 设置为 TeamACounter。
- 将 Image 设置为 rbxassetid://14304826831 以显示方向渐变。
- 将 ImageColor3 设置为 88, 218, 171 以将图像染成薄荷绿色。

配置自定义属性以跟踪此标签用于绿色团队。
- 在 属性 窗口中,导航到 属性 部分,然后点击加号图标。弹出对话框显示。
- 在 名称 字段中,输入 teamColor。
- 在 类型 下拉菜单中,选择 BrickColor。
- 点击 保存 按钮。
- 将新的 teamColor 属性设置为 Mint。
在 TeamACounter 中插入一个 TextLabel 以显示提示。
选择新标签,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 1, 0.5,以将新标签的原点设置在右中间。
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Position 设置为 {0.95, 0},{0.5, 0},以将标签移动到其父标签的右边。
- 将 Size 设置为 {0.85, 0},{0.39, 0},使文本空间宽度超出父标签一半(水平85% 和 垂直39%)。
- 将 FontFace 设置为 Montserrat 以符合未来主义美学。
- 将 Weight 设置为 Bold。
- 将 TextColor3 设置为 255, 255, 255,以使文本在黑色背景上显得为白色。
- 将 Text 设置为 -。
- 启用 TextScaled。
- 将 TextXAlignment 设置为 Right。

在 TextLabel 中插入一个 UIStroke 对象,然后在 属性 窗口中将 Color 设置为 8, 78, 52 以使用深绿色的笔划描边。

创建粉色团队计分器元素。
复制 TeamAICounter 及其子项。
选择复制的 TeamACounter,然后在 属性 窗口中,
- 将 Name 设置为 TeamBCounter。
- 将 Image 设置为 rbxassetid://14305849451,以显示方向渐变相反的状态。
- 将 ImageColor3 设置为 255, 170, 255,将图像染成康乃馨粉红色。
- 将 teamColor 属性设置为 康乃馨粉色。
选择复制的 TextLabel 子项, 然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0, 0.5,以使新标签的原点置于左中间。
- 将 Position 设置为 {0.05, 0},{0.5, 0},以将标签移至其父标签的左边。
- 将 TextXAlignment 设置为 Left。
选择复制的 UIStroke 子项, 在 属性 窗口中,将 Color 设置为 158, 18, 94,以使虚线描边变为深粉色。

引用以下 ReplicatedStorage 脚本,以通过示例 激光标签 场景文件程序化更新目标提示和跟踪团队得分。
以下脚本需要一组模块脚本,它们共同设置主信息显示(HUD),包括 setObjective 和 startSyncingTeamPoints。在玩家加入一轮并选择他们的光束后,该脚本确保所有HUD UI元素根据玩家的状态、设备和团队状态适当地显示。
local Players = game:GetService("Players")local setPlayerPortrait = require(script.setPlayerPortrait)local setPlayerName = require(script.setPlayerName)local startSyncingTeamColor = require(script.startSyncingTeamColor)local setObjective = require(script.setObjective)local setupTouchButtonAsync = require(script.setupTouchButtonAsync)local startSyncingTeamPoints = require(script.startSyncingTeamPoints)local disableMouseWhileGuiEnabled = require(script.disableMouseWhileGuiEnabled)local setupHitmarker = require(script.setupHitmarker)local localPlayer = Players.LocalPlayerlocal gui = localPlayer.PlayerGui:WaitForChild("HUDGui")setPlayerPortrait(gui)setPlayerName(gui)startSyncingTeamColor(gui)setObjective(gui)startSyncingTeamPoints(gui)disableMouseWhileGuiEnabled(gui)setupHitmarker(gui)setupTouchButtonAsync(gui)
光束UI
根据 线框你的布局 的视觉层级最佳实践,本节教你如何实现与玩家光束相关的所有屏幕上的UI元素。这组UI元素占据了屏幕中心附近的大部分空间,因为它作为吸引玩家注意力的焦点,并在游戏中具有最大的意义。

十字准星
十字准星是一个UI元素,通知玩家他们发射武器时将在哪里造成影响。这个UI元素是第一人称射击体验的重要游戏要求,因为玩家需要能够准确地瞄准他们的光束并标记敌方团队成员。
与第一人称射击类别中的大多数其他体验一样,示例激光标签体验将十字准星放置在屏幕中心,以便玩家在其角色在3D空间中移动时有一个静态的集中点。除了减少晕动病外,这种布局还允许十字准星和整体环境完美融合。

要在示例 激光标签 体验中准确重建十字准星:
在 HUDGui ScreenGui 对象中插入一个 ImageLabel。
在 资源管理器 窗口中,导航到 StarterGui 服务。
悬停在其子 HUDGui 对象上,然后单击 ⊕ 图标。一菜单显示。
从菜单中插入一个 ImageLabel。

选择新的 ImageLabel,然后在 属性 窗口中,
- 将 Image 设置为 rbxassetid://14400935446。
- 将 AnchorPoint 设置为 0.5, 0.5,以将标签的原点设置在标签的中间。
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Name 设置为 Crosshair。
- 将 Position 设置为 {0.5,0},{0.5,0},以将标签设置在屏幕中间。
- 将 ScaleType 设置为 Fit,以便图像适应其容器,并且在各种屏幕尺寸上不拉伸。
(可选) 在 Crosshair 中插入一个 UIAspectRatioConstraint,以确保标签的纵横比在任何屏幕尺寸下保持一致。示例将其 UIAspectRatioConstraint.AspectRatio 属性设置为 0.895。
命中标记
命中标记是一个UI元素,仅在攻击成功时显示,即当一次轰击击中敌方队伍的另一个玩家时。与十字准星一样,这个UI元素是第一人称射击体验的重要游戏要求,因为它提供了视觉反馈,表明玩家成功标记了对手。

要在示例 激光标签 体验中准确重建命中标记:
在 Crosshair ImageLabel 对象中插入一个 ImageLabel。
在 资源管理器 窗口中,导航到 StarterGui 服务。
悬停在其子 Crosshair 对象上,然后单击 ⊕ 图标。一菜单显示。
从菜单中插入一个 ImageLabel。

选择新的 ImageLabel,然后在 属性 窗口中,
- 将 Image 设置为 rbxassetid://14401148736,以显示矩形命中标记图标。
- 将 AnchorPoint 设置为 0.5, 0.5,以将标签的原点设置在标签的中间。
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Position 设置为 {0.5,0},{0.5,0},以将标签设置在屏幕中间。
- 将 Name 设置为 Hitmarker。
- 将 Size 设置为 {0.6, 0},{0.06, 0},以减少十字准星周围矩形的大小。
- 将 ImageTransparency 设置为 1,以使命中标记完全透明。在以下步骤中,脚本每次玩家的光束与另一名敌方玩家命中时将透明度设置回0。
引用以下 ReplicatedStorage 脚本,在示例 激光标签 场景文件中,程序化显示命中标记,当轰击与敌方玩家命中时。
以下脚本需要一组模块脚本,它们共同设置主信息显示(HUD),包括 setupHitmarker。在玩家加入一轮并选择他们的光束后,该脚本确保所有HUD UI元素根据玩家的状态、设备和团队状态适当地显示。
local Players = game:GetService("Players")local setPlayerPortrait = require(script.setPlayerPortrait)local setPlayerName = require(script.setPlayerName)local startSyncingTeamColor = require(script.startSyncingTeamColor)local setObjective = require(script.setObjective)local setupTouchButtonAsync = require(script.setupTouchButtonAsync)local startSyncingTeamPoints = require(script.startSyncingTeamPoints)local disableMouseWhileGuiEnabled = require(script.disableMouseWhileGuiEnabled)local setupHitmarker = require(script.setupHitmarker)local localPlayer = Players.LocalPlayerlocal gui = localPlayer.PlayerGui:WaitForChild("HUDGui")setPlayerPortrait(gui)setPlayerName(gui)startSyncingTeamColor(gui)setObjective(gui)startSyncingTeamPoints(gui)disableMouseWhileGuiEnabled(gui)setupHitmarker(gui)setupTouchButtonAsync(gui)
光束选择器
光束选择器是一个UI组件,玩家在加入或重新加入一轮之前使用它选择他们的光束类型。示例激光标签体验提供了两种类型的光束:一种是产出几个束流且呈水平扩散,另一种则产生单一束流。玩家选择的光束类型影响他们在轮次中的策略,使这个UI组件成为整体体验的重要工作流。
以下步骤详细说明了如何为不同的UI元素组、标题和提示、导航与选择按钮、以及光束按钮预制件创建若干容器。整体组件的脚本逻辑根据代表每种光束类型的 Configuration 实例将不同的视觉特性填充到光束按钮预制件中。
这种设置允许你为更多光束类型创建额外的 Configuration 实例,这些实例将自动在光束选择器中正确显示,而不需要在 StarterGui.PickABlasterGui 中创建单独的按钮。

要在示例 激光标签 体验中准确重建光束选择器:
为整个组件创建一个容器。
在 PickABlaster ScreenGui 对象中插入一个 Frame。
- 在 资源管理器 窗口中,导航到 StarterGui 服务。
- 悬停在其子 PickABlaster 对象上,然后点击 ⊕ 图标。一菜单显示。
- 从菜单中插入一个 Frame。

选择新的框架,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 1 以使框架的原点位于底部中间(框架左侧到右侧50%,顶部到底部100%)。
- 将 BackgroundTransparency 设置为 1 ,以使框架的背景完全透明。
- 将 Position 设置为 {0.5, 0},{0.9, 0},以将框架设置在接近屏幕底部中间的位置(离左侧50%、离顶部92.4%)。
- 将 Size 设置为 {0.8, 0},{0.25, 0},使光束选择器的UI组件能够占据大部分屏幕以吸引玩家的注意(水平占80%和垂直占25%)。
- 将 Name 设置为 Component。

(可选) 在 Component 中插入一个 UIAspectRatioConstraint,以确保框架及其子UI元素的纵横比在任何屏幕尺寸下保持一致。示例将其 UIAspectRatioConstraint.AspectRatio 属性设置为 5。
创建一个容器来持有UI元素组。
在 Component 中插入一个 Frame。
选择新的框架,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 0.5,使框架的原点位于中间。
- 将 BackgroundTransparency 设置为 1,使框架的背景完全透明。
- 将 Position 设置为 {0.5, 0},{0.375, 0},将框架设置于容器的顶部中间。
- 将 Size 设置为 {1, 0},{0.75, 0},使选择UI组件占据容器的3/4(水平100%和垂直75%)。
- 将 Name 设置为 SelectionFrame。

创建光束选择器的提示。
在 SelectionFrame 中插入一个 ImageLabel。
选择新标签,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 1,以将标签的原点设置在底部中间。
- 将 BackgroundTransparency 设置为 1 ,使标签的背景完全透明。
- 将 LayoutOrder 设置为 -1。
- 将 Position 设置为 {0.5, 0},{0.22, 0},将标签设置在框架的顶部中间。
- 将 Size 设置为 {0.45, 0},{0.22, 0},使提示区域宽度接近框架的一半(水平占45%和垂直占22%)。
- 将 Name 设置为 Header。
- 将 Image 设置为 rbxassetid://14304828123,以显示梯形。
- 将 ImageTransparency 设置为 0.15,使标题半透明。

(可选) 在标签中插入一个 UIAspectRatioConstraint,以确保标签的纵横比在任何屏幕尺寸下保持一致。示例将其 UIAspectRatioConstraint.AspectRatio 属性设置为 13.78。
在 Header 中插入一个 TextLabel,以显示提示。
选择新标签,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 0.5,以使新标签的原点位于其自身的中心。
- 将 BackgroundTransparency 设置为 1 使标签的背景完全透明。
- 将 Position 设置为 {0.5, 0},{0.5, 0},以将标签移动至其父标签的中间。
- 将 Size 设置为 {0.6, 0},{0.55, 0},使文本空间宽度超出父标签的一半(水平占60%和垂直占55%)。
- 将 Name 设置为 HeaderTextLabel。
- 将 FontFace 设置为 Montserrat ,以符合未来主义美学。
- 将 Weight 设置为 Medium。
- 将 Text 设置为 选择光束。
- 启用 TextScaled。

创建用于光束按钮容器和选择箭头的容器。
在 SelectionFrame 中插入一个 ImageLabel。
选择新标签,然后在 属性 窗口中,
- 删除默认 Image 值。
- 将 AnchorPoint 设置为 0.5, 1,以将标签的原点设置在底部中间。
- 将 BackgroundColor 设置为 0, 0, 0,使标签变为黑色。
- 将 BackgroundTransparency 设置为 0.3,将标签的不透明度减少30%,以匹配体验中的所有黑色UI元素。
- 将 Position 设置为 {0.5, 0},{1, 0},将标签设置在框架的底部中间。
- 将 Size 设置为 {1, 0},{0.77, 0},使标签区域宽度超出提示的空间(水平占100%和垂直占77%)。

圆角处理容器。
- 在标签中插入一个 UICorner 对象。
- 选择新角落对象,然后在 属性 窗口中,将 CornerRadius 设置为 0.075, 0 以圆润角落。

创建用于光束按钮的容器。
在第4步中的标签中插入一个 Frame。
选择新的框架,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 0.5,以将新框架的原点置中。
- 将 BackgroundTransparency 设置为 1,使框架的背景完全透明。
- 将 Position 设置为 {0.5, 0},{0.5, 0},将框架设置在其父标签的中间。
- 将 Size 设置为 {0.85, 0},{0.77, 0},使框架区域接近标签的大部分(水平占85% 和 垂直占77%)。
- 将 Name 设置为 Container。

为所有未来光束按钮创建填充。
- 在第5步中的框架中插入一个 UIListLayout 对象。
- 选择新的布局对象,然后在 属性 窗口中,
- 将 Padding 设置为 0.035, 0,以在所有未来按钮之间提供空间。
- 将 FillDirection 设置为 Horizontal,使每个按钮并排显示。
- 将 HorizontalAlignment 和 VerticalAlignment 都设置为 Center,确保每个按钮在同时对齐。
创建左侧导航按钮。
在第4步中的 ImageLabel 中插入一个 ImageButton 对象。
选择新按钮,然后在 属性 窗口中,
- 删除默认 Image 值。
- 将 AnchorPoint 设置为 0, 0.5,以将新按钮的原点设置在左中间。
- 将 BackgroundTransparency 设置为 0.15,以提供悬停时视觉反馈来表明按钮是可选择的。
- 将 Position 设置为 {0.02, 0},{0.5, 0},为按钮提供自父容器的左侧填充(离左侧2%、离顶部50%)。
- 将 Size 设置为 {0.04, 0},{0.33, 0},使选择按钮尺寸远小于光束按钮(水平4% 和 垂直33%)。

圆角按钮。
- 在按钮中插入一个 UICorner 对象。
- 选择新的圆角对象,然后在 属性 窗口中,将 CornerRadius 设置为 0.1, 0 以圆化角。

在按钮中插入一个 ImageLabel 对象。
选择新的标签,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 0.5,以将新标签的原点设置在其中心(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
- 将 Position 设置为 {0.45, 0},{0.5, 0},将标签放置在其父按钮的中间附近(从父按钮的左侧到右侧 45%,从父按钮的顶部到底部 50%)。此值不在中间,因为箭头在 {0.5, 0},{0.5, 0} 时 视觉上 看起来并不在按钮的中心。
- 将 Size 设置为 {0.8, 0},{0.8, 0},以放宽标签区域以适应提示下方(在父框架的水平和垂直方向上分别占 80%)。
- 将 BackgroundTransparency 设置为 1 以使图像的背景完全透明。
- 将 Image 设置为 rbxassetid://14309187238。
- 将 ScaleType 设置为 Fit。

创建右侧导航按钮。
复制 NavigationButtonLeft。
选择复制的按钮,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 1, 0.5,以将新按钮的原点设置在其左中部(从按钮的左侧到右侧 100%,从按钮的顶部到底部 50%)。
- 将 Position 设置为 {0.98, 0},{0.5, 0},以在按钮右侧提供填充(从父标签的左侧到右侧 98%,从父标签的顶部到底部 50%)。
- 将 Name 设置为 NavigationButtonRight。
选择其 ImageLabel 子对象。
- 将 Rotation 设置为 180,以翻转图像。
- 将 Position 设置为 {0.55, 0},{0.5, 0},将标签放置在其父按钮的中间附近(从父按钮的左侧到右侧 55%,从父按钮的顶部到底部 50%)。此值不在中间,因为箭头在 {0.5, 0},{0.5, 0} 时 视觉上 看起来并不在按钮的中心。

创建 SELECT 按钮。
在 Component 中插入一个 ImageButton。请注意,此过程使选择按钮与 SelectionFrame 分开,以便您可以在组件的主要部分与选择按钮之间添加填充。
选择新的按钮,然后在 属性 窗口中,
- 删除默认的 Image 值。
- 将 AnchorPoint 设置为 0.5, 1,以将新按钮的原点设置在其底部中部(从按钮的左侧到右侧 50%,从按钮的顶部到底部 100%)。
- 将 BackgroundTransparency 设置为 0.15,以在悬停时提供视觉反馈,表明按钮是可选择的。
- 将 Position 设置为 {0.5, 0},{0.99, 0},将按钮放置在其容器的底部中间附近(从父框架的左侧到右侧 50%,从父框架的顶部到底部 99%)。
- 将 Size 设置为 {0.17, 0},{0.18, 0},使按钮在闪光灯按钮下方延长(在父框架的水平方向上占 17%,在垂直方向上占 18%)。
- 将 Name 设置为 SelectButton。

圆角按钮。
- 在按钮中插入一个 UICorner 对象。
- 选择新的圆角对象,然后在 属性 窗口中,将 CornerRadius 设置为 0.2, 0 以圆化角。

在按钮中插入一个 TextLabel 对象,以便显示行动呼吁。
选择新的标签,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0.5, 0.5,以将新标签的原点设置在其中心(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Position 设置为 {0.5, 0},{0.5, 0},将标签移动到按钮的中间(从父按钮的左侧到右侧 50%,从父按钮的顶部到底部 50%)。
- 将 Size 设置为 {0.9, 0},{0.55, 0},使文本空间宽一些,几乎占据父标签的全部宽度(在父标签的水平方向上占 90%,在垂直方向上占 55%)。
- 将 Name 设置为 SelectTextLabel。
- 将 FontFace 设置为 Montserrat,以符合未来派美学。
- 将 Weight 设置为 Medium,以使字体加厚。
- 将 Text 设置为 SELECT。
- 启用 TextScaled。

创建一个闪光按钮预制件。
- 在 ReplicatedStorage 服务中,创建一个文件夹结构以组织您的 UI 对象。示例使用一个 Instances 文件夹,其中包含一个 Guis 子文件夹。
- 在 Guis 文件夹中插入一个 ImageButton 对象。
- 选择新的按钮,然后在 属性 窗口中,
- 删除默认的 Image 值。
- 将 AnchorPoint 设置为 0.5, 0.5,以将新按钮的原点设置在其中心(从按钮的左侧到右侧 50%,从按钮的顶部到底部 50%)。
- 将 BackgroundTransparency 设置为 0.65,以提供视觉反馈,表明按钮不在焦点内。第 12 步中的脚本在按钮获得焦点时提供程序性的视觉反馈。
- 将 LayoutOrder 设置为 2。
- 将 Name 设置为 BlasterButtonPrefab。
- 将 Size 设置为 {0.8, 0},{0.8, 0}。
- 将 ImageTransparency 设置为 1,使图像完全透明。
- 在 BlasterButtonPrefab 中插入一个 UIAspectRatioConstraint,以确保按钮的纵横比在组件中保持不变,无论玩家的屏幕尺寸如何。
- 圆角按钮。
- 在 BlasterButtonPrefab 中插入一个 UICorner 对象。
- 选择 UICorner,然后在 属性 窗口中,将 CornerRadius 设置为 0.05, 0 以圆化角。
- 在 BlasterButtonPrefab 中插入一个 ImageLabel。
- 选择新的标签,然后在 属性 窗口中,
- 删除 Image 属性中的占位符资产 ID。第 7 步中的脚本会程序性地将玩家的肖像插入到图像标签中。
- 将 AnchorPoint 设置为 0.5, 0.5,以将新标签的原点设置在其中心(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Position 设置为 {0.52, 0},{0.497, 0},将标签放置在其父按钮的中间附近(从父按钮的左侧到右侧 52%,从父按钮的顶部到底部 49.7%)。此值不在中间,因为闪光灯在 {0.5, 0},{0.5, 0} 时 视觉上 看起来并不在按钮的中心。
- 将 Size 设置为 {1.20, 0},{0.9, 0},以放宽标签区域,使其超出按钮(在父按钮的水平上占 120% 和在垂直上占 90%)。
- 将 ScaleType 设置为 Fit。
参考以下 ReplicatedStorage 脚本,这些脚本在示例 Laser Tag 放置文件中,以程序性地显示每个闪光枪的按钮,当玩家选择一个不在焦点内的按钮时,缩放按钮,并将玩家的闪光选择附加到他们的化身上。
以下脚本需要一组模块脚本协同工作,以创建闪光枪选择器。当玩家加入体验或在健康值降至零后重新进入回合时,此脚本激活所有闪光枪选择器的 UI 元素,直到玩家做出选择。
local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local GuiAttribute = require(ReplicatedStorage.GuiAttribute)local setupBlasterButtons = require(script.setupBlasterButtons)local connectResetSelectionOnEnabled = require(script.connectResetSelectionOnEnabled)local localPlayer = Players.LocalPlayerlocal gui = localPlayer.PlayerGui:WaitForChild("PickABlasterGui")setupBlasterButtons(gui)connectResetSelectionOnEnabled(gui)gui:SetAttribute(GuiAttribute.selectedIndex, 1)
闪光按钮
闪光按钮是玩家用于在通过移动设备或平板电脑访问体验时发射闪光枪的 UI 组件。示例激光标记体验使用一个带有十字准星和闪光图标的闪光按钮,以便在没有文本的情况下传达按钮的功能。

为了在示例 Laser Tag 体验中准确重现闪光按钮:
在 HUDGui ScreenGui 对象中插入一个 ImageButton。
在 资源管理器 窗口中,导航到 StarterGui 服务。
将鼠标悬停在其子 HUDGui 对象上,然后单击 ⊕ 图标。会显示上下文菜单。
从上下文菜单中插入一个 ImageButton。

在视口中,将按钮移动到玩家的拇指自然休息的位置,这样您就可以获得在玩家设备上按钮的外观的视觉感觉,然后在 属性 窗口中,
- 将 Image 设置为 rbxassetid://18308375035 以显示闪光按钮图标。
- 将 PressedImage 设置为 rbxassetid://18308372558 以在玩家按下按钮时显示闪光按钮图标的反转版本。
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Name 设置为 BlastButton。
- 将 ScaleType 设置为 Fit,以使图像适应其容器,而不会在不同的屏幕尺寸上拉伸。
- 将 ImageTransparency 设置为 0.3,以降低标签的不透明度,使其与体验中的所有黑色 UI 元素匹配。
在 BlastButton 中插入一个 UIAspectRatioConstraint,以确保按钮的纵横比在玩家的屏幕尺寸下保持不变。
参考以下 ReplicatedStorage 脚本,这些脚本在示例 Laser Tag 放置文件中,以程序性地显示闪光按钮,当玩家在接受触控输入的设备上使用触控输入时。
以下脚本需要一组模块脚本协同工作,以设置主要读取显示器 (HUD),包括 setupTouchButtonAsync。在玩家加入回合并选择他们的闪光枪后,此脚本确保所有 HUD UI 元素适当地根据玩家的状态、设备和团队状态显示。
local Players = game:GetService("Players")local setPlayerPortrait = require(script.setPlayerPortrait)local setPlayerName = require(script.setPlayerName)local startSyncingTeamColor = require(script.startSyncingTeamColor)local setObjective = require(script.setObjective)local setupTouchButtonAsync = require(script.setupTouchButtonAsync)local startSyncingTeamPoints = require(script.startSyncingTeamPoints)local disableMouseWhileGuiEnabled = require(script.disableMouseWhileGuiEnabled)local setupHitmarker = require(script.setupHitmarker)local localPlayer = Players.LocalPlayerlocal gui = localPlayer.PlayerGui:WaitForChild("HUDGui")setPlayerPortrait(gui)setPlayerName(gui)startSyncingTeamColor(gui)setObjective(gui)startSyncingTeamPoints(gui)disableMouseWhileGuiEnabled(gui)setupHitmarker(gui)setupTouchButtonAsync(gui)
玩家 UI
遵循 线框您的布局 的视觉层次最佳实践,本节教您如何实现与玩家状态相关的所有屏幕 UI 元素。这组 UI 元素位于屏幕的侧面,因为玩家可以在不分散注意力进行游戏的情况下理解这些外围信息。

玩家指示器
玩家指示器是玩家参考的一种 UI 组件,使他们在进入团队的出生区时立即识别出自己属于哪个团队。示例激光标签体验提供了两个不同版本的玩家指示器,具体取决于玩家是 绿色 还是 粉色 队伍。


遵循 选择颜色主题 的指导,两个版本的玩家指示器结合了团队颜色和特定的简单图标,并具有最小的细节,以保持其在小屏幕上的可读性。提供两种形式的视觉反馈非常重要,因为这有助于为色盲玩家保持设计的可访问性。

为了在示例 Laser Tag 体验中准确重现玩家指示器组件:
在 HUDGui ScreenGui 对象中插入一个 Frame。
- 在 资源管理器 窗口中,导航到 StarterGui 服务。
- 将鼠标悬停在其子 HUDGui 对象上,然后单击 ⊕ 图标。会显示上下文菜单。
- 从上下文菜单中插入一个 Frame。
选择新的 Frame,然后在 属性 窗口中,
将 AnchorPoint 设置为 0, 1,以将框架的原点设置在其底部中部(从框架的左侧到右侧 0%,从框架的顶部到底部 100%)。
将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
将 Name 设置为 PlayerDisplay。
将 Position 设置为 {0.02, 0},{0.97, 0},将框架放置在屏幕的左下角附近。
将 Size 设置为 {0.23, 0},{0.08, 0},以缩短和加宽框架。
启用 ClipsDescendants,以修剪超出框架的子 GuiObjects。

创建多边形形状。
在 PlayerDisplay 中插入一个 ImageLabel。
选择新的标签,然后在 属性 窗口中,
- 将 Image 设置为 rbxassetid://14304828123 以显示梯形图标。
- 将 AnchorPoint 设置为 1, 1,以将标签的原点设置在其底部右侧(从标签的左侧到右侧 100%,从标签的顶部到底部 100%)。
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Name 设置为 Block。
- 将 Position 设置为 {1,0},{1,0},将标签放置在框架的右侧。
- 将 Size 设置为 {1.858, 0},{0.581, 0},以使标签超出框架并缩短,使其略超过框架的一半长度。
- 将 ImageTransparency 设置为 0.15,以使标签略微透明。
- 将 ScaleType 设置为 Fit,以使图像适应其容器,而不会在不同的屏幕尺寸上拉伸。

- 在 Block 中插入一个 UIAspectRatioConstraint,以确保标签及其子 UI 元素的纵横比在玩家的屏幕尺寸下保持不变。
- 选择新的约束,然后在 属性 窗口中,将 AspectRatio 设置为 13.78。
创建玩家肖像的框。
在 PlayerDisplay 中插入一个 ImageLabel。
选择新的标签,然后在 属性 窗口中,
- 删除 Image 属性中的占位符资产 ID。第 7 步中的脚本将程序性地将玩家的肖像插入到图像标签中。
- 将 AnchorPoint 设置为 0, 1,以将标签的原点设置在其左下角(从标签的左侧到右侧 0%,从标签的顶部到底部 100%)。
- 将 BackgroundColor3 设置为 0, 0, 0 以将标签的背景颜色设置为黑色。
- 将 BackgroundTransparency 设置为 0.3,以将标签的不透明度降低 30%,并与体验中的所有黑色 UI 元素匹配。
- 将 Name 设置为 PlayerPortrait。
- 将 Position 设置为 {0.11, 0},{1, 0},将标签放置在多边形形状的左侧。
- 将 Size 设置为 {0.23, 0},{1, 0},以缩小标签。
- 将 ImageTransparency 设置为 0.15,以使标签略微透明。
- 将 ScaleType 设置为 Fit,以使图像适应其容器,而不会在不同的屏幕尺寸上拉伸。

- 在 PlayerPortrait 中插入一个 UIAspectRatioConstraint,以确保标签及其子 UI 元素的纵横比在玩家的屏幕尺寸下保持不变。
- 在 PlayerPortrait 中插入一个 UICorner,然后在 属性 窗口中,将 CornerRadius 设置为 0.05, 0 以稍微圆化角。

创建玩家名称的文本标签。
在 PlayerDisplay 中插入一个 TextLabel 对象。
选择新的标签,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0, 0.5,以将新按钮的原点设置在其中左部(从按钮的左侧到右侧 0%,从按钮的顶部到底部 50%)。
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Name 设置为 PlayerNameTextLabel。
- 将 Position 设置为 {0.35, 0},{0.72, 0},将标签放置在其容器的右侧(从父标签的左侧到右侧 35%,从父标签的顶部到底部 72%)。
- 将 Size 设置为 {0.52, 0},{0.3, 0},以使文本占据多边形形状区域的大部分(在父框架的水平上占 52% 和在垂直上占 30%)。
- 将 FontFace 设置为 Montserrat,以符合未来派美学。
- 将 Weight 设置为 Bold,以使字体加厚。
- 删除 Text 属性中的占位符文本。第 7 步中的脚本将程序性地将玩家的名称插入到文本标签中。
- 启用 TextScaled。
- 将 TextXAlignment 设置为 Left。

创建显示在玩家肖像左侧的团队图标和颜色。
在 PlayerDisplay 中插入一个 Folder,然后将其重命名为 TeamIcons。
创建 绿色 团队图标和颜色。
- 在 TeamIcons 中插入一个 ImageLabel。
- 选择新的标签,然后在 属性 窗口中,
- 将 AnchorPoint 设置为 0, 1,以将标签的原点设置在其左下角(从标签的左侧到右侧 0%,从标签的顶部到底部 100%)。
- 将 BackgroundColor3 设置为 88, 218, 171 以将标签的背景颜色设置为薄荷绿色。
- 将 Name 设置为 TeamAIcon。
- 将 Position 设置为 {0, 0},{1, 0},将标签放置在框架的左侧。
- 将 Size 设置为 {0.135, 0},{0.58, 0},以将标签缩小到玩家肖像的左侧。
- 将 ImageTransparency 设置为 1 以使标签透明。

- 配置自定义属性以跟踪该标签用于绿色团队。此步骤对于第 7 步中的脚本非常重要。
- 在 属性 窗口中,导航到 属性 部分,然后单击加号图标。会显示弹出对话框。
- 在 名称 字段中,输入 teamColor。
- 在 类型 下拉菜单中,选择 BrickColor。
- 单击 保存 按钮。
- 将新的 teamColor 属性设置为 Mint。
- 在 TeamAIcon 中插入一个 UIAspectRatioConstraint,以确保标签及其子 UI 元素的纵横比在玩家的屏幕尺寸下保持不变。
- 创建图标。
- 在 TeamAIcon 中插入一个 ImageLabel。
- 选择新的标签,然后在 属性 窗口中,
- 将 Image 设置为 rbxassetid://14309678670 以显示绿色团队图标。
- 将 AnchorPoint 设置为 0.5, 0.5,以将标签的原点设置在其中心(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
- 将 BackgroundTransparency 设置为 1,以使标签的背景完全透明。
- 将 Name 设置为 Icon。
- 将 Position 设置为 {0.5, 0},{0.5, 0},将标签放置在其父标签的中间。
- 将 Size 设置为 {0.7, 0},{0.6, 0},以缩小标签。
- 将 ScaleType 设置为 Fit,以使图像适应其容器,而不会在不同的屏幕尺寸上拉伸。

创建 粉色 团队图标和颜色。
- 复制 TeamAIcon 及其子对象。
- 选择复制的 TeamAIcon,然后在 属性 窗口中,
- 将 BackgroundColor3 设置为 255, 170, 255 以将标签的背景颜色设置为肉色的粉色。
- 将 Name 设置为 TeamBIcon。
- 将 teamColor 属性设置为 Carnation Pink。
- 选择 TeamBIcon 的复制的 Icon 子对象,然后在 属性 窗口中,将 Image 设置为 rbxassetid://14309678549 以显示粉色团队图标。

参考以下 ReplicatedStorage 脚本,这些脚本在示例 Laser Tag 放置文件中,以程序性地显示玩家指示器,使用适当的团队颜色和图标,同时玩家在回合中处于活跃状态。
以下脚本需要一组模块脚本协同工作,以设置主要读取显示器 (HUD),包括 startSyncingTeamColor、setPlayerName 和 setPlayerPortrait。在玩家加入回合并选择他们的闪光枪后,此脚本确保所有 HUD UI 元素适当地根据玩家的状态、设备和团队状态显示。
local Players = game:GetService("Players")local setPlayerPortrait = require(script.setPlayerPortrait)local setPlayerName = require(script.setPlayerName)local startSyncingTeamColor = require(script.startSyncingTeamColor)local setObjective = require(script.setObjective)local setupTouchButtonAsync = require(script.setupTouchButtonAsync)local startSyncingTeamPoints = require(script.startSyncingTeamPoints)local disableMouseWhileGuiEnabled = require(script.disableMouseWhileGuiEnabled)local setupHitmarker = require(script.setupHitmarker)local localPlayer = Players.LocalPlayerlocal gui = localPlayer.PlayerGui:WaitForChild("HUDGui")setPlayerPortrait(gui)setPlayerName(gui)startSyncingTeamColor(gui)setObjective(gui)startSyncingTeamPoints(gui)disableMouseWhileGuiEnabled(gui)setupHitmarker(gui)setupTouchButtonAsync(gui)
力场屏幕
力场屏幕是一个 UI 元素,覆盖在视口中,以通知玩家在加入或重新加入回合时,他们在敌队火力下是安全的。为了遵循 选择艺术风格 中的图标美学指南,示例激光标签体验利用半透明的六边形图案来象征力场。这一设计决定不仅强化了该体验中所有 UI 的未来主义艺术风格,还在没有任何文本或额外指导的情况下传达了玩家的状态。

为了在示例 Laser Tag 体验中准确重现力场屏幕:
在 ForceFieldGui ScreenGui 对象中插入一个 ImageLabel。
在 资源管理器 窗口中,导航到 StarterGui 服务。
将鼠标悬停在其子 ForceFieldGui 对象上,然后单击 ⊕ 图标。会显示上下文菜单。
从上下文菜单中插入一个 ImageLabel。

选择新的标签,然后在 属性 窗口中,
将 Image 设置为 rbxassetid://14462567888。
将 BackgroundTransparency 设置为 0.8,以使力场半透明。
将 Size 设置为 {1, 0},{1, 0},以使图像填充整个屏幕(在父 ScreenGui 中的水平和垂直均为 100%)。
将 ScaleType 设置为 Tile,以使六边形在整个屏幕上平铺。
将 TileSize 设置为 {0, 104},{0, 180}。

在标签中插入一个 UIGradient 对象。
选择新的渐变对象,然后在 属性 窗口中,
将 Color 设置为从蓝色开始、变为白色,再变为蓝色的颜色序列。
将 Color 设置为 120, 192, 250,以将所有六边形应用为浅蓝色。
单击 Color 属性,然后单击 ⋯ 按钮。会弹出颜色序列弹出窗口。

每个三角形在颜色序列的底部轴上是一个关键点,确定从左到右图像在该点的颜色值。
点击并拖动颜色序列,直到达到 时间 值为 0.05,然后单击 Color 旁边的小方块以打开 Colors 弹出窗口。
选择一个明亮的白色,然后关闭弹出窗口。
点击并拖动颜色序列,直到达到 时间 值为 0.95,然后再次打开 Colors 弹出窗口,选择与之前相同的白色。

将 Rotation 设置为 225,以使颜色序列的蓝色部分显示在左上和右下角。

将 Transparency 设置为数值序列,使力场看起来像在闪烁。
单击 Transparency 属性,然后单击 ⋯ 按钮。会弹出数字序列弹出窗口。数字序列开头和结尾的每个方块都是一个关键点,确定从左到右图像在该点的透明度值。
在数字序列中设置以下时间和值属性:
- 时间 = 0,值 = 0.25
- 时间 = .101,值 = 0.875
- 时间 = .183,值 = 0
- 时间 = .3,值 = 1
- 时间 = .7,值 = 1
- 时间 = 1,值 = 0.9


复制第 2 步中的 ImageLabel。
选择复制标签中的 UIGradient 对象,然后在 属性 窗口中,
将 Rotation 设置为 -45,以翻转图像,使其沿 Y 轴几乎镜像。

修改 Transparency 以使闪烁看起来更有机。
- 单击 Transparency 属性,然后单击 ⋯ 按钮。会弹出数字序列弹出窗口。
- 选择第三个关键帧,然后单击 删除 按钮。

参考以下 ReplicatedStorage 脚本,这些脚本在示例 Laser Tag 放置文件中,以程序性地显示力场屏幕,当玩家加入或重新加入回合时。
以下 ReplicatedStorage.ForceFieldClientVisuals 客户端脚本用 StarterGui.ForceFieldGui 替代默认的 ForceField 视觉效果。当玩家加载进入体验并在 SpawnLocation 上生成,且其 Duration 属性大于 0 时,每个体验中的默认行为是为他们的头像提供一个保护性的蓝色球体,暂时防止他们失去生命值。
该脚本首先监听 ForceField 何时被添加到角色,禁用默认的第一人称力场视觉效果,然后启用 ForceFieldGui ScreenGui 对象。请注意,这不会影响玩家在观看其他玩家重新生成回到体验时的第三人称视觉效果。


local Players = game:GetService("Players")
local localPlayer = Players.LocalPlayer
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
if localPlayer.Character then
onCharacterAddedAsync(localPlayer.Character)
end
localPlayer.CharacterAdded:Connect(onCharacterAddedAsync)
重新生成屏幕
重新生成屏幕是一个 UI 元素,能够调暗视口以通知玩家他们已被击出,并且服务器正在将他们重新生成回他们的出生区域。这个 UI 元素很重要,因为它给予玩家时间来处理他们被击出的事实,并在重新加入活跃回合前策划他们的下一步行动。
有关样本激光标记体验中自定义重新生成行为的更多信息,请参见 Respawn Characters 游戏脚本课程。

为了在样本 Laser Tag 体验中准确重现重新生成屏幕:
创建中间信息横幅。
在 OutStateGui ScreenGui 对象中插入一个 ImageLabel。
在 Explorer 窗口中,导航到 StarterGui 服务。
将光标悬停在其子 OutStateGui 对象上,然后单击 ⊕ 图标。上下文菜单显示。
从上下文菜单中插入一个 ImageLabel。

选择新的标签,然后在 Properties 窗口中,
将 AnchorPoint 设置为 0.5, 0.5,将新按钮的原点设在中间(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
将 BackgroundTransparency 设置为 1 以使标签的背景完全透明。
将 Position 设置为 {0.5, 0},{0.5, 0} 以将标签放在其容器中间(从父 ScreenGui 的左侧到右侧 50%,从父 ScreenGui 的顶部到底部 50%)。
将 Size 设置为 {0.48, 0},{0.06, 0} 以使标签变宽(在父 ScreenGui 中的水平 48% 和垂直 6%)。
将 Name 设置为 Block。
将 Image 设置为 rbxassetid://14304827265 以使图像成为一个梯形。
将 ImageColor 设置为 0,0,0 使梯形为黑色。
将 ImageTransparency 设置为 0.3 使标签的透明度减少 30%,并与体验中的所有黑色 UI 元素匹配。

在 Block 中插入一个 UIAspectRatioConstraint 以确保标签及其子 UI 元素的宽高比在玩家的屏幕尺寸下保持不变。
选择新的约束,在 Properties 窗口中,将 AspectRatio 设置为 13.78。
在 Block 中插入一个 TextLabel 以显示信息文本。
选择新的标签,在 Properties 窗口中,
将 AnchorPoint 设置为 0.5, 0.5 将标签的原点设在中间(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
将 BackgroundTransparency 设置为 1以使标签的背景完全透明。
将 Position 设置为 {0.5, 0},{0.5, 0} 以将标签置于其父标签中间(从父标签的左侧到右侧 50%,从父标签的顶部到底部 50%)。
将 Size 设置为 {.85, 0},{0.55, 0} 以便文本能够占据大部分梯形区域(在父标签中的水平 85% 和垂直 55%)。
将 Name 设置为 BodyTextLabel。
将 FontFace 设置为 Montserrat 以符合未来主义美学。
将 Weight 设置为 Bold 以加粗字体。
将 Text 设置为 Respawning…。
将 TextColor3 设置为 255, 255, 255 使文本为白色。
启用 TextScaled。

创建标题。
在 Block 中插入一个 ImageLabel。
选择新的标签,然后在 Properties 窗口中,
将 AnchorPoint 设置为 0.5, 1 将标签的原点设在底部中间(从标签的左侧到右侧 50%,从标签的顶部到底部 100%)。
将 BackgroundTransparency 设置为 1 使标签的背景完全透明。
将 Position 设置为 {0.5, 0},{0, 0} 以将标签放在其父标签的顶部中间(从父标签的左侧到右侧 50%,从父标签的顶部到底部 0%)。
将 Size 设置为 {0.46, 0},{0.56, 0} 以使标签变宽(在父标签中的水平 46% 和垂直 56%)。
将 Name 设置为 Header。
将 Image 设置为 rbxassetid://14304826985 以使图像成为多方向渐变。
将 ImageColor 设置为 245, 46, 46 使渐变为红色,以表明玩家在被击出时暂时处于非活动状态。

在 Header 中插入一个 TextLabel 以显示信息文本。
选择新的标签,然后在 Properties 窗口中,
将 AnchorPoint 设置为 0.5, 0.5 将标签的原点设在中间(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
将 BackgroundTransparency 设置为 1 使标签的背景完全透明。
将 Position 设置为 {0.5, 0},{0.5, 0} 以将标签放在其父标签中间(从父标签的左侧到右侧 50%,从父标签的顶部到底部 50%)。
将 Size 设置为 {.85, 0},{0.55, 0} 以便文本能够占据大部分渐变区域(在父标签中的水平 85% 和垂直 55%)。
将 Name 设置为 HeaderTextLabel。
将 FontFace 设置为 Montserrat 以符合未来主义美学。
将 Weight 设置为 Black 以加粗字体。
将 Text 设置为 TAGGED - YOU'RE OUT!
将 TextColor3 设置为 255, 255, 255 使文本为白色。
启用 TextScaled。

创建屏幕边缘的渐变。
- 在 OutStateGui 中插入一个 ImageLabel。
- 选择新的标签,然后在 Properties 窗口中,
- 将 AnchorPoint 设置为 0.5, 0.5 将标签的原点设在中间(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
- 将 BackgroundColor3 设置为 0,0,0 将标签的背景颜色设置为黑色。
- 将 BackgroundTransparency 设置为 0.5 使标签的背景半透明。
- 将 Position 设置为 {0.5, 0},{0.5, 0} 以将标签放在其容器中间(从父 ScreenGui 的左侧到右侧 50%,从父 ScreenGui 的顶部到底部 50%)。
- 将 Size 设置为 {1, 0},{1, 0} 将标签宽到整个屏幕(在父 ScreenGui 中的水平 100% 和垂直 100%)。
- 将 ZIndex 设置为 -1 以在其他 UI 元素后面显示渐变。
- 将 Name 设置为 Header。
- 将 Image 设置为 rbxassetid://14309518613 使图像成为边框渐变。
- 将 ImageTransparency 设置为 0.1 使渐变稍微半透明。
在样本 Laser Tag 场所文件中引用以下 ReplicatedStorage 脚本,该脚本在玩家的生命值达到零时以编程方式显示重新生成屏幕,并且在重新生成回他们的团队生成区域的过程中显示。
以下 ReplicatedStorage.PlayerStateHandler 客户端脚本包含根据 playerState 属性触发不同类型行为的函数。所有事件响应在此脚本中逻辑上分组在一起,因为它们要求类似的行为来启用或禁用玩家控制、相机移动,以及哪个 UI 层是可见的。
当玩家的生命值达到零时,他们的 playerState 变为 TaggedOut,这会触发 onTaggedOut() 函数。onTaggedOut() 立即触发以下行为:
- 玩家无法在竞技场中移动。
- 玩家无法移动他们的相机。
- 玩家无法使用他们的喷射器。
- StarterGui.OutStateGui 被独占启用。
当玩家重新生成时,他们的 playerState 变为 SelectingBlaster,这会触发 onSelectingBlaster() 函数。onSelectingBlaster() 然后独占启用 StarterGui.PickABlasterGui,这会自动禁用重新生成屏幕。有关这些条件的更多信息,请参见 Handle Client State 游戏脚本课程。
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local PlayerState = require(ReplicatedStorage.PlayerState)
local PlayerAttribute = require(ReplicatedStorage.PlayerAttribute)
local BlasterState = require(ReplicatedStorage.Blaster.BlasterState)
local togglePlayerMovement = require(script.togglePlayerMovement)
local togglePlayerCamera = require(script.togglePlayerCamera)
local scheduleDestroyForceField = require(ReplicatedStorage.scheduleDestroyForceField)
local localPlayer = Players.LocalPlayer
local playerGui = localPlayer.PlayerGui
local guiLayers = {
playerGui:WaitForChild("HUDGui"),
playerGui:WaitForChild("OutStateGui"),
playerGui:WaitForChild("PickABlasterGui"),
}
-- 禁用所有 UI 层,除了给定的例外
local function setGuiExclusivelyEnabled(enabledGui: ScreenGui?)
-- guiLayers 包含应独占设置的 guis 列表。
for _, screenGui in guiLayers do
screenGui.Enabled = screenGui == enabledGui
end
end
local function onSelectingBlaster()
-- 启用相机以便玩家可以在选择喷射器时环顾四周
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
-- 在选择喷射器时禁用喷射器
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
local function onPlaying()
-- 在选择喷射器后启用玩家移动
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
-- 在游戏中启用喷射器
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
-- 当玩家开始游戏时安排销毁力场逻辑
scheduleDestroyForceField()
end
local function onTaggedOut()
-- 被击出时禁用控制
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- 被击出时禁用喷射器
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
local function onInLobby()
-- 在大厅中启用控制
togglePlayerMovement(true)
togglePlayerCamera(true)
-- 在大厅中隐藏所有 HUD
setGuiExclusivelyEnabled(nil)
-- 在大厅中禁用喷射器
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
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
-- 如果设置了初始玩家状态,则处理
local initialPlayerState = localPlayer:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(initialPlayerState)
local function updateFromPlayerState()
onPlayerStateChanged(localPlayer:GetAttribute(PlayerAttribute.playerState))
end
-- 处理未来的玩家状态更新
localPlayer:GetAttributeChangedSignal(PlayerAttribute.playerState):Connect(updateFromPlayerState)
-- 确保在重新生成后仍能应用变更
localPlayer.CharacterAdded:Connect(updateFromPlayerState)
创建 SurfaceGui 对象
要在 3D 空间的零件表面上显示响应每个玩家脚本逻辑的 UI,您可以将 SurfaceGui 对象作为子对象附加到您希望在 ReplicatedStorage 服务中显示 UI 的部分。此技术确保您的 UI 及其脚本逻辑对服务器和每个玩家的客户端都是可用的。
SurfaceGui 对象包含所有在 3D 空间部分表面上显示的 GuiObjects。示例激光标记体验仅包括一个 SurfaceGui 对象实例:显示在每个玩家喷射器上的冷却计。此对象需要为每个玩家编写脚本逻辑,因为它会根据每个玩家的输入积极响应,并提供他们何时可以再次发射喷射器的视觉反馈。
要创建 SurfaceGui 对象:
- 在 Explorer 窗口中,将光标悬停在 ReplicatedStorage 服务上,然后点击 ⊕ 图标。上下文菜单显示。
- 从上下文菜单中插入一个 Part 对象。
- 在部分中插入一个 ScreenGui 对象。
- 根据其子 UI 元素的上下文重命名 SurfaceGui。
- 对于您需要在 3D 空间的部分表面上显示的每个 UI 元素重复此过程。

冷却计
冷却计是一个 UI 组件,用于通知玩家在能够再次发射喷射器之前需要等待多长时间。这稍微的暂停防止玩家能够以他们可以点击或按下按钮的速度闪电般发射,这对激光标记游戏玩法来说并不现实。
为了在样本 Laser Tag 体验中准确重现冷却计:
创建一个部分来容纳您的 SurfaceGui 对象。
- 在 Explorer 窗口中,将光标悬停在 Workspace 中,单击 ⊕ 图标。上下文菜单显示。
- 从上下文菜单中插入一个 block 部件。这是部分的临时位置,以便您可以在每一个步骤的过程中可视化更改。
将部件定位和定向到玩家角色持有喷射器的位置,然后在 Properties 窗口中,
将 Transparency 设置为 1 以使部分完全透明。
将 Name 设置为 CooldownBarPrefab。
将 Size 设置为 0.169, 0.027, 2.537 以将部件缩放到与喷射器长度相当的大小。
禁用 CanCollide 和 CanQuery。

将 SurfaceGui 插入到 CooldownBarPrefab 中。
选择新的 SurfaceGui,然后在 Properties 窗口中,
将 Face 设置为 Top 以便 UI 显示面向上方。
将 LightInfluence 和 MaxDistance 设置为 0。
将 PixelsPerStud 设置为 200。

创建黑色条。
在 SurfaceGui 中插入一个 ImageLabel。
选择新的标签,然后在 Properties 窗口中,
- 删除默认的 Image 值。
- 将 AnchorPoint 设置为 0.5, 0.5 将标签的原点设在中间(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
- 将 BackgroundColor3 设置为 0,0,0 将标签的背景颜色设置为黑色。
- 将 BackgroundTransparency 设置为 0.4 使标签的背景半透明。
- 将 Position 设置为 {0.5, 0},{0.5, 0} 以将标签放在其容器中间(从父 SurfaceGui 的左侧到右侧 50%,从父 SurfaceGui 的顶部到底部 50%)。
- 将 Size 设置为 {1, 0},{1, 0} 以将标签宽到整个部分(在父 SurfaceGui 中的水平 100% 和垂直 100%)。
- 将 Name 设置为 Container。

圆角容器。
在 Container 中插入一个 UICorner 对象。
选择 UICorner,然后在 Properties 窗口中,将 CornerRadius 设置为 0.15, 0 以略微圆角。

创建红色条。
- 在 Container 中插入一个 ImageLabel。
- 选择新的标签,然后在 Properties 窗口中,
- 删除默认的 Image 值。
- 将 AnchorPoint 设置为 1, 0.5 将标签的原点设在右中间(从标签的左侧到右侧 100%,从标签的顶部到底部 50%)。
- 将 BackgroundColor3 设置为 172, 13, 13 将标签的背景颜色设置为深红色。
- 将 BackgroundTransparency 设置为 0.2 使标签的背景略微透明。
- 将 Name 设置为 Bar。
- 将 Position 设置为 {1, 0},{0.5, 0} 以将标签放在其容器的右中间(从父标签的左侧到右侧 100%,从父标签的顶部到底部 50%)。
- 将 Size 设置为 {0, 0},{1, 0} 以将标签长度延伸到其父标签的顶部(在父标签中的水平 0% 和垂直 100%)。这一步对于在步骤 8 中发生的 tweening 行为也有益。
圆角标签。
- 在 Bar 中插入一个 UICorner 对象。
- 选择 UICorner,然后在 Properties 窗口中,将 CornerRadius 设置为 0.15, 0 以略微圆角。
将 CooldownBarPrefab 移动到 ReplicatedStorage。
创建一个文件夹结构以组织您的 UI 对象。示例中使用 Instances 文件夹和子文件夹 Guis。

将 CooldownBarPrefab 移动到 Guis。
在样本 Laser Tag 场所文件中引用以下 ReplicatedStorage 脚本,该脚本以编程方式将冷却计附加到玩家的喷射器,然后在玩家发射喷射器后动画红色条。
以下 ReplicatedStorage.FirstPersonBlasterVisuals 客户端脚本处理玩家第一人称喷射器的所有视觉逻辑。它需要一组协同工作的模块脚本,以设置更真实的喷射器视觉效果,以便于激光标记游戏玩法,包括 FirstPersonBlasterVisuals.addCooldownBar 和 FirstPersonBlasterVisuals.runCooldownBarEffect。
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")
local BlastData = require(ReplicatedStorage.Blaster.BlastData)
local PlayerAttribute = require(ReplicatedStorage.PlayerAttribute)
local PlayerState = require(ReplicatedStorage.PlayerState)
local getBlasterConfig = require(ReplicatedStorage.Blaster.getBlasterConfig)
local runBlastVisuals = require(script.runBlastVisuals)
local setupAnimations = require(script.setupAnimations)
local addCooldownBar = require(script.addCooldownBar)
local runCooldownBarEffect = require(script.runCooldownBarEffect)
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local RIG_OFFSET_FROM_CAMERA = CFrame.new(2, -2, -3) * CFrame.Angles(math.rad(0.25), math.rad(95.25), 0)
local localPlayer = Players.LocalPlayer
local currentCamera = Workspace.CurrentCamera
local rigModel = nil
local cooldownBar = nil
local animations = {}
local function addFirstPersonVisuals()
local blasterConfig = getBlasterConfig()
-- 添加第一人称设备
rigModel = blasterConfig.RigModel:Clone()
rigModel.Parent = Workspace
-- 添加冷却条
cooldownBar = addCooldownBar(rigModel.PrimaryPart.CooldownBarAttachment)
animations = setupAnimations(blasterConfig, rigModel)
end
local function removeFirstPersonVisuals()
for _, animation in animations do
animation:Stop()
animation:Destroy()
animation = nil
end
if rigModel then
-- 这也会销毁冷却条,因为它依附于设备
rigModel:Destroy()
rigModel = nil
end
end
-- 当发生爆炸时运行第一人称视觉效果
laserBlastedBindableEvent.Event:Connect(function(blastData: BlastData.Type)
runBlastVisuals(rigModel.PrimaryPart.TipAttachment, blastData, animations.blastAnimation)
runCooldownBarEffect(cooldownBar)
end)
-- 如果存在,则将设备绑定到相机
RunService.RenderStepped:Connect(function()
if rigModel then
-- 根据相机的位置和 RIG_OFFSET_FROM_CAMERA 更新设备的 CFrame
rigModel:PivotTo(currentCamera.CFrame * RIG_OFFSET_FROM_CAMERA)
end
end)
-- 当玩家在游戏中改变喷射器类型时处理视觉变化
localPlayer:GetAttributeChangedSignal(PlayerAttribute.blasterType):Connect(function()
local playerState = localPlayer:GetAttribute(PlayerAttribute.playerState)
if playerState == PlayerState.Playing then
removeFirstPersonVisuals()
addFirstPersonVisuals()
end
end)
-- 当玩家状态改变时处理视觉变化
localPlayer:GetAttributeChangedSignal(PlayerAttribute.playerState):Connect(function()
local newPlayerState = localPlayer:GetAttribute(PlayerAttribute.playerState)
-- 当玩家选择喷射器或在大厅时移除视觉效果
if newPlayerState == PlayerState.SelectingBlaster or newPlayerState == PlayerState.InLobby then
removeFirstPersonVisuals()
-- 当玩家完成选择喷射器时添加视觉效果
elseif newPlayerState == PlayerState.Playing then
addFirstPersonVisuals()
end
end)
创建 BillboardGui 对象
为了在 3D 空间中显示 UI 元素,这些元素响应脚本逻辑并始终面对每个玩家的相机,无论他们的视角如何,例如玩家姓名或地图标记,您可以创建一个 BillboardGui 对象作为 BasePart 或 Attachment 的子对象,该对象存在于 3D 空间中。
示例激光标记体验包含两个单独的 BillboardGui 对象,在 ReplicatedStorage 服务中:
- OtherPlayerIndicatorGuiPrefab - 在每个玩家的头上显示一个粉色或绿色圆圈,当他们在回合中处于活动状态时。
- TaggedOutIndicatorGuiPrefab - 在玩家被击出回合时显示在他们的头上。
在您创建 BillboardGui 对象后,您可以根据每个容器的目的创建和自定义其子 GuiObjects。为了进行演示,在接下来的部分中,您将学习如何在示例激光标记体验中实现两种指示器类型的 UI 元素。您可以调整该过程的任何部分以满足您自己体验的规格。
要创建 BillboardGui 对象:
- 从上下文菜单中插入一个 BillboardGui 对象。
- 根据其子 UI 元素的上下文重命名 BillboardGui。
- 对于您需要在玩家头上上下文显示的每个 UI 元素重复此过程。
队伍指示器
队伍指示器是一个 UI 元素,通知玩家回合中其他玩家属于哪个队伍,以便他们能够轻松区分其盟友和敌方队员。此信息很重要,因为第一人称射击体验的游戏玩法要求玩家在战斗区域迅速做出战略决策,以确保他们不会被击出并输掉比赛。

为了在样本 Laser Tag 体验中准确重现队伍指示器:
将 BillboardGui 对象插入到临时设备中。
从工具栏的 Home 或 Avatar 选项卡中,点击 Character。
从可用选项中选择。该示例使用 R15 设备类型、女性 体型和 Rthro 头像。该设备在 3D 视口和 Explorer 窗口中显示,名称为 Rig。
在 Explorer 窗口中,导航到设备的子对象 Head 模型,然后点击 ⊕ 图标。上下文菜单显示。
从上下文菜单中插入一个 BillboardGui。

选择新的 BillboardGui,然后在 Properties 窗口中,
将 LightInfluence 设置为 0 以防止环境光影响指示器的颜色。
将 Name 设置为 OtherPlayerIndicatorPrefab。
将 Size 设置为 {0, 10},{0, 10} 使标签显著更小。
将 StudsOffsetWorldSpace 设置为 0, 4, 0 使其位置位于设备头部上方。

向 OtherPlayerIndicatorPrefab 中插入一个 Frame 对象。
选择新的框架,然后在 Properties 窗口中,
将 AnchorPoint 设置为 0.5, 0.5 将框架的原点设在其中心(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
将 BackgroundColor3 设置为 255, 3, 0 将框架的背景颜色设置为红色作为占位符颜色。
将 Position 设置为 {0.5, 0},{0.5, 0} 将框架放在其容器的中间(从父 BillboardGui 的左侧到右侧 50%,从父 BillboardGui 的顶部到底部 50%)。
将 Size 设置为 {1, -2},{1, -2} 使框架缩短到 BillboardGui 的表面区域。

向 Frame 中插入一个 UICorner 对象,以完全圆角。

向 Frame 中插入一个 UIStroke 对象,以描边指示器的圆。

将 OtherPlayerIndicatorPrefab 移动到 ReplicatedStorage。
在样本 Laser Tag 1A 场所文件中引用以下 ReplicatedStorage 脚本,该脚本以编程方式显示所有在活动回合中的玩家的团队指示器,除非他们在敌方团队且被遮挡。
以下 ReplicatedStorage.OtherPlayerIndicatorGuiSetup 脚本在玩家生成到竞技场中以便于活动回合时运行。它通过调用 addIndicatorToCharacter() 函数来附加团队指示器,该函数定位每个参与回合的玩家角色的 Head 对象。如果他们还没有团队指示器,脚本会克隆并将 otherPlayerIndicatorPrefab UI 添加到角色的 Head 中,并将团队指示器的颜色设置为他们的团队颜色。
如果其他玩家在同一队,则团队指示器始终显示,即使他们在 3D 空间中躲在物体后面;如果其他玩家在敌队,则团队指示器仅在 3D 空间中没有物体遮挡他们时显示。
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local localPlayer = Players.LocalPlayer
local otherPlayerIndicatorPrefab = ReplicatedStorage.Instances.Guis.OtherPlayerIndicatorPrefab
local characterSpawnConnectionsByPlayer: { [Player]: RBXScriptConnection } = {}
local playerAddedConnection: RBXScriptConnection?
local function removeIndicatorFromPlayer(player: Player)
if not player.Character then
return
end
local head = player.Character:WaitForChild("Head", 3)
if not head then
return
end
local gui = head:FindFirstChild(otherPlayerIndicatorPrefab.Name)
if gui then
gui:Destroy()
end
end
local function addIndicatorToCharacter(otherCharacter: Model?)
local otherPlayer = Players:GetPlayerFromCharacter(otherCharacter)
if not otherPlayer then
return
end
task.spawn(function()
local otherHead = otherCharacter:WaitForChild("Head", 3)
if not otherHead then
return
end
-- 只有在参与回合的玩家添加指示器
if not otherPlayer.Team then
return
end
-- 避免添加重复的指示器,仅在不存在时创建新的
local gui = otherHead:FindFirstChild(otherPlayerIndicatorPrefab.Name)
if not gui then
gui = otherPlayerIndicatorPrefab:Clone()
gui.Frame.BackgroundColor3 = otherPlayer.TeamColor.Color
gui.Parent = otherHead
end
--指示器仅在玩家友好的情况下始终在顶部显示
local isFriendly = otherPlayer.Team == localPlayer.Team
gui.AlwaysOnTop = isFriendly
end)
end
local function addIndicatorWhenCharacterSpawns(player: Player)
if characterSpawnConnectionsByPlayer[player] then
return
end
local connection = player.CharacterAdded:Connect(addIndicatorToCharacter)
characterSpawnConnectionsByPlayer[player] = connection
end
local function stopSyncingIndicators()
for _, connection in characterSpawnConnectionsByPlayer do
connection:Disconnect()
end
table.clear(characterSpawnConnectionsByPlayer)
if playerAddedConnection then
playerAddedConnection:Disconnect()
playerAddedConnection = nil
end
for _, player in Players:GetPlayers() do
removeIndicatorFromPlayer(player)
end
end
local function addIndicatorToPlayer(player: Player)
if player == localPlayer then
return
end
addIndicatorToCharacter(player.Character)
addIndicatorWhenCharacterSpawns(player)
end
local function startSyncingIndicators()
for _, player in Players:GetPlayers() do
addIndicatorToPlayer(player)
end
if not playerAddedConnection then
playerAddedConnection = Players.PlayerAdded:Connect(addIndicatorToPlayer)
end
end
local function onLocalTeamChanged()
local localTeam = localPlayer.Team
if localTeam then
startSyncingIndicators()
else
stopSyncingIndicators()
end
end
localPlayer:GetPropertyChangedSignal("Team"):Connect(onLocalTeamChanged)
onLocalTeamChanged()
被击出指示器
被击出指示器是一个 UI 元素,通知玩家其他玩家不再活跃于回合,并正处于重新生成回他们的出生区域的过程中。此信息很重要,因为第一人称射击体验的游戏玩法要求玩家在击出一个玩家后立即转向下一个目标,以便他们不会因在同一位置停留太久而变得脆弱。

为了在样本 Laser Tag 体验中准确重现被击出指示器:
将 BillboardGui 对象插入到临时设备中,以便您可以可视化每一步过程中的更改。
从工具栏的 Home 或 Avatar 选项卡中,点击 Character。
从可用选项中选择。该示例使用 R15 设备类型、男性 体型和 Rthro 头像。该设备在 3D 视口和 Explorer 窗口中显示,名称为 Rig。
在 Explorer 窗口中,导航到设备的子对象 Head 模型,然后点击 ⊕ 图标。上下文菜单显示。
从上下文菜单中插入一个 BillboardGui。

选择新的 BillboardGui,然后在 Properties 窗口中,
将 LightInfluence 设置为 0 以防止环境光影响指示器的颜色。
将 Name 设置为 TaggedOutIndicatorGuiPrefab。
将 Size 设置为 {3, 0},{0.5, 0} 以宽化标签以容纳标签。
将 StudsOffset 设置为 0, 3.25, 0 使其位置位于玩家的头部上方。

在 TaggedOutIndicatorGuiPrefab 中插入一个 ImageLabel 对象。
选择新的标签,然后在 Properties 窗口中,
- 将 AnchorPoint 设置为 0.5, 0.5 将标签的原点设在中间(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
- 将 BackgroundTransparency 设置为 1 以使标签的背景完全透明。
- 将 Name 设置为 Frame。
- 将 Position 设置为 {0.5, 0},{0.5, 0} 以将标签放在其容器的中间(从父 BillboardGui 的左侧到右侧 50%,从父 BillboardGui 的顶部到底部 50%)。
- 将 Size 设置为 {1, 0},{1, 0} 以宽化标签到整个 BillboardGui(在父 BillboardGui 中的水平 100% 和垂直 100%)。
- 将 Image 设置为 rbxassetid://14304826985 以使图像成为多方向渐变。
- 将 ImageColor 设置为 245, 46, 46 以使标签变为红色。

在 Frame 中插入一个 TextLabel 对象。
选择新的标签,然后在 Properties 窗口中,
将 AnchorPoint 设置为 0.5, 0.5 将标签的原点设在中间(从标签的左侧到右侧 50%,从标签的顶部到底部 50%)。
将 BackgroundTransparency 设置为 1 以使标签的背景完全透明。
将 Name 设置为 BodyTextLabel。
将 Position 设置为 {0.5, 0},{0.5, 0} 以将标签放在其容器的中间(从父标签的左侧到右侧 50%,从父标签的顶部到底部 50%)。
将 Size 设置为 {0.85, 0},{0.7, 0} 以便文本能够占据大部分渐变区域(在父图像标签中的水平 85% 和垂直 70%)。
将 FontFace 设置为 Montserrat 以符合未来主义美学。
将 Weight 设置为 Bold 以加粗字体。
将 Text 设置为 TAGGED。
将 TextColor3 设置为 255, 255, 255 使文本为白色。
启用 TextScaled。

将 TaggedOutIndicatorGuiPrefab 移动到 ReplicatedStorage。
在样本 Laser Tag 1A 场所文件中引用以下 ServerScriptService 脚本,该脚本以编程方式在玩家重新生成回他们的团队生成区域时显示被击出指示器。
以下 ServerScriptService.SetupHumanoid 服务器脚本在玩家加载体验时立即运行。它确保每当一个玩家的角色被添加到数据模型时,setupHumanoidAsync 被调用并传入他们的 Humanoid。
local Players = game:GetService("Players")
local setupHumanoidAsync = require(script.setupHumanoidAsync)
local function onCharacterAdded(player: Player, character: Model)
local humanoid = character:WaitForChild("Humanoid")
setupHumanoidAsync(player, humanoid)
end
local function onPlayerAdded(player: Player)
-- 如果玩家已经有角色,则调用 onCharacterAdded
if player.Character then
onCharacterAdded(player, player.Character)
end
-- 调用 onCharacterAdded 以处理该玩家的所有未来角色生成
player.CharacterAdded:Connect(function(character: Model)
onCharacterAdded(player, character)
end)
end
-- 处理游戏内已经存在的任何玩家
for _, player in Players:GetPlayers() do
onPlayerAdded(player)
end
-- 处理所有未来的玩家
Players.PlayerAdded:Connect(onPlayerAdded)
恭喜您完成了用户界面设计课程!现在您已经掌握了创建艺术风格、线框布局设计和从头到尾在 Studio 中实现设计的经验,您可以通过新的 UI 和功能扩展项目,或遵循其他教程课程,例如 Gameplay Scripting Curriculum,该课程教您关于示例激光标记体验的一般组织和关键实施细节。祝您创作愉快!