Trong hướng dẫn này, bạn sẽ học cách phát tia laser từ súng bắn trong Tạo công cụ cho người chơi và phát hiện xem nó có va chạm với người chơi hay không.
Raycasting để tìm va chạm
Raycasting tạo ra một tia vô hình từ một vị trí bắt đầu theo một hướng nhất định với một độ dài xác định. Nếu tia va chạm với các đối tượng hoặc địa hình trên đường đi của nó, nó sẽ trả về thông tin về va chạm như vị trí và đối tượng mà nó đã va chạm.

Tìm vị trí chuột
Trước khi một tia laser có thể được bắn, bạn phải biết người chơi đang nhắm vào đâu. Điều này có thể được tìm thấy bằng cách raycasting từ vị trí chuột 2D của người chơi trên màn hình trực tiếp về phía trước từ camera vào thế giới 3D. Tia sẽ va chạm với bất cứ thứ gì mà người chơi đang nhắm đến bằng chuột.
Mở script ToolController bên trong công cụ Blaster từ Tạo công cụ cho người chơi. Nếu bạn chưa hoàn thành hướng dẫn đó, bạn có thể tải mô hình Blaster và chèn nó vào StarterPack.

Ở đầu script, khai báo một hằng số có tên MAX_MOUSE_DISTANCE với giá trị 1000.
Tạo một hàm có tên getWorldMousePosition.
local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()endlocal function toolActivated()tool.Handle.Activate:Play()end-- Kết nối các sự kiện với các hàm thích hợptool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)Sử dụng hàm GetMouseLocation của UserInputService để lấy vị trí chuột 2D của người chơi trên màn hình. Gán điều này cho một biến có tên mouseLocation.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
Bây giờ vị trí chuột 2D đã được biết, các thuộc tính X và Y của nó có thể được sử dụng làm tham số cho hàm Camera:ViewportPointToRay() , tạo ra một Ray từ màn hình vào thế giới 3D.
Sử dụng các thuộc tính X và Y của mouseLocation làm đối số cho hàm ViewportPointToRay(). Gán điều này cho một biến có tên screenToWorldRay.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Tạo một tia từ vị trí chuột 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)end
Đã đến lúc sử dụng hàm Raycast để kiểm tra xem tia có va chạm với một đối tượng hay không. Điều này yêu cầu một vị trí bắt đầu và vector hướng: trong ví dụ này, bạn sẽ sử dụng các thuộc tính origin và direction của screenToWorldRay.
Độ dài của vector hướng xác định tia sẽ đi xa bao nhiêu. Tia cần dài bằng MAX_MOUSE_DISTANCE, vì vậy bạn sẽ phải nhân vector hướng với MAX_MOUSE_DISTANCE.
Khai báo một biến có tên directionVector và gán cho nó giá trị của screenToWorldRay.Direction nhân với MAX_MOUSE_DISTANCE.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Tạo một tia từ vị trí chuột 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- Vector hướng đơn vị của tia nhân với một khoảng cách tối đalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCEGọi hàm Raycast của workspace, truyền thuộc tính Origin của screenToWorldRay làm đối số đầu tiên và directionVector làm đối số thứ hai. Gán điều này cho một biến có tên raycastResult.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Tạo một tia từ vị trí chuột 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- Vector hướng đơn vị của tia nhân với một khoảng cách tối đalocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE-- Raycast từ nguồn gốc của tia theo hướng của nólocal raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
Thông tin va chạm
Nếu thao tác raycast tìm thấy một đối tượng bị tia va chạm, nó sẽ trả về một RaycastResult, chứa thông tin về va chạm giữa tia và đối tượng.
| Thuộc tính RaycastResult | Mô tả |
|---|---|
| Instance | Đối tượng BasePart hoặc ô Terrain mà tia đã giao nhau. |
| Position | Nơi mà sự giao nhau xảy ra; thường là một điểm trực tiếp trên bề mặt của một phần hoặc địa hình. |
| Material | Vật liệu tại điểm va chạm. |
| Normal | Vector pháp tuyến của mặt đã giao nhau. Điều này có thể được sử dụng để xác định mặt nào đang hướng về phía nào. |
Thuộc tính Position sẽ là vị trí của đối tượng mà chuột đang di chuyển qua. Nếu chuột không di chuyển qua bất kỳ đối tượng nào trong khoảng cách MAX_MOUSE_DISTANCE, raycastResult sẽ là nil.
Tạo một câu lệnh if để kiểm tra xem raycastResult có tồn tại hay không.
Nếu raycastResult có giá trị, trả về thuộc tính Position của nó.
Nếu raycastResult là nil, hãy tìm điểm cuối của raycast. Tính toán vị trí 3D của chuột bằng cách cộng screenToWorldRay.Origin và directionVector lại với nhau.
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Tạo một tia từ vị trí chuột 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Vector hướng đơn vị của tia nhân với một khoảng cách tối đa
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast từ nguồn gốc của tia theo hướng của nó
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Trả về điểm 3D của sự giao nhau
return raycastResult.Position
else
-- Không có đối tượng nào bị va chạm nên tính toán vị trí ở cuối tia
return screenToWorldRay.Origin + directionVector
end
endBắn về phía mục tiêu
Bây giờ mà vị trí chuột 3D đã được biết, nó có thể được sử dụng làm vị trí mục tiêu để bắn tia laser về phía đó. Một tia thứ hai có thể được phát giữa vị trí vũ khí của người chơi và vị trí mục tiêu bằng cách sử dụng hàm Raycast.
Khai báo một hằng số có tên MAX_LASER_DISTANCE ở đầu script và gán cho nó giá trị 500, hoặc khoảng cách mà bạn chọn cho súng laser.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Tạo một hàm có tên fireWeapon dưới hàm getWorldMousePosition.
Gọi getWorldMousePosition và gán kết quả cho một biến có tên mousePosition. Đây sẽ là vị trí mục tiêu cho raycast.
-- Không có đối tượng nào bị va chạm nên tính toán vị trí ở cuối tiareturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()end
Lần này, vector hướng cho hàm raycast sẽ đại diện cho hướng từ vị trí công cụ của người chơi đến vị trí mục tiêu.
Khai báo một biến có tên targetDirection và tính toán vector hướng bằng cách trừ vị trí công cụ từ mouseLocation.
Chuẩn hóa vector bằng cách sử dụng thuộc tính Unit của nó. Điều này sẽ cho nó một độ lớn bằng 1, giúp dễ dàng nhân với một độ dài sau này.
local function fireWeapon()local mouseLocation = getWorldMousePosition()-- Tính toán một vector hướng đã chuẩn hóa và nhân với khoảng cách laserlocal targetDirection = (mouseLocation - tool.Handle.Position).UnitendKhai báo một biến có tên directionVector và gán cho nó giá trị của targetDirection nhân với MAX_LASER_DISTANCE.
local targetDirection = (mouseLocation - tool.Handle.Position).Unit-- Hướng để bắn vũ khí, nhân với một khoảng cách tối đalocal directionVector = targetDirection * MAX_LASER_DISTANCEend
Một đối tượng RaycastParams có thể được sử dụng để lưu trữ các tham số bổ sung cho hàm raycast. Nó sẽ được sử dụng trong súng laser của bạn để đảm bảo rằng raycast không vô tình va chạm với người chơi đang bắn vũ khí. Bất kỳ phần nào được bao gồm trong thuộc tính FilterDescendantsInstances của một đối tượng RaycastParams sẽ được bỏ qua trong raycast.
Tiếp tục hàm fireWeapon và khai báo một biến có tên weaponRaycastParams. Gán cho nó một đối tượng RaycastParams mới.
Tạo một bảng chứa nhân vật cục bộ của người chơi và gán nó cho thuộc tính weaponRaycastParams.FilterDescendantsInstances.
Raycast từ vị trí tay cầm công cụ của người chơi, theo hướng về phía directionVector. Nhớ thêm weaponRaycastParams như một đối số lần này. Gán điều này cho một biến có tên weaponRaycastResult.
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local tool = script.Parent
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local function getWorldMousePosition()local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Tính toán một vector hướng đã chuẩn hóa và nhân với khoảng cách laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- Hướng để bắn vũ khí nhân với một khoảng cách tối đa
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Bỏ qua nhân vật của người chơi để ngăn họ tự gây sát thương
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
endCuối cùng, bạn sẽ cần kiểm tra xem thao tác raycast có trả về giá trị hay không. Nếu một giá trị được trả về, một đối tượng đã bị tia va chạm và một tia laser có thể được tạo ra giữa vũ khí và vị trí va chạm. Nếu không có gì được trả về, vị trí cuối cùng cần được tính toán để tạo ra tia laser.
Khai báo một biến rỗng có tên hitPosition.
Sử dụng một câu lệnh if để kiểm tra xem weaponRaycastResult có giá trị hay không. Nếu một đối tượng bị va chạm, gán weaponRaycastResult.Position cho hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Kiểm tra xem có bất kỳ đối tượng nào bị va chạm giữa vị trí bắt đầu và kết thúclocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendNếu weaponRaycastResult không có giá trị, tính toán vị trí cuối cùng của raycast bằng cách cộng vị trí của tay cầm công cụ với directionVector. Gán điều này cho hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Kiểm tra xem có bất kỳ đối tượng nào bị va chạm giữa vị trí bắt đầu và kết thúclocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- Tính toán vị trí cuối cùng dựa trên khoảng cách laser tối đahitPosition = tool.Handle.Position + directionVectorendendĐiều hướng đến hàm toolActivated và gọi hàm fireWeapon để tia laser bắn mỗi khi công cụ được kích hoạt.
local function toolActivated()tool.Handle.Activate:Play()fireWeapon()end
Kiểm tra đối tượng bị va chạm
Để tìm xem đối tượng bị tia laser va chạm có phải là một phần của nhân vật người chơi hay chỉ là một phần của cảnh vật, bạn sẽ cần tìm một Humanoid, vì mỗi nhân vật đều có một cái.
Đầu tiên, bạn sẽ cần tìm mô hình nhân vật. Nếu một phần của nhân vật bị va chạm, bạn không thể giả định rằng cha của đối tượng bị va chạm sẽ là nhân vật. Tia laser có thể đã va chạm với một phần cơ thể, một phụ kiện, hoặc một công cụ, tất cả đều nằm trong các phần khác nhau của cấu trúc phân cấp của nhân vật.

Bạn có thể sử dụng FindFirstAncestorOfClass để tìm một tổ tiên mô hình nhân vật của đối tượng bị tia laser va chạm, nếu có. Nếu bạn tìm thấy một mô hình và nó chứa một humanoid, trong hầu hết các trường hợp bạn có thể giả định đó là một nhân vật.
Thêm mã được đánh dấu bên dưới vào câu lệnh if của weaponRaycastResult để kiểm tra xem một nhân vật có bị va chạm hay không.
-- Kiểm tra xem có bất kỳ đối tượng nào bị va chạm giữa vị trí bắt đầu và kết thúclocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Position-- Đối tượng bị va chạm sẽ là một phần của mô hình nhân vật-- Nếu một humanoid được tìm thấy trong mô hình thì có khả năng đó là nhân vật của người chơilocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("Người chơi bị va chạm")endendelse-- Tính toán vị trí cuối cùng dựa trên khoảng cách laser tối đahitPosition = tool.Handle.Position + directionVectorend
Bây giờ súng laser nên in ra Người chơi bị va chạm vào cửa sổ Output mỗi khi thao tác raycast va chạm với một người chơi khác.
Kiểm tra với nhiều người chơi
Cần hai người chơi để kiểm tra xem raycast vũ khí có tìm thấy người chơi khác hay không, vì vậy bạn cần bắt đầu một máy chủ cục bộ. Xem mô phỏng nhiều khách hàng để biết hướng dẫn.
Trên một khách hàng, hãy thử bắn người chơi khác bằng cách nhấp vào họ. "Người chơi bị va chạm" sẽ được hiển thị trong đầu ra mỗi khi một người chơi bị bắn.
Tìm vị trí tia laser
Súng bắn nên bắn ra một chùm ánh sáng đỏ về phía mục tiêu của nó. Hàm cho điều này sẽ nằm trong một ModuleScript để có thể được sử dụng lại trong các script khác sau này. Đầu tiên, script sẽ cần tìm vị trí mà tia laser nên được hiển thị.
Tạo một ModuleScript có tên LaserRenderer, được gán cho StarterPlayerScripts dưới StarterPlayer.

Mở script và đổi tên bảng mô-đun thành tên của script LaserRenderer.
Khai báo một biến có tên SHOT_DURATION với giá trị 0.15. Đây sẽ là khoảng thời gian (tính bằng giây) mà tia laser có thể nhìn thấy.
Tạo một hàm của LaserRenderer có tên createLaser với hai tham số có tên toolHandle và endPosition.
local LaserRenderer = {}local SHOT_DURATION = 0.15 -- Thời gian mà tia laser có thể nhìn thấy-- Tạo một tia laser từ một vị trí bắt đầu đến một vị trí kết thúcfunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererKhai báo một biến có tên startPosition và đặt thuộc tính Position của toolHandle làm giá trị của nó. Đây sẽ là vị trí của súng laser của người chơi.
Khai báo một biến có tên laserDistance và trừ endPosition từ startPosition để tìm sự khác biệt giữa hai vector. Sử dụng thuộc tính Magnitude của điều này để lấy chiều dài của tia laser.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).MagnitudeendKhai báo một biến laserCFrame để lưu trữ vị trí và hướng của tia laser. Vị trí cần phải là điểm giữa của điểm bắt đầu và điểm kết thúc của tia. Sử dụng CFrame.lookAt để tạo một CFrame mới nằm tại startPosition và hướng về phía endPosition. Nhân điều này với một CFrame mới có giá trị trục Z bằng một nửa của -laserDistance để có được điểm giữa.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)end
Tạo phần tia laser
Bây giờ mà bạn đã biết nơi để tạo một tia laser, bạn cần thêm chính tia laser đó. Điều này có thể được thực hiện dễ dàng với một phần Neon.
Khai báo một biến laserPart và gán cho nó một đối tượng Part mới.
Đặt các thuộc tính sau của laserPart:
- Size: Vector3.new(0.2, 0.2, laserDistance)
- CFrame: laserCFrame
- Anchored: true
- CanCollide: false
- Color: Color3.fromRGB(225, 0, 0) (một màu đỏ mạnh)
- Material: Enum.Material.Neon
Gán laserPart cho Workspace.
Thêm phần này vào dịch vụ Debris để nó được xóa sau khoảng thời gian trong biến SHOT_DURATION.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)local laserPart = Instance.new("Part")laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)laserPart.CFrame = laserCFramelaserPart.Anchored = truelaserPart.CanCollide = falselaserPart.Color = Color3.fromRGB(225, 0, 0)laserPart.Material = Enum.Material.NeonlaserPart.Parent = workspace-- Thêm tia laser vào dịch vụ Debris để được xóa và dọn dẹpDebris:AddItem(laserPart, SHOT_DURATION)end
Bây giờ hàm để hiển thị tia laser đã hoàn thành, nó có thể được gọi bởi ToolController.
Ở đầu script ToolController, khai báo một biến có tên LaserRenderer và yêu cầu mô-đun LaserRenderer nằm trong PlayerScripts.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.ParentỞ cuối hàm fireWeapon, gọi hàm createLaser của LaserRenderer sử dụng tay cầm công cụ và hitPosition làm đối số.
-- Tính toán vị trí cuối cùng dựa trên khoảng cách laser tối đahitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endKiểm tra vũ khí bằng cách nhấp vào nút Chơi. Một tia laser nên được hiển thị giữa vũ khí và chuột khi công cụ được kích hoạt.
Kiểm soát tỷ lệ bắn của vũ khí
Vũ khí cần có một độ trễ giữa mỗi lần bắn để ngăn người chơi gây quá nhiều sát thương trong một khoảng thời gian ngắn. Điều này có thể được kiểm soát bằng cách kiểm tra xem có đủ thời gian đã trôi qua kể từ lần bắn cuối cùng của người chơi.
Khai báo một biến ở đầu ToolController có tên FIRE_RATE. Đây sẽ là thời gian tối thiểu giữa mỗi lần bắn. Gán cho nó một giá trị mà bạn chọn; ví dụ này sử dụng 0.3 giây.
Khai báo một biến khác bên dưới có tên timeOfPreviousShot với giá trị 0. Điều này lưu trữ thời gian cuối cùng mà người chơi bắn và sẽ được cập nhật với mỗi lần bắn.
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0Tạo một hàm có tên canShootWeapon không có tham số. Hàm này sẽ xem xét thời gian đã trôi qua kể từ lần bắn trước và trả về true hoặc false.
local FIRE_RATE = 0.3local timeOfPreviousShot = 0-- Kiểm tra xem có đủ thời gian đã trôi qua kể từ lần bắn trướclocal function canShootWeapon()endlocal function getWorldMousePosition()Bên trong hàm khai báo một biến có tên currentTime; gán cho nó kết quả của việc gọi hàm tick(). Điều này trả về thời gian đã trôi qua, tính bằng giây, kể từ ngày 1 tháng 1 năm 1970 (một ngày tùy ý thường được sử dụng để tính toán thời gian).
Trừ timeOfPreviousShot từ currentTime và trả về false nếu kết quả nhỏ hơn FIRE_RATE; nếu không, trả về true.
-- Kiểm tra xem có đủ thời gian đã trôi qua kể từ lần bắn trướclocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendỞ cuối hàm fireWeapon, cập nhật timeOfPreviousShot mỗi khi vũ khí được bắn bằng cách sử dụng tick.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endBên trong hàm toolActivated, tạo một câu lệnh if và gọi canShootWeapon để kiểm tra xem vũ khí có thể được bắn hay không.
local function toolActivated()if canShootWeapon() thentool.Handle.Activate:Play()fireWeapon()endend
Khi bạn kiểm tra súng laser, bạn sẽ thấy rằng bất kể bạn nhấp nhanh như thế nào, sẽ luôn có một độ trễ ngắn 0.3 giây giữa mỗi lần bắn.
Gây sát thương cho người chơi
Các khách hàng không thể gây sát thương cho các khách hàng khác trực tiếp; máy chủ cần phải chịu trách nhiệm về việc phát hành sát thương khi một người chơi bị bắn.
Các khách hàng có thể sử dụng một RemoteEvent để thông báo cho máy chủ rằng một nhân vật đã bị bắn. Những điều này nên được lưu trữ trong ReplicatedStorage, nơi chúng có thể nhìn thấy cả khách hàng và máy chủ.
Tạo một Thư mục trong ReplicatedStorage có tên Events.

Chèn một RemoteEvent vào thư mục Events và đặt tên là DamageCharacter.

Trong ToolController, tạo các biến ở đầu script cho ReplicatedStorage và thư mục Events.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.Parentlocal eventsFolder = ReplicatedStorage.Eventslocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Thay thế câu lệnh in "Người chơi bị va chạm" trong fireWeapon bằng một dòng Luau để phát sự kiện DamageCharacter với biến characterModel làm đối số.
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endendelse-- Tính toán vị trí cuối cùng dựa trên khoảng cách laser tối đahitPosition = tool.Handle.Position + directionVectorend
Máy chủ cần phải gây sát thương cho người chơi đã bị bắn khi sự kiện được phát.
Chèn một Script vào ServerScriptService và đặt tên là ServerLaserManager.

Khai báo một biến có tên LASER_DAMAGE và đặt nó thành 10, hoặc một giá trị mà bạn chọn.
Tạo một hàm có tên damageCharacter với hai tham số có tên playerFired và characterToDamage.
Bên trong hàm, tìm Humanoid của nhân vật và trừ LASER_DAMAGE từ sức khỏe của nó.
Kết nối hàm damageCharacter với sự kiện DamageCharacter trong thư mục Events.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Giảm sức khỏe của nhân vậthumanoid.Health -= LASER_DAMAGEendend-- Kết nối các sự kiện với các hàm thích hợpeventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)Kiểm tra súng laser với 2 người chơi bằng cách bắt đầu một máy chủ cục bộ. Khi bạn bắn người chơi khác, sức khỏe của họ sẽ giảm theo giá trị được gán cho LASER_DAMAGE.
Hiển thị tia laser của người chơi khác
Hiện tại, tia laser được tạo ra bởi khách hàng bắn vũ khí, vì vậy chỉ họ mới có thể nhìn thấy tia laser.
Nếu tia laser được tạo ra trên máy chủ thì mọi người sẽ có thể nhìn thấy nó. Tuy nhiên, sẽ có một độ trễ nhỏ giữa việc khách hàng bắn vũ khí và máy chủ nhận thông tin về cú bắn. Điều này có nghĩa là khách hàng bắn vũ khí sẽ thấy một độ trễ giữa khi họ kích hoạt vũ khí và khi họ thấy tia laser; vũ khí sẽ cảm thấy bị lag như một kết quả.
Để giải quyết vấn đề này, mỗi khách hàng sẽ tạo ra tia laser của riêng họ. Điều này có nghĩa là khách hàng bắn vũ khí sẽ thấy tia laser ngay lập tức. Các khách hàng khác sẽ trải qua một độ trễ nhỏ giữa khi một người chơi khác bắn và một tia xuất hiện. Đây là kịch bản tốt nhất: không có cách nào để giao tiếp tia laser của một khách hàng đến các khách hàng khác nhanh hơn.
Khách hàng bắn
Đầu tiên, khách hàng cần thông báo cho máy chủ rằng họ đã bắn một tia laser và cung cấp vị trí kết thúc.
Chèn một RemoteEvent vào thư mục Events trong ReplicatedStorage và đặt tên là LaserFired.

Tìm hàm fireWeapon trong script ToolController. Ở cuối hàm, phát sự kiện LaserFired sử dụng hitPosition làm đối số.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()eventsFolder.LaserFired:FireServer(hitPosition)LaserRenderer.createLaser(tool.Handle, hitPosition)end
Máy chủ
Bây giờ máy chủ phải nhận sự kiện rằng khách hàng đã bắn và thông báo cho tất cả khách hàng về vị trí bắt đầu và kết thúc của tia laser để họ cũng có thể hiển thị nó.
Trong script ServerLaserManager, tạo một hàm có tên playerFiredLaser ở trên hàm damageCharacter với hai tham số có tên playerFired và endPosition.
Kết nối hàm với sự kiện LaserFired.
-- Thông báo cho tất cả khách hàng rằng một tia laser đã được bắn để họ có thể hiển thị tia laserlocal function playerFiredLaser(playerFired, endPosition)end-- Kết nối các sự kiện với các hàm thích hợpeventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
Máy chủ cần vị trí bắt đầu của tia laser. Điều này có thể được gửi từ khách hàng, nhưng tốt nhất là tránh tin tưởng khách hàng khi có thể. Vị trí tay cầm vũ khí của nhân vật sẽ là vị trí bắt đầu, vì vậy máy chủ có thể tìm thấy nó từ đó.
Tạo một hàm getPlayerToolHandle ở trên hàm playerFiredLaser với một tham số có tên player.
Sử dụng mã sau để tìm kiếm nhân vật của người chơi để tìm vũ khí và trả về đối tượng tay cầm.
local LASER_DAMAGE = 10-- Tìm tay cầm của công cụ mà người chơi đang cầmlocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- Thông báo cho tất cả khách hàng rằng một tia laser đã được bắn để họ có thể hiển thị tia laserlocal function playerFiredLaser(playerFired, endPosition)
Bây giờ máy chủ có thể gọi FireAllClients trên sự kiện LaserFired để gửi thông tin cần thiết để hiển thị tia laser đến các khách hàng. Điều này bao gồm người chơi đã bắn tia laser (để khách hàng của người đó không hiển thị tia laser hai lần), tay cầm của súng bắn (đóng vai trò là vị trí bắt đầu cho tia) và vị trí kết thúc của tia laser.
Trong hàm playerFiredLaser, gọi hàm getPlayerToolHandle với playerFired làm đối số và gán giá trị cho một biến có tên toolHandle.
Nếu toolHandle tồn tại, phát sự kiện LaserFired cho tất cả khách hàng sử dụng playerFired, toolHandle và endPosition làm đối số.
-- Thông báo cho tất cả khách hàng rằng một tia laser đã được bắn để họ có thể hiển thị tia laserlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
Hiển thị trên các khách hàng
Bây giờ FireAllClients đã được gọi, mỗi khách hàng sẽ nhận được một sự kiện từ máy chủ để hiển thị một tia laser. Mỗi khách hàng có thể sử dụng lại mô-đun LaserRenderer từ trước để hiển thị tia laser bằng cách sử dụng vị trí tay cầm của công cụ và giá trị vị trí kết thúc được gửi bởi máy chủ. Người chơi đã bắn tia laser ban đầu nên bỏ qua sự kiện này nếu không họ sẽ thấy 2 tia laser.
Tạo một LocalScript trong StarterPlayerScripts có tên ClientLaserManager.

Bên trong script, yêu cầu mô-đun LaserRenderer.
Tạo một hàm có tên createPlayerLaser với các tham số playerWhoShot, toolHandle và endPosition.
Kết nối hàm với sự kiện LaserFired trong thư mục Events.
Trong hàm, sử dụng một câu lệnh if để kiểm tra xem playerWhoShot có không bằng LocalPlayer hay không.
Bên trong câu lệnh if, gọi hàm createLaser từ mô-đun LaserRenderer sử dụng toolHandle và endPosition làm đối số.
local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))local eventsFolder = ReplicatedStorage.Events-- Hiển thị tia laser của người chơi kháclocal function createPlayerLaser(playerWhoShot, toolHandle, endPosition)if playerWhoShot ~= Players.LocalPlayer thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)Kiểm tra súng laser với 2 người chơi bằng cách bắt đầu một máy chủ cục bộ. Đặt mỗi khách hàng ở các bên khác nhau của màn hình để bạn có thể thấy cả hai cửa sổ cùng một lúc. Khi bạn bắn ở một khách hàng, bạn nên thấy tia laser ở khách hàng khác.
Hiệu ứng âm thanh
Âm thanh bắn hiện tại chỉ phát trên khách hàng đang bắn viên đạn. Bạn sẽ cần di chuyển mã để phát âm thanh để các người chơi khác cũng nghe thấy.
Trong script ToolController, điều hướng đến hàm toolActivated và xóa dòng phát âm thanh Kích hoạt.
local function toolActivated()if canShootWeapon() thenfireWeapon()endendỞ cuối hàm createLaser trong LaserRenderer, khai báo một biến có tên shootingSound và sử dụng phương thức FindFirstChild() của toolHandle để kiểm tra âm thanh Activate.
Sử dụng một câu lệnh if để kiểm tra xem shootingSound có tồn tại hay không; nếu có, gọi hàm Play() của nó.
laserPart.Parent = workspace-- Thêm tia laser vào dịch vụ Debris để được xóa và dọn dẹpDebris:AddItem(laserPart, SHOT_DURATION)-- Phát âm thanh bắn của vũ khílocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
Bảo mật các remote bằng cách xác thực
Nếu máy chủ không kiểm tra dữ liệu từ các yêu cầu đến, một hacker có thể lạm dụng các hàm và sự kiện từ xa và sử dụng chúng để gửi các giá trị giả đến máy chủ. Điều quan trọng là sử dụng xác thực phía máy chủ để ngăn chặn điều này.
Trong hình thức hiện tại, sự kiện DamageCharacter rất dễ bị tấn công. Các hacker có thể sử dụng sự kiện này để gây sát thương cho bất kỳ người chơi nào mà họ muốn mà không cần bắn họ.
Xác thực là quá trình kiểm tra rằng các giá trị được gửi đến máy chủ là hợp lý. Trong trường hợp này, máy chủ sẽ cần phải:
- Kiểm tra xem khoảng cách giữa người chơi và vị trí bị va chạm bởi tia laser có nằm trong một ranh giới nhất định hay không.
- Raycast giữa vũ khí đã bắn tia laser và vị trí bị va chạm để đảm bảo cú bắn là hợp lệ và không đi qua bất kỳ bức tường nào.
Khách hàng
Khách hàng cần gửi cho máy chủ vị trí bị va chạm bởi raycast để nó có thể kiểm tra xem khoảng cách có hợp lý hay không.
Trong ToolController, điều hướng đến dòng mà sự kiện DamageCharacter được phát trong hàm fireWeapon.
Thêm hitPosition làm một đối số.
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
Máy chủ
Khách hàng hiện đang gửi một tham số bổ sung thông qua sự kiện DamageCharacter, vì vậy ServerLaserManager cần được điều chỉnh để chấp nhận nó.
Trong script ServerLaserManager, thêm một tham số hitPosition vào hàm damageCharacter.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Giảm sức khỏe của nhân vậthumanoid.Health -= LASER_DAMAGEendendDưới hàm getPlayerToolHandle, tạo một hàm có tên isHitValid với ba tham số: playerFired, characterToDamage và hitPosition.
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
Kiểm tra đầu tiên sẽ là khoảng cách giữa vị trí bị va chạm và nhân vật bị va chạm.
Khai báo một biến có tên MAX_HIT_PROXIMITY ở đầu script và gán cho nó giá trị 10. Đây sẽ là khoảng cách tối đa cho phép giữa vị trí bị va chạm và nhân vật. Một độ dung sai là cần thiết vì nhân vật có thể đã di chuyển một chút kể từ khi khách hàng bắn sự kiện.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10Trong hàm isHitValid, tính toán khoảng cách giữa nhân vật và vị trí bị va chạm. Nếu khoảng cách lớn hơn MAX_HIT_PROXIMITY, thì trả về false.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Xác thực khoảng cách giữa nhân vật bị va chạm và vị trí bị va chạmlocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
Kiểm tra thứ hai sẽ liên quan đến một raycast giữa vũ khí đã bắn và vị trí bị va chạm. Nếu raycast trả về một đối tượng không phải là nhân vật, bạn có thể giả định rằng cú bắn không hợp lệ vì có thứ gì đó đã chặn cú bắn.
Sao chép mã dưới đây để thực hiện kiểm tra này. Trả về true ở cuối hàm: nếu nó đến cuối, tất cả các kiểm tra đã vượt qua.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Xác thực khoảng cách giữa nhân vật bị va chạm và vị trí bị va chạmlocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > 10 thenreturn falseend-- Kiểm tra xem có bắn xuyên qua tường khônglocal toolHandle = getPlayerToolHandle(playerFired)if toolHandle thenlocal rayLength = (hitPosition - toolHandle.Position).Magnitudelocal rayDirection = (hitPosition - toolHandle.Position).Unitlocal raycastParams = RaycastParams.new()raycastParams.FilterDescendantsInstances = {playerFired.Character}local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)-- Nếu một đối tượng đã bị va chạm không phải là nhân vật thì bỏ qua cú bắnif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendKhai báo một biến trong hàm damageCharacter có tên validShot. Gán cho nó kết quả của việc gọi hàm isHitValid với ba đối số: playerFired, characterToDamage và hitPosition.
Trong câu lệnh if bên dưới, thêm một toán tử and để kiểm tra xem validShot có phải là true hay không.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")local validShot = isHitValid(playerFired, characterToDamage, hitPosition)if humanoid and validShot then-- Giảm sức khỏe của nhân vậthumanoid.Health -= LASER_DAMAGEendend
Bây giờ sự kiện damageCharacter đã an toàn hơn và sẽ ngăn hầu hết người chơi lạm dụng nó. Lưu ý rằng một số người chơi độc hại sẽ thường tìm cách vượt qua xác thực; giữ cho các sự kiện từ xa an toàn là một nỗ lực liên tục.
Súng laser của bạn hiện đã hoàn thành, với một hệ thống phát hiện va chạm cơ bản sử dụng raycasting. Hãy thử hướng dẫn Phát hiện đầu vào người dùng để tìm hiểu cách bạn có thể thêm một hành động nạp lại vào súng laser của mình, hoặc tạo một bản đồ thú vị và thử súng laser của bạn với các người chơi khác!
Mã cuối cùng
ToolController
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
local tool = script.Parent
local eventsFolder = ReplicatedStorage.Events
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local FIRE_RATE = 0.3
local timeOfPreviousShot = 0
-- Kiểm tra xem có đủ thời gian đã trôi qua kể từ lần bắn trước
local function canShootWeapon()
local currentTime = tick()
if currentTime - timeOfPreviousShot < FIRE_RATE then
return false
end
return true
end
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Tạo một tia từ vị trí chuột 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Vector hướng đơn vị của tia nhân với một khoảng cách tối đa
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast từ nguồn gốc của tia theo hướng của nó
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Trả về điểm 3D của sự giao nhau
return raycastResult.Position
else
-- Không có đối tượng nào bị va chạm nên tính toán vị trí ở cuối tia
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Tính toán một vector hướng đã chuẩn hóa và nhân với khoảng cách laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- Hướng để bắn vũ khí, nhân với một khoảng cách tối đa
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Bỏ qua nhân vật của người chơi để ngăn họ tự gây sát thương
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- Kiểm tra xem có bất kỳ đối tượng nào bị va chạm giữa vị trí bắt đầu và kết thúc
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- Đối tượng bị va chạm sẽ là một phần của mô hình nhân vật
-- Nếu một humanoid được tìm thấy trong mô hình thì có khả năng đó là nhân vật của người chơi
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
if characterModel then
local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
if humanoid then
eventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)
end
end
else
-- Tính toán vị trí cuối cùng dựa trên khoảng cách laser tối đa
hitPosition = tool.Handle.Position + directionVector
end
timeOfPreviousShot = tick()
eventsFolder.LaserFired:FireServer(hitPosition)
LaserRenderer.createLaser(tool.Handle, hitPosition)
end
local function toolEquipped()
tool.Handle.Equip:Play()
end
local function toolActivated()
if canShootWeapon() then
fireWeapon()
end
end
tool.Equipped:Connect(toolEquipped)
tool.Activated:Connect(toolActivated)LaserRenderer
local LaserRenderer = {}
local Debris = game:GetService("Debris")
local SHOT_DURATION = 0.15 -- Thời gian mà tia laser có thể nhìn thấy
-- Tạo một tia laser từ một vị trí bắt đầu đến một vị trí kết thúc
function LaserRenderer.createLaser(toolHandle, endPosition)
local startPosition = toolHandle.Position
local laserDistance = (startPosition - endPosition).Magnitude
local laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)
local laserPart = Instance.new("Part")
laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)
laserPart.CFrame = laserCFrame
laserPart.Anchored = true
laserPart.CanCollide = false
laserPart.Color = Color3.fromRGB(255, 0, 0)
laserPart.Material = Enum.Material.Neon
laserPart.Parent = workspace
-- Thêm tia laser vào dịch vụ Debris để được xóa và dọn dẹp
Debris:AddItem(laserPart, SHOT_DURATION)
-- Phát âm thanh bắn của vũ khí
local shootingSound = toolHandle:FindFirstChild("Activate")
if shootingSound then
shootingSound:Play()
end
end
return LaserRendererServerLaserManager
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_HIT_PROXIMITY = 10
-- Tìm tay cầm của công cụ mà người chơi đang cầm
local function getPlayerToolHandle(player)
local weapon = player.Character:FindFirstChildOfClass("Tool")
if weapon then
return weapon:FindFirstChild("Handle")
end
end
local function isHitValid(playerFired, characterToDamage, hitPosition)
-- Xác thực khoảng cách giữa nhân vật bị va chạm và vị trí bị va chạm
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Kiểm tra xem có bắn xuyên qua tường không
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
local rayLength = (hitPosition - toolHandle.Position).Magnitude
local rayDirection = (hitPosition - toolHandle.Position).Unit
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {playerFired.Character}
local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)
-- Nếu một đối tượng đã bị va chạm không phải là nhân vật thì bỏ qua cú bắn
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Thông báo cho tất cả khách hàng rằng một tia laser đã được bắn để họ có thể hiển thị tia laser
local function playerFiredLaser(playerFired, endPosition)
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
end
end
function damageCharacter(playerFired, characterToDamage, hitPosition)
local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
if humanoid and validShot then
-- Giảm sức khỏe của nhân vật
humanoid.Health -= LASER_DAMAGE
end
end
-- Kết nối các sự kiện với các hàm thích hợp
eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)ClientLaserManager
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts:WaitForChild("LaserRenderer"))
local eventsFolder = ReplicatedStorage.Events
-- Hiển thị tia laser của người chơi khác
local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
if playerWhoShot ~= Players.LocalPlayer then
LaserRenderer.createLaser(toolHandle, endPosition)
end
end
eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)