Dalam tutorial ini, Anda akan belajar cara memancarkan laser dari blaster di Buat alat pemain dan mendeteksi apakah laser tersebut mengenai pemain atau tidak.
Raycasting untuk menemukan tabrakan
Raycasting menciptakan sebuah sinar tak terlihat dari posisi awal menuju arah yang ditentukan dengan panjang yang ditentukan. Jika sinar tersebut bertabrakan dengan objek atau medan di jalurnya, ia akan mengembalikan informasi mengenai tabrakan tersebut seperti posisi dan objek yang terlibat.

Temukan lokasi mouse
Sebelum laser dapat ditembakkan, Anda terlebih dahulu harus mengetahui di mana pemain mengarahkan. Hal ini dapat ditemukan dengan melakukan raycasting dari lokasi mouse 2D pemain di layar langsung ke depan dari kamera ke dunia 3D. Sinar tersebut akan bertabrakan dengan apa pun yang diarahkan pemain dengan mouse.
Buka skrip ToolController di dalam alat Blaster dari Buat alat pemain. Jika Anda belum menyelesaikan tutorial tersebut, Anda dapat mengunduh model Blaster dan menyisipkannya ke dalam StarterPack.

Di bagian atas skrip, deklarasikan sebuah konstanta bernama MAX_MOUSE_DISTANCE dengan nilai 1000.
Buat fungsi bernama 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-- Sambungkan kejadian ke fungsi yang sesuaitool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)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.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
Sekarang lokasi mouse 2D diketahui, properti X dan Y-nya dapat digunakan sebagai parameter untuk fungsi Camera:ViewportPointToRay(), yang menciptakan Ray dari layar ke dunia 3D.
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()-- Membuat sinar dari lokasi mouse 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)end
Kini saatnya menggunakan fungsi Raycast untuk memeriksa apakah sinar tersebut 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.
Deklarasikan variabel bernama directionVector dan tetapkan nilai dari screenToWorldRay.Direction dikalikan dengan MAX_MOUSE_DISTANCE.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Membuat sinar dari lokasi mouse 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- Vektor arah unit dari sinar yang dikalikan dengan jarak maksimumlocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCEPanggil fungsi Raycast dari workspace, dengan 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()-- Membuat sinar dari lokasi mouse 2Dlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- Vektor arah unit dari sinar yang dikalikan dengan jarak maksimumlocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE-- Melakukan raycast dari asal sinar menuju arah sinarlocal raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
Informasi tabrakan
Jika operasi raycast menemukan objek yang tertabrak oleh sinar, ia akan mengembalikan RaycastResult, yang berisi informasi tentang tabrakan antara sinar dan objek.
| Properti RaycastResult | Deskripsi |
|---|---|
| Instance | Objek BasePart atau Terrain yang disilangkan oleh sinar. |
| Position | Tempat di mana perpotongan terjadi; biasanya titik langsung di permukaan bagian atau medan. |
| Material | Bahan di titik tabrakan. |
| Normal | Vektor normal dari muka yang terpotong. Ini dapat digunakan untuk menentukan arah mana muka menghadap. |
Properti Position akan menjadi posisi objek yang sedang dijelajahi mouse. Jika mouse tidak menjelajahi objek mana pun dalam jarak MAX_MOUSE_DISTANCE, raycastResult akan menjadi nil.
Buat pernyataan if untuk memeriksa apakah raycastResult ada.
Jika raycastResult memiliki nilai, kembalikan properti Position-nya.
Jika raycastResult adalah nil, maka temukan akhir dari raycast. Hitung posisi 3D mouse dengan menambahkan screenToWorldRay.Origin dan directionVector bersama-sama.
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Membuat sinar dari lokasi mouse 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Vektor arah unit dari sinar yang dikalikan dengan jarak maksimum
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Melakukan raycast dari asal sinar menuju arah sinar
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Kembalikan titik 3D dari perpotongan
return raycastResult.Position
else
-- Tidak ada objek yang tertabrak, jadi hitung posisi di akhir sinar
return screenToWorldRay.Origin + directionVector
end
end
Tembak ke arah target
Sekarang posisi mouse 3D diketahui, dapat digunakan sebagai posisi target untuk menembakkan laser ke arahnya. Sinar kedua dapat dipancarkan antara posisi senjata pemain dan posisi target menggunakan fungsi Raycast.
Deklarasikan konstanta bernama MAX_LASER_DISTANCE di bagian atas skrip dan tetapkan ke 500, atau jarak pilihan Anda untuk blaster laser.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Buat fungsi bernama fireWeapon di bawah fungsi getWorldMousePosition.
Panggil getWorldMousePosition dan tetapkan hasilnya ke variabel bernama mousePosition. Ini akan menjadi posisi target untuk raycast.
-- Tidak ada objek yang tertabrak, jadi hitung posisi di akhir sinarreturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()end
Kali ini, vektor arah untuk fungsi raycast akan merepresentasikan arah dari posisi alat pemain ke lokasi target.
Deklarasikan variabel bernama targetDirection dan hitung vektor arah dengan mengurangkan posisi alat dari mouseLocation.
Normalisasi vektor dengan menggunakan properti Unit-nya. Ini memberikannya besar 1, yang memudahkan untuk mengalikan dengan panjang nanti.
local function fireWeapon()local mouseLocation = getWorldMousePosition()-- Hitung vektor arah yang dinormalisasi dan kalikan dengan jarak laserlocal targetDirection = (mouseLocation - tool.Handle.Position).UnitendDeklarasikan variabel bernama directionVector dan tetapkan ke targetDirection yang dikalikan dengan MAX_LASER_DISTANCE.
local targetDirection = (mouseLocation - tool.Handle.Position).Unit-- Arah untuk menembakkan senjata, dikalikan dengan jarak maksimumlocal directionVector = targetDirection * MAX_LASER_DISTANCEend
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. Setiap bagian yang termasuk dalam properti FilterDescendantsInstances dari objek RaycastParams akan diabaikan dalam raycast.
Lanjutkan fungsi fireWeapon dan deklarasikan variabel bernama weaponRaycastParams. Tetapkan objek RaycastParams baru ke variabel ini.
Buat tabel yang berisi karakter lokal pemain dan tetapkan ke properti weaponRaycastParams.FilterDescendantsInstances.
Raycast dari posisi pegangan alat pemain, dalam arah ke arah 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 terkena 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 bahwa operasi raycast mengembalikan nilai. Jika sebuah nilai dikembalikan, maka sebuah objek telah tertabrak oleh sinar dan laser dapat dibuat antara senjata dan lokasi yang terkena. Jika tidak ada yang dikembalikan, posisi akhir perlu dihitung untuk membuat laser.
Deklarasikan variabel kosong bernama hitPosition.
Gunakan pernyataan if untuk memeriksa apakah weaponRaycastResult memiliki nilai. Jika sebuah objek tertabrak, tetapkan weaponRaycastResult.Position ke hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Periksa apakah ada objek yang tertabrak antara posisi awal dan akhirlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendJika weaponRaycastResult tidak memiliki nilai, hitung posisi akhir dari raycast dengan menjumlahkan posisi pegangan alat dengan directionVector. Tetapkan ini ke hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Periksa apakah ada objek yang tertabrak antara posisi awal dan akhirlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- Hitung posisi akhir berdasarkan jarak maksimum laserhitPosition = tool.Handle.Position + directionVectorendendArahkan ke fungsi toolActivated dan panggil fungsi fireWeapon agar 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 oleh laser adalah bagian dari karakter pemain atau hanya bagian dari pemandangan, Anda perlu mencari Humanoid, karena setiap karakter memiliki satu.
Pertama, Anda perlu menemukan model karakter. Jika bagian dari karakter tertabrak, Anda tidak dapat menganggap orang tua objek yang tertabrak adalah karakter. Laser bisa mengenai bagian tubuh, aksesori, atau alat, yang semuanya berada di bagian yang berbeda dari hierarki karakter.

Anda dapat menggunakan FindFirstAncestorOfClass untuk menemukan nenek moyang model karakter dari objek yang tertabrak oleh laser, jika ada. Jika Anda menemukan model dan itu berisi humanoid, dalam banyak kasus Anda dapat menganggap itu adalah karakter.
Tambahkan kode yang disorot di bawah ini ke pernyataan if weaponRaycastResult untuk memeriksa apakah karakter tertabrak.
-- Periksa apakah ada objek yang tertabrak antara posisi awal dan akhirlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Position-- Instance yang tertabrak akan menjadi anak dari model karakter-- Jika sebuah humanoid ditemukan dalam model maka kemungkinan besar itu karakter pemainlocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("Pemain terkena")endendelse-- Hitung posisi akhir berdasarkan jarak maksimum laserhitPosition = tool.Handle.Position + directionVectorend
Sekarang blaster laser akan 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 petunjuk.
Di satu klien, coba tembak pemain lain dengan senjata dengan mengklik mereka. "Pemain terkena" harus ditampilkan di output setiap kali seorang pemain ditembak.
Temukan posisi laser
Blaster harus memancarkan sinar merah ke arah targetnya. Fungsi untuk ini akan berada di dalam ModuleScript sehingga dapat digunakan kembali di skrip lain nanti. Pertama, skrip akan perlu menemukan posisi di mana sinar laser harus dirender.
Buat ModuleScript bernama LaserRenderer, yang di-parenting ke StarterPlayerScripts di bawah StarterPlayer.

Buka skrip dan ubah nama tabel modul menjadi nama skrip LaserRenderer.
Deklarasikan variabel bernama SHOT_DURATION dengan nilai 0.15. Ini akan menjadi periode waktu (dalam detik) sinar laser terlihat.
Buat fungsi LaserRenderer bernama createLaser dengan dua parameter bernama toolHandle dan endPosition.
local LaserRenderer = {}local SHOT_DURATION = 0.15 -- Waktu yang laser terlihat-- Membuat sinar laser dari posisi awal ke posisi akhirfunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererDeklarasikan variabel bernama startPosition dan tetapkan properti Position dari toolHandle sebagai nilainya. Ini akan menjadi posisi blaster laser pemain.
Deklarasikan variabel bernama laserDistance dan kurangi endPosition dari startPosition untuk menemukan perbedaan antara kedua vektor. Gunakan properti Magnitude untuk mendapatkan panjang sinar laser.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).MagnitudeendDeklarasikan 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.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)end
Buat bagian laser
Sekarang Anda tahu di mana harus membuat sinar laser, Anda perlu menambahkan sinar itu sendiri. Ini dapat dilakukan dengan mudah dengan sebuah bagian Neon.
Deklarasikan variabel laserPart dan tetapkan ke instance baru dari Part.
Setel properti berikut dari laserPart:
- Size: Vector3.new(0.2, 0.2, laserDistance)
- CFrame: laserCFrame
- Anchored: true
- CanCollide: false
- Color: Color3.fromRGB(225, 0, 0) (warna merah terang)
- Material: Enum.Material.Neon
Parenting laserPart ke Workspace.
Tambahkan bagian tersebut ke layanan Debris sehingga dihapus setelah jumlah detik di variabel 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-- Menambahkan sinar laser ke layanan Debris untuk dihapus & dibersihkanDebris:AddItem(laserPart, SHOT_DURATION)end
Sekarang fungsi untuk merender sinar laser sudah lengkap, dapat dipanggil oleh ToolController.
Di bagian atas skrip ToolController, deklarasikan variabel bernama LaserRenderer dan memerlukan ModulScript 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.ParentDi bagian bawah fungsi fireWeapon, panggil fungsi createLaser dari LaserRenderer menggunakan pegangan alat dan hitPosition sebagai argumen.
-- Hitung posisi akhir berdasarkan jarak maksimum laserhitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endUji senjata dengan mengklik tombol Play. Sebuah sinar laser harus terlihat antara senjata dan mouse ketika alat diaktifkan.
Kontrol laju tembakan senjata
Senjata membutuhkan jeda antara setiap tembakan untuk mencegah pemain memberikan terlalu banyak kerusakan dalam waktu singkat. Ini dapat dikontrol dengan memeriksa apakah cukup waktu telah berlalu sejak pemain terakhir kali menembak.
Deklarasikan variabel di bagian atas ToolController bernama FIRE_RATE. Ini akan menjadi waktu minimum antara setiap tembakan. Berikan nilai sesuai pilihan Anda; contoh ini menggunakan 0.3 detik.
Deklarasikan variabel lain di bawahnya bernama timeOfPreviousShot dengan nilai 0. Ini menyimpan waktu terakhir pemain menembak dan akan diperbarui setiap kali menembak.
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0Buat 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.3local timeOfPreviousShot = 0-- Periksa apakah cukup waktu telah berlalu sejak tembakan sebelumnyalocal function canShootWeapon()endlocal function getWorldMousePosition()Di dalam fungsi, deklarasikan variabel bernama currentTime; tetapkan hasil dari panggilan fungsi tick(). Ini mengembalikan berapa banyak waktu yang telah berlalu, dalam detik, sejak tanggal 1 Januari 1970 (tanggal sewenang-wenang yang umum digunakan untuk menghitung waktu).
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 sebelumnyalocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendDi akhir fungsi fireWeapon, perbarui timeOfPreviousShot setiap kali senjata ditembakkan menggunakan tick.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endDi dalam fungsi toolActivated, buat pernyataan if dan panggil canShootWeapon untuk memeriksa apakah senjata dapat ditembakkan.
local function toolActivated()if canShootWeapon() thentool.Handle.Activate:Play()fireWeapon()endend
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 harus 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.
Buat Folder di ReplicatedStorage bernama Events.

Sisipkan RemoteEvent ke dalam folder Events dan beri nama DamageCharacter.

Dalam 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.Parentlocal eventsFolder = ReplicatedStorage.Eventslocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Ganti pernyataan cetak "Pemain terkena" di fireWeapon dengan satu baris Luau untuk memicu remote event DamageCharacter dengan variabel characterModel sebagai argumen.
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endendelse-- Hitung posisi akhir berdasarkan jarak maksimum laserhitPosition = tool.Handle.Position + directionVectorend
Server perlu memberikan kerusakan pada pemain yang telah terkena saat event tersebut dipicu.
Sisipkan sebuah Skrip ke dalam ServerScriptService dan beri nama ServerLaserManager.

Deklarasikan variabel bernama LASER_DAMAGE dan atur ke 10, atau nilai pilihan Anda.
Buat fungsi bernama damageCharacter dengan dua parameter bernama playerFired dan characterToDamage.
Di dalam fungsi, temukan Humanoid karakter dan kurangi LASER_DAMAGE dari kesehatannya.
Hubungkan fungsi damageCharacter ke remote event DamageCharacter di folder Events.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Mengurangi kesehatan dari karakterhumanoid.Health -= LASER_DAMAGEendend-- Hubungkan kejadian ke fungsi yang sesuaieventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)Uji blaster dengan 2 pemain dengan memulai server lokal. Ketika Anda menembak pemain lain, kesehatan mereka akan berkurang sesuai dengan jumlah yang ditetapkan di 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 menembak senjata dan server yang menerima informasi tentang tembakan. Ini berarti klien yang menembakkan senjata akan melihat penundaan antara ketika mereka mengaktifkan senjata dan ketika mereka melihat sinar laser; senjata akan terasa lag.
Untuk menyelesaikan masalah ini, setiap klien akan membuat sinar laser mereka sendiri. Ini berarti klien yang menembakkan senjata akan melihat sinar laser segera. Klien lain akan mengalami sedikit penundaan antara saat pemain lain menembak dan ketika sinar muncul. Ini adalah skenario terbaik: tidak ada cara untuk mengkomunikasikan sinar laser satu klien ke klien lain dengan lebih cepat.
Klien penembak
Pertama, klien perlu memberi tahu server bahwa mereka telah menembakkan laser dan menyediakan posisi akhir.
Sisipkan RemoteEvent ke dalam folder Events di ReplicatedStorage dan beri nama LaserFired.

Temukan fungsi fireWeapon dalam skrip ToolController. Di akhir fungsi, kerjakan remote event LaserFired dengan menggunakan hitPosition sebagai argumen.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()eventsFolder.LaserFired:FireServer(hitPosition)LaserRenderer.createLaser(tool.Handle, hitPosition)end
Server
Server sekarang harus menerima peristiwa bahwa klien telah menembak dan memberi tahu semua klien tentang posisi awal dan akhir sinar laser sehingga mereka juga dapat merendernya.
Dalam skrip ServerLaserManager, buat fungsi bernama playerFiredLaser di atas damageCharacter dengan dua parameter bernama playerFired dan endPosition.
Hubungkan fungsi tersebut ke remote event LaserFired.
-- Memberitahukan semua klien bahwa laser telah ditembakkan sehingga mereka dapat menampilkan laserlocal function playerFiredLaser(playerFired, endPosition)end-- Hubungkan kejadian ke fungsi yang sesuaieventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
Server membutuhkan posisi awal laser. Ini bisa dikirim dari klien, tetapi sebaiknya menghindari mempercayai klien di mana pun memungkinkan. Pegangan senjata karakter akan menjadi posisi awal, sehingga server dapat menemukannya dari sana.
Buat fungsi getPlayerToolHandle di atas fungsi playerFiredLaser dengan parameter bernama player.
Gunakan kode berikut untuk mencari karakter pemain untuk senjata dan kembalikan objek pegangan.
local LASER_DAMAGE = 10-- Menemukan pegangan alat yang dipegang oleh pemainlocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- Beritahu semua klien bahwa laser telah ditembakkan sehingga mereka dapat menampilkan laserlocal function playerFiredLaser(playerFired, endPosition)
Server sekarang dapat memanggil FireAllClients pada remote event LaserFired untuk mengirim informasi yang diperlukan untuk merender laser ke klien. Ini mencakup pemain yang menembakkan laser (sehingga klien untuk pemain tersebut tidak merender laser dua kali), pegangan blaster (yang berfungsi sebagai posisi awal untuk laser) dan posisi akhir laser.
Dalam fungsi playerFiredLaser, panggil fungsi getPlayerToolHandle dengan playerFired sebagai argumen dan tetapkan nilai tersebut ke variabel bernama toolHandle.
Jika toolHandle ada, kerjakan event LaserFired untuk semua klien menggunakan playerFired, toolHandle dan endPosition sebagai argumen.
-- Memberitahukan semua klien bahwa laser telah ditembakkan sehingga mereka dapat menampilkan laserlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
Render di klien
Sekarang FireAllClients telah dipanggil, setiap klien akan menerima peristiwa dari server untuk merender sinar laser. Setiap klien dapat menggunakan kembali modul LaserRenderer dari sebelumnya untuk merender sinar laser menggunakan pegangan alat dan nilai posisi akhir yang dikirim oleh server. Pemain yang menembakkan sinar laser tersebut harus mengabaikan peristiwa ini, jika tidak mereka akan melihat 2 laser.
Buat LocalScript di StarterPlayerScripts bernama ClientLaserManager.

Di dalam skrip, buat modul LaserRenderer.
Buat fungsi bernama createPlayerLaser dengan parameter playerWhoShot, toolHandle dan endPosition.
Sambungkan fungsi ini ke remote event LaserFired di folder Events.
Dalam fungsi, gunakan pernyataan if untuk memeriksa apakah playerWhoShot tidak sama dengan LocalPlayer.
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-- Menampilkan sinar laser pemain lainlocal function createPlayerLaser(playerWhoShot, toolHandle, endPosition)if playerWhoShot ~= Players.LocalPlayer thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)Uji blaster dengan 2 pemain dengan memulai server lokal. Posisi setiap klien di sisi monitor Anda yang berbeda sehingga Anda dapat melihat kedua jendela sekaligus. Ketika Anda menembak di satu klien, Anda seharusnya 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.
Dalam skrip ToolController, arahkan ke fungsi toolActivated dan hapus baris yang memutar suara Activate.
local function toolActivated()if canShootWeapon() thenfireWeapon()endendDi bagian bawah fungsi createLaser di LaserRenderer, deklarasikan variabel bernama shootingSound dan gunakan metode FindFirstChild() dari toolHandle untuk memeriksa suara Activate.
Gunakan pernyataan if untuk memeriksa apakah shootingSound ada; jika ada, panggil fungsi Play()-nya.
laserPart.Parent = workspace-- Menambahkan sinar laser ke layanan Debris untuk dihapus & dibersihkanDebris:AddItem(laserPart, SHOT_DURATION)-- Putar suara tembakan senjatalocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
Amankan remote menggunakan validasi
Jika server tidak memeriksa data dari permintaan masuk, seorang peretas dapat menyalahgunakan fungsi dan peristiwa remote dan menggunakannya untuk mengirim nilai palsu ke server. Sangat penting untuk menggunakan validasi sisi server untuk mencegah hal ini.
Dalam bentuknya saat ini, remote event DamageCharacter sangat rentan terhadap serangan. Para peretas bisa saja 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.
- Melakukan raycast antara senjata yang menembakkan laser dan posisi yang terkena untuk memastikan tembakan itu mungkin dan tidak melewati dinding.
Klien
Klien perlu mengirimkan posisi yang terkena oleh raycast ke server sehingga dapat memeriksa apakah jaraknya realistis.
Dalam ToolController, arahkan ke baris di mana remote event DamageCharacter dipicu dalam fungsi fireWeapon.
Tambahkan hitPosition sebagai argumennya.
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
Server
Klien sekarang mengirimkan parameter tambahan melalui remote event DamageCharacter, jadi ServerLaserManager perlu disesuaikan untuk menerimanya.
Dalam skrip ServerLaserManager, tambahkan parameter hitPosition ke fungsi damageCharacter.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Mengurangi kesehatan dari karakterhumanoid.Health -= LASER_DAMAGEendendDi bawah fungsi getPlayerToolHandle, buat fungsi bernama isHitValid dengan tiga parameter: playerFired, characterToDamage dan hitPosition.
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
Pemeriksaan pertama akan menjadi jarak antara posisi yang terkena dan karakter yang terluka.
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 sedikit bergerak sejak klien menembakkan event.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10Di dalam fungsi isHitValid, hitung jarak antara karakter dan posisi yang terkena. Jika jarak lebih besar dari MAX_HIT_PROXIMITY, maka kembalikan false.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Validasi jarak antara karakter yang terkena dan posisi yang terkenalocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
Pemeriksaan kedua akan melibatkan raycast antara senjata yang ditembakkan dan posisi yang terkena. Jika raycast mengembalikan objek yang bukan karakter, Anda dapat menganggap bahwa tembakan tersebut tidak valid karena sesuatu menghalangi tembakan.
Salin kode di bawah ini untuk melakukan pemeriksaan ini. Kembalikan true di akhir fungsi: jika mencapai akhir, semua pemeriksaan telah terlewati.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Validasi jarak antara karakter yang terkena dan posisi yang terkenalocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > 10 thenreturn falseend-- Periksa jika menembak melalui dindinglocal 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)-- Jika sebuah instance tertabrak yang bukan karakter, maka abaikan tembakanif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendDeklarasikan variabel di dalam fungsi damageCharacter bernama validShot. Tetapkan ke hasil dari pemanggilan fungsi isHitValid dengan tiga argumen: playerFired, characterToDamage dan hitPosition.
Dalam pernyataan if di bawahnya, 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-- Mengurangi kesehatan dari karakterhumanoid.Health -= LASER_DAMAGEendend
Sekarang remote event damageCharacter lebih aman dan akan mencegah sebagian besar pemain dari penyalahgunaannya. Perlu dicatat bahwa beberapa pemain berbahaya sering menemukan cara untuk melewati validasi; menjaga keamanan remote event adalah upaya berkelanjutan.
Blaster laser Anda sekarang lengkap, dengan sistem deteksi tabrakan dasar menggunakan raycasting. Coba tutorial Mendektesi Input Pengguna untuk menemukan cara Anda dapat menambahkan aksi memuat 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()
-- Membuat sinar dari lokasi mouse 2D
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Vektor arah unit dari sinar yang dikalikan dengan jarak maksimum
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast dari asal sinar menuju arah sinar
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Kembalikan titik 3D dari penggalian
return raycastResult.Position
else
-- Tidak ada objek yang tertabrak, 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 terkena 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 tertabrak antara posisi awal dan akhir
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- Instance yang tertabrak adalah anak dari model karakter
-- Jika humanoid ditemukan di 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 yang laser terlihat
-- Membuat sinar laser dari posisi awal ke 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
-- Menambahkan 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
-- Menemukan pegangan alat yang dipegang oleh 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 jika 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 sebuah instance tertabrak yang bukan karakter, maka abaikan tembakan
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Beritahu 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
-- Mengurangi kesehatan dari karakter
humanoid.Health -= LASER_DAMAGE
end
end
-- Hubungkan kejadian 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
-- Menampilkan 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)