Samouczki dla średnio zaawansowanych

Wykrywanie trafień za pomocą laserów

*Ta zawartość została przetłumaczona przy użyciu narzędzi AI (w wersji beta) i może zawierać błędy. Aby wyświetlić tę stronę w języku angielskim, kliknij tutaj.

W tym samouczku nauczysz się, jak wystrzelić laser z blastera w Tworzenie narzędzi dla graczy i wykrywać, czy trafił w gracza.

Raycasting do znajdowania kolizji

Raycasting tworzy niewidzialny promień z pozycji startowej w kierunku określonym przez dany kierunek o zdefiniowanej długości. Jeśli promień koliduje z obiektami lub terenem na swojej drodze, zwróci informacje o kolizji, takie jak pozycja i obiekt, z którym kolidował.

Raycast z A do B kolidujący ze ścianą

Znajdź lokalizację myszy

Zanim laser może zostać wystrzelony, najpierw musisz wiedzieć, w co celuje gracz. Można to znaleźć, wykonując raycasting z lokalizacji myszy 2D gracza na ekranie wprost w świat 3D. Promień koliduje z tym, w co gracz celuje myszą.

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

  2. Na początku skryptu zadeklaruj stałą o nazwie MAX_MOUSE_DISTANCE z wartością 1000.

  3. Stwórz funkcję o nazwie 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
    -- Połącz zdarzenia z odpowiednimi funkcjami
    tool.Equipped:Connect(toolEquipped)
    tool.Activated:Connect(toolActivated)
  4. Użyj funkcji GetMouseLocation z UserInputService, aby uzyskać lokalizację 2D myszy na ekranie. Przypisz to do zmiennej o nazwie mouseLocation.


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

Teraz znana jest lokalizacja 2D 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 w świat 3D.

  1. Użyj właściwości X i Y zmiennej mouseLocation jako argumentów dla funkcji ViewportPointToRay(). Przypisz to do zmiennej o nazwie screenToWorldRay.


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Stwórz promień z lokalizacji 2D myszy
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    end

Czas użyć funkcji Raycast, aby sprawdzić, czy promień uderza w obiekt. Wymaga to pozycji startowej i wektora kierunku: w tym przykładzie użyjesz właściwości origin i direction zmiennej screenToWorldRay.

Długość wektora kierunku 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 kierunku przez MAX_MOUSE_DISTANCE.

  1. Zdeklaruj zmienną o nazwie directionVector i przypisz jej wartość screenToWorldRay.Direction pomnożoną przez MAX_MOUSE_DISTANCE.


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Stwórz promień z lokalizacji 2D myszy
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- Jednostkowy wektor kierunku promienia pomnożony przez maksymalną odległość
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
  2. Wywołaj funkcję Raycast obiektu workspace, przekazując właściwość Origin zmiennej screenToWorldRay jako pierwszy argument i directionVector jako drugi. Przypisz to do zmiennej o nazwie raycastResult.


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- Stwórz promień z lokalizacji 2D myszy
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- Jednostkowy wektor kierunku promienia pomnożony przez maksymalną odległość
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
    -- Raycast z położenia pochodzenia promienia w kierunku jego kierunku
    local 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ść RaycastResultOpis
InstanceObiekt BasePart lub Terrain, z którym promień się przeciął.
PozycjaMiejsce, w którym nastąpiło przecięcie; zazwyczaj punkt na powierzchni części lub terenu.
MateriałMateriał w punkcie kolizji.
NormalnyJednostkowy wektor normalny przeciętej powierzchni. Można go użyć do określenia, w którą stronę zwrócona jest powierzchnia.

Właściwość Pozycja będzie pozycją obiektu, nad którym unosi się myszka. Jeśli myszka nie unosi się nad żadnym obiektem w odległości MAX_MOUSE_DISTANCE, raycastResult będzie miało wartość nil.

  1. Stwórz instrukcję if, aby sprawdzić, czy raycastResult istnieje.

  2. Jeśli raycastResult ma wartość, zwróć jego właściwość Pozycja.

  3. 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()
-- Stwórz promień z lokalizacji 2D myszy
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Jednostkowy wektor kierunku promienia pomnożony przez maksymalną odległość
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast z położenia 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

Strzelaj w kierunku celu

Teraz, gdy znana jest 3D pozycja myszy, można ją wykorzystać jako pozycję celu, w którą wystrzeliwany jest laser. Można wykonać drugi raycast pomiędzy pozycją broni gracza a pozycją celu za pomocą funkcji Raycast.

  1. 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.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  2. Stwórz funkcję o nazwie fireWeapon pod funkcją getWorldMousePosition.

  3. 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 promienia
    return screenToWorldRay.Origin + directionVector
    end
    end
    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    end
    local function toolEquipped()
    tool.Handle.Equip:Play()
    end

Tym razem wektor kierunku funkcji raycast będzie reprezentował kierunek od pozycji narzędzia gracza do lokalizacji celu.

  1. Zdeklaruj zmienną o nazwie targetDirection i oblicz wektor kierunku, odejmując pozycję narzędzia od mouseLocation.

  2. Normalizuj wektor, używając jego właściwości Unit. Daje to mu długość równą 1, co ułatwia mnożenie przez długość później.


    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    -- Oblicz znormalizowany wektor kierunku i pomnóż przez odległość lasera
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. Zdeklaruj zmienną o nazwie directionVector i przypisz jej wartość targetDirection pomnożoną przez MAX_LASER_DISTANCE.


    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    -- Kierunek do wystrzału broni, pomnożony przez maksymalną odległość
    local directionVector = targetDirection * MAX_LASER_DISTANCE
    end

Obiekt RaycastParams może być użyty do przechowywania dodatkowych parametrów dla funkcji raycast. Będzie on używany w twoim blasterze laserowym, aby upewnić się, że raycast przypadkowo nie koliduje z graczem strzelającym, który używa tej broni. Wszelkie części zawarte w właściwości FilterDescendantsInstances obiektu RaycastParams będą ignorowane w raycast.

  1. Kontynuuj funkcję fireWeapon i zadeklaruj zmienną o nazwie weaponRaycastParams. Przypisz jej nowy obiekt RaycastParams.

  2. Stwórz tabelę zawierającą lokalną postać gracza i przypisz ją do właściwości weaponRaycastParams.FilterDescendantsInstances.

  3. Zrób raycast z pozycji uchwytu narzędzia gracza w kierunku directionVector. Nie zapomnij 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 kierunku i pomnóż przez odległość lasera
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- Kierunek do wystrzału broni pomnożony przez maksymalną odległość
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignorowanie postaci gracza, aby zapobiec uszkodzeniu samego siebie
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
end

Na końcu należy sprawdzić, czy operacja raycast zwróciła wartość. Jeśli zwróciła wartość, obiekt został trafiony przez promień, a laser można utworzyć pomiędzy bronią a miejscem trafienia. Jeśli nic nie zostało zwrócone, należy obliczyć ostateczną pozycję, aby stworzyć laser.

  1. Zdeklaruj pustą zmienną o nazwie hitPosition.

  2. 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 pomiędzy pozycjami startową a końcową
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. Jeśli weaponRaycastResult nie ma wartości, oblicz końcową pozycję raycast, dodając do siebie pozycję uchwytu narzędzia i directionVector. Przypisz to do hitPosition.


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- Sprawdź, czy jakiekolwiek obiekty były trafione pomiędzy pozycjami startową a końcową
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- Oblicz końcową pozycję na podstawie maksymalnej odległości lasera
    hitPosition = tool.Handle.Position + directionVector
    end
    end
  4. Przejdź do funkcji toolActivated i wywołaj funkcję fireWeapon, aby laser był odpalany 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 po prostu elementem scenerii, będziesz musiał poszukać Humanoid, ponieważ każda postać go posiada.

Najpierw musisz znaleźć model postaci. Jeśli część postaci została trafiona, nie możesz założyć, że rodzicem trafionego obiektu będzie postać. Laser mógł trafić w część ciała, akcesorium lub narzędzie, które wszystkie mają różne położenia w hierarchii postaci.

Możesz użyć FindFirstAncestorOfClass, aby znaleźć przodek modelu postaci trafionego obiektu, jeśli taki istnieje. Jeśli znajdziesz model i będzie zawierał humanoida, w większości przypadków możesz założyć, że to postać gracza.

  1. Dodaj zaznaczony kod poniżej do instrukcji if weaponRaycastResult, aby sprawdzić, czy postać zostałą trafiona.


    -- Sprawdź, czy jakiekolwiek obiekty zostały trafione pomiędzy pozycjami startową a końcową
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    -- Trafiony obiekt będzie dzieckiem modelu postaci
    -- Jeśli w modelu jest humanoid, to prawdopodobnie jest to postać gracza
    local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    print("Gracz trafiony")
    end
    end
    else
    -- Oblicz końcową pozycję na podstawie maksymalnej odległości lasera
    hitPosition = tool.Handle.Position + directionVector
    end

Teraz blaster laserowy powinien drukować Gracz trafiony w oknie Wyjście za każdym razem, gdy operacja raycast trafi w innego gracza.

Testuj z wieloma graczami

Do przetestowania, czy raycast broni znajduje innych graczy, potrzebni są dwaj gracze, więc musisz uruchomić lokalny serwer. Zobacz symulację wielokliencką w celu uzyskania instrukcji.

Na jednym kliencie przetestuj strzelanie do drugiego gracza za pomocą kliknięcia na nim. "Gracz trafiony" powinno być wyświetlane w wyniku za każdym razem, gdy gracz jest trafiany.

Znajdź pozycję lasera

Blaster powinien wystrzelić czerwoną wiązkę światła w kierunku celu. Funkcja do tego będzie znajdować się w ModuleScript, aby można było ją użyć w innych skryptach później. Najpierw skrypt musi znaleźć miejsce, w którym wiązka laserowa powinna zostać renderowana.

  1. Stwórz ModuleScript o nazwie LaserRenderer, umieszczony w StarterPlayerScripts pod StarterPlayer.

  2. Otwórz skrypt i zmień nazwę tabeli modułowej na nazwę skryptu LaserRenderer.

  3. Zadeklaruj zmienną o nazwie SHOT_DURATION z wartością 0.15. Będzie to czas (w sekundach), przez jaki laser jest widoczny.

  4. Stwórz funkcję LaserRenderer o nazwie createLaser z dwoma parametrami zwanymi toolHandle i endPosition.


    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- Czas, przez jaki laser jest widoczny
    -- Stwórz promień lasera od pozycji startowej do pozycji końcowej
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. Zdeklaruj zmienną o nazwie startPosition i ustaw właściwość Position toolHandle jako jej wartość. To będzie pozycja blastera lasera gracza.

  6. Zdeklaruj zmienną o nazwie laserDistance i odejmij endPosition od startPosition, aby znaleźć różnicę między dwoma wektorami. Użyj właściwości Magnitude, aby uzyskać długość promienia lasera.


    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. Zdeklaruj zmienną laserCFrame, aby przechować położenie i orientację wiązki lasera. Pozycja musi być środkiem startowego i końcowego końca promienia. Użyj CFrame.lookAt, aby stworzyć nowy CFrame znajdujący się w startPosition i skierowany w stronę endPosition. Pomnóż to przez nowy CFrame z wartością osi Z równą połowie negatywnej laserDistance, aby uzyskać środek.


    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

Stwórz część lasera

Teraz, gdy wiesz, gdzie stworzyć promień lasera, musisz dodać sam promień. Można to łatwo zrobić za pomocą Neonowej części.

  1. Zdeklaruj zmienną laserPart i przypisz do niej nową instancję Part.

  2. Ustaw następujące właściwości laserPart:

    1. Rozmiar: Vector3.new(0.2, 0.2, laserDistance)
    2. CFrame: laserCFrame
    3. Zawiązane: true
    4. Może kolidować: false
    5. Kolor: Color3.fromRGB(225, 0, 0) (silny kolor czerwony)
    6. Materiał: Enum.Material.Neon
  3. Podłącz laserPart do Workspace.

  4. Dodaj część do usługi Debris, aby została usunięta po czasie określonym w zmiennej 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
    -- Dodaj promień lasera do usługi Debris, aby został usunięty i posprzątany
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

Teraz funkcja renderująca promień lasera jest ukończona i można ją wywołać w ToolController.

  1. Na początku skryptu ToolController zadeklaruj zmienną o nazwie LaserRenderer i wymagaj 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.Parent
  2. Na 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 lasera
    hitPosition = tool.Handle.Position + directionVector
    end
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  3. Przetestuj broń, klikając przycisk Odtwórz. Promień lasera powinien być widoczny pomiędzy bronią a myszką, gdy narzędzie jest aktywowane.

Kontroluj częstość strzałów broni

Broń potrzebuje opóźnienia pomiędzy każdym strzałem, aby zatrzymać graczy przed zadawaniem 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.

  1. Zadeklaruj zmienną na początku ToolController o nazwie FIRE_RATE. To będzie minimalny czas pomiędzy każdym strzałem. Przypisz do niej wartość, którą wybierzesz; w tym przykładzie użyto 0.3 sekundy.

  2. Zadeklaruj kolejną zmienną poniżej, o nazwie timeOfPreviousShot z wartością 0. To przechowuje czas ostatniego strzału i będzie aktualizowane z każdym strzałem.


    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 300
    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
  3. Stwórz funkcję o nazwie canShootWeapon bez parametrów. Ta funkcja będzie sprawdzać, ile czasu minęło od poprzedniego strzału i zwróci wartość prawda lub fałsz.


    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- Sprawdź, czy wystarczająco dużo czasu minęło od ostatniego strzału
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. Wewnątrz funkcji zadeklaruj zmienną o nazwie currentTime; przypisz do niej wynik wywołania funkcji tick(). Zwraca to, ile czasu minęło, w sekundach, od 1 stycznia 1970 r. (arbitralna data powszechnie używana do obliczania czasu).

  5. 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łu
    local function canShootWeapon()
    local currentTime = tick()
    if currentTime - timeOfPreviousShot < FIRE_RATE then
    return false
    end
    return true
    end
  6. Na końcu funkcji fireWeapon zaktualizuj timeOfPreviousShot za każdym razem, gdy broń jest wystrzeliwana, używając tick.


    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  7. Wewnątrz funkcji toolActivated stwórz instrukcję if i wywołaj canShootWeapon, aby sprawdzić, czy broń może być wystrzelona.


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

Gdy przetestujesz blaster, powinieneś zauważyć, że niezależnie od tego, jak szybko klikniesz, 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 odpowiadać za wydawanie obrażeń, gdy gracz zostanie trafiony.

Klienci mogą użyć RemoteEvent, aby poinformować serwer o tym, że postać została trafiona. Powinny być one przechowywane w ReplicatedStorage, gdzie są widoczne zarówno dla klienta, jak i serwera.

  1. Stwórz Folder w ReplicatedStorage o nazwie Wydarzenia.

  2. Wstaw RemoteEvent do folderu Wydarzenia i nazwij go DamageCharacter.

  3. W ToolController utwórz zmienne na początku skryptu dla ReplicatedStorage i folderu Wydarzeń.


    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. Zastąp instrukcję drukującą "Gracz trafiony" w fireWeapon następującą linią Luau, aby wywołać zdalne zdarzenie DamageCharacter z zmienną characterModel jako argument.


    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

Serwer musi zadać obrażenia graczowi, który został trafiony, kiedy zdarzenie jest wywoływane.

  1. Wstaw Skrypt do ServerScriptService i nazwij go ServerLaserManager.

  2. Zadeklaruj zmienną o nazwie LASER_DAMAGE i ustaw ją na 10, lub inną wartość według własnego wyboru.

  3. Stwórz funkcję o nazwie damageCharacter z dwoma parametrami o nazwach playerFired i characterToDamage.

  4. Wewnątrz funkcji znajdź Humanoida postaci i odejmij od jego zdrowia LASER_DAMAGE.

  5. Połącz funkcję damageCharacter z DamageCharacter zdalnym zdarzeniem w folderze Wydarzeń.


    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
    -- Odejmij zdrowie od postaci
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- Połącz zdarzenia z odpowiednimi funkcjami
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. Przetestuj blaster przy użyciu 2 graczy, uruchamiając lokalny serwer. Kiedy strzelisz do drugiego gracza, jego zdrowie zmniejszy się o wartość przypisaną do LASER_DAMAGE.

Renderuj wiązki laserowe innych graczy

Obecnie wiązka lasera jest tworzona przez klienta strzelającego bronią, więc tylko on będzie widział wiązkę lasera.

Gdyby wiązka lasera była tworzona na serwerze, wszyscy mogliby ją zobaczyć. Jednak wystąpiłoby niewielkie opóźnienie między klientem strzelającym bronią a serwerem, który otrzymuje informacje o strzale. Oznaczałoby to, że klient strzelający bronią dostrzegłby opóźnienie między aktywacją broni a pojawieniem się wiązki lasera; w wyniku tego broń wydawałaby się „lagująca”.

Aby rozwiązać ten problem, każdy klient stworzy swoje własne wiązki laserowe. Oznacza to, że klient strzelający bronią natychmiast zobaczy wiązkę lasera. Inni klienci doświadczą niewielkiego opóźnienia między momentem, w którym inny gracz strzela, a pojawieniem się wiązki. To jest najlepszy scenariusz: nie ma sposobu, aby komunikować wiązkę jednego klienta innym klientom szybciej.

Klient strzelca

Najpierw klient musi poinformować serwer, że wystrzelił laser i podać pozycję końcową.

  1. Wstaw RemoteEvent do folderu Wydarzeń w ReplicatedStorage i nazwij go LaserFired.

  2. 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 + directionVector
    end
    timeOfPreviousShot = tick()
    eventsFolder.LaserFired:FireServer(hitPosition)
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end

Serwer

Serwer teraz musi otrzymać zdarzenie, że klient strzelił i poinformować wszystkich klientów o początkowej i końcowej pozycji wiązki laserowej, aby mogły ją również renderować.

  1. W skrypcie ServerLaserManager stwórz funkcję o nazwie playerFiredLaser powyżej damageCharacter z dwoma parametrami o nazwach playerFired i endPosition.

  2. Połącz funkcję z LaserFired zdalnym zdarzeniem.


    -- Powiadom wszystkich klientów, że laser został wystrzelony, aby mogli wyświetlić laser
    local function playerFiredLaser(playerFired, endPosition)
    end

    -- Połącz zdarzenia z odpowiednimi funkcjami
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

Serwer potrzebuje pozycji startowej lasera. To mogłoby być wysyłane z klienta, ale najlepiej unikać zaufania klientowi, gdzie to możliwe. Uchwyt broni postaci będzie położeniem początkowym, więc serwer może znaleźć ją tam.

  1. Zrób funkcję getPlayerToolHandle powyżej funkcji playerFiredLaser z parametrem o nazwie player.

  2. Użyj poniższego kodu, aby przeszukać postać gracza pod kątem broni i zwrócić obiekt uchwytu.


    local LASER_DAMAGE = 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
    -- Powiadom wszystkich klientów, że laser został wystrzelony, aby mogli wyświetlić laser
    local function playerFiredLaser(playerFired, endPosition)

Serwer teraz może wywołać FireAllClients na zdalnym zdarzeniu LaserFired, aby wysłać informacje wymagane do renderowania lasera do klientów. Obejmuje to gracza, który wystrzelił laser (więc klient dla tego gracza nie renderuje wiązki dwukrotnie), uchwyt blastera (który działa jako położenie startowe dla lasera) i końcową pozycję lasera.

  1. W funkcji playerFiredLaser wywołaj funkcję getPlayerToolHandle z playerFired jako argument i przypisz wartość do zmiennej o nazwie toolHandle.

  2. 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ć laser
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

Renderuj na klientach

Teraz, gdy FireAllClients zostało wywołane, każdy klient otrzyma zdarzenie z serwera, aby renderować wiązkę lasera. Każdy klient może ponownie użyć modułu LaserRenderer z wcześniejszych kroków, aby renderować wiązkę lasera za pomocą pozycji uchwytu narzędzia i wartości pozycji końcowej przesłanej przez serwer. Gracz, który wystrzelił wiązkę laserową, powinien zignorować to zdarzenie, w przeciwnym razie zobaczą 2 laserowe wiązki.

  1. Stwórz LocalScript w StarterPlayerScripts o nazwie ClientLaserManager.

  2. Wewnątrz skryptu wymagaj moduł LaserRenderer.

  3. Stwórz funkcję o nazwie createPlayerLaser z parametrami playerWhoShot, toolHandle i endPosition.

  4. Połącz funkcję z zdalnym zdarzeniem LaserFired w folderze Wydarzeń.

  5. W funkcji użyj instrukcji if, aby sprawdzić, czy playerWhoShot nie równa się LocalPlayer.

  6. 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 wiązkę lasera innego gracza
    local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
    if playerWhoShot ~= Players.LocalPlayer then
    LaserRenderer.createLaser(toolHandle, endPosition)
    end
    end
    eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)
  7. Przetestuj blaster z 2 graczami, uruchamiając lokalny serwer. Ustaw każdego klienta po różnych stronach monitora, aby móc widzieć oba okna jednocześnie. Kiedy strzelasz 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ż mogli go usłyszeć.

  1. W skrypcie ToolController przejdź do funkcji toolActivated i usuń linię, która odtwarza dźwięk aktywacji.


    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. Na 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.

  3. Użyj instrukcji if, aby sprawdzić, czy shootingSound istnieje; Jeśli tak, wywołaj jego funkcję Play().


    laserPart.Parent = workspace
    -- Dodaj promień lasera do usługi Debris, aby został usunięty i posprzątany
    Debris:AddItem(laserPart, SHOT_DURATION)
    -- Odtwarzaj dźwięk strzału broni
    local shootingSound = toolHandle:FindFirstChild("Activate")
    if shootingSound then
    shootingSound:Play()
    end
    end

Zabezpiecz zdarzenia zdalne za pomocą walidacji

Jeśli serwer nie sprawdza danych z przychodzących żądań, haker może nadużywać funkcji i zdarzeń zdalnych i używać ich do wysyłania fałszywych wartości do serwera. Ważne jest, aby stosować walidację po stronie serwera, aby temu zapobiec.

W bieżącej formie, zdalne zdarzenie DamageCharacter jest bardzo podatne na ataki. Hakerzy mogą użyć tego zdarzenia, aby zadać obrażenia dowolnemu graczowi, którego chcą, bez jego trafiania.

Walidacja to proces sprawdzania, czy wysyłane do serwera wartości 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ślonej granicy.
  • Wystrzelić promień między bronią, która wystrzeliła laser, a pozycją trafienia, aby upewnić się, że strzał był możliwy i nie przeszedł przez żadną ścianę.

Klient

Klient musi wysłać serwerowi pozycję trafienia promieniem, aby mógł sprawdzić, czy odległość jest realistyczna.

  1. W ToolController przejdź do linii, w której zdalne zdarzenie DamageCharacter jest wywoływane w funkcji fireWeapon.

  2. Dodaj hitPosition jako argument.


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

Serwer

Klient teraz wysyła dodatkowy parametr przez zdalne zdarzenie DamageCharacter, więc ServerLaserManager musi być dostosowany, aby go zaakceptować.

  1. W skrypcie ServerLaserManager dodaj parametr hitPosition do funkcji damageCharacter.


    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- Odejmij zdrowie od postaci
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. Pod funkcją getPlayerToolHandle stwórz funkcję o nazwie isHitValid z trzema parametrami: playerFired, characterToDamage i hitPosition.


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

Pierwsze sprawdzenie będzie dotyczyć odległości między trafioną pozycją a postacią trafioną.

  1. Zdeklaruj zmienną o nazwie MAX_HIT_PROXIMITY na początku skryptu i przypisz jej wartość 10. To będzie maksymalna odległość dopuszczalna między trafieniem a postacią. Tolerancja jest potrzebna, ponieważ postać mogła się lekko przesunąć od momentu, gdy klient wystrzelił zdarzenie.


    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    local MAX_HIT_PROXIMITY = 10
  2. W 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 pozycją trafienia
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > MAX_HIT_PROXIMITY then
    return false
    end
    end

Drugie sprawdzenie będzie dotyczyło raycastu pomiędzy wystrzeloną bronią a pozycją trafienia. Jeśli raycast zwróci obiekt, który nie jest postacią, można założyć, że strzał był nieważny, ponieważ coś blokowało strzał.

  1. Skopiuj poniższy kod, aby wykonać to sprawdzenie. Zwróć true na końcu funkcji: jeśli dotrze do końca, wszystkie sprawdzenia zostały pomyślnie ukończone.


    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- Walidacja odległości między trafioną postacią a pozycją trafienia
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 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 instancję, która nie była postacią, zignoruj strzał
    if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
    return false
    end
    end
    return true
    end
  2. Zdeklaruj zmienną w funkcji damageCharacter o nazwie validShot. Przypisz do niej wynik wywołania funkcji isHitValid z trzema argumentami: playerFired, characterToDamage i hitPosition.

  3. W poniższym instrukcji if dodaj operator and, aby sprawdzić, czy validShot to true.


    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
    if humanoid and validShot then
    -- Odejmij zdrowie od postaci
    humanoid.Health -= LASER_DAMAGE
    end
    end

Teraz zdarzenie damageCharacter jest bardziej bezpieczne i zapobiega większości graczy w jego nadużywaniu. Należy zauważyć, że niektórzy złośliwi gracze często znajdują sposoby na obejście walidacji; utrzymanie bezpieczeństwa zdalnych zdarzeń to ciągła praca.

Twój blaster laserowy jest teraz kompletny, z podstawowym systemem wykrywania trafień używającym raycastu. Spróbuj samouczka Wykrywanie wejścia użytkownika, aby dowiedzieć się, jak dodać akcję przeładowania do swojego blastera laserowego, lub stwórz fajną mapę i przetestuj swój blaster laserowy z innymi graczami!

Ostateczny 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()
-- Stwórz promień z lokalizacji 2D myszy
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- Jednostkowy wektor kierunku promienia pomnożony przez maksymalną odległość
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- Raycast z położenia 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 kierunku i pomnóż przez odległość lasera
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- Kierunek do wystrzału broni, pomnożony przez maksymalną odległość
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- Ignorowanie postaci 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 pomię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 jest humanoid, 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 jaki laser jest widoczny
-- Stwórz promień lasera od pozycji startowej do 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ń lasera do usługi Debris, aby został usunięty i posprzątany
Debris:AddItem(laserPart, SHOT_DURATION)
-- Odtwarzaj dźwięk strzału broni
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
-- 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 pozycją trafienia
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 instancję, która nie była 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
-- Odejmij zdrowie od 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 wiązkę lasera innego gracza
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. Nazwa Roblox, logo Roblox oraz hasło „Powering Imagination” należą do naszych zarejestrowanych i niezarejestrowanych znaków towarowych na terenie Stanów Zjednoczonych oraz w innych krajach.