Triển khai hành vi bắn là quá trình lập trình một cơ chế bắn trong các trò chơi bắn súng góc nhìn thứ nhất. Trong khi người chơi có thể bắn bằng một cú nhấp chuột hoặc nhấn nút, việc tạo ra một hành vi bắn thỏa mãn và chính xác là rất quan trọng vì nó nâng cao sự thích thú của người chơi đối với toàn bộ gameplay.
Sử dụng trò chơi laser tag mẫu làm tài liệu tham khảo, phần này của hướng dẫn sẽ dạy bạn về các kịch bản phía sau việc triển khai hành vi bắn cho hai loại súng bắn khác nhau, bao gồm hướng dẫn về:
- Phát hiện khi người chơi nhấn nút bắn.
- Kiểm tra xem người chơi có thể sử dụng súng bắn của họ hay không nếu họ vừa nhấn nút bắn.
- Tạo dữ liệu bắn cho biết ai đã khởi xướng cú bắn, nó đến từ đâu và điểm đến cuối cùng của mỗi tia laser là gì.
- Thông báo cho máy chủ về dữ liệu bắn để nó có thể thực hiện các hành động thích hợp nếu cú bắn va chạm với một người chơi khác.
- Đặt lại súng bắn giữa mỗi cú bắn để cho súng có đủ thời gian để làm mát trước khi có thể bắn lại.
Sau khi bạn hoàn thành phần này, bạn sẽ tìm hiểu về các kịch bản cho phép súng bắn phát hiện khi các cú bắn của nó va chạm với những người chơi khác, sau đó trừ đi lượng máu tương ứng theo từng loại súng bắn.
Phát hiện đầu vào của người chơi
Bước đầu tiên để triển khai hành vi bắn là lắng nghe khi một người chơi nhấn nút bắn. Loại đầu vào mà người chơi sử dụng để nhấn nút bắn phụ thuộc vào thiết bị mà họ đang sử dụng để truy cập trò chơi. Ví dụ, trò chơi laser tag mẫu hỗ trợ điều khiển bằng chuột và bàn phím, tay cầm chơi game và điều khiển cảm ứng. Bạn có thể thấy từng loại đầu vào này trong ReplicatedStorage ⟩ UserInputHandler.
Kịch bản khách này sử dụng ContextActionService để liên kết MouseButton1 và ButtonR2 với hành động bắn. Điều này có nghĩa là mỗi khi một người chơi nhấn nút chuột trái hoặc nút R2 của tay cầm, nó sẽ kích hoạt một tia laser bắn ra từ súng bắn. Lưu ý rằng HUDGui chứa một nút để bắn trên các thiết bị di động, mà nó sẽ kết nối sau trong kịch bản.
ContextActionService:BindAction("_", onBlasterActivated, false,
Enum.UserInputType.MouseButton1,
Enum.KeyCode.ButtonR2
)Một lưu ý quan trọng khác là việc sử dụng Enum.UserInputState.Begin trong định nghĩa onBlasterActivated(). Nhiều tương tác giao diện người dùng, chẳng hạn như chọn một súng bắn trong ví dụ này, không xảy ra cho đến khi nút chuột được nhả ra (Enum.UserInputState.End), điều này cho phép người dùng có cơ hội cuối cùng để tránh tương tác. Tuy nhiên, một cơ chế bắn không cảm thấy phản hồi trừ khi nó xảy ra ngay lập tức khi nút được nhấn xuống.
Để minh họa, bạn có thể thay đổi Enum.UserInputState.Begin thành Enum.UserInputState.End, sau đó thử nghiệm để xem cách phản hồi của cú bắn ảnh hưởng đến gameplay của trò chơi. Ví dụ, nếu người chơi có thể giữ nút mà không kích hoạt cú bắn, điều đó có thể thay đổi trải nghiệm của họ khi gán nhãn cho những người chơi khác như thế nào?
local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- dòng đã cập nhật, hãy chắc chắn thay đổi lại
attemptBlastClient()
end
endKiểm tra xem người chơi có thể bắn hay không
Sau khi UserInputHandler phát hiện một cú nhấn nút hoặc chạm màn hình, nó gọi ReplicatedStorage ⟩ Blaster ⟩ attemptBlastClient để kiểm tra xem người chơi có thể bắn hay không. Giống như hầu hết các kiểm tra trong trò chơi laser tag mẫu, nó xảy ra hai lần: đầu tiên trên máy khách, sau đó sau đó trên máy chủ. attemptBlastClient sau đó gọi ReplicatedStorage ⟩ Blaster ⟩ canLocalPlayerBlast để thực hiện một kiểm tra đơn giản về thuộc tính người chơi blasterStateClient:
local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
endNếu bạn xem xét ReplicatedStorage ⟩ Blaster ⟩ BlasterState, bạn có thể thấy rằng trò chơi có ba trạng thái súng bắn: Ready, Blasting, và Disabled. Để xem tác động của từng trạng thái này, bạn có thể thử nghiệm trò chơi, chọn người chơi của bạn dưới dịch vụ Players, sau đó quan sát thuộc tính blasterStateClient trong cửa sổ Properties. Lưu ý cách nó hiển thị Disabled trong khi bạn chọn súng bắn của mình, Ready hầu hết thời gian, và Blasting trong chưa đầy một giây sau khi bạn nhấn nút.
Sự chậm trễ nhẹ này ngăn bạn bắn nhanh như bạn có thể nhấp chuột. Ví dụ, nếu bạn thay đổi hàm để luôn trả về true, bạn có thể bắn súng bắn của mình một cách nhanh chóng mà không có bất kỳ độ trễ nào, điều này là không thực tế cho gameplay laser tag.
local function canLocalPlayerBlast(): boolean
return true -- dòng đã cập nhật, hãy chắc chắn thay đổi lại
endTạo dữ liệu bắn
Sau khi xác minh rằng súng bắn của người chơi đang ở trạng thái Ready, attemptBlastClient gọi ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient. Bước đầu tiên mà blastClient thực hiện là đặt thuộc tính người chơi blasterStateClient thành Blasting, điều này tránh trường hợp bắn nhanh như trước.
Bước tiếp theo là tạo dữ liệu bắn. Nếu bạn xem xét ReplicatedStorage ⟩ Blaster ⟩ BlastData, bạn có thể thấy rằng mỗi cú bắn bao gồm ba mảnh thông tin:
- Người chơi khởi xướng cú bắn.
- Một DataType.CFrame đại diện cho điểm xuất phát của cú bắn.
- Một bảng RayResult chứa điểm đến cuối cùng của mỗi tia laser và người chơi bị trúng, nếu có.
Để tạo dữ liệu này, blastClient gọi ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient ⟩ generateBlastData, mà bạn có thể xem bên dưới.
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
endHàm này bắt đầu bằng cách sử dụng getBlasterConfig để lấy loại súng bắn của người chơi. Mẫu cung cấp hai loại súng bắn: một loại tạo ra nhiều tia với độ lan tỏa ngang rộng, và một loại tạo ra một tia duy nhất. Bạn có thể tìm thấy cấu hình của chúng trong ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder.
Hàm sau đó sử dụng currentCamera.CFrame làm điểm xuất phát cho cú bắn, truyền nó cho getDirectionsForBlast. Tại thời điểm này, mã không còn liên quan đến súng bắn, mà là về tia laser, điều mà bạn sẽ tìm hiểu thêm trong phần phát hiện va chạm của hướng dẫn. Cuối cùng, sau khi tạo bảng rayResults, generateBlastData có tất cả thông tin cần thiết để trả về dữ liệu bắn cho blastClient.
Thông báo cho máy chủ
Khi blastClient có dữ liệu hoàn chỉnh cho cú bắn, nó phát hai sự kiện:
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
laserBlastedBindableEvent:Fire(blastData)
laserBlastedEvent:FireServer(blastData)BindableEvent thông báo cho các kịch bản khách khác về cú bắn. Ví dụ, ReplicatedStorage ⟩ FirstPersonBlasterVisuals sử dụng sự kiện này để biết khi nào hiển thị hiệu ứng hình ảnh, chẳng hạn như hoạt ảnh bắn và thanh làm mát. Tương tự, RemoteEvent thông báo cho các kịch bản máy chủ về cú bắn, điều này bắt đầu xử lý cú bắn trong 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Để giúp ngăn chặn gian lận, máy chủ phải xác minh tất cả dữ liệu mà mỗi khách gửi. Những kiểm tra này bao gồm:
- BlastData có phải là một bảng không? Nó có chứa một Class.CFrame và một bảng khác có tên rayResults không?
- Người chơi có súng bắn được trang bị không?
- Người chơi có một nhân vật và một vị trí trong thế giới không?
- Sau khi gửi dữ liệu bắn, người chơi có di chuyển một khoảng cách quá mức từ nơi họ đã bắn tia laser không?
Kiểm tra cuối cùng này liên quan đến một quyết định phán đoán, và theo độ trễ của máy chủ và tốc độ di chuyển của người chơi, bạn có thể quyết định rằng các giá trị khác nhau là quá mức cho trò chơi của riêng bạn. Để minh họa cách đưa ra quyết định này, bạn có thể cảm nhận được độ lớn điển hình của sự thay đổi vị trí bằng cách thêm một câu lệnh in trong getValidatedBlastData và thử nghiệm trò chơi.
local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- dòng đã cập nhật, hãy chắc chắn xóa
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
warn(`Người chơi {player.Name} đã thất bại trong kiểm tra tính hợp lý của điểm xuất phát trong khi bắn`)
return
endKhi bạn di chuyển và bắn, hãy lưu ý đầu ra. Nó có thể trông giống như thế này:
1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807Nếu bạn tăng tốc độ di chuyển cho người chơi trong ReplicatedStorage ⟩ PlayerStateHandler ⟩ togglePlayerMovement, thì hãy thử nghiệm lại, bạn sẽ có thể gặp nhiều kiểm tra thất bại do di chuyển quá mức giữa các cú bắn.
local ENABLED_WALK_SPEED = 60 -- dòng đã cập nhật, hãy chắc chắn thay đổi lạiMáy chủ sau đó thực hiện các bước sau:
- Xác thực rayResults.
- Kiểm tra xem người chơi có thể bắn hay không.
- Đặt lại trạng thái súng bắn.
- Giảm máu cho bất kỳ người chơi nào bị gán nhãn.
- Nhân bản cú bắn cho tất cả người chơi khác để họ có thể thấy hiệu ứng hình ảnh từ góc nhìn thứ ba.
Để biết thêm thông tin về các hoạt động máy chủ này, hãy xem phần phát hiện va chạm của hướng dẫn.
Đặt lại súng bắn
Trong trò chơi laser tag mẫu, súng bắn sử dụng cơ chế nhiệt. Thay vì nạp lại sau một số cú bắn nhất định, chúng cần thời gian để "làm mát" giữa mỗi cú bắn. Độ trễ làm mát này cũng xảy ra trên cả máy khách (blastClient) và máy chủ (blastServer), với máy chủ đóng vai trò là nguồn thông tin chính xác.
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)Thuộc tính secondsBetweenBlasts là một phần của cấu hình súng bắn trong ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder. Sau khi độ trễ secondsBetweenBlasts trôi qua, người chơi có thể bắn lại, và toàn bộ quá trình lặp lại. Để giúp người chơi hiểu khi nào họ có thể bắn lại, trò chơi bao gồm một thanh làm mát.
Tại thời điểm này, người chơi có thể xuất hiện và tái xuất hiện, ngắm bắn và bắn, nhưng trò chơi vẫn phải xác định kết quả của mỗi cú bắn. Trong phần tiếp theo của hướng dẫn, bạn sẽ học cách lập trình khả năng cho súng bắn phát hiện khi cú bắn va chạm với một người chơi khác, sau đó giảm lượng máu phù hợp của người chơi theo cài đặt của súng bắn.