Memulai
Tutorial menengah

Deteksi hit dengan laser

*Konten ini diterjemahkan menggunakan AI (Beta) dan mungkin mengandung kesalahan. Untuk melihat halaman ini dalam bahasa Inggris, klik di sini.

Dalam tutorial ini, Anda akan belajar cara menembakkan laser dari blaster di Buat alat pemain dan mendeteksi apakah laser tersebut mengenai pemain atau tidak.

Raycasting untuk menemukan tabrakan

Raycasting membuat sinar tak terlihat dari posisi awal menuju arah tertentu dengan panjang yang ditentukan. Jika sinar tersebut bertabrakan dengan objek atau terrain di jalurnya, ia akan mengembalikan informasi tentang tabrakan seperti posisi dan objek yang bertabrakan.

Raycast dari A menuju B yang bertabrakan dengan dinding

Temukan lokasi mouse

Sebelum laser dapat ditembakkan, Anda harus terlebih dahulu mengetahui di mana pemain mengarahkan. Ini dapat ditemukan dengan melakukan raycasting dari lokasi mouse 2D pemain di layar langsung ke depan dari kamera ke dunia 3D. Sinar akan bertabrakan dengan apa pun yang dituju pemain dengan mouse.

  1. Buka skrip ToolController di dalam alat Blaster dari Buat alat pemain. Jika Anda belum menyelesaikan tutorial itu, Anda dapat mengunduh model Blaster dan menyisipkannya ke dalam StarterPack.

  2. Di bagian atas skrip, deklarasikan sebuah konstanta bernama MAX_MOUSE_DISTANCE dengan nilai 1000.

  3. Buat fungsi bernama 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
    -- Hubungkan acara ke fungsi yang sesuai
    tool.Equipped:Connect(toolEquipped)
    tool.Activated:Connect(toolActivated)
  4. Gunakan fungsi GetMouseLocation dari UserInputService untuk mendapatkan lokasi mouse 2D pemain di layar. Tetapkan ini ke variabel bernama mouseLocation.

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

Sekarang lokasi mouse 2D sudah diketahui, properti X dan Y-nya dapat digunakan sebagai parameter untuk fungsi Camera:ViewportPointToRay(), yang membuat Ray dari layar ke dunia 3D.

  1. Gunakan properti X dan Y dari mouseLocation sebagai argumen untuk fungsi ViewportPointToRay(). Tetapkan ini ke variabel bernama screenToWorldRay.

    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Buat sinar dari lokasi mouse 2D
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    end

Saatnya menggunakan fungsi Raycast untuk memeriksa apakah sinar mengenai objek. Ini memerlukan posisi awal dan vektor arah: dalam contoh ini, Anda akan menggunakan properti origin dan direction dari screenToWorldRay.

Panjang vektor arah menentukan seberapa jauh sinar akan bergerak. Sinar perlu sepanjang MAX_MOUSE_DISTANCE, jadi Anda harus mengalikan vektor arah dengan MAX_MOUSE_DISTANCE.

  1. Deklarasikan variabel bernama directionVector dan tetapkan nilainya dari screenToWorldRay.Direction dikalikan dengan MAX_MOUSE_DISTANCE.

    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Buat sinar dari lokasi mouse 2D
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- Vektor arah unit dari sinar dikalikan dengan jarak maksimum
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
  2. Panggil fungsi Raycast dari workspace, dengan melewatkan properti Origin dari screenToWorldRay sebagai argumen pertama dan directionVector sebagai argumen kedua. Tetapkan ini ke variabel bernama raycastResult.

    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Buat sinar dari lokasi mouse 2D
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- Vektor arah unit dari sinar dikalikan dengan jarak maksimum
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
    -- Raycast dari asal sinar menuju arah
    local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)

Informasi tabrakan

Jika operasi raycast menemukan objek yang terkena sinar, ia akan mengembalikan RaycastResult, yang berisi informasi tentang tabrakan antara sinar dan objek.

Properti RaycastResultDeskripsi
InstanceObjek BasePart atau Terrain yang disilangkan oleh sinar.
PositionDi mana perpotongan terjadi; biasanya titik langsung di permukaan bagian atau terrain.
MaterialMaterial di titik tabrakan.
NormalVektor normal dari wajah yang disilangkan. Ini dapat digunakan untuk menentukan arah wajah tersebut.

Properti Position akan menjadi posisi objek yang sedang dihover oleh mouse. Jika mouse tidak menghover objek mana pun dalam jarak MAX_MOUSE_DISTANCE, raycastResult akan bernilai nil.

  1. Buat pernyataan if untuk memeriksa apakah raycastResult ada.

  2. Jika raycastResult memiliki nilai, kembalikan properti Position-nya.

  3. Jika raycastResult adalah nil, maka temukan akhir dari raycast. Hitung posisi 3D dari mouse dengan menambahkan screenToWorldRay.Origin dan directionVector bersama-sama.

local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Buat sinar dari lokasi mouse 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Vektor arah unit dari sinar dikalikan dengan jarak maksimum
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast dari asal sinar menuju arah
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Kembalikan titik 3D dari perpotongan
return raycastResult.Position
else
-- Tidak ada objek yang terkena jadi hitung posisi di akhir sinar
return screenToWorldRay.Origin + directionVector
end
end

Tembak ke arah target

Sekarang posisi mouse 3D sudah diketahui, itu dapat digunakan sebagai posisi target untuk menembakkan laser. Sinar kedua dapat ditembakkan antara posisi senjata pemain dan posisi target menggunakan fungsi Raycast.

  1. Deklarasikan sebuah konstanta bernama MAX_LASER_DISTANCE di bagian atas skrip dan tetapkan nilainya ke 500, atau jarak yang Anda pilih untuk blaster laser.

    local UserInputService = game:GetService("UserInputService")
    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  2. Buat fungsi bernama fireWeapon di bawah fungsi getWorldMousePosition.

  3. Panggil getWorldMousePosition dan tetapkan hasilnya ke variabel bernama mousePosition. Ini akan menjadi posisi target untuk raycast.

    -- Tidak ada objek yang terkena jadi hitung posisi di akhir sinar
    return screenToWorldRay.Origin + directionVector
    end
    end
    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    end
    local function toolEquipped()
    tool.Handle.Equip:Play()
    end

Kali ini, vektor arah untuk fungsi raycast akan mewakili arah dari posisi alat pemain ke lokasi target.

  1. Deklarasikan variabel bernama targetDirection dan hitung vektor arah dengan mengurangkan posisi alat dari mouseLocation.

  2. Normalisasi vektor dengan menggunakan properti Unit-nya. Ini memberikannya magnitudo 1, yang memudahkan untuk mengalikan dengan panjang nanti.

    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    -- Hitung vektor arah yang dinormalisasi dan kalikan dengan jarak laser
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. Deklarasikan variabel bernama directionVector dan tetapkan nilainya dari targetDirection dikalikan dengan MAX_LASER_DISTANCE.

    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    -- Arah untuk menembakkan senjata, dikalikan dengan jarak maksimum
    local directionVector = targetDirection * MAX_LASER_DISTANCE
    end

Objek RaycastParams dapat digunakan untuk menyimpan parameter tambahan untuk fungsi raycast. Ini akan digunakan di blaster laser Anda untuk memastikan raycast tidak secara tidak sengaja bertabrakan dengan pemain yang menembakkan senjata. Bagian mana pun yang termasuk dalam properti FilterDescendantsInstances dari objek RaycastParams akan diabaikan dalam raycast.

  1. Lanjutkan fungsi fireWeapon dan deklarasikan variabel bernama weaponRaycastParams. Tetapkan objek RaycastParams baru ke dalamnya.

  2. Buat tabel yang berisi karakter lokal pemain dan tetapkan ke properti weaponRaycastParams.FilterDescendantsInstances.

  3. Raycast dari posisi pegangan alat pemain, dalam arah menuju directionVector. Ingat untuk menambahkan weaponRaycastParams sebagai argumen kali ini. Tetapkan ini ke variabel bernama 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()
-- Hitung vektor arah yang dinormalisasi dan kalikan dengan jarak laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- Arah untuk menembakkan senjata dikalikan dengan jarak maksimum
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Abaikan karakter pemain untuk mencegah mereka melukai diri sendiri
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
end

Akhirnya, Anda perlu memeriksa apakah operasi raycast mengembalikan nilai. Jika nilai dikembalikan, objek telah terkena sinar dan laser dapat dibuat antara senjata dan lokasi yang terkena. Jika tidak ada yang dikembalikan, posisi akhir perlu dihitung untuk membuat laser.

  1. Deklarasikan variabel kosong bernama hitPosition.

  2. Gunakan pernyataan if untuk memeriksa apakah weaponRaycastResult memiliki nilai. Jika objek terkena, tetapkan weaponRaycastResult.Position ke hitPosition.

    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Periksa apakah ada objek yang terkena antara posisi awal dan akhir
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. Jika weaponRaycastResult tidak memiliki nilai, hitung posisi akhir dari raycast dengan menambahkan posisi pegangan alat dengan directionVector. Tetapkan ini ke hitPosition.

    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Periksa apakah ada objek yang terkena antara posisi awal dan akhir
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- Hitung posisi akhir berdasarkan jarak maksimum laser
    hitPosition = tool.Handle.Position + directionVector
    end
    end
  4. Navigasikan ke fungsi toolActivated dan panggil fungsi fireWeapon sehingga laser ditembakkan setiap kali alat diaktifkan.

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

Periksa objek yang terkena

Untuk mengetahui apakah objek yang terkena laser adalah bagian dari karakter pemain atau hanya bagian dari pemandangan, Anda perlu mencari Humanoid, karena setiap karakter memilikinya.

Pertama, Anda perlu menemukan model karakter. Jika bagian dari karakter terkena, Anda tidak dapat mengasumsikan bahwa orang tua dari objek yang terkena adalah karakter. Laser bisa saja mengenai bagian tubuh, aksesori, atau alat, yang semuanya terletak di bagian yang berbeda dari hierarki karakter.

Anda dapat menggunakan FindFirstAncestorOfClass untuk menemukan nenek moyang model karakter dari objek yang terkena laser, jika ada. Jika Anda menemukan model dan itu mengandung humanoid, dalam banyak kasus Anda dapat mengasumsikan itu adalah karakter.

  1. Tambahkan kode yang disorot di bawah ini ke pernyataan if weaponRaycastResult untuk memeriksa apakah karakter terkena.

    -- Periksa apakah ada objek yang terkena antara posisi awal dan akhir
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    -- Instance yang terkena akan menjadi anak dari model karakter
    -- Jika humanoid ditemukan dalam model maka kemungkinan itu adalah karakter pemain
    local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    print("Pemain terkena")
    end
    end
    else
    -- Hitung posisi akhir berdasarkan jarak maksimum laser
    hitPosition = tool.Handle.Position + directionVector
    end

Sekarang blaster laser harus mencetak Pemain terkena ke jendela Output setiap kali operasi raycast mengenai pemain lain.

Uji dengan beberapa pemain

Dua pemain diperlukan untuk menguji apakah raycast senjata menemukan pemain lain, jadi Anda perlu memulai server lokal. Lihat simulasi multi-klien untuk instruksi.

Di satu klien, uji menembak pemain lain dengan senjata dengan mengklik mereka. "Pemain terkena" harus ditampilkan di output setiap kali seorang pemain ditembak.

Temukan posisi laser

Blaster harus menembakkan sinar cahaya merah ke targetnya. Fungsi untuk ini akan berada di dalam ModuleScript sehingga dapat digunakan kembali di skrip lain nanti. Pertama, skrip perlu menemukan posisi di mana sinar laser harus dirender.

  1. Buat ModuleScript bernama LaserRenderer, yang diparentkan ke StarterPlayerScripts di bawah StarterPlayer.

  2. Buka skrip dan ganti nama tabel modul dengan nama skrip LaserRenderer.

  3. Deklarasikan variabel bernama SHOT_DURATION dengan nilai 0.15. Ini akan menjadi jumlah waktu (dalam detik) sinar laser terlihat.

  4. Buat fungsi LaserRenderer bernama createLaser dengan dua parameter bernama toolHandle dan endPosition.

    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- Waktu sinar laser terlihat
    -- Buat sinar laser dari posisi awal menuju posisi akhir
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. Deklarasikan variabel bernama startPosition dan tetapkan properti Position dari toolHandle sebagai nilainya. Ini akan menjadi posisi blaster laser pemain.

  6. Deklarasikan variabel bernama laserDistance dan kurangi endPosition dari startPosition untuk menemukan perbedaan antara kedua vektor. Gunakan properti Magnitude dari ini untuk mendapatkan panjang sinar laser.

    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. Deklarasikan variabel laserCFrame untuk menyimpan posisi dan orientasi sinar laser. Posisi perlu menjadi titik tengah dari awal dan akhir sinar. Gunakan CFrame.lookAt untuk membuat CFrame baru yang terletak di startPosition dan menghadap ke endPosition. Kalikan ini dengan CFrame baru dengan nilai sumbu Z setengah dari negatif laserDistance untuk mendapatkan titik tengah.

    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

Buat bagian laser

Sekarang Anda tahu di mana untuk membuat sinar laser, Anda perlu menambahkan sinar itu sendiri. Ini dapat dilakukan dengan mudah menggunakan bagian Neon.

  1. Deklarasikan variabel laserPart dan tetapkan ke instansi baru Part.

  2. Atur properti berikut dari 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) (warna merah yang kuat)
    6. Material: Enum.Material.Neon
  3. Parent laserPart ke Workspace.

  4. Tambahkan bagian ke layanan Debris sehingga dihapus setelah jumlah detik dalam variabel 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
    -- Tambahkan sinar laser ke layanan Debris untuk dihapus & dibersihkan
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

Sekarang fungsi untuk merender sinar laser sudah lengkap, dapat dipanggil oleh ToolController.

  1. Di bagian atas skrip ToolController, deklarasikan variabel bernama LaserRenderer dan require modul LaserRenderer yang terletak di PlayerScripts.

    local UserInputService = game:GetService("UserInputService")
    local Players = game:GetService("Players")
    local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
    local tool = script.Parent
  2. Di bagian bawah fungsi fireWeapon, panggil fungsi createLaser dari LaserRenderer menggunakan pegangan alat dan hitPosition sebagai argumen.

    -- Hitung posisi akhir berdasarkan jarak maksimum laser
    hitPosition = tool.Handle.Position + directionVector
    end
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  3. Uji senjata dengan mengklik tombol Play. Sinar laser harus terlihat antara senjata dan mouse saat alat diaktifkan.

Kontrol laju tembakan senjata

Senjata memerlukan jeda antara setiap tembakan untuk menghentikan pemain dari memberikan terlalu banyak kerusakan dalam waktu singkat. Ini dapat dikontrol dengan memeriksa apakah cukup waktu telah berlalu sejak pemain terakhir kali menembak.

  1. Deklarasikan variabel di bagian atas ToolController bernama FIRE_RATE. Ini akan menjadi waktu minimum antara setiap tembakan. Berikan nilai yang Anda pilih; contoh ini menggunakan 0.3 detik.

  2. Deklarasikan variabel lain di bawahnya bernama timeOfPreviousShot dengan nilai 0. Ini menyimpan waktu terakhir pemain menembak dan akan diperbarui dengan setiap tembakan.

    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 300
    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
  3. Buat fungsi bernama canShootWeapon tanpa parameter. Fungsi ini akan melihat berapa banyak waktu yang telah berlalu sejak tembakan sebelumnya dan mengembalikan true atau false.

    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- Periksa apakah cukup waktu telah berlalu sejak tembakan sebelumnya
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. Di dalam fungsi, deklarasikan variabel bernama currentTime; tetapkan nilainya dengan hasil pemanggilan fungsi tick(). Ini mengembalikan berapa banyak waktu yang telah berlalu, dalam detik, sejak 1 Januari 1970 (tanggal acak yang banyak digunakan untuk menghitung waktu).

  5. Kurangi timeOfPreviousShot dari currentTime dan kembalikan false jika hasilnya lebih kecil dari FIRE_RATE; jika tidak, kembalikan true.

    -- Periksa apakah cukup waktu telah berlalu sejak tembakan sebelumnya
    local function canShootWeapon()
    local currentTime = tick()
    if currentTime - timeOfPreviousShot < FIRE_RATE then
    return false
    end
    return true
    end
  6. Di akhir fungsi fireWeapon, perbarui timeOfPreviousShot setiap kali senjata ditembakkan menggunakan tick.

    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  7. Di dalam fungsi toolActivated, buat pernyataan if dan panggil canShootWeapon untuk memeriksa apakah senjata dapat ditembakkan.

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

Saat Anda menguji blaster, Anda harus menemukan bahwa tidak peduli seberapa cepat Anda mengklik, akan selalu ada jeda singkat 0.3 detik antara setiap tembakan.

Kerusakan pada pemain

Klien tidak dapat merusak klien lain secara langsung; server perlu bertanggung jawab untuk memberikan kerusakan ketika seorang pemain terkena.

Klien dapat menggunakan RemoteEvent untuk memberi tahu server bahwa karakter telah terkena. Ini harus disimpan di ReplicatedStorage, di mana mereka terlihat oleh klien dan server.

  1. Buat Folder di ReplicatedStorage bernama Events.

  2. Sisipkan RemoteEvent ke dalam folder Events dan beri nama DamageCharacter.

  3. Di ToolController, buat variabel di awal skrip untuk ReplicatedStorage dan folder 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. Ganti pernyataan cetak "Pemain terkena" di fireWeapon dengan baris Luau untuk menembakkan event DamageCharacter dengan variabel characterModel sebagai argumen.

    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
    -- Hitung posisi akhir berdasarkan jarak maksimum laser
    hitPosition = tool.Handle.Position + directionVector
    end

Server perlu memberikan kerusakan kepada pemain yang telah terkena ketika event tersebut ditembakkan.

  1. Sisipkan Script ke dalam ServerScriptService dan beri nama ServerLaserManager.

  2. Deklarasikan variabel bernama LASER_DAMAGE dan tetapkan nilainya ke 10, atau nilai pilihan Anda.

  3. Buat fungsi bernama damageCharacter dengan dua parameter bernama playerFired dan characterToDamage.

  4. Di dalam fungsi, temukan Humanoid dari karakter dan kurangi LASER_DAMAGE dari kesehatannya.

  5. Hubungkan fungsi damageCharacter ke event DamageCharacter di folder 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
    -- Kurangi kesehatan dari karakter
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- Hubungkan acara ke fungsi yang sesuai
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. Uji blaster dengan 2 pemain dengan memulai server lokal. Ketika Anda menembak pemain lain, kesehatan mereka akan berkurang sesuai dengan nilai yang ditetapkan pada LASER_DAMAGE.

Render sinar laser pemain lain

Saat ini, sinar laser dibuat oleh klien yang menembakkan senjata, jadi hanya mereka yang dapat melihat sinar laser tersebut.

Jika sinar laser dibuat di server, maka semua orang akan dapat melihatnya. Namun, akan ada sedikit penundaan antara klien yang menembakkan senjata dan server menerima informasi tentang tembakan. Ini berarti klien yang menembakkan senjata akan melihat penundaan antara saat mereka mengaktifkan senjata dan saat mereka melihat sinar laser; senjata akan terasa laggy sebagai hasilnya.

Untuk menyelesaikan masalah ini, setiap klien akan membuat sinar laser mereka sendiri. Ini berarti klien yang menembakkan senjata akan melihat sinar laser secara instan. Klien lain akan mengalami sedikit penundaan antara saat pemain lain menembak dan sinar muncul. Ini adalah skenario terbaik: tidak ada cara untuk mengkomunikasikan sinar laser satu klien ke klien lain lebih cepat.

Klien penembak

Pertama, klien perlu memberi tahu server bahwa mereka telah menembakkan laser dan memberikan posisi akhir.

  1. Sisipkan RemoteEvent ke dalam folder Events di ReplicatedStorage dan beri nama LaserFired.

  2. Temukan fungsi fireWeapon di skrip ToolController. Di akhir fungsi, tembak event LaserFired menggunakan hitPosition sebagai argumen.

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

Server

Server sekarang harus menerima event bahwa klien telah menembak dan memberi tahu semua klien tentang posisi awal dan akhir sinar laser sehingga mereka juga dapat merendernya.

  1. Di skrip ServerLaserManager, buat fungsi bernama playerFiredLaser di atas damageCharacter dengan dua parameter bernama playerFired dan endPosition.

  2. Hubungkan fungsi ke event LaserFired.

    -- Beri tahu semua klien bahwa laser telah ditembakkan sehingga mereka dapat menampilkan laser
    local function playerFiredLaser(playerFired, endPosition)
    end
    -- Hubungkan acara ke fungsi yang sesuai
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

Server memerlukan posisi awal dari laser. Ini bisa dikirim dari klien, tetapi sebaiknya hindari mempercayai klien jika memungkinkan. Pegangan senjata karakter akan menjadi posisi awal, jadi server dapat menemukannya dari sana.

  1. Buat fungsi getPlayerToolHandle di atas fungsi playerFiredLaser dengan parameter bernama player.

  2. Gunakan kode berikut untuk mencari karakter pemain untuk senjata dan mengembalikan objek pegangan.

    local LASER_DAMAGE = 10
    -- Temukan pegangan alat yang dipegang pemain
    local function getPlayerToolHandle(player)
    local weapon = player.Character:FindFirstChildOfClass("Tool")
    if weapon then
    return weapon:FindFirstChild("Handle")
    end
    end
    -- Beri tahu semua klien bahwa laser telah ditembakkan sehingga mereka dapat menampilkan laser
    local function playerFiredLaser(playerFired, endPosition)

Server sekarang dapat memanggil FireAllClients pada event LaserFired untuk mengirim informasi yang diperlukan untuk merender laser ke klien. Ini termasuk pemain yang menembakkan laser (sehingga klien untuk pemain tersebut tidak merender laser dua kali), pegangan blaster (yang bertindak sebagai posisi awal untuk laser) dan posisi akhir laser.

  1. Di dalam fungsi playerFiredLaser, panggil fungsi getPlayerToolHandle dengan playerFired sebagai argumen dan tetapkan nilainya ke variabel bernama toolHandle.

  2. Jika toolHandle ada, tembak event LaserFired untuk semua klien menggunakan playerFired, toolHandle dan endPosition sebagai argumen.

    -- Beri tahu semua klien bahwa laser telah ditembakkan sehingga mereka dapat menampilkan laser
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

Render di klien

Sekarang FireAllClients telah dipanggil, setiap klien akan menerima event dari server untuk merender sinar laser. Setiap klien dapat menggunakan kembali modul LaserRenderer dari sebelumnya untuk merender sinar laser menggunakan posisi pegangan alat dan nilai posisi akhir yang dikirim oleh server. Pemain yang menembakkan sinar laser pada awalnya harus mengabaikan event ini jika tidak mereka akan melihat 2 laser.

  1. Buat LocalScript di StarterPlayerScripts bernama ClientLaserManager.

  2. Di dalam skrip, require modul LaserRenderer.

  3. Buat fungsi bernama createPlayerLaser dengan parameter playerWhoShot, toolHandle dan endPosition.

  4. Hubungkan fungsi ke event LaserFired di folder Events.

  5. Di dalam fungsi, gunakan pernyataan if untuk memeriksa apakah playerWhoShot tidak sama dengan LocalPlayer.

  6. Di dalam pernyataan if, panggil fungsi createLaser dari modul LaserRenderer menggunakan toolHandle dan endPosition sebagai argumen.

    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))
    local eventsFolder = ReplicatedStorage.Events
    -- Tampilkan sinar laser pemain lain
    local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
    if playerWhoShot ~= Players.LocalPlayer then
    LaserRenderer.createLaser(toolHandle, endPosition)
    end
    end
    eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)
  7. Uji blaster dengan 2 pemain dengan memulai server lokal. Posisi setiap klien di sisi yang berbeda dari monitor Anda sehingga Anda dapat melihat kedua jendela sekaligus. Ketika Anda menembak di satu klien, Anda harus melihat sinar di klien lainnya.

Efek suara

Efek suara tembakan saat ini hanya diputar di klien yang menembakkan proyektil. Anda perlu memindahkan kode untuk memutar suara sehingga pemain lain juga akan mendengarnya.

  1. Di skrip ToolController, navigasikan ke fungsi toolActivated dan hapus baris yang memutar suara Activate.

    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. Di bagian bawah fungsi createLaser di LaserRenderer, deklarasikan variabel bernama shootingSound dan gunakan metode FindFirstChild() dari toolHandle untuk memeriksa suara Activate.

  3. Gunakan pernyataan if untuk memeriksa apakah shootingSound ada; jika ada, panggil fungsi Play()-nya.

    laserPart.Parent = workspace
    -- Tambahkan sinar laser ke layanan Debris untuk dihapus & dibersihkan
    Debris:AddItem(laserPart, SHOT_DURATION)
    -- Putar suara tembakan senjata
    local shootingSound = toolHandle:FindFirstChild("Activate")
    if shootingSound then
    shootingSound:Play()
    end
    end

Amankan remotes menggunakan validasi

Jika server tidak memeriksa data dari permintaan yang masuk, seorang hacker dapat menyalahgunakan fungsi dan event remote dan menggunakannya untuk mengirim nilai palsu ke server. Penting untuk menggunakan validasi sisi server untuk mencegah ini.

Dalam bentuknya saat ini, event DamageCharacter sangat rentan terhadap serangan. Hacker dapat menggunakan event ini untuk merusak pemain mana pun yang mereka inginkan tanpa menembak mereka.

Validasi adalah proses memeriksa bahwa nilai yang dikirim ke server adalah realistis. Dalam hal ini, server perlu:

  • Memeriksa apakah jarak antara pemain dan posisi yang terkena laser berada dalam batas tertentu.
  • Raycast antara senjata yang menembakkan laser dan posisi yang terkena untuk memastikan tembakan itu mungkin dan tidak melewati dinding.

Klien

Klien perlu mengirim posisi yang terkena oleh raycast ke server sehingga dapat memeriksa jaraknya realistis.

  1. Di ToolController, navigasikan ke baris di mana event DamageCharacter ditembakkan di fungsi fireWeapon.

  2. Tambahkan hitPosition sebagai argumen.

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

Server

Klien sekarang mengirim parameter tambahan melalui event DamageCharacter, jadi ServerLaserManager perlu disesuaikan untuk menerimanya.

  1. Di skrip ServerLaserManager, tambahkan parameter hitPosition ke fungsi damageCharacter.

    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- Kurangi kesehatan dari karakter
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. Di bawah fungsi getPlayerToolHandle, buat fungsi bernama isHitValid dengan tiga parameter: playerFired, characterToDamage dan hitPosition.

    end
    local function isHitValid(playerFired, characterToDamage, hitPosition)
    end

Pemeriksaan pertama akan menjadi jarak antara posisi yang terkena dan karakter yang terkena.

  1. Deklarasikan variabel bernama MAX_HIT_PROXIMITY di bagian atas skrip dan tetapkan nilai 10. Ini akan menjadi jarak maksimum yang diizinkan antara yang terkena dan karakter. Toleransi diperlukan karena karakter mungkin telah bergerak sedikit sejak klien menembakkan event.

    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    local MAX_HIT_PROXIMITY = 10
  2. Di dalam fungsi isHitValid, hitung jarak antara karakter dan posisi yang terkena. Jika jaraknya lebih besar dari MAX_HIT_PROXIMITY, maka kembalikan false.

    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Validasi jarak antara karakter yang terkena dan posisi yang terkena
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > MAX_HIT_PROXIMITY then
    return false
    end
    end

Pemeriksaan kedua akan melibatkan raycast antara senjata yang ditembakkan dan posisi yang terkena. Jika raycast mengembalikan objek yang bukan karakter, Anda dapat mengasumsikan bahwa tembakan tidak valid karena sesuatu menghalangi tembakan.

  1. Salin kode di bawah ini untuk melakukan pemeriksaan ini. Kembalikan true di akhir fungsi: jika mencapai akhir, semua pemeriksaan telah dilalui.

    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Validasi jarak antara karakter yang terkena dan posisi yang terkena
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 then
    return false
    end
    -- Periksa apakah menembak melalui dinding
    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)
    -- Jika instance yang terkena bukan karakter maka abaikan tembakan
    if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
    return false
    end
    end
    return true
    end
  2. Deklarasikan variabel di dalam fungsi damageCharacter bernama validShot. Tetapkan nilainya dari hasil panggilan ke fungsi isHitValid dengan tiga argumen: playerFired, characterToDamage dan hitPosition.

  3. Di dalam pernyataan if di bawah, tambahkan operator and untuk memeriksa apakah validShot adalah true.

    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
    if humanoid and validShot then
    -- Kurangi kesehatan dari karakter
    humanoid.Health -= LASER_DAMAGE
    end
    end

Sekarang event damageCharacter lebih aman dan akan mencegah sebagian besar pemain dari menyalahgunakannya. Perlu dicatat bahwa beberapa pemain jahat sering menemukan cara untuk menghindari validasi; menjaga keamanan event remote adalah usaha yang berkelanjutan.

Blaster laser Anda sekarang lengkap, dengan sistem deteksi hit dasar menggunakan raycasting. Cobalah tutorial Mendeteksi Input Pengguna untuk menemukan bagaimana Anda dapat menambahkan aksi pengisian ulang ke blaster laser Anda, atau buat peta yang menyenangkan dan coba blaster laser Anda dengan pemain lain!

Kode akhir

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
-- Periksa apakah cukup waktu telah berlalu sejak tembakan sebelumnya
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()
-- Buat sinar dari lokasi mouse 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Vektor arah unit dari sinar dikalikan dengan jarak maksimum
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast dari asal sinar menuju arah
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Kembalikan titik 3D dari perpotongan
return raycastResult.Position
else
-- Tidak ada objek yang terkena jadi hitung posisi di akhir sinar
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Hitung vektor arah yang dinormalisasi dan kalikan dengan jarak laser
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- Arah untuk menembakkan senjata, dikalikan dengan jarak maksimum
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Abaikan karakter pemain untuk mencegah mereka melukai diri sendiri
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- Periksa apakah ada objek yang terkena antara posisi awal dan akhir
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- Instance yang terkena akan menjadi anak dari model karakter
-- Jika humanoid ditemukan dalam model maka kemungkinan itu adalah karakter pemain
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
-- Hitung posisi akhir berdasarkan jarak maksimum 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 -- Waktu sinar laser terlihat
-- Buat sinar laser dari posisi awal menuju posisi akhir
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
-- Tambahkan sinar laser ke layanan Debris untuk dihapus & dibersihkan
Debris:AddItem(laserPart, SHOT_DURATION)
-- Putar suara tembakan senjata
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
-- Temukan pegangan alat yang dipegang pemain
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)
-- Validasi jarak antara karakter yang terkena dan posisi yang terkena
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Periksa apakah menembak melalui dinding
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)
-- Jika instance yang terkena bukan karakter maka abaikan tembakan
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Beri tahu semua klien bahwa laser telah ditembakkan sehingga mereka dapat menampilkan 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
-- Kurangi kesehatan dari karakter
humanoid.Health -= LASER_DAMAGE
end
end
-- Hubungkan acara ke fungsi yang sesuai
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
-- Tampilkan sinar laser pemain lain
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, dan Powering Imagination termasuk dalam merek dagang kami yang terdaftar dan tidak terdaftar di AS dan negara lainnya.