實作爆破行為 是在第一人稱射擊遊戲中編程爆破機制的過程。雖然玩家可以通過單擊或按下按鈕來進行爆破,但創建一個令人滿意且準確的爆破行為是很重要的,因為這增強了玩家對整體遊戲體驗的享受。
以 範例激光標籤遊戲 為參考,本教程的這一部分將教你有關實作兩種不同類型爆破器行為的腳本,包括以下指導:
- 偵測玩家何時按下爆破按鈕。
- 檢查玩家是否可以使用他們的爆破器,如果他們最近按下了爆破按鈕。
- 生成爆破數據,告訴伺服器誰啟動了爆破,爆破來自哪裡,以及每個激光束的最終目的地是什麼。
- 通知伺服器爆破數據,以便它可以在爆破與其他玩家碰撞時執行適當的操作。
- 在每次爆破之間重置爆破器,以便給予爆破器足夠的時間冷卻,然後才能再次爆破。
完成這一部分後,你將學習到允許爆破器偵測其爆破何時與其他玩家碰撞的腳本,然後根據每種爆破器類型扣除相應的生命值。
偵測玩家輸入
實作爆破行為的第一步是監聽玩家何時按下爆破按鈕。玩家用來按下爆破按鈕的輸入類型取決於他們用來訪問遊戲的設備。例如,範例激光標籤遊戲支持鼠標和鍵盤控制、遊戲手柄和觸控控制。你可以在 ReplicatedStorage ⟩ UserInputHandler 中看到這些輸入類型。
這個客戶端腳本使用 ContextActionService 將 MouseButton1 和 ButtonR2 綁定到爆破動作。這意味著每當玩家按下左鍵鼠標或遊戲手柄的 R2 按鈕時,會觸發激光束從爆破器中爆發出來。請注意,HUDGui 包含一個用於移動設備的爆破按鈕,稍後在腳本中將其連接。
ContextActionService:BindAction("_", onBlasterActivated, false,
Enum.UserInputType.MouseButton1,
Enum.KeyCode.ButtonR2
)另一個重要的注意事項是 onBlasterActivated() 定義中使用的 Enum.UserInputState.Begin。許多用戶界面交互,例如在此示例中選擇爆破器,直到鼠標按鈕抬起 (Enum.UserInputState.End) 之後才會發生,這給用戶最後一秒的機會來避免交互。然而,爆破機制如果在按鈕按下的瞬間不發生,就不會感覺到響應。
為了演示,你可以將 Enum.UserInputState.Begin 更改為 Enum.UserInputState.End,然後進行遊戲測試以查看爆破的響應性如何影響遊戲體驗。例如,如果玩家可以按住按鈕而不觸發爆破,這將如何改變他們在標記其他玩家時的體驗?
local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- 更新行,請務必改回
attemptBlastClient()
end
end檢查玩家是否可以爆破
在 UserInputHandler 偵測到按鈕按下或屏幕點擊後,它調用 ReplicatedStorage ⟩ Blaster ⟩ attemptBlastClient 來檢查玩家是否可以爆破。與範例激光標籤遊戲中的大多數檢查一樣,它發生兩次:第一次在客戶端,然後在伺服器上。attemptBlastClient 然後調用 ReplicatedStorage ⟩ Blaster ⟩ canLocalPlayerBlast 來執行 blasterStateClient 玩家屬性的簡單檢查:
local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
end如果你檢查 ReplicatedStorage ⟩ Blaster ⟩ BlasterState,你會看到遊戲有三個爆破器狀態:Ready、Blasting 和 Disabled。要查看這些狀態的效果,你可以進行遊戲測試,選擇 Players 服務下的玩家,然後在 Properties 窗口中觀察 blasterStateClient 屬性。注意在你選擇爆破器時它顯示為 Disabled,大部分時間顯示為 Ready,在你按下按鈕後顯示為 Blasting 不到一秒。
這個輕微的延遲防止你能夠像點擊那樣快速爆破。例如,如果你將函數更改為始終返回 true,你可以快速爆破而不會有任何延遲,這對於激光標籤遊戲來說是不現實的。
local function canLocalPlayerBlast(): boolean
return true -- 更新行,請務必改回
end生成爆破數據
在確認玩家的爆破器處於 Ready 狀態後,attemptBlastClient 調用 ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient。blastClient 的第一步是將 blasterStateClient 玩家屬性設置為 Blasting,這避免了之前的快速射擊情況。
下一步是生成爆破數據。如果你查看 ReplicatedStorage ⟩ Blaster ⟩ BlastData,你會看到每次爆破由三個信息組成:
- 發起爆破的玩家。
- 一個表示爆破起點的 DataType.CFrame。
- 一個 RayResult 表,包含每個激光束的最終目的地和被擊中的玩家(如果擊中其他玩家)。
為了生成這些數據,blastClient 調用 ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient ⟩ generateBlastData,你可以在下面查看。
local function generateBlastData(): BlastData.Type
local blasterConfig = getBlasterConfig()
local rayDirections = getDirectionsForBlast(
currentCamera.CFrame, blasterConfig)
local rayResults = castLaserRay(
localPlayer, currentCamera.CFrame.Position, rayDirections)
local blastData: BlastData.Type = {
player = localPlayer,
originCFrame = currentCamera.CFrame,
rayResults = rayResults,
}
return blastData
end這個函數首先使用 getBlasterConfig 來檢索玩家的爆破器類型。範例提供了兩種類型的爆破器:一種產生多個具有寬廣水平擴散的光束,另一種產生單一光束。你可以在 ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder 中找到它們的配置。
然後,該函數使用 currentCamera.CFrame 作為爆破的起點,並將其傳遞給 getDirectionsForBlast。此時,代碼不再關於爆破器,而是關於激光束,這部分你將在本教程的 偵測擊中 部分中學到。最後,在創建 rayResults 表後,generateBlastData 擁有返回爆破數據給 blastClient 所需的所有信息。
通知伺服器
一旦 blastClient 擁有完整的爆破數據,它會觸發兩個事件:
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
laserBlastedBindableEvent:Fire(blastData)
laserBlastedEvent:FireServer(blastData)BindableEvent 通知其他客戶端腳本有關爆破的情況。例如,ReplicatedStorage ⟩ FirstPersonBlasterVisuals 使用此事件來知道何時顯示視覺效果,例如爆破動畫和冷卻條。類似地,RemoteEvent 通知伺服器腳本有關爆破,這開始在 ServerScriptService ⟩ LaserBlastHandler 中處理爆破。
local function onLaserBlastedEvent(playerBlasted: Player, blastData: BlastData.Type)
local validatedBlastData = getValidatedBlastData(playerBlasted, blastData)
if not validatedBlastData then
return
end
if not canPlayerBlast(playerBlasted) then
return
end
blastServer(playerBlasted)
processTaggedPlayers(playerBlasted, blastData)
for _, replicateToPlayer in Players:GetPlayers() do
if playerBlasted == replicateToPlayer then
continue
end
replicateBlastEvent:FireClient(replicateToPlayer, playerBlasted, blastData)
end
end為了幫助防止作弊,伺服器必須驗證每個客戶端發送的所有數據。這些檢查包括:
- BlastData 是否為表?它是否包含 Class.CFrame 和另一個名為 rayResults 的表?
- 玩家是否裝備了爆破器?
- 玩家是否在世界中有角色和位置?
- 在發送爆破數據後,玩家是否從他們爆破激光束的位置移動了過大的距離?
最後一個檢查涉及判斷,根據伺服器延遲和玩家移動速度,你可能會決定對於自己的遊戲,哪些值是過大的。為了演示如何做出這個判斷,你可以在 getValidatedBlastData 中添加一個打印語句並進行遊戲測試。
local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- 更新行,請務必移除
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
warn(`Player {player.Name} failed an origin sanity check while blasting`)
return
end當你移動並爆破時,注意輸出。它可能看起來像這樣:
1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807如果你在 ReplicatedStorage ⟩ PlayerStateHandler ⟩ togglePlayerMovement 中增加玩家的移動速度,然後再次進行遊戲測試,你可能會因為在爆破之間的過度移動而遇到許多失敗的檢查。
local ENABLED_WALK_SPEED = 60 -- 更新行,請務必改回然後伺服器執行以下操作:
- 驗證 rayResults。
- 檢查玩家是否可以爆破。
- 重置爆破器狀態。
- 為任何標記的玩家減少生命值。
- 將爆破複製到所有其他玩家,以便他們可以看到第三人稱視覺效果。
有關這些伺服器操作的更多信息,請參見本教程的 偵測擊中 部分。
重置爆破器
在範例激光標籤遊戲中,爆破器使用熱量機制。它們不是在設定的爆破次數後重新裝填,而是需要時間在每次爆破之間“冷卻”。這個冷卻延遲在客戶端 (blastClient) 和伺服器 (blastServer) 上都會發生,伺服器作為真相的來源。
local blasterConfig = getBlasterConfig(player)
local secondsBetweenBlasts = blasterConfig:GetAttribute("secondsBetweenBlasts")
task.delay(secondsBetweenBlasts, function()
local currentState = player:GetAttribute(PlayerAttribute.blasterStateServer)
if currentState == BlasterState.Blasting then
player:SetAttribute(PlayerAttribute.blasterStateServer, BlasterState.Ready)
end
end)secondsBetweenBlasts 屬性是 ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder 中爆破器配置的一部分。在 secondsBetweenBlasts 延遲過後,玩家可以再次爆破,整個過程重複。為了幫助玩家理解何時可以再次爆破,遊戲包括一個冷卻條。
此時,玩家可以生成和重生,瞄準和爆破,但遊戲仍然需要確定每次爆破的結果。在本教程的下一部分中,你將學習如何編程使爆破器能夠偵測爆破何時擊中其他玩家,然後根據爆破器設置減少相應的玩家生命值。