Hướng dẫn trung cấp

Phát hiện va chạm với laser

*Nội dung này được dịch bằng AI (Beta) và có thể có lỗi. Để xem trang này bằng tiếng Anh, hãy nhấp vào đây.

Trong hướng dẫn này, bạn sẽ học cách phát 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.

Raycast để tìm va chạm

Raycasting tạo một tia vô hình từ vị trí bắt đầu theo một hướng nhất định với chiều 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.

Raycast từ A về B va chạm với một bức tường

Tìm vị trí chuột

Trước khi một laser có thể được bắn, trước tiê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 hướng 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.

  1. 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 xuống mô hình Blaster và chèn nó vào StarterPack.

  2. Ở đầu script, khai báo một hằng số có tên MAX_MOUSE_DISTANCE với giá trị là 1000.

  3. Tạo một hàm có tên getWorldMousePosition.


    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local function getWorldMousePosition()
    end
    local function toolEquipped()
    tool.Handle.Equip:Play()
    end
    local function toolActivated()
    tool.Handle.Activate:Play()
    end
    -- Kết nối sự kiện với các hàm phù hợp
    tool.Equipped:Connect(toolEquipped)
    tool.Activated:Connect(toolActivated)
  4. 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 là mouseLocation.


    local UserInputService = game:GetService("UserInputService")
    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    end

Bây giờ vị trí chuột 2D đã được biết, các thuộc tính XY của nó có thể được sử dụng làm tham số cho hàm Camera:ViewportPointToRay() để tạo một Ray từ màn hình vào thế giới 3D.

  1. Sử dụng thuộc tính XY của mouseLocation làm tham số cho hàm ViewportPointToRay(). Gán điều này cho một biến có tên là screenToWorldRay.


    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)
    end

Bây giờ đã đế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 thuộc tính origin và direction của screenToWorldRay.

Chiều dài của vector hướng xác định tia sẽ di chuyển xa bao nhiêu. Tia sẽ 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.

  1. Khai báo một biến có tên là 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 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
  2. Gọi hàm Raycast của workspace, truyền thuộc tính Origin của screenToWorldRay làm tham số đầu tiên và directionVector làm tham số thứ hai. Gán điều này cho một biến có tên là raycastResult.


    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ừ gốc của tia về hướng của nó
    local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)

Thông tin va chạm

Nếu phép toán raycast tìm thấy một đối tượng bị va chạm bởi tia, 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 RaycastResultMô tả
InstanceĐối tượng BasePart hoặc tế bào Terrain mà tia đã giao cắt.
PositionNơi mà sự giao cắt xảy ra; thường là một điểm chính xác trên bề mặt của một phần hoặc địa hình.
MaterialVật liệu tại điểm va chạm.
NormalVector pháp tuyến của mặt đã giao cắt. Điều này có thể được sử dụng để xác định hướng mà mặt đang chỉ.

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.

  1. Tạo một câu lệnh if để kiểm tra xem raycastResult có tồn tại hay không.

  2. Nếu raycastResult có giá trị, hãy trả về thuộc tính Position của nó.

  3. Nếu raycastResultnil, hãy tìm điểm cuối của phép raycast. Tính toán vị trí 3D của chuột bằng cách cộng screenToWorldRay.OrigindirectionVector 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ừ gốc của tia về hướng của nó
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Trả về điểm 3D giao cắt
return raycastResult.Position
else
-- Không có đối tượng nào bị va chạm, tính toán vị trí ở cuối tia
return screenToWorldRay.Origin + directionVector
end
end

Bắn về phía mục tiêu

Bây giờ mà vị trí chuột 3D đã được xác định, nó có thể được sử dụng làm vị trí mục tiêu để bắn 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.

  1. 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 bạn chọn cho súng bắn laser.


    local UserInputService = game:GetService("UserInputService")
    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  2. Tạo một hàm có tên fireWeapon dưới hàm getWorldMousePosition.

  3. Gọi getWorldMousePosition và gán kết quả cho một biến có tên là mousePosition. Đây sẽ là vị trí mục tiêu cho raycast.


    -- Không có đối tượng nào bị va chạm, tính toán vị trí ở cuối tia
    return screenToWorldRay.Origin + directionVector
    end
    end
    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    end
    local 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.

  1. 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.

  2. 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, điều này làm cho nó dễ dàng nhân với một chiều dài sau này.


    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    -- Tính toán một vector hướng được chuẩn hóa và nhân với khoảng cách laser
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. Khai báo một biến có tên directionVector và gán cho nó giá trị 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 đa
    local directionVector = targetDirection * MAX_LASER_DISTANCE
    end

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 bắn 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 đối tượng RaycastParams sẽ được bỏ qua trong raycast.

  1. 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.

  2. Tạo một bảng chứa nhân vật của người chơi và gán nó cho thuộc tính weaponRaycastParams.FilterDescendantsInstances.

  3. Raycast từ vị trí tay cầm công cụ của người chơi, theo hướng tới directionVector. Nhớ thêm weaponRaycastParams như một tham số trong lần này. Gán điều này cho một biến có tên là 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 được 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 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ự làm tổn thương chính mình
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
end

Cuối cùng, bạn sẽ cần kiểm tra xem phép raycast có trả về giá trị hay không. Nếu giá trị được trả về, một đối tượng đã bị va chạm bởi tia và một laser có thể được tạo giữa vị trí 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 laser.

  1. Khai báo một biến trống có tên hitPosition.

  2. 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úc
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. Nếu weaponRaycastResult không có giá trị, hãy tính toán vị trí cuối cùng của phép 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úc
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- Tính toán vị trí cuối cùng dựa trên khoảng cách tối đa laser
    hitPosition = tool.Handle.Position + directionVector
    end
    end
  4. Điều hướng đến hàm toolActivated và gọi hàm fireWeapon để 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ị 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ả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 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. Laser có thể đã va chạm với một bộ phận cơ thể, một phụ kiện hoặc một công cụ, tất cả những thứ này nằm trong các phần khác nhau của phân cấp 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ị va chạm bởi laser, nếu tìm thấy. 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.

  1. Thêm đoạn mã nổi bật dưới đây 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úc
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    -- Đối tượng bị va chạm sẽ là một phần tử của một 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
    print("Người chơi bị va chạm")
    end
    end
    else
    -- Tính toán vị trí cuối cùng dựa trên khoảng cách tối đa laser
    hitPosition = tool.Handle.Position + directionVector
    end

Bây giờ súng bắn laser sẽ in ra Người chơi bị va chạm trong cửa sổ Output mỗi khi phép raycast va chạm với một người chơi khác.

Thử nghiệm với nhiều người chơi

Cần có hai người chơi để kiểm tra xem raycast vũ khí có tìm thấy những người chơi khác không, vì vậy bạn cần khởi động một máy chủ cục bộ. Xem mô phỏng nhiều khách để biết hướng dẫn.

Trên một client, 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 output mỗi khi một người chơi bị bắn.

Tìm vị trí laser

Súng bắn nên phát một tia sáng đỏ về phía mục tiêu. Hàm cho điều này sẽ nằm trong một ModuleScript để có thể tái sử dụng trong các script khác sau này. Đầu tiên, script sẽ cần tìm vị trí mà tia laser should được hiển thị.

  1. Tạo một ModuleScript có tên LaserRenderer, được gán cho StarterPlayerScripts dưới StarterPlayer.

  2. Mở script và đặt tên bảng module thành tên của script LaserRenderer.

  3. Khai báo một biến có tên SHOT_DURATION với giá trị 0.15. Đây sẽ là thời gian (tính bằng giây) mà laser hiển thị.

  4. Tạo một hàm của LaserRenderer có tên createLaser với hai tham số có tên là toolHandleendPosition.


    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- Thời gian mà laser sẽ hiển thị
    -- Tạo một tia laser từ vị trí bắt đầu về phía một vị trí kết thúc
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. Khai báo một biến có tên startPosition và thiết lập thuộc tính Position của toolHandle làm giá trị của nó. Đây sẽ là vị trí của súng bắn laser của người chơi.

  6. 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 nó để lấy chiều dài của tia laser.


    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. Khai 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à kết thúc của tia. Sử dụng CFrame.lookAt để tạo một CFrame ở vị trí startPosition và hướng về endPosition. Nhân điều này với một CFrame mới có giá trị trên trục Z là một nửa của -laserDistance để lấy điểm giữa.


    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)
    end

Tạo phần laser

Bây giờ bạn đã biết vị trí 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.

  1. Khai báo một biến laserPart và gán cho nó một thể hiện mới của Part.

  2. Thiết lập các thuộc tính sau của laserPart:

    1. Size: Vector3.new(0.2, 0.2, laserDistance)
    2. CFrame: laserCFrame
    3. Anchored: true
    4. CanCollide: false
    5. Color: Color3.fromRGB(225, 0, 0) (một màu đỏ mạnh)
    6. Material: Enum.Material.Neon
  3. Gán laserPart cho Workspace.

  4. Thêm phần vào dịch vụ Debris để nó được xóa sau khoản thời gian trong biến SHOT_DURATION.


    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(225, 0, 0)
    laserPart.Material = Enum.Material.Neon
    laserPart.Parent = workspace
    -- Thêm tia laser vào dịch vụ Debris để được xóa & dọn dẹp
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

Bây giờ hàm để hiển thị tia laser đã hoàn tất, nó có thể được gọi bởi ToolController.

  1. Ở đầu script ToolController, khai báo một biến có tên LaserRenderer và yêu cầu ModuleScript 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
  2. Ở cuối hàm fireWeapon, gọi hàm tạo tia laser của LaserRenderer bằng cách sử dụng tay cầm công cụ và hitPosition làm tham số.


    -- Tính toán vị trí cuối cùng dựa trên khoảng cách tối đa laser
    hitPosition = tool.Handle.Position + directionVector
    end
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  3. Thử nghiệm vũ khí bằng cách nhấp vào nút Play. Một tia laser nên hiển thị giữa vũ khí và con chuột khi công cụ được kích hoạt.

Kiểm soát tốc độ 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 ra quá nhiều sát thương trong thời gian ngắn. Điều này có thể được kiểm soát bằng cách kiểm tra xem đã đủ thời gian kể từ lần bắn cuối cùng của người chơi hay chưa.

  1. 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 nó một giá trị theo ý bạn; trong ví dụ này sử dụng 0.3 giây.

  2. Khai báo một biến khác bên dưới có tên là timeOfPreviousShot với giá trị là 0. Điều này lưu trữ thời gian lần cuối người chơi bắn và sẽ được cập nhật sau mỗi lần bắn.


    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 300
    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
  3. Tạo một hàm có tên canShootWeapon mà không có tham số nào. Hàm này sẽ xem xem đã đủ thời gian kể từ lần bắn trước đó và trả về true hoặc false.


    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- Kiểm tra xem đã đủ thời gian kể từ lần bắn trước đó
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. Bên trong hàm khai báo một biến có tên là 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 ngẫu nhiên được sử dụng rộng rãi để tính toán thời gian).

  5. 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 đã đủ thời gian 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
  6. Ở 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 + directionVector
    end
    timeOfPreviousShot = tick()
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  7. Bên trong hàm toolActivated, tạo một câu lệnh if và gọi hàm canShootWeapon để kiểm tra xem vũ khí có thể được bắn hay không.


    local function toolActivated()
    if canShootWeapon() then
    tool.Handle.Activate:Play()
    fireWeapon()
    end
    end

Khi bạn thử nghiệm súng bắn, bạn sẽ thấy rằng dù bạn có nhấp chuột nhanh đến đâu, 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 client không thể gây sát thương trực tiếp cho các client khác; máy chủ cần phải chịu trách nhiệm phát xít khi một người chơi bị va chạm.

Các client có thể sử dụng một RemoteEvent để báo cho máy chủ rằng một nhân vật đã bị va chạm. Những điều này nên được lưu trữ trong ReplicatedStorage, nơi mà chúng có thể nhìn thấy cả client và server.

  1. Tạo một Thư mục trong ReplicatedStorage có tên là Events.

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

  3. Trong ToolController, tạo 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.Parent
    local eventsFolder = ReplicatedStorage.Events
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  4. Thay thế câu lệnh in "Người chơi bị va chạm" trong hàm fireWeapon bằng một dòng Luau để bắn sự kiện DamageCharacter với biến characterModel làm đối số.


    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 tối đa laser
    hitPosition = tool.Handle.Position + directionVector
    end

Máy chủ cần phải gây sát thương cho người chơi đã bị va chạm khi sự kiện được bắn.

  1. Chèn một Script vào ServerScriptService và đặt tên là ServerLaserManager.

  2. Khai báo một biến có tên LASER_DAMAGE và gán cho nó giá trị 10, hoặc một giá trị theo ý của bạn.

  3. Tạo một hàm có tên damageCharacter với hai tham số có tên là playerFiredcharacterToDamage.

  4. Bên trong hàm, tìm Humanoid của nhân vật và trừ LASER_DAMAGE khỏi sức khỏe của nó.

  5. Kết nối hàm damageCharacter với sự kiện từ xa DamageCharacter trong thư mục Events.


    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    function damageCharacter(playerFired, characterToDamage)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- Giảm sức khỏe của nhân vật
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- Kết nối sự kiện với các hàm phù hợp
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. Thử nghiệm súng bắn với 2 người chơi bằng cách khởi động một máy chủ cục bộ. Khi bạn bắn vào người chơi khác, sức khỏe của họ sẽ giảm đi số nào được chỉ định 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 client 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 server thì mọi người sẽ đều có thể nhìn thấy nó. Tuy nhiên, sẽ có một sự trễ nhỏ giữa việc client bắn vũ khí và server nhận được thông tin về cú bắn. Điều này có nghĩa là client đang bắn sẽ thấy một sự 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 do đó.

Để giải quyết vấn đề này, mỗi client sẽ tạo tia laser của riêng họ. Điều này có nghĩa rằng client đang bắn sẽ thấy tia laser ngay lập tức. Các client khác sẽ trải qua một độ trễ nhỏ giữa khi người chơi khác bắn và khi có một tia xuất hiện. Đây là kịch bản tốt nhất: không có cách nào để truyền đạt tia laser của một client cho các client khác nhanh hơn.

Client của Shooter

Đầu tiên, client cần thông báo cho server rằng họ đã bắn một tia laser và cung cấp vị trí cuối cùng.

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

  2. Tìm hàm fireWeapon trong script ToolController. Ở cuối hàm, bắn sự kiện LaserFired bằng cách sử dụng hitPosition làm đối số.


    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    eventsFolder.LaserFired:FireServer(hitPosition)
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end

Máy chủ

Máy chủ bây giờ phải nhận sự kiện rằng client đã bắn và thông báo cho tất cả các client về vị trí bắt đầu và kết thúc của tia laser để họ cũng có thể hiển thị nó.

  1. Trong script ServerLaserManager, tạo một hàm có tên playerFiredLaser trên hàm damageCharacter với hai tham số gọi là playerFiredendPosition.

  2. Kết nối hàm với sự kiện LaserFired từ xa.


    -- Thông báo cho tất cả các client rằng một tia laser đã được bắn để họ có thể hiển thị tia
    local function playerFiredLaser(playerFired, endPosition)
    end

    -- Kết nối sự kiện với các hàm phù hợp
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

Máy chủ cần biết vị trí bắt đầu của tia laser. Điều này có thể được gởi từ client, nhưng tốt nhất là nên tránh tin tưởng client bất cứ khi nào có thể. Vị trí tay cầm của 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 nó từ đó.

  1. Tạo một hàm getPlayerToolHandle trên hàm playerFiredLaser với một tham số gọi là player.

  2. Sử dụng đoạn mã dưới đây để tìm kiếm nhân vật của người chơi 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ầm
    local function getPlayerToolHandle(player)
    local weapon = player.Character:FindFirstChildOfClass("Tool")
    if weapon then
    return weapon:FindFirstChild("Handle")
    end
    end
    -- Thông báo cho tất cả các client rằng một tia laser đã được bắn để họ có thể hiển thị tia
    local function playerFiredLaser(playerFired, endPosition)

Bây giờ server có thể gọi FireAllClients trên sự kiện LaserFired để gửi thông tin cần thiết để hiển thị tia cho các client. Điều này bao gồm người chơi đã bắn tia (để client của người đó không hiển thị tia hai lần), tay cầm của súng bắn (tác động như một vị trí bắt đầu cho tia) và vị trí cuối cùng của tia.

  1. 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 là toolHandle.

  2. Nếu toolHandle tồn tại, bắn sự kiện LaserFired cho tất cả các client bằng cách sử dụng playerFired, toolHandleendPosition làm đối số.


    -- Thông báo cho tất cả các client rằng một tia laser đã được bắn để họ có thể hiển thị tia
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

Hiển thị trên các client

Bây giờ FireAllClients đã được gọi, mỗi client sẽ nhận một sự kiện từ server để hiển thị một tia laser. Mỗi client có thể tái sử dụng module LaserRenderer từ trước để hiển thị tia laser bằng cách sử dụng vị trí tay cầm của vũ khí và giá trị vị trí cuối cùng mà server gửi.

  1. Tạo một LocalScript trong StarterPlayerScripts có tên là ClientLaserManager.

  2. Bên trong script, yêu cầu module LaserRenderer.

  3. Tạo một hàm có tên createPlayerLaser với các tham số playerWhoShot, toolHandleendPosition.

  4. Kết nối hàm với sự kiện LaserFired trong thư mục Events.

  5. Trong hàm, sử dụng một câu lệnh if để kiểm tra xem playerWhoShot không bằng LocalPlayer.

  6. Bên trong câu lệnh if, gọi hàm createLaser từ module LaserRenderer bằng cách sử dụng toolHandleendPosition 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 một 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)
  7. Thử nghiệm súng bắn với 2 người chơi bằng cách khởi động một máy chủ cục bộ. Đặt mỗi client ở hai bên khác nhau của màn hình của bạn để bạn có thể thấy cả hai cửa sổ cùng một lúc. Khi bạn bắn từ một client, bạn sẽ thấy tia laser trên client khác.

Hiệu ứng âm thanh

Âm thanh bắn hiện tại chỉ phát trên client đang bắn viên đạn. Bạn sẽ cần di chuyển mã để phát âm thanh sao cho các người chơi khác cũng nghe thấy.

  1. Trong script ToolController, điều hướng đến hàm toolActivated và xóa dòng mã phát âm thanh Kích hoạt.


    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. Ở dưới đáy 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 Kích hoạt.

  3. Sử dụng một câu lệnh if để kiểm tra xem shootingSound có tồn tại; 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 & 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

Bảo mật remotes sử dụng xác thực

Nếu server không kiểm tra dữ liệu từ các yêu cầu vào, một hacker có thể lợi 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ả mạo đến server. Điều quan trọng là sử dụng xác thực phía server để ngăn chặn điều này.

Trong hình thức hiện tại, sự kiện từ xa 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 các giá trị được gửi đến server có thực tế hay không. Trong trường hợp này, server 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 laser có nằm trong một ngưỡng nhất định hay không.
  • Raycast giữa vũ khí đã bắn laser và vị trí bị va chạm để đảm bảo cú bắn là khả thi và không xuyên qua bất kỳ bức tường nào.

Client

Client cần gửi cho server vị trí bị va chạm bởi raycast để nó có thể kiểm tra xem khoảng cách có thực tế hay không.

  1. Trong ToolController, điều hướng đến dòng mà sự kiện DamageCharacter được bắn trong hàm fireWeapon.

  2. Thêm hitPosition làm một đối số.


    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    eventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)
    end
    end

Server

Client bây giờ đang gửi một tham số bổ sung qua sự kiện DamageCharacter, vì vậy ServerLaserManager cần được điều chỉnh để chấp nhận nó.

  1. 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ật
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. Dưới hàm getPlayerToolHandle, tạo một hàm có tên isHitValid với ba tham số: playerFired, characterToDamagehitPosition.


    end
    local 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.

  1. Khai báo một biến có tên MAX_HIT_PROXIMITY ở đầu script và gán cho nó một giá trị là 10. Đây sẽ là khoảng cách tối đa cho phép giữa cú bắn và nhân vật. Cần một độ dung sai vì nhân vật có thể đã di chuyển một chút kể từ khi client bắn sự kiện.


    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    local MAX_HIT_PROXIMITY = 10
  2. Trong 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, hãy 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ạm
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > MAX_HIT_PROXIMITY then
    return false
    end
    end

Kiểm tra thứ hai sẽ liên quan đến một raycast giữa vị trí 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 cú bắn không hợp lệ vì có một cái gì đó chặn cú bắn.

  1. Sao chép đoạn 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ạm
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 then
    return false
    end
    -- Kiểm tra xem có bắ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 được tìm thấy mà không phải là nhân vật, thì cú bắn bị bỏ qua
    if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
    return false
    end
    end
    return true
    end
  2. Khai báo một biến trong hàm damageCharacter có tên là validShot. Gán cho nó kết quả từ việc gọi hàm isHitValid với ba đối số: playerFired, characterToDamagehitPosition.

  3. Trong câu lệnh if bên dưới, thêm một toán tử and để kiểm tra nếu validShottrue.


    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

Bây giờ sự kiện damageCharacter từ xa đã an toàn hơn và sẽ ngăn chặ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ó ý định xấu thường tìm cách vượt qua xác thực; việc giữ an toàn cho các sự kiện từ xa là một nỗ lực liên tục.

Súng bắn laser của bạn bây giờ đã 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ử Xác định đầu vào người dùng hướng dẫn để tìm hiểu cách bạn có thể thêm hành động nạp lại vào súng bắn laser của bạn, hoặc tạo một bản đồ thú vị và thử nghiệm súng bắn laser của bạn với những 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 đã đủ thời gian 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ừ gốc của tia về hướng của nó
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Trả về điểm 3D giao cắt
return raycastResult.Position
else
-- Không có đối tượng nào bị va chạm, 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 được 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ự làm tổn thương chính mình
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 tử của một 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 tối đa laser
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à laser sẽ hiển thị
-- Tạo một tia laser từ vị trí bắt đầu về phía 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 & 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 LaserRenderer

ServerLaserManager


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 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 được tìm thấy mà không phải là nhân vật thì cú bắn bị bỏ qua
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Thông báo cho tất cả các client rằng một tia laser đã được bắn để họ có thể hiển thị tia
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 sự kiện với các hàm phù 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 một 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)
©2026 Roblox Corporation. Roblox, logo Roblox và Powering Imagination là các nhãn hiệu đã đăng ký và chưa đăng ký của chúng tôi tại Hoa Kỳ và các quốc gia khác.