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

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

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

  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. Utwó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ć 2D lokalizację myszy gracza 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, 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.

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

  1. 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 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
  2. Wywoł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 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)

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ął.
PositionMiejsce, w którym nastąpiło przecięcie; zazwyczaj punkt bezpośrednio na powierzchni części lub terenu.
MaterialMateriał w punkcie kolizji.
NormalWektor 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.

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

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

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

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

  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. Utwó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 kierunkowy dla funkcji raycast będzie reprezentował kierunek od pozycji narzędzia gracza do lokalizacji celu.

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

  2. 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ść lasera
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. Zadeklaruj 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_DISTANCE
    end

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.

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

  2. Utwórz tabelę zawierającą lokalny character gracza i przypisz ją do właściwości weaponRaycastParams.FilterDescendantsInstances.

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

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

  1. Zadeklaruj 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 między pozycją 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 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 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ł 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.

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

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

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

  3. Zadeklaruj zmienną o nazwie SHOT_DURATION z wartością 0.15. To będzie czas (w sekundach), przez który laser jest widoczny.

  4. 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ńcowej
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. Zadeklaruj zmienną o nazwie startPosition i ustaw właściwość Position toolHandle jako jej wartość. To będzie pozycja blastera laserowego gracza.

  6. 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.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. Zadeklaruj 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.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    local 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.

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

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

    1. Size: Vector3.new(0.2, 0.2, laserDistance)
    2. CFrame: laserCFrame
    3. Anchored: true
    4. CanCollide: false
    5. Color: Color3.fromRGB(225, 0, 0) (intensywny czerwony kolor)
    6. Material: Enum.Material.Neon
  3. Rodzicem laserPart ustaw Workspace.

  4. 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.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ń laserowy do usługi Debris, aby został usunięty i oczyszczony
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

Teraz funkcja renderująca promień laserowy jest zakończona, może być wywoływana przez ToolController.

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

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

  2. 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 = 1000
    local MAX_LASER_DISTANCE = 300
    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
  3. Utwó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.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(). To zwraca, ile czasu upłynęło, w sekundach, od 1 stycznia 1970 roku (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 utwó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 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.

  1. Utwórz Folder w ReplicatedStorage o nazwie Events.

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

  3. 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.Parent
    local eventsFolder = ReplicatedStorage.Events
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  4. Zastą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 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, gdy 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 wartość według własnego uznania.

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

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

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

    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    function damageCharacter(playerFired, characterToDamage)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- Zmniejsz zdrowie postaci
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- Połącz zdarzenia z odpowiednimi funkcjami
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. 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ą.

  1. Wstaw RemoteEvent do folderu Events 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 odebrać zdarzenie, że klient wystrzelił i poinformować wszystkich klientów o pozycji startowej i końcowej promienia laserowego, aby mogli go również renderować.

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

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

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

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

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

  1. W funkcji playerFiredLaser wywołaj funkcję getPlayerToolHandle z playerFired jako argumentem 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 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.

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

  2. Wewnątrz skryptu załaduj moduł LaserRenderer.

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

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

  5. W funkcji użyj instrukcji if, aby sprawdzić, czy playerWhoShot nie jest równy 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 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)
  7. 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.

  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ń 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

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.

  1. W ToolController przejdź do linii, w której wywoływane jest zdalne zdarzenie DamageCharacter 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 wysyła teraz dodatkowy parametr przez zdalne zdarzenie DamageCharacter, więc ServerLaserManager musi zostać 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
    -- Zmniejsz zdrowie postaci
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. Poniżej funkcji getPlayerToolHandle utwórz funkcję o nazwie isHitValid z trzema parametrami: playerFired, characterToDamage i hitPosition.

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

Pierwsza kontrola będzie dotyczyła odległości między trafioną pozycją a postacią.

  1. 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.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 trafioną pozycją
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > MAX_HIT_PROXIMITY then
    return false
    end
    end

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

  1. 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).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 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
  2. Zadeklaruj 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ż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 postaci
    humanoid.Health -= LASER_DAMAGE
    end
    end

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 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 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)
©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.