我們使用以下系統來支援 基礎遊戲系統 和任何 主要設計要求 的目標。
UseManager
UseManager 提供了一個簡單的 API,將一個抓取的物件應用到某個物體上,例如將一件分層衣物放到一個模特兒上。此 API 的主要功能是 UseManager.AddUse (tags, targetObjects, distance, onSuccess, onNothingEquipped, onWrongEquipped, extraData),它將一組標籤綁定到 targetObjects。當玩家擁有一個帶有標籤的物件並單擊目標物體時,onSuccess 回調函數將被調用。其他調用函數允許我們在玩家未抓取物品或抓取錯誤類型物品時顯示額外的視覺信息。
我們可以使用 UseManager.RemoveUse 來移除“使用”,這通常在任務完成或特定物品被“使用”時很有幫助。此外,我們還可以使用 AddUseTargets 和 RemoveUseTargets 來添加或移除目標。
Highlights
當玩家靠近一個感興趣的物品時,例如印章,我們希望該物品能夠從周圍環境中脫穎而出。為了實現這一點,我們創建了一個名為 HighlightItems 的 LocalScript,它使用圍繞玩家的球體來檢測與其他網格的接觸,連接到 Touched 和 TouchEnded 事件。getHighlight 函數會使用 GetTaggedObjectUpHierarchy 助手函數檢查觸摸的網格或其父級上的多個標籤。如果不需要任何高亮,我們可以強行通過使用 NoHighlight 標籤來移除它。但是,如果需要但與各種其他標籤不太匹配,我們可以使用 Important 標籤來強制它。
這個 LocalScript 利用一個新的引擎功能 Highlight,它可以繪製物體的輪廓和/或用定義的顏色填充物體的內部;有關如何使用此功能的更多信息,請參閱 Highlighting Objects。Highlights 和鼠標光標 OnItemIndicator 系統共同工作,因此 Highlights 不僅決定網格是否需要高亮,還提供了一種類型的網格給 OnItemIndicator。
HighlightItemsFunc 用於與其他客戶端系統進行通訊。例如, EventManager 使用它與 Enable 命令以啟用或禁用在某些過場動畫中的 Highlight,而 OnItemIndicator 則使用 GetType 來詢問物體的類型。為了檢測某個物品何時不再存在,例如當一個損壞的房間被摧毀時,我們連接到 CollectionService.GetInstanceRemovedSignal。
Lore 和 ThoughtBubbles
Lore 和 ThoughtBubbles 是兩個相似的系統。Lore 使用 ScreenGui 作為螢幕上的 UI 容器,並具有一個子 Frame 來控制其子 TextLabels 和 ImageLabels 的大小和重新縮放,當玩家在螢幕上單擊任意位置時,Lore 會自動移除。同樣,思維氣泡使用 BillboardGui 及其子 TextLabel 來顯示對非 Lore 對象的對話,並在指定的時間和冷卻時間內在 3D 空間中顯示對話,而不會佔用整個螢幕。關於這些系統背後的設計,請參見 Lore 和 Thought Bubbles。
Lore 是在 LoreManger 的 LocalScript 中實現的。當被點擊或觸摸時,它會使用助手函數 utils.RaycastAlongPointingDir 發射一個射線,並使用 NoPlayerCollision 組。若某網格或其父級下的點擊點具有 Lore 或 ThoughtBubble 標籤,則顯示 UI。文本、標題和圖像由物體上的 LoreText、LoreCaption 和 LoreImage 屬性定義。
請注意,我們使用 Camera.ViewportPointToRay 或 Camera.ScreenPointToRay 來構造射線,具體取決於是否是從非觸控或觸控中調用。這些坐標位於略有不同的坐標系中。對於鼠標,我們從 UserInputService.InputEnded:Connect中獲取鼠標按鈕 1 的坐標,對於觸控設備,我們從Class.UserInputService.TouchTapInWorld:Connect 中獲取坐標。
ThoughtBubbles 的整體工作原理相似,使用射線檢查網格或其父級是否具有 ThoughtBubble 標籤。它還使用 ThoughtText 屬性來顯示文本,並使用 ThoughtBubble 標籤指向用於在世界中定位 UI 的佔位符物體。使用相同定位物體但具有不同文本的思維氣泡會有不同的冷卻時間。
特殊情況
Lore 有幾個特殊情況,其中之一是損壞的印章。當玩家點擊損壞的印章時,會顯示 Lore UI,並等待點擊以開始任務,這將影響遊戲流程。這是通過使用可綁定的 LoreManagerFunc 來要求 Lore UI 的 GameStateClient 來處理的。GameStateClient 提供了一個回調給 Lore 系統,以知道何時 Lore 被玩家“關閉”。另一個特殊情況是當 ThoughtBubbles 和 Lore 標籤在同一物體上時。此時,為了避免 Lore 和思維氣泡文本重疊,我們在 Lore 關閉後運行思維氣泡。
LoreManager 也處理了一個特殊情況,即在點擊禁用的門時顯示小過場動畫,這些門在玩家拾取房間的印章之前是鎖定的。
OnItemIndicator
我們希望在玩家查看某些感興趣的物品時,在螢幕中央顯示不同的圖標。客戶端腳本 OnItemindicator 沿著相機的 Class.CFrame.LookVector 進行射線檢測並分析結果。根據結果,它在 OnItemIndicator2 ScreenGui 中設置一個圖像。
當沒有感興趣的物品被擊中時,默認圖標是一個小點。我們可以通過為特定網格添加 OnItemIndicator 字串屬性來設置任何圖標,使用來自 onItemIndicatorImages 的名稱,例如手、眼睛或門當前鎖定。該屬性僅在少數情況下需要,大多數時候其他現有標籤或系統提供圖標類型。更多細節請參見 Update 函數。
在優先順序中進行類型檢查。在 OnItemIndicator 覆蓋後,我們檢查它是否是可抓取的或用於 "手" 圖標的抽屜,通過 utils.CanGrabModel(model) 或 utils.GetTaggedObjectUpHierarchy("Drawer2", model)。之後,我們調用 HighlightManager 來確定高亮狀態、物品類型以及要使用的圖標。例如:
highlightItemsFunc:Invoke({"GetType", curInst})
Lore 和 ThoughtBubble 標籤稍後會通過檢查標籤進行檢查。對於門,我們有兩個不同的圖標:DoorCurrentlyClosed 和 DoorAlwaysLocked。DoorManager 為可以打開或關閉的門設置一個 true 或 false 的 DoorEnabled 屬性,並使用該屬性的存在和價值。看起來像門但不會打開的物體具有 DoorLocked 標籤。
DoorManager
DoorManager LocalScript 使用 Door 標籤和 CollectionService 來管理門的開啟和關閉。門具有前後側觸發器,通過觸控和 touchEnded 事件連接。 我們創建了動畫來從前後側開關門。我們維護一個 playersNear 地圖(接觸觸發器的玩家,前後側分開)。
每扇門都有一個簡單的狀態系統 DoorState(閉合、開啟、開啟中、關閉中),使用動畫進行過渡。我們可以通過調用 DoorManager.EnableDoor 來啟用或禁用門的開關能力,這樣會設置 DoorEnabled 屬性。
MasterAnimator
MasterAnimator LocalScript 播放動畫圖像(紋理圖集),我們用於動畫 TV 螢幕。要滾動圖像,我們需要知道一組參數:行和列計數、總幀數、每個周期、圖像尺寸以及一組圖像 ID。該系統允許我們在多個圖像之間進行動畫,每個圖像可能分為行和列的子圖像。我們可以通過屬性或值提供這些數據,但在此體驗中,我們使用助手腳本。UpdateImageAnimations(dT) 使用時間和參數計算我們需要顯示的圖像或子圖像。如果需要更改為新圖像,我們設置 Image。如果需要更改任何子圖像,我們則設置 ImageRectOffset。
擁有動畫 SurfaceGui 的物體會有一個 Animator ModuleScript,其主要目的是提供一個 Animator.GetParams 函數來返回所有參數。這幫助 MasterAnimator LocalScript,它使用 ImageAnimation 標籤和 CollectionService 來聚集這類物體,並找到其下的 Animator ModuleScript。然後使用 pcall 來 require Animator ModuleScript 並呼叫 GetParams。
LocalSpaceAnimations
LocalSpaceAnimations LocalScript 使用 LocalSpaceRotation 標籤圍繞 X、Y 或 Z 軸以給定的旋轉速度和延遲旋轉主要是“化妝”的物體。我們使用這個系統處理玩家無法互動的遙遠物體,或對模擬影響不大的小物體。參數通過 Speed、Delay 和 Axis 值定義。 有關實現細節,請參見 Rotating Cloud Meshes。
HeadlampManager
HeadlampManager LocalScript 處理用戶選擇螢幕上的 ImageButton 來切換其頭頂聚光燈的開/關,向伺服器發送 HeadlampEvent 註解,並播放開關聲音。 當一個角色被添加或更改其 Head 時,giveCharacterHeadlamp 函數會克隆 templateHeadlamp 燈,並使用 FaceFrontAttachment 的一些偏移和旋轉來定位燈。
SeatManager
我們不希望玩家在接近可以坐下的物體時自動坐下。相反,我們希望要求用戶在座位附近單擊以坐下。SeatManager 腳本基於 Seat 標籤添加 ClickDetectors,並在被點擊時調用 seat:Sit(humanoid)。當將玩家在房間的正常狀態和損壞狀態之間傳送時,我們不能讓玩家坐下,因為 CFrame 坐標變換無法正常工作,因此 SeatManager 擁有在傳送之前和之後幾秒鐘禁用或啟用坐下的功能。
DrawerManager
DrawerManager 腳本使用 Drawer2 標籤和 CollectionService 來處理點擊抽屜以打開或關閉它們,並播放任何相應的音頻。打開和關閉的操作通過為 PrismaticConstraint 設置 TargetPosition 來完成。
KillVolumes
在主遊戲區域的幾個區域,例如在通往房屋的道路起點附近的電火花和水域,當玩家進入具有 KillVolume 標籤的體積時,可以將其 Humanoid.Health 設置為 0。KillVolumes 腳本使用 Touched:Connect 來確定玩家何時進入體積,然後將其健康值降低至 0。
PlayerMissionRespawn
PlayerMissionRespawn 腳本使用 RespawnVolume 標籤和 CollectionService 來處理接觸後使玩家重生的體積。我們將這些體積放置在損壞的房間中,因為許多任務中存在玩家可能掉落的空隙或移動平台。當接觸時,該腳本播放一個小的 Teleport_Jump 過場動畫並調用 GameStateFunc,執行 GameEvents.PlayerRespawn 命令。
在處理 GameEvents.PlayerRespawn 的時候,腳本可以使用 RespawnPositions,如果任務配置提供它。如果沒有,它將使用特定任務的 TeleportPositions。我們沒有“檢查點”系統,所以 CalcClosestTeleportPos 只是從玩家擊中 RespawnVolume 之處選擇最近的 Respawn 或 Teleport 地點,使用唯一的水平“2D”距離。
小幫手系統
PianoManager
PianoManager 腳本使用 Piano 標籤和 CollectionService 來添加 ClickDetectors,並在點擊鍵盤時播放鋼琴聲音。
RitualSupport
玩家放置印章的玄關有一個複雜的機械裝置,隨著每個印章放置在其定義的位置上會發生變化。例如,根據放置的印章數量,會播放特定事件以啟用/禁用燈光和光束,改變某些物體的透明度等。RitualSupport ModuleScript 是 EventManager:Invoke 調用的小包裝,為這些事件提供參數,例如根據放置的具體印章播放它的“根物體”。
RestorableManager
一些可抓取的物體對遊戲至關重要,例如印章,我們不希望它們在玩家將其隨意放下時丟失。如果一個物體具有 Restorable 標籤,則 RestorableManager 腳本會在其被添加到可恢復系統時記住其變換。當玩家丟掉這種物體時,抓取系統會調用 restorableManager.StartTracking。如果該物體在五秒內未被重新拾取,則 RestorableManger 腳本會將其定位在原始變換位置並重置追蹤時間。
Portals
在幾個任務中,我們會在任務內短距離傳送玩家,例如 重新生成玩家 在旋轉平台掉落的情況下。為了簡化這類傳送的設置,我們在腳本中稱之為“Portals”,一個助手函數 ProcessPortal 在 DemoUtils 中使用。例如,如果 P1 是定義初始觸發器的部分,P2 是定義目的地玩家變換的部分,以下代碼片段可以定義這種傳送功能:
P1.Touched:Connect(function(otherPart) utils.ProcessPortal(otherPart, P2) end)
ProcessPortal 處理檢查 otherPart 是否為人類,通過 CFrame 坐標變換傳送玩家,並進行小的過場動畫以隱藏過渡,使用 Teleport_Jump 事件在 EventManager 中。
設定腳本
我們有幾個配置、數據定義和共通功能的腳本:
DemoConfig。任務定義。遊戲狀態的列舉,客戶端與伺服器的通訊事件。
DemoGlobalSettings。我們在一個地方開發,但在其他地方發布(並進行測試)。該腳本檢查 placeID 並啟用/禁用各種作弊和調試功能。
DemoUtils。各種實用函數。處理變換。設置可見性、固定或其他屬性。檢查框中的一個點。通過“點狀”名稱在層級中查找物體。管理 TempStorage(可以用來暫時將模型移動到“遠處”,以便稍後取回)。點擊檢測器助手。抓取支援。檢查標籤的支援(特別是沿著層級)。將觸發器連接到 EventManager。
AudioUtils。幾個從一組加權隨機聲音播放的函數。
GrabUtil。抓取的助手函數。