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

使用 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 在其父容器中,無論是水平行還是垂直列,包括應用 flex 概念的能力。

為新的 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代碼解釋
滑塊控制器
另一個模塊初始化並控制兩個音量滑塊。它還允許你將回調函數連接到每個滑塊,以檢測玩家與滑塊的互動並在遊戲中應用所需的更改。
在 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)-- Set initial valueself:setValue(self.value)-- Connect detector to player manipulationself.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-- Update the handle position and inner frame size to matchself.handle.Position = UDim2.fromScale(clampedValue, 0.5)self.innerFill.Size = UDim2.fromScale(clampedValue, 1)-- Run the user's callback with the latest valuelocal changeSuccess, changeResult = pcall(self.onChanged, clampedValue)if not changeSuccess thenwarn("Error in slider callback:", 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-- Initialize settings buttonlocal 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"})-- Initialize settings menu framelocal 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"})-- Initialize effects volume sliderlocal 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,})-- Initialize background volume sliderlocal 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,})-- Connect buttons to player interactionHUDContainer: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() 函數進行初始化,並將 HUDContainer 中的 SettingsButton 按鈕引用為要初始化的對象。 12‑25 傳遞一個 states 表,包含兩個唯一狀態,menuOpen 和 menuClosed。每個狀態都包含一個過渡,通過 TweenInfo 聲明特定狀態將如何達到在 properties 表中定義的目標屬性。在這種情況下,menuOpen 聲明按鈕的 Rotation 的狀態變更為 45°,並使用 Exponential 的半秒動畫,而 menuClosed 實際上將按鈕恢復到其默認旋轉 0。 26 按鈕的 初始 狀態設置為 menuClosed(這可以設置為 states 表中的任何狀態名稱)。 在腳本的下方(行 76-86),按鈕連接到玩家互動以觸發 UI 狀態變更:
行數 目的 76‑82 SettingsButton 連接到 Activated 事件的 GuiButton 類,以調用匿名函數。在匿名函數內,行 77‑79 在 menuOpen 和 menuClosed 之間切換 targetState 變量。行 80 然後調用 setState() 將按鈕的狀態設置為 targetState,行 81 對 SettingsMenu 做同樣的事情,以便它隨著 SettingsButton 的激活而切換開啟或關閉。 83‑86 CloseButton 連接到 Activated 事件以調用匿名函數,該函數將 SettingsButton 和 SettingsMenu 的狀態設置為 menuClosed。 設置菜單行 30-49 初始化並附加行為到 設置菜單,具體如下:
行數 目的 30‑31 聲明一個本地引用 settingsMenu,並使用 StatefulObjectController.hydrate() 函數進行初始化,並將 HUDContainer 中的 SettingsMenu 框架引用為要初始化的對象。 32‑46 與設置按鈕類似,傳遞一個 states 表,包含兩個唯一狀態,menuOpen 和 menuClosed。在這裡,menuOpen 聲明框架的 Position 的狀態變更為 UDim2.fromScale(0.5, 0.5),並使用 Bounce 的半秒動畫,還有 Visible 狀態為 true。相反,menuClosed 聲明位置狀態變更為 UDim2.fromScale(0.5, 0.4),並將 Visible 設置為 false,但行 41 的 TweenInfo 以 0 持續時間有效地使狀態變更立即發生。 48 框架的初始狀態設置為 menuClosed。 在腳本的下方(行 76-86),設置菜單與玩家互動相連:
行數 目的 81 為 SettingsMenu 設置相同的 targetState,以便與 SettingsButton 的激活同步。 85 當 CloseButton 被激活時,將 SettingsMenu 的狀態設置為 menuClosed。 音量滑塊行 51-73 初始化並附加行為到 EffectsVolumeSlider 和 BackgroundVolumeSlider,具體如下:
行數 目的 52 將本地引用設置為 Effects 的 SoundGroup 在 SoundService 中。SoundGroup 讓你可以相互關聯多個 Sounds,並通過該組的 Volume 屬性控制組中每個聲音的音量。 53‑54 聲明一個本地引用 effectsVolumeSlider,並使用 SliderController.hydrate() 函數進行初始化,並將 HUDContainer 中的 EffectsVolumeSlider 框架 ⟩ SettingsMenu 引用為要初始化的對象。請注意,FindFirstChild() 的第二個參數 true 告訴該方法在 HUDContainer 分支中遞歸查找以定位 EffectsVolumeSlider。 55 滑塊的初始值設置為當前 Effects 聲音組的 Volume,或 0.5 作為後備。 56‑60 建立回調函數以將滑塊值更改應用於 Effects 聲音組的音量。 64‑73 基本上與行 52-61 相同,但用於初始化 HUDContainer 中的 BackgroundVolumeSlider 框架 ⟩ SettingsMenu,並將滑塊值更改應用於 Background 聲音組的音量。
隨著 SettingsScript 的到位,遊戲現在提供了一個完全運行的互動式 UI 示例,將一組相關的 UI 對象與玩家互動鏈接在一起。























