一個 HUD (Heads-Up Display) 在您的體驗中通常顯示非互動式 UI 元素,例如在 創建 HUD 儀表 中演示的健康計量器。在此基礎上,幾乎每個體驗都需要 互動式 UI,例如響應玩家激活的按鈕、在激活時進行動畫、與其他互動控制一起進行 tween 進出菜單等。

使用 UI 基礎知識 - HUD 儀表 作為起點,並以 UI 基礎知識 - 互動性 作為完成的參考,本教程演示:
- 如何在屏幕頂部邊緣定位設置按鈕。
- 包含可互動可拖動滑塊的設置菜單的設計。
- 使用 ModuleScripts 形成 "控制器" 模組,以便對具有狀態聲明的 UI 對象進行可擴展控制。
- 如何將按鈕連接到玩家激活,以切換設置菜單的顯示和隱藏。
- 如何將可拖動的 UI 滑塊連接到音效和背景氛圍的音量調整,分別進行調整。
啟用裝置模擬器
如 創建 HUD 儀表 中所述,手機和平板電腦擁有最少的屏幕空間,因此確保您的 UI 元素適應較小屏幕並且對玩家清晰可見是很重要的。如果您還沒有這樣做,請在 Studio 中啟用 裝置模擬器:
在 Studio 中打開 UI 基礎知識 - HUD 儀表 模板。
在 Studio 的 測試 菜單中,開啟 裝置模擬器。
在主視口正上方的工具欄中,選擇手機模擬例如 iPhone X 或 Samsung Galaxy A51。然後設置視圖大小為 適合窗口,以利用 Studio 中的最大空間。

創建設置按鈕
GuiButtons 是具有內建功能的互動用戶界面元素,例如在按下或點擊時觸發的跨平台 Activated 事件。GuiButton 類擴展為兩種變體,TextButton 和 ImageButton,本教程使用 "齒輪" 形狀的 ImageButton 來切換設置菜單的開啟或關閉。

要構建設置按鈕:
在 Explorer 窗口中,找到 StarterGui 中的 HUDContainer。

選擇新按鈕,並在 屬性 窗口中設置以下屬性:
- AnchorPoint = 0.5, 0.25(水平居中,垂直上四分之一)
- BackgroundTransparency = 1(完全透明)
- Position = 0.5, 0, 0, 0(頂部中心定位)
- Size = 0.1, 0, 0.1, 0(佔屏幕高度的 10% 一旦比例被約束)
- Image = rbxassetid://104919049969988("齒輪" 符號的資源 ID)
在按鈕中插入一個 UIAspectRatioConstraint,並使用默認屬性(1:1 寬高比)。

創建設置菜單
新的 設置按鈕 將被編寫為可以切換設置菜單的開啟和關閉,為玩家提供隨需訪問設置或其他信息的功能。在本教程中,菜單將包含可互動的滑塊,以獨立調整背景音頻和音效的音量級別。

創建父框架
如 創建 HUD 儀表 中所述,Frame 作為其他 UI 對象的容器。整個設置菜單將用一個父框架構建,使其簡單地作為狀態對象進行管理,根據當前狀態以不同方式響應輸入。
在 HUDContainer 中插入 Frame 並將其重命名為 SettingsMenu。

選擇 SettingsMenu 並設置以下屬性:
- AnchorPoint = 0.5(中心錨點)
- BackgroundColor3 = 30, 30, 60(海軍藍)
- BackgroundTransparency = 0.25(75% 不透明)
- Position = 0.5, 0, 0.5, 0(屏幕中央)
- Size = 0.75, 0, 0.75, 0(插入屏幕區域寬度/高度的 75%)

在 SettingsMenu 中插入一個 UIAspectRatioConstraint,並將其 AspectRatio 屬性設置為 2.5。(2.5:1 寬高比)。


在 SettingsMenu 中插入一個 UICorner 修飾符,並將其 CornerRadius 設置為 0.1, 0。


構建滑塊
為了讓玩家調整音量級別,設置菜單將包含兩個可拖動的 滑塊小部件,由 UIDragDetector 提供支持,這是一個方便的對象,可促進與 2D 用戶界面元素的交互。
要為第一個滑塊創建父容器:
在 SettingsMenu 容器中插入一個新的 Frame 並將其重命名為 EffectsVolumeSlider。

選擇 EffectsVolumeSlider 框架,並設置以下屬性:
- AnchorPoint = 0.5(中心錨點)
- BackgroundTransparency = 1(完全透明)
- Position = 0.5, 0, 0.35, 0(水平居中,距容器上方的 35%)
- Size = 0.8, 0, 0.1, 0(佔容器寬度的 80% 和高度的 10%)

在 EffectsVolumeSlider 中插入一個新的 UIListLayout。這個佈局修飾符是自動排列兄弟 GuiObjects 進入其父容器中的橫向行或縱向列的一種強大方式,包括應用 彈性佈局 的能力。

為新的 UIListLayout 設置以下屬性:
- Padding = 0.06, 0(列表元素之間的 6% 填充)
- FillDirection = Horizontal(元素的左到右排列)
- HorizontalFlex = Fill(水平調整元素大小以適應整個父容器,覆蓋它們定義的寬度)
- VerticalAlignment = Center(沿其垂直中心對齊元素)
滑塊圖標
一個簡單的圖標,包括音頻音符和 "爆炸" 符號,幫助玩家識別滑塊在打開設置菜單時的用途。

選擇 Icon,並設置以下屬性:
- BackgroundTransparency = 1(完全透明)
- Size = 2.5, 0, 2.5, 0(高度是父框架的 250% 一旦比例被約束)
- Image = rbxassetid://90019827067389(音頻音符和 "爆炸" 符號的資源 ID)
範圍框架
在 圖標 的右側,滑塊的互動部分應該包含在另一個 Frame 中。

在 EffectsVolumeSlider 框架中插入一個新的 Frame 並將其重命名為 SliderFrame。請注意,它必須是 UIListLayout 佈局修飾符的直接 兄弟。

選擇 SliderFrame 並設置以下屬性:
- BackgroundColor3 = 0(黑色)
- BackgroundTransparency = 0.75(25% 不透明)
- LayoutOrder = 1
- Size = 1, 0, 1, 0(父框架的 100% 寬度/高度)
在 SliderFrame 中插入以下修飾符:

互動手柄
構建好 滑塊容器 後,您現在可以為玩家創建一個 可拖動的手柄 以便在遊玩期間進行互動。

在 SliderFrame 容器中插入一個新的 Frame 並將其重命名為 Handle。

選擇 Handle,並設置以下屬性:
- AnchorPoint = 0.5(中心錨點)
- Position = 0.5, 0, 0.5, 0(父框架的水平和垂直中心)
- Size = 1.2, 0, 1.2, 0(父框架的高度的 120% 一旦比例被約束)
- ZIndex = 3(將手柄視覺上層疊在其他滑塊元素前面)
在 Handle 中插入以下修飾符:
- UIAspectRatioConstraint,其默認屬性(1:1 寬高比)
在 Handle 中插入一個 UIDragDetector 對象。這個方便的對象可促進並鼓勵與 2D 用戶界面元素的交互。

為新的 UIDragDetector 設置以下屬性:
- ResponseStyle = Scale(通過檢測器父物體的位置的縮放值進行移動)
為了確保手柄的線性拖動範圍限於其容器,將其 BoundingUI 屬性鏈接到 SliderFrame 容器:
- 選擇 UIDragDetector。
- 在 屬性 窗口中單擊其 BoundingUI 屬性。
- 返回到 Explorer 窗口,單擊手柄的父級 SliderFrame。

現在 BoundingUI 屬性鏈接反映 SliderFrame 容器:

如果您現在進行遊戲測試,您將能夠在其父容器內左右拖動手柄:
內部填充
為了更清晰地指示滑塊控制 0% 到 100% 的範圍,您可以在容器的左側添加一個 內部填充,與手柄的變化位置同步。

在 SliderFrame 容器中插入一個新的 Frame 並將其重命名為 InnerFill。

為 InnerFill 框架設置以下屬性:
- AnchorPoint = 0, 0.5(左邊緣和垂直中心)
- BackgroundColor3 = [0, 150, 255](與滑塊的手柄顏色匹配的藍色)
- BackgroundTransparency = 0.35(65% 不透明)
- Position = 0, 0, 0.5, 0
- Size = 0.5, 0, 1, 0(父框架的 50% 寬度和 100% 高度)
- ZIndex = 2(在父框架的填充/輪廓前的層)
複製滑塊
在構建好 第一個滑塊 後,您可以輕鬆地複製它並修改一些視覺方面以指示另一種用途,在這種情況下即是由音樂音符圖標代表的背景音頻的音量級別。

選擇完成的 EffectsVolumeSlider 物件。

複製它 (CtrlD 或 ⌘D),然後將複製品重命名為 BackgroundVolumeSlider。

將複製品的 Position 更改為 0.5, 0, 0.7, 0,以將其移動到第一個滑塊的下方。

展開 BackgroundVolumeSlider 的頂層分支,選擇 Icon 圖像標籤,並將其 Image 屬性更改為 rbxassetid://101125859760167(音樂音符符號的資源 ID)。



創建關閉按鈕
設置菜單的最後一個元素是 關閉按鈕,為玩家提供另一個輸入來關閉菜單(位於頂部中心的 SettingsButton 也將執行相同的功能)。

選擇 CloseButton,並設置以下屬性:
- AnchorPoint = 1, 0(右上角)
- BackgroundTransparency = 1(完全透明)
- Position = 1, -10, 0, 10(距離右上角 10 像素內縮)
- Size = 0.15, 0, 0.15, 0(佔框架高度的 15% 一旦比例被約束)
- Image = rbxassetid://5577404210(關閉按鈕符號的資源 ID)
- ImageTransparency = 0.25(75% 不透明)
創建控制模塊
可擴展的 控制模塊 設置使互動式 UI 管理比在每個對象中放置單獨腳本更簡化。ModuleScripts 通過允許您在客戶端服務器邊界的不同側或相同側的腳本之間重用代碼來促進這一可擴展功能。
有狀態對象控制器
以下有狀態對象控制器模塊讓您將行為附加到 UI 對象,例如 SettingsButton 和 SettingsMenu,並輕鬆切換/淡入淡出各種狀態。要創建該模組:
在 ReplicatedStorage 容器中插入一個 ModuleScript,並將其重命名為 StatefulObjectController。

將以下代碼粘貼到模組中:
StatefulObjectControllerlocal TweenService = game:GetService("TweenService")local StatefulObjectController = {}StatefulObjectController.__index = StatefulObjectControllerexport type StateName = stringexport type State = {transition: TweenInfo,properties: { [string]: any },}function StatefulObjectController.hydrate(props: {object: Instance,states: { [StateName]: State },initialStateName: StateName})local object, states, initialStateName = props.object, props.states, props.initialStateNamelocal self = setmetatable({states = states,currentStateName = initialStateName,tweens = {},}, StatefulObjectController)-- Create tweens for reuse to avoid making new tweens every time state is changedfor stateName, state in states doself.tweens[stateName] = TweenService:Create(object, state.transition, state.properties)endself:setState(self.currentStateName)return selfendfunction StatefulObjectController:setState(stateName: StateName)local stateTween: Tween = self.tweens[stateName]if not stateTween thenwarn(string.format("Attempted to set %s to unknown state '%s'", self.object:GetFullName(), stateName))returnendself.currentStateName = stateName-- Make sure other tweens aren't conflictingfor _, tween in self.tweens dotween:Cancel()endstateTween:Play()endreturn StatefulObjectController代碼解釋行數 目的 6‑10 定義並導出了 Luau 類型,以改善使用此模組時的自動完成和檢查。 12‑33 用於初始化並將行為附加到有狀態 UI 對象的函數,接受 GuiObject 作為 object,一個狀態及其各自 TweenInfo 數據的表,以及初始狀態名稱。在此函數內第 26‑28 行為每個狀態構建 Tweens,第 30 行調用模組的 setState() 函數來設置對象為初始狀態。 35‑50 用於設置/更改有狀態對象的狀態的函數;第 42 行設置對象的 currentStateName 為正在設置的狀態,第 45‑47 行取消任何運行中的 tweens 以防止衝突,而第 49 行執行該狀態的 tween。
滑塊控制器
另一個模組初始化並控制這兩個音量滑塊。它還允許您將回調函數連接到每個滑塊,以便檢測玩家對滑塊的互動並在體驗中應用所需的變化。
在 ReplicatedStorage 容器中插入一個 ModuleScript,並將其重命名為 SliderController。

將以下代碼粘貼到模組中:
SliderControllerlocal SliderController = {}SliderController.__index = SliderControllerexport type Value = numberexport type OnChanged = (Value) -> ()function SliderController.hydrate(props: {object: Instance,onChanged: OnChanged,initialValue: Value?})local object, onChanged, initialValue = props.object, props.onChanged, props.initialValuelocal handle = object:FindFirstChild("Handle", true)if not handle thenwarn(string.format("Attempted to hydrate slider %s but couldn't find Handle", object:GetFullName()))endlocal innerFill = object:FindFirstChild("InnerFill", true)if not innerFill thenwarn(string.format("Attempted to hydrate slider %s but couldn't find InnerFill", object:GetFullName()))endlocal dragDetector = handle:FindFirstChildWhichIsA("UIDragDetector")if not dragDetector thenwarn(string.format("Attempted to hydrate slider %s but couldn't find UIDragDetector", object:GetFullName()))endlocal self = setmetatable({handle = handle,innerFill = innerFill,dragDetector = dragDetector,value = initialValue or 0.5,onChanged = onChanged,}, SliderController)-- 設定初始值self:setValue(self.value)-- 連接檢測器到玩家操作self.dragConnection = dragDetector.DragContinue:Connect(function()self:setValue(handle.Position.X.Scale)end)return selfendfunction SliderController:setValue(value: Value)local clampedValue = math.clamp(value, 0, 1)self.value = clampedValue-- 更新手柄位置和內部框架大小以匹配self.handle.Position = UDim2.fromScale(clampedValue, 0.5)self.innerFill.Size = UDim2.fromScale(clampedValue, 1)-- 在最新值上運行用戶回調local changeSuccess, changeResult = pcall(self.onChanged, clampedValue)if not changeSuccess thenwarn("滑塊回調中的錯誤:", changeResult)endendreturn SliderController代碼解釋行數 目的 7‑46 用於初始化並為滑塊元素附加行為的函數,接受滑塊的父 Frame 作為 object,一個 onChanged 回調函數以處理滑塊變更,以及滑塊手柄位置的初始值。在函數內,第 14‑27 行確認滑塊的 手柄、內部填充、和 UIDragDetector 元素存在。第 38 行調用模組的 setValue() 函數,第 41‑43 行連接拖動檢測器的 DragContinue 事件到模組的 setValue() 函數(有關詳細內容,請參見下一行)。 48‑61 用於設置滑塊的值的函數。在函數內,第 53‑54 行確保手柄的位置和內部填充的大小同步,而第 57 行將更改的值傳遞給回調函數,以便您可以根據需要利用該值。
創建設置腳本
隨著 設置按鈕 和 設置菜單 的完成,您可以通過一個腳本將所有內容連接在一起,該腳本利用了 控制模塊。
在 HUDContainer 中插入一個新的 LocalScript,並將其重命名為 SettingsScript 以描述其目的。請注意,該腳本應該與 SettingsMenu 和 SettingsButton 以同級的方式(兄弟)設置,這些是它將管理的頂層 UI 對象。

將以下代碼粘貼到腳本中:
SettingsScriptlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local SoundService = game:GetService("SoundService")local SliderController = require(ReplicatedStorage.SliderController)local StatefulObjectController = require(ReplicatedStorage.StatefulObjectController)local HUDContainer = script.Parent-- 初始化設置按鈕local settingsButton = StatefulObjectController.hydrate({object = HUDContainer:FindFirstChild("SettingsButton"),states = {menuOpen = {transition = TweenInfo.new(0.5, Enum.EasingStyle.Exponential, Enum.EasingDirection.Out),properties = {Rotation = 45,},},menuClosed = {transition = TweenInfo.new(0.5, Enum.EasingStyle.Exponential, Enum.EasingDirection.Out),properties = {Rotation = 0,},},},initialStateName = "menuClosed"})-- 初始化設置菜單框架local settingsMenu = StatefulObjectController.hydrate({object = HUDContainer:FindFirstChild("SettingsMenu"),states = {menuOpen = {transition = TweenInfo.new(0.5, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out),properties = {Position = UDim2.fromScale(0.5, 0.5),Visible = true,},},menuClosed = {transition = TweenInfo.new(0),properties = {Position = UDim2.fromScale(0.5, 0.4),Visible = false,},},},initialStateName = "menuClosed"})-- 初始化效果音量滑塊local effectsAudio = SoundService:FindFirstChild("Effects")local effectsVolumeSlider = SliderController.hydrate({object = HUDContainer:FindFirstChild("EffectsVolumeSlider", true),initialValue = effectsAudio and effectsAudio.Volume or 0.5,onChanged = function(value: SliderController.Value)if effectsAudio theneffectsAudio.Volume = valueendend,})-- 初始化背景音量滑塊local backgroundAudio = SoundService:FindFirstChild("Background")local backgroundVolumeSlider = SliderController.hydrate({object = HUDContainer:FindFirstChild("BackgroundVolumeSlider", true),initialValue = backgroundAudio and backgroundAudio.Volume or 0.5,onChanged = function(value: SliderController.Value)if backgroundAudio thenbackgroundAudio.Volume = valueendend,})-- 將按鈕連接到玩家操作HUDContainer:FindFirstChild("SettingsButton").Activated:Connect(function()local targetState = if settingsButton.currentStateName == "menuClosed"then "menuOpen"else "menuClosed"settingsButton:setState(targetState)settingsMenu:setState(targetState)end)HUDContainer:FindFirstChild("CloseButton", true).Activated:Connect(function()settingsButton:setState("menuClosed")settingsMenu:setState("menuClosed")end)參考以下部分以探索腳本如何利用 控制模組。
設置按鈕行數 10-27 初始化並附加行為到 設置按鈕 具體如下:
行數 目的 10‑11 宣告了一個本地引用 settingsButton,並用 StatefulObjectController.hydrate() 函數進行初始化,並將 SettingsButton 按鈕作為初始化的對象。 12‑25 傳遞了一個 states 表,該表具有兩個唯一狀態 menuOpen 和 menuClosed。每個狀態包含一個過渡來聲明(通過 TweenInfo)特定狀態將如何達到 properties 表中定義的目標屬性。在這種情況下,menuOpen 聲明將按鈕的 Rotation 狀態變更為 45°,並應用 0.5 秒的 Exponential 畫面過渡,而 menuClosed 有效地將按鈕恢復為其默認的 0 旋轉狀態。 26 按鈕的 初始 狀態設置為 menuClosed(這可以設置為任何名稱表中的狀態)。 腳本稍後的部分(行數 76-86)中,按鈕連接到玩家互動以觸發 UI 狀態變更:
行數 目的 76‑82 SettingsButton 連接到 Activated 事件以調用匿名函數。在匿名函數內,第 77‑79 行在 menuOpen 和 menuClosed 之間切換 targetState 變數。第 80 行然後調用 setState() 將按鈕的狀態設置為 targetState,第 81 行對 SettingsMenu 執行相同操作,以便它在去激活 SettingsButton 時切換開啟或關閉。 83‑86 當 CloseButton 被激活時,將 SettingsButton 和 SettingsMenu 的狀態設置為 menuClosed。 設置菜單行數 30-49 初始化並附加行為到 設置菜單 具體如下:
行數 目的 30‑31 宣告了一個本地引用 settingsMenu,並用 StatefulObjectController.hydrate() 函數進行初始化,並將 SettingsMenu 框架作為初始化的對象。 32‑46 與設置按鈕類似,傳遞了一個 states 表,該表具有兩個唯一狀態,menuOpen 和 menuClosed。這裡,menuOpen 聲明框架的 Position 狀態變更為 UDim2.fromScale(0.5, 0.5),並應用 0.5 秒的 Bounce 畫面過渡,此外,將框架的 Visible 狀態設為 true。相比之下,menuClosed 聲明位置狀態變更為 UDim2.fromScale(0.5, 0.4),並將 Visible 設置為 false,但第 41 行的 TweenInfo 定義為 0 的持續時間將使狀態變更瞬間發生。 48 設置框架的初始狀態為 menuClosed。 腳本稍後部分(行數 76-86)連接了設置菜單以響應玩家交互:
行數 目的 81 將與 SettingsButton 的啟用同步的相同 targetState 設置為 SettingsMenu。 85 當 CloseButton 被激活時,將 SettingsMenu 的狀態設置為 menuClosed。 音量滑塊行數 51-73 初始化並附加行為到 EffectsVolumeSlider 和 BackgroundVolumeSlider 具體如下:
行數 目的 52 將本地引用設置為 Effects 的 SoundGroup,該組在 SoundService 中。 SoundGroup 允許您相互關聯多個 Sounds,並通過該組的 Volume 屬性控制該組中每個聲音的音量。 53‑54 宣告了一個本地引用 effectsVolumeSlider,並用 SliderController.hydrate() 函數進行初始化,並將 EffectsVolumeSlider 框架作為初始化的對象。請注意,對於 FindFirstChild() 的第二個參數設置為 true,告知方法在 HUDContainer 分支中向下遞歸搜尋以找到 EffectsVolumeSlider。 55 滑塊的初始值設置為當前的 Volume 的 Effects 聲音組,或作為回退的 0.5。 56‑60 設置回調函數以將滑塊值變更應用於 Effects 聲音組的音量。 64‑73 與第 52‑61 行基本相同,除了初始化 BackgroundVolumeSlider 框架於 HUDContainer ⟩ SettingsMenu,並對 Background 聲音組的音量應用滑塊值變更。 隨著 SettingsScript 的到位,此體驗現在提供了一個完整操作的互動式 UI 示例,將一組相關的 UI 對象鏈接到玩家互動。























