W tym samouczku nauczysz się, jak wystrzelić laser z blastera w Tworzeniu narzędzi dla graczy i wykrywać, czy trafia on w gracza.
Raycasting w celu znalezienia kolizji
Raycasting tworzy niewidzialny promień z pozycji startowej w kierunku danym z określoną długością. Jeśli promień koliduje z obiektami lub terenem na swojej drodze, zwraca informacje o kolizji, takie jak pozycja i obiekt, z którym się zderzył.

Znajdź lokalizację myszy
Zanim laser może zostać wystrzelony, musisz najpierw wiedzieć, w co celuje gracz. Można to znaleźć, wykonując raycasting z 2D lokalizacji myszy gracza na ekranie bezpośrednio do przodu z kamery w świat 3D. Promień koliduje z tym, w co gracz celuje myszą.
Otwórz skrypt ToolController wewnątrz narzędzia Blaster z Tworzenia narzędzi dla graczy. Jeśli jeszcze nie ukończyłeś tego samouczka, możesz pobrać model Blaster i wstawić go do StarterPack.

Na początku skryptu zadeklaruj stałą o nazwie MAX_MOUSE_DISTANCE z wartością 1000.
Utwórz funkcję o nazwie 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-- Połącz zdarzenia z odpowiednimi funkcjamitool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)Użyj funkcji GetMouseLocation z UserInputService, aby uzyskać 2D lokalizację myszy gracza na ekranie. Przypisz to do zmiennej o nazwie mouseLocation.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
Teraz, gdy znana jest 2D lokalizacja myszy, jej właściwości X i Y mogą być użyte jako parametry dla funkcji Camera:ViewportPointToRay(), która tworzy Ray z ekranu do świata 3D.
Użyj właściwości X i Y mouseLocation jako argumentów dla funkcji ViewportPointToRay(). Przypisz to do zmiennej o nazwie screenToWorldRay.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Utwórz promień z 2D lokalizacji myszylocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)end
Czas użyć funkcji Raycast, aby sprawdzić, czy promień trafia w obiekt. Wymaga to pozycji startowej i wektora kierunkowego: w tym przykładzie użyjesz właściwości origin i direction screenToWorldRay.
Długość wektora kierunkowego określa, jak daleko promień będzie podróżować. Promień musi być tak długi, jak MAX_MOUSE_DISTANCE, więc musisz pomnożyć wektor kierunkowy przez MAX_MOUSE_DISTANCE.
Zadeklaruj zmienną o nazwie directionVector i przypisz jej wartość screenToWorldRay.Direction pomnożoną przez MAX_MOUSE_DISTANCE.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Utwórz promień z 2D lokalizacji myszylocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- Jednostkowy wektor kierunkowy promienia pomnożony przez maksymalną odległośćlocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCEWywołaj funkcję Raycast workspace, przekazując właściwość Origin screenToWorldRay jako pierwszy argument i directionVector jako drugi. Przypisz to do zmiennej o nazwie raycastResult.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- Utwórz promień z 2D lokalizacji myszylocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- Jednostkowy wektor kierunkowy promienia pomnożony przez maksymalną odległośćlocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE-- Raycast z pochodzenia promienia w kierunku jego kierunkulocal raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
Informacje o kolizji
Jeśli operacja raycast znajdzie obiekt trafiony przez promień, zwróci RaycastResult, który zawiera informacje o kolizji między promieniem a obiektem.
| Właściwość RaycastResult | Opis |
|---|---|
| Instance | Obiekt BasePart lub Terrain, z którym promień się przeciął. |
| Position | Miejsce, w którym nastąpiło przecięcie; zazwyczaj punkt bezpośrednio na powierzchni części lub terenu. |
| Material | Materiał w punkcie kolizji. |
| Normal | Wektor normalny przeciętej powierzchni. Może być użyty do określenia, w którą stronę wskazuje powierzchnia. |
Właściwość Position będzie pozycją obiektu, nad którym unosi się mysz. Jeśli mysz nie unosi się nad żadnym obiektem w odległości MAX_MOUSE_DISTANCE, raycastResult będzie nil.
Utwórz instrukcję if, aby sprawdzić, czy raycastResult istnieje.
Jeśli raycastResult ma wartość, zwróć jego właściwość Position.
Jeśli raycastResult jest nil, znajdź koniec raycast. Oblicz 3D pozycję myszy, dodając screenToWorldRay.Origin i directionVector.
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- Utwórz promień z 2D lokalizacji myszy
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Jednostkowy wektor kierunkowy promienia pomnożony przez maksymalną odległość
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast z pochodzenia promienia w kierunku jego kierunku
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Zwróć 3D punkt przecięcia
return raycastResult.Position
else
-- Żaden obiekt nie został trafiony, więc oblicz pozycję na końcu promienia
return screenToWorldRay.Origin + directionVector
end
endStrzelaj w kierunku celu
Teraz, gdy znana jest 3D pozycja myszy, może być użyta jako pozycja celu do wystrzelenia lasera. Można wystrzelić drugi promień między pozycją broni gracza a pozycją celu, używając funkcji Raycast.
Zadeklaruj stałą o nazwie MAX_LASER_DISTANCE na początku skryptu i przypisz jej wartość 500, lub wybraną przez siebie odległość dla blastera laserowego.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500Utwórz funkcję o nazwie fireWeapon pod funkcją getWorldMousePosition.
Wywołaj getWorldMousePosition i przypisz wynik do zmiennej o nazwie mousePosition. To będzie pozycja celu dla raycast.
-- Żaden obiekt nie został trafiony, więc oblicz pozycję na końcu promieniareturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()end
Tym razem wektor kierunkowy dla funkcji raycast będzie reprezentował kierunek od pozycji narzędzia gracza do lokalizacji celu.
Zadeklaruj zmienną o nazwie targetDirection i oblicz wektor kierunkowy, odejmując pozycję narzędzia od mouseLocation.
Normalizuj wektor, używając jego właściwości Unit. To nada mu długość 1, co ułatwia późniejsze mnożenie przez długość.
local function fireWeapon()local mouseLocation = getWorldMousePosition()-- Oblicz znormalizowany wektor kierunkowy i pomnóż przez odległość laseralocal targetDirection = (mouseLocation - tool.Handle.Position).UnitendZadeklaruj zmienną o nazwie directionVector i przypisz do niej targetDirection pomnożony przez MAX_LASER_DISTANCE.
local targetDirection = (mouseLocation - tool.Handle.Position).Unit-- Kierunek do wystrzelenia broni, pomnożony przez maksymalną odległośćlocal directionVector = targetDirection * MAX_LASER_DISTANCEend
Obiekt RaycastParams może być użyty do przechowywania dodatkowych parametrów dla funkcji raycast. Będzie użyty w twoim blasterze laserowym, aby upewnić się, że raycast nie koliduje przypadkowo z graczem strzelającym bronią. Jakiekolwiek części zawarte w właściwości FilterDescendantsInstances obiektu RaycastParams będą ignorowane w raycast.
Kontynuuj funkcję fireWeapon i zadeklaruj zmienną o nazwie weaponRaycastParams. Przypisz do niej nowy obiekt RaycastParams.
Utwórz tabelę zawierającą lokalny character gracza i przypisz ją do właściwości weaponRaycastParams.FilterDescendantsInstances.
Raycast z pozycji uchwytu narzędzia gracza, w kierunku directionVector. Pamiętaj, aby dodać weaponRaycastParams jako argument tym razem. Przypisz to do zmiennej o nazwie 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()
-- Oblicz znormalizowany wektor kierunkowy i pomnóż przez odległość lasera
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- Kierunek do wystrzelenia broni, pomnożony przez maksymalną odległość
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignoruj postać gracza, aby zapobiec uszkodzeniu samego siebie
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
endNa koniec musisz sprawdzić, czy operacja raycast zwróciła wartość. Jeśli zwrócono wartość, obiekt został trafiony przez promień i można utworzyć laser między bronią a miejscem trafienia. Jeśli nic nie zostało zwrócone, należy obliczyć ostateczną pozycję, aby utworzyć laser.
Zadeklaruj pustą zmienną o nazwie hitPosition.
Użyj instrukcji if, aby sprawdzić, czy weaponRaycastResult ma wartość. Jeśli obiekt został trafiony, przypisz weaponRaycastResult.Position do hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Sprawdź, czy jakiekolwiek obiekty zostały trafione między pozycją startową a końcowąlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendJeśli weaponRaycastResult nie ma wartości, oblicz końcową pozycję raycast, dodając do siebie pozycję uchwytu narzędzia z directionVector. Przypisz to do hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- Sprawdź, czy jakiekolwiek obiekty zostały trafione między pozycją startową a końcowąlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- Oblicz końcową pozycję na podstawie maksymalnej odległości laserahitPosition = tool.Handle.Position + directionVectorendendPrzejdź do funkcji toolActivated i wywołaj funkcję fireWeapon, aby laser był wystrzeliwany za każdym razem, gdy narzędzie jest aktywowane.
local function toolActivated()tool.Handle.Activate:Play()fireWeapon()end
Sprawdź trafiony obiekt
Aby sprawdzić, czy obiekt trafiony przez laser jest częścią postaci gracza, czy tylko kawałkiem scenerii, musisz poszukać Humanoid, ponieważ każda postać go ma.
Najpierw musisz znaleźć model postaci. Jeśli część postaci została trafiona, nie możesz zakładać, że rodzicem trafionego obiektu będzie postać. Laser mógł trafić w część ciała, akcesorium lub narzędzie, które znajdują się w różnych częściach hierarchii postaci.

Możesz użyć FindFirstAncestorOfClass, aby znaleźć model postaci będący przodkiem trafionego obiektu, jeśli taki istnieje. Jeśli znajdziesz model i zawiera on humanoida, w większości przypadków możesz założyć, że to postać gracza.
Dodaj poniższy kod do instrukcji if weaponRaycastResult, aby sprawdzić, czy trafiona została postać.
-- Sprawdź, czy jakiekolwiek obiekty zostały trafione między pozycją startową a końcowąlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Position-- Trafiony obiekt będzie dzieckiem modelu postaci-- Jeśli w modelu znajdziesz humanoida, to prawdopodobnie jest to postać graczalocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("Gracz trafiony")endendelse-- Oblicz końcową pozycję na podstawie maksymalnej odległości laserahitPosition = tool.Handle.Position + directionVectorend
Teraz blaster laserowy powinien wyświetlać Gracz trafiony w oknie Wyjście za każdym razem, gdy operacja raycast trafi innego gracza.
Testuj z wieloma graczami
Aby przetestować, czy raycast broni znajduje innych graczy, potrzebni są dwaj gracze, więc musisz uruchomić lokalny serwer. Zobacz symulację wielu klientów po instrukcje.
Na jednym kliencie przetestuj strzelanie do drugiego gracza za pomocą broni, klikając na niego. "Gracz trafiony" powinien być wyświetlany w wyjściu za każdym razem, gdy gracz zostanie trafiony.
Znajdź pozycję lasera
Blaster powinien wystrzelić czerwony promień światła w kierunku celu. Funkcja do tego będzie znajdować się w ModuleScript, aby mogła być używana w innych skryptach później. Najpierw skrypt będzie musiał znaleźć pozycję, w której promień lasera powinien być renderowany.
Utwórz ModuleScript o nazwie LaserRenderer, umieszczony w StarterPlayerScripts pod StarterPlayer.

Otwórz skrypt i zmień nazwę tabeli modułu na nazwę skryptu LaserRenderer.
Zadeklaruj zmienną o nazwie SHOT_DURATION z wartością 0.15. To będzie czas (w sekundach), przez który laser jest widoczny.
Utwórz funkcję LaserRenderer o nazwie createLaser z dwoma parametrami o nazwach toolHandle i endPosition.
local LaserRenderer = {}local SHOT_DURATION = 0.15 -- Czas, przez który laser jest widoczny-- Utwórz promień laserowy z pozycji startowej w kierunku pozycji końcowejfunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererZadeklaruj zmienną o nazwie startPosition i ustaw właściwość Position toolHandle jako jej wartość. To będzie pozycja blastera laserowego gracza.
Zadeklaruj zmienną o nazwie laserDistance i odejmij endPosition od startPosition, aby znaleźć różnicę między dwoma wektorami. Użyj właściwości Magnitude tego, aby uzyskać długość promienia laserowego.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).MagnitudeendZadeklaruj zmienną laserCFrame, aby przechować pozycję i orientację promienia laserowego. Pozycja musi być punktem środkowym między początkiem a końcem promienia. Użyj CFrame.lookAt, aby utworzyć nowy CFrame znajdujący się w startPosition i zwrócony w kierunku endPosition. Pomnóż to przez nowy CFrame z wartością osi Z równą połowie ujemnej laserDistance, aby uzyskać punkt środkowy.
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
Utwórz część lasera
Teraz, gdy wiesz, gdzie utworzyć promień laserowy, musisz dodać sam promień. Można to łatwo zrobić za pomocą części Neon.
Zadeklaruj zmienną laserPart i przypisz do niej nową instancję Part.
Ustaw następujące właściwości laserPart:
- Size: Vector3.new(0.2, 0.2, laserDistance)
- CFrame: laserCFrame
- Anchored: true
- CanCollide: false
- Color: Color3.fromRGB(225, 0, 0) (intensywny czerwony kolor)
- Material: Enum.Material.Neon
Rodzicem laserPart ustaw Workspace.
Dodaj część do usługi Debris, aby została usunięta po czasie w sekundach w zmiennej 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-- Dodaj promień laserowy do usługi Debris, aby został usunięty i oczyszczonyDebris:AddItem(laserPart, SHOT_DURATION)end
Teraz funkcja renderująca promień laserowy jest zakończona, może być wywoływana przez ToolController.
Na początku skryptu ToolController zadeklaruj zmienną o nazwie LaserRenderer i załaduj moduł LaserRenderer znajdujący się w PlayerScripts.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.ParentNa końcu funkcji fireWeapon wywołaj funkcję LaserRenderer createLaser, używając uchwytu narzędzia i hitPosition jako argumentów.
-- Oblicz końcową pozycję na podstawie maksymalnej odległości laserahitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endPrzetestuj broń, klikając przycisk Odtwórz. Promień laserowy powinien być widoczny między bronią a myszą, gdy narzędzie jest aktywowane.
Kontroluj tempo strzałów broni
Bronie potrzebują opóźnienia między każdym strzałem, aby zapobiec zadawaniu zbyt dużych obrażeń w krótkim czasie. Można to kontrolować, sprawdzając, czy wystarczająco dużo czasu minęło od ostatniego strzału gracza.
Zadeklaruj zmienną na początku ToolController o nazwie FIRE_RATE. To będzie minimalny czas między każdym strzałem. Nadaj jej wartość według własnego uznania; w tym przykładzie użyto 0.3 sekundy.
Zadeklaruj inną zmienną poniżej o nazwie timeOfPreviousShot z wartością 0. To przechowuje ostatni czas, w którym gracz strzelał i będzie aktualizowane przy każdym strzale.
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0Utwórz funkcję o nazwie canShootWeapon bez parametrów. Ta funkcja sprawdzi, ile czasu minęło od poprzedniego strzału i zwróci prawdę lub fałsz.
local FIRE_RATE = 0.3local timeOfPreviousShot = 0-- Sprawdź, czy wystarczająco dużo czasu minęło od ostatniego strzałulocal function canShootWeapon()endlocal function getWorldMousePosition()Wewnątrz funkcji zadeklaruj zmienną o nazwie currentTime; przypisz do niej wynik wywołania funkcji tick(). To zwraca, ile czasu upłynęło, w sekundach, od 1 stycznia 1970 roku (arbitralna data powszechnie używana do obliczania czasu).
Odejmij timeOfPreviousShot od currentTime i zwróć false, jeśli wynik jest mniejszy niż FIRE_RATE; w przeciwnym razie zwróć true.
-- Sprawdź, czy wystarczająco dużo czasu minęło od ostatniego strzałulocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendNa końcu funkcji fireWeapon zaktualizuj timeOfPreviousShot za każdym razem, gdy broń jest wystrzeliwana, używając tick.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endWewnątrz funkcji toolActivated utwórz instrukcję if i wywołaj canShootWeapon, aby sprawdzić, czy broń może być wystrzelona.
local function toolActivated()if canShootWeapon() thentool.Handle.Activate:Play()fireWeapon()endend
Gdy przetestujesz blaster, powinieneś zauważyć, że niezależnie od tego, jak szybko klikasz, zawsze będzie krótkie opóźnienie 0.3 sekundy między każdym strzałem.
Zadaj obrażenia graczowi
Klienci nie mogą bezpośrednio zadawać obrażeń innym klientom; serwer musi być odpowiedzialny za zadawanie obrażeń, gdy gracz zostanie trafiony.
Klienci mogą używać RemoteEvent, aby poinformować serwer, że postać została trafiona. Powinny być one przechowywane w ReplicatedStorage, gdzie są widoczne zarówno dla klienta, jak i serwera.
Utwórz Folder w ReplicatedStorage o nazwie Events.

Wstaw RemoteEvent do folderu Events i nazwij go DamageCharacter.

W ToolController utwórz zmienne na początku skryptu dla ReplicatedStorage i folderu 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 = 500Zastąp instrukcję drukowania "Gracz trafiony" w fireWeapon linią Luau, aby wywołać zdalne zdarzenie DamageCharacter z zmienną characterModel jako argumentem.
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endendelse-- Oblicz końcową pozycję na podstawie maksymalnej odległości laserahitPosition = tool.Handle.Position + directionVectorend
Serwer musi zadać obrażenia graczowi, który został trafiony, gdy zdarzenie jest wywoływane.
Wstaw Skrypt do ServerScriptService i nazwij go ServerLaserManager.

Zadeklaruj zmienną o nazwie LASER_DAMAGE i ustaw ją na 10, lub wartość według własnego uznania.
Utwórz funkcję o nazwie damageCharacter z dwoma parametrami o nazwach playerFired i characterToDamage.
Wewnątrz funkcji znajdź Humanoida postaci i odejmij LASER_DAMAGE od jego zdrowia.
Połącz funkcję damageCharacter z zdalnym zdarzeniem DamageCharacter w folderze Events.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Zmniejsz zdrowie postacihumanoid.Health -= LASER_DAMAGEendend-- Połącz zdarzenia z odpowiednimi funkcjamieventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)Przetestuj blaster z 2 graczami, uruchamiając lokalny serwer. Gdy strzelisz do drugiego gracza, jego zdrowie zmniejszy się o wartość przypisaną do LASER_DAMAGE.
Renderuj promienie laserowe innych graczy
Obecnie promień laserowy jest tworzony przez klienta strzelającego bronią, więc tylko on będzie mógł zobaczyć promień laserowy.
Jeśli promień laserowy zostałby utworzony na serwerze, wszyscy mogliby go zobaczyć. Jednak wystąpiłoby małe opóźnienie między klientem strzelającym bronią a serwerem otrzymującym informacje o strzale. Oznaczałoby to, że klient strzelający bronią zobaczyłby opóźnienie między aktywacją broni a pojawieniem się promienia laserowego; broń wydawałaby się w rezultacie opóźniona.
Aby rozwiązać ten problem, każdy klient utworzy swoje własne promienie laserowe. Oznacza to, że klient strzelający bronią zobaczy promień laserowy natychmiast. Inni klienci doświadczą małego opóźnienia między strzałem innego gracza a pojawieniem się promienia. To najlepszy scenariusz: nie ma sposobu, aby komunikować promień jednego klienta innym klientom szybciej.
Klient strzelca
Najpierw klient musi poinformować serwer, że wystrzelił laser i podać pozycję końcową.
Wstaw RemoteEvent do folderu Events w ReplicatedStorage i nazwij go LaserFired.

Zlokalizuj funkcję fireWeapon w skrypcie ToolController. Na końcu funkcji wywołaj zdalne zdarzenie LaserFired, używając hitPosition jako argumentu.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()eventsFolder.LaserFired:FireServer(hitPosition)LaserRenderer.createLaser(tool.Handle, hitPosition)end
Serwer
Serwer teraz musi odebrać zdarzenie, że klient wystrzelił i poinformować wszystkich klientów o pozycji startowej i końcowej promienia laserowego, aby mogli go również renderować.
W skrypcie ServerLaserManager utwórz funkcję o nazwie playerFiredLaser powyżej damageCharacter z dwoma parametrami o nazwach playerFired i endPosition.
Połącz funkcję z zdalnym zdarzeniem LaserFired.
-- Powiadom wszystkich klientów, że laser został wystrzelony, aby mogli wyświetlić laserlocal function playerFiredLaser(playerFired, endPosition)end-- Połącz zdarzenia z odpowiednimi funkcjamieventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
Serwer potrzebuje pozycji startowej lasera. To mogłoby być wysłane z klienta, ale najlepiej unikać zaufania klientowi, gdzie to możliwe. Uchwycenie broni postaci będzie pozycją startową, więc serwer może to znaleźć stamtąd.
Utwórz funkcję getPlayerToolHandle powyżej funkcji playerFiredLaser z parametrem o nazwie player.
Użyj poniższego kodu, aby przeszukać postać gracza w poszukiwaniu broni i zwrócić obiekt uchwytu.
local LASER_DAMAGE = 10-- Znajdź uchwyt narzędzia, które trzyma graczlocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- Powiadom wszystkich klientów, że laser został wystrzelony, aby mogli wyświetlić laserlocal function playerFiredLaser(playerFired, endPosition)
Serwer może teraz wywołać FireAllClients na zdalnym zdarzeniu LaserFired, aby wysłać informacje potrzebne do renderowania lasera do klientów. Obejmuje to gracza, który wystrzelił laser (aby klient tego gracza nie renderował lasera dwa razy), uchwyt blastera (który działa jako pozycja startowa dla lasera) i pozycję końcową lasera.
W funkcji playerFiredLaser wywołaj funkcję getPlayerToolHandle z playerFired jako argumentem i przypisz wartość do zmiennej o nazwie toolHandle.
Jeśli toolHandle istnieje, wywołaj zdarzenie LaserFired dla wszystkich klientów, używając playerFired, toolHandle i endPosition jako argumentów.
-- Powiadom wszystkich klientów, że laser został wystrzelony, aby mogli wyświetlić laserlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
Renderuj na klientach
Teraz, gdy FireAllClients zostało wywołane, każdy klient otrzyma zdarzenie od serwera, aby renderować promień laserowy. Każdy klient może ponownie użyć modułu LaserRenderer z wcześniejszego etapu, aby renderować promień laserowy, używając pozycji uchwytu narzędzia i wartości pozycji końcowej wysłanej przez serwer. Gracz, który wystrzelił promień laserowy, powinien zignorować to zdarzenie, w przeciwnym razie zobaczą 2 lasery.
Utwórz LocalScript w StarterPlayerScripts o nazwie ClientLaserManager.

Wewnątrz skryptu załaduj moduł LaserRenderer.
Utwórz funkcję o nazwie createPlayerLaser z parametrami playerWhoShot, toolHandle i endPosition.
Połącz funkcję z zdalnym zdarzeniem LaserFired w folderze Events.
W funkcji użyj instrukcji if, aby sprawdzić, czy playerWhoShot nie jest równy LocalPlayer.
Wewnątrz instrukcji if wywołaj funkcję createLaser z modułu LaserRenderer, używając toolHandle i endPosition jako argumentów.
local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))local eventsFolder = ReplicatedStorage.Events-- Wyświetl promień laserowy innego graczalocal function createPlayerLaser(playerWhoShot, toolHandle, endPosition)if playerWhoShot ~= Players.LocalPlayer thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)Przetestuj blaster z 2 graczami, uruchamiając lokalny serwer. Ustaw każdego klienta po przeciwnych stronach monitora, abyś mógł zobaczyć oba okna jednocześnie. Gdy strzelisz na jednym kliencie, powinieneś zobaczyć laser na drugim kliencie.
Efekty dźwiękowe
Efekt dźwiękowy strzału obecnie odtwarzany jest tylko na kliencie, który strzela pociskiem. Musisz przenieść kod odtwarzający dźwięk, aby inni gracze również go słyszeli.
W skrypcie ToolController przejdź do funkcji toolActivated i usuń linię, która odtwarza dźwięk aktywacji.
local function toolActivated()if canShootWeapon() thenfireWeapon()endendNa końcu funkcji createLaser w LaserRenderer zadeklaruj zmienną o nazwie shootingSound i użyj metody FindFirstChild() toolHandle, aby sprawdzić, czy istnieje dźwięk Activate.
Użyj instrukcji if, aby sprawdzić, czy shootingSound istnieje; jeśli tak, wywołaj jego funkcję Play().
laserPart.Parent = workspace-- Dodaj promień laserowy do usługi Debris, aby został usunięty i oczyszczonyDebris:AddItem(laserPart, SHOT_DURATION)-- Odtwórz dźwięk strzału bronilocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
Zabezpiecz zdalne zdarzenia za pomocą walidacji
Jeśli serwer nie sprawdza danych z nadchodzących żądań, haker może nadużyć funkcji zdalnych i zdarzeń i użyć ich do wysyłania fałszywych wartości do serwera. Ważne jest, aby używać walidacji po stronie serwera, aby temu zapobiec.
W obecnej formie zdalne zdarzenie DamageCharacter jest bardzo podatne na atak. Hakerzy mogą użyć tego zdarzenia, aby zadać obrażenia dowolnemu graczowi, którego chcą, bez strzelania do niego.
Walidacja to proces sprawdzania, czy wartości wysyłane do serwera są realistyczne. W tym przypadku serwer będzie musiał:
- Sprawdzić, czy odległość między graczem a pozycją trafioną przez laser mieści się w określonym zakresie.
- Wykonać raycast między bronią, która wystrzeliła laser, a pozycją trafienia, aby upewnić się, że strzał był możliwy i nie przeszedł przez żadne ściany.
Klient
Klient musi wysłać serwerowi pozycję trafioną przez raycast, aby mógł sprawdzić, czy odległość jest realistyczna.
W ToolController przejdź do linii, w której wywoływane jest zdalne zdarzenie DamageCharacter w funkcji fireWeapon.
Dodaj hitPosition jako argument.
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
Serwer
Klient wysyła teraz dodatkowy parametr przez zdalne zdarzenie DamageCharacter, więc ServerLaserManager musi zostać dostosowany, aby go zaakceptować.
W skrypcie ServerLaserManager dodaj parametr hitPosition do funkcji damageCharacter.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- Zmniejsz zdrowie postacihumanoid.Health -= LASER_DAMAGEendendPoniżej funkcji getPlayerToolHandle utwórz funkcję o nazwie isHitValid z trzema parametrami: playerFired, characterToDamage i hitPosition.
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
Pierwsza kontrola będzie dotyczyła odległości między trafioną pozycją a postacią.
Zadeklaruj zmienną o nazwie MAX_HIT_PROXIMITY na początku skryptu i przypisz jej wartość 10. To będzie maksymalna dozwolona odległość między trafieniem a postacią. Potrzebna jest tolerancja, ponieważ postać mogła się nieco poruszyć od momentu, gdy klient wystrzelił zdarzenie.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10W funkcji isHitValid oblicz odległość między postacią a trafioną pozycją. Jeśli odległość jest większa niż MAX_HIT_PROXIMITY, zwróć false.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Walidacja odległości między trafioną postacią a trafioną pozycjąlocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
Druga kontrola będzie dotyczyła raycastu między wystrzeloną bronią a trafioną pozycją. Jeśli raycast zwróci obiekt, który nie jest postacią, możesz założyć, że strzał nie był ważny, ponieważ coś blokowało strzał.
Skopiuj poniższy kod, aby wykonać tę kontrolę. Zwróć true na końcu funkcji: jeśli dotrze do końca, wszystkie kontrole zostały pomyślnie zakończone.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- Walidacja odległości między trafioną postacią a trafioną pozycjąlocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > 10 thenreturn falseend-- Sprawdź, czy strzelano przez ścianylocal 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)-- Jeśli trafiono w obiekt, który nie był postacią, zignoruj strzałif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendZadeklaruj zmienną w funkcji damageCharacter o nazwie validShot. Przypisz do niej wynik wywołania funkcji isHitValid z trzema argumentami: playerFired, characterToDamage i hitPosition.
W poniższej instrukcji if dodaj operator and, aby sprawdzić, czy validShot jest true.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")local validShot = isHitValid(playerFired, characterToDamage, hitPosition)if humanoid and validShot then-- Zmniejsz zdrowie postacihumanoid.Health -= LASER_DAMAGEendend
Teraz zdalne zdarzenie damageCharacter jest bardziej zabezpieczone i zapobiegnie większości graczy w nadużywaniu go. Należy pamiętać, że niektórzy złośliwi gracze często znajdą sposoby na obejście walidacji; utrzymanie bezpieczeństwa zdalnych zdarzeń to ciągły wysiłek.
Twój blaster laserowy jest teraz kompletny, z podstawowym systemem wykrywania trafień za pomocą raycast. Spróbuj samouczka Wykrywanie wejścia użytkownika, aby dowiedzieć się, jak możesz dodać akcję przeładowania do swojego blastera laserowego, lub stwórz fajną mapę i przetestuj swój blaster laserowy z innymi graczami!
Końcowy kod
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
-- Sprawdź, czy wystarczająco dużo czasu minęło od ostatniego strzału
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()
-- Utwórz promień z 2D lokalizacji myszy
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Jednostkowy wektor kierunkowy promienia pomnożony przez maksymalną odległość
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast z pochodzenia promienia w kierunku jego kierunku
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- Zwróć 3D punkt przecięcia
return raycastResult.Position
else
-- Żaden obiekt nie został trafiony, więc oblicz pozycję na końcu promienia
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- Oblicz znormalizowany wektor kierunkowy i pomnóż przez odległość lasera
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- Kierunek do wystrzelenia broni, pomnożony przez maksymalną odległość
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignoruj postać gracza, aby zapobiec uszkodzeniu samego siebie
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- Sprawdź, czy jakiekolwiek obiekty zostały trafione między pozycją startową a końcową
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- Trafiony obiekt będzie dzieckiem modelu postaci
-- Jeśli w modelu znajdziesz humanoida, to prawdopodobnie jest to postać gracza
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
-- Oblicz końcową pozycję na podstawie maksymalnej odległości lasera
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 -- Czas, przez który laser jest widoczny
-- Utwórz promień laserowy z pozycji startowej w kierunku pozycji końcowej
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
-- Dodaj promień laserowy do usługi Debris, aby został usunięty i oczyszczony
Debris:AddItem(laserPart, SHOT_DURATION)
-- Odtwórz dźwięk strzału broni
local shootingSound = toolHandle:FindFirstChild("Activate")
if shootingSound then
shootingSound:Play()
end
end
return LaserRendererServerLaserManager
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_HIT_PROXIMITY = 10
-- Znajdź uchwyt narzędzia, które trzyma gracz
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)
-- Walidacja odległości między trafioną postacią a trafioną pozycją
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- Sprawdź, czy strzelano przez ściany
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)
-- Jeśli trafiono w obiekt, który nie był postacią, zignoruj strzał
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- Powiadom wszystkich klientów, że laser został wystrzelony, aby mogli wyświetlić 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
-- Zmniejsz zdrowie postaci
humanoid.Health -= LASER_DAMAGE
end
end
-- Połącz zdarzenia z odpowiednimi funkcjami
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
-- Wyświetl promień laserowy innego gracza
local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
if playerWhoShot ~= Players.LocalPlayer then
LaserRenderer.createLaser(toolHandle, endPosition)
end
end
eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)