레이저로 히트 감지

*이 콘텐츠는 AI(베타)를 사용해 번역되었으며, 오류가 있을 수 있습니다. 이 페이지를 영어로 보려면 여기를 클릭하세요.

이 튜토리얼에서는 플레이어 도구 만들기에서 블래스터로 레이저를 발사하고 플레이어에 맞는지 여부를 감지하는 방법을 배웁니다.

충돌을 찾기 위한 레이캐스팅

레이캐스팅은 시작 위치에서 주어진 방향으로 정의된 길이의 보이지 않는 레이를 생성합니다. 레이가 경로에 있는 물체나 지형과 충돌하면 충돌에 대한 정보(예: 위치 및 충돌한 물체)를 반환합니다.

A에서 B로 향하는 레이캐스트가 벽과 충돌하는 모습

마우스 위치 찾기

레이저를 발사하기 전에 플레이어가 어디를 조준하고 있는지 알아야 합니다. 이는 플레이어의 2D 마우스 위치에서 카메라를 통해 3D 세계로 직접 레이캐스팅하여 찾을 수 있습니다. 레이는 플레이어가 마우스로 조준하는 대상을 충돌합니다.

  1. 플레이어 도구 만들기에서 블래스터 도구의 ToolController 스크립트를 엽니다. 아직 해당 튜토리얼을 완료하지 않았다면 블래스터 모델을 다운로드하여 StarterPack에 삽입할 수 있습니다.

  2. 스크립트 상단에 MAX_MOUSE_DISTANCE라는 이름의 상수를 선언하고 값을 1000으로 설정합니다.

  3. 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
    -- 적절한 함수에 이벤트 연결
    tool.Equipped:Connect(toolEquipped)
    tool.Activated:Connect(toolActivated)
  4. UserInputService의 GetMouseLocation 함수를 사용하여 화면에서 플레이어의 2D 마우스 위치를 가져옵니다. 이를 mouseLocation이라는 변수에 할당합니다.

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

이제 2D 마우스 위치가 알려졌으므로, Camera:ViewportPointToRay() 함수의 매개변수로 XY 속성을 사용할 수 있습니다. 이 함수는 화면에서 3D 세계로 Ray를 생성합니다.

  1. mouseLocationXY 속성을 Camera:ViewportPointToRay() 함수의 인수로 사용합니다. 이를 screenToWorldRay라는 변수에 할당합니다.

    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- 2D 마우스 위치에서 레이 생성
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    end

이제 WorldRoot:Raycast() 함수를 사용하여 레이가 물체에 충돌하는지 확인할 시간입니다. 이는 시작 위치와 방향 벡터가 필요합니다. 이 예제에서는 screenToWorldRay의 원점과 방향 속성을 사용할 것입니다.

방향 벡터의 길이는 레이가 얼마나 멀리 이동할지를 결정합니다. 레이는 MAX_MOUSE_DISTANCE만큼 길어야 하므로 방향 벡터에 MAX_MOUSE_DISTANCE를 곱해야 합니다.

  1. directionVector라는 이름의 변수를 선언하고 screenToWorldRay.DirectionMAX_MOUSE_DISTANCE를 곱한 값을 할당합니다.

    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- 2D mouseLocation에서 레이 생성
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- 최대 거리로 곱한 레이의 단위 방향 벡터
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
  2. workspaceWorldRoot:Raycast() 함수를 호출하여 screenToWorldRayOrigin 속성을 첫 번째 인수로, directionVector를 두 번째 인수로 전달합니다. 이를 raycastResult라는 변수에 할당합니다.

    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- 2D mouseLocation에서 레이 생성
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- 최대 거리로 곱한 레이의 단위 방향 벡터
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
    -- 레이의 원점에서 방향으로 레이캐스트
    local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)

충돌 정보

레이캐스트 작업이 레이에 의해 충돌된 물체를 찾으면 RaycastResult를 반환합니다. 이 객체는 레이와 물체 간의 충돌에 대한 정보를 포함합니다.

RaycastResult 속성설명
Instance레이가 교차한 BasePart 또는 Terrain 셀입니다.
Position교차가 발생한 위치; 일반적으로 부품 또는 지형의 표면에 직접 있는 점입니다.
Material충돌 지점의 재질입니다.
Normal교차된 면의 법선 벡터입니다. 이를 사용하여 면이 어떤 방향을 가리키고 있는지 확인할 수 있습니다.

Position 속성은 마우스가 가리키고 있는 물체의 위치가 됩니다. 만약 마우스가 MAX_MOUSE_DISTANCE 거리 내의 어떤 물체 위에 있지 않다면, raycastResultnil이 됩니다.

  1. raycastResult가 존재하는지 확인하는 if 문을 만듭니다.

  2. raycastResult에 값이 있으면 Position 속성을 반환합니다.

  3. raycastResultnil인 경우 레이캐스트의 끝을 찾습니다. screenToWorldRay.OrigindirectionVector를 더하여 마우스의 3D 위치를 계산합니다.

local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- 2D mouseLocation에서 레이 생성
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- 최대 거리로 곱한 레이의 단위 방향 벡터
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- 레이의 원점에서 방향으로 레이캐스트
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- 3D 교차점 반환
return raycastResult.Position
else
-- 물체가 충돌하지 않았으므로 레이의 끝 위치 계산
return screenToWorldRay.Origin + directionVector
end
end

목표를 향해 발사하기

이제 3D 마우스 위치가 알려졌으므로 이를 목표 위치로 사용하여 레이저를 발사할 수 있습니다. 플레이어의 무기와 목표 위치 사이에 두 번째 레이를 발사할 수 있습니다.

  1. 스크립트 상단에 MAX_LASER_DISTANCE라는 상수를 선언하고 500으로 설정합니다. 이는 레이저 블래스터의 범위입니다.

    local UserInputService = game:GetService("UserInputService")
    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  2. getWorldMousePosition 함수 아래에 fireWeapon이라는 함수를 만듭니다.

  3. getWorldMousePosition을 호출하고 결과를 mousePosition이라는 변수에 할당합니다. 이는 레이캐스트의 목표 위치가 됩니다.

    -- 물체가 충돌하지 않았으므로 레이의 끝 위치 계산
    return screenToWorldRay.Origin + directionVector
    end
    end
    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    end
    local function toolEquipped()
    tool.Handle.Equip:Play()
    end

이번에는 레이캐스트 함수의 방향 벡터가 플레이어의 도구 위치에서 목표 위치로의 방향을 나타냅니다.

  1. targetDirection이라는 이름의 변수를 선언하고 mouseLocation에서 도구 위치를 빼서 방향 벡터를 계산합니다.

  2. 벡터를 정규화하여 Unit 속성을 사용합니다. 이렇게 하면 나중에 길이를 곱하기 쉽게 크기가 1이 됩니다.

    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    -- 정규화된 방향 벡터를 계산하고 레이저 거리로 곱합니다.
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. directionVector라는 이름의 변수를 선언하고 targetDirectionMAX_LASER_DISTANCE를 곱한 값을 할당합니다.

    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    -- 무기를 발사할 방향, 최대 거리로 곱한 값
    local directionVector = targetDirection * MAX_LASER_DISTANCE
    end

RaycastParams 객체를 사용하여 레이캐스트 함수에 대한 추가 매개변수를 저장할 수 있습니다. 이는 레이저 블래스터에서 레이캐스트가 무기를 발사하는 플레이어와 우연히 충돌하지 않도록 하는 데 사용됩니다. RaycastParams 객체의 FilterDescendantsInstances 속성에 포함된 모든 부품은 레이캐스트에서 무시됩니다.

  1. fireWeapon 함수를 계속하고 weaponRaycastParams라는 변수를 선언합니다. 여기에 새 RaycastParams 객체를 할당합니다.

  2. 플레이어의 로컬 character를 포함하는 테이블을 생성하고 이를 weaponRaycastParams.FilterDescendantsInstances 속성에 할당합니다.

  3. 플레이어의 도구 핸들 위치에서 directionVector 방향으로 레이캐스트합니다. 이번에는 weaponRaycastParams를 인수로 추가하는 것을 잊지 마세요. 이를 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()
-- 정규화된 방향 벡터를 계산하고 레이저 거리로 곱합니다.
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- 무기를 발사할 방향, 최대 거리로 곱한 값
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- 플레이어의 캐릭터를 무시하여 스스로를 다치지 않도록 합니다.
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
end

마지막으로 레이캐스트 작업이 값을 반환했는지 확인해야 합니다. 값이 반환되면 물체가 레이에 의해 충돌되었고 무기와 충돌 위치 사이에 레이저를 생성할 수 있습니다. 아무것도 반환되지 않으면 레이캐스트의 최종 위치를 계산해야 합니다.

  1. hitPosition이라는 빈 변수를 선언합니다.

  2. weaponRaycastResult에 값이 있는지 확인하는 if 문을 사용합니다. 물체가 충돌되었다면 weaponRaycastResult.PositionhitPosition에 할당합니다.

    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- 시작 위치와 끝 위치 사이에 어떤 물체가 충돌했는지 확인
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. weaponRaycastResult에 값이 없으면 도구 핸들의 positiondirectionVector를 더하여 레이캐스트의 끝 위치를 계산합니다. 이를 hitPosition에 할당합니다.

    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- 시작 위치와 끝 위치 사이에 어떤 물체가 충돌했는지 확인
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- 최대 레이저 거리 기반으로 끝 위치 계산
    hitPosition = tool.Handle.Position + directionVector
    end
    end
  4. toolActivated 함수로 이동하여 도구가 활성화될 때마다 레이저가 발사되도록 fireWeapon 함수를 호출합니다.

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

충돌한 물체 확인

레이저에 의해 맞은 물체가 플레이어의 캐릭터의 일부인지 아니면 단순한 경관인지 확인하려면 Humanoid를 찾아야 합니다. 모든 캐릭터는 하나를 가지고 있습니다.

먼저 캐릭터 모델을 찾아야 합니다. 캐릭터의 일부가 맞았다면, 맞은 물체의 부모가 캐릭터일 것이라고 가정할 수 없습니다. 레이저는 신체 부위, 액세서리 또는 도구에 맞을 수 있으며, 이들은 모두 캐릭터의 계층 구조의 서로 다른 부분에 위치합니다.

FindFirstAncestorOfClass를 사용하여 레이저에 의해 맞은 물체의 캐릭터 모델 조상을 찾을 수 있습니다. 모델을 찾고 그 안에 휴머노이드가 포함되어 있다면, 대부분의 경우 캐릭터라고 가정할 수 있습니다.

  1. weaponRaycastResult if 문 아래에 강조된 코드를 추가하여 캐릭터가 맞았는지 확인합니다.

    -- 시작 위치와 끝 위치 사이에 어떤 물체가 충돌했는지 확인
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    -- 맞은 인스턴스는 캐릭터 모델의 자식이 됩니다.
    -- 모델에서 휴머노이드가 발견되면 플레이어의 캐릭터일 가능성이 높습니다.
    local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    print("플레이어가 맞았습니다.")
    end
    end
    else
    -- 최대 레이저 거리 기반으로 끝 위치 계산
    hitPosition = tool.Handle.Position + directionVector
    end

이제 레이저 블래스터는 레이캐스트 작업이 다른 플레이어를 맞출 때마다 출력 창에 플레이어가 맞았습니다.를 출력해야 합니다.

여러 플레이어로 테스트하기

무기 레이캐스트가 다른 플레이어를 찾고 있는지 테스트하려면 두 플레이어가 필요하므로 로컬 서버를 시작해야 합니다. 다중 클라이언트 시뮬레이션에서 지침을 참조하세요.

한 클라이언트에서 다른 플레이어를 클릭하여 무기로 쏘는 것을 테스트합니다. 플레이어가 맞을 때마다 출력에 "플레이어가 맞았습니다."가 표시되어야 합니다.

레이저 위치 찾기

블래스터는 목표를 향해 빨간색 빛줄기를 발사해야 합니다. 이를 위한 함수는 나중에 다른 스크립트에서 재사용할 수 있도록 ModuleScript 안에 있어야 합니다. 먼저 레이저 빔이 렌더링되어야 할 위치를 찾아야 합니다.

  1. StarterPlayer 아래의 StarterPlayerScripts에 LaserRenderer라는 이름의 ModuleScript를 생성합니다.

  2. 스크립트를 열고 모듈 테이블의 이름을 스크립트의 이름인 LaserRenderer로 변경합니다.

  3. SHOT_DURATION이라는 변수를 선언하고 값을 0.15로 설정합니다. 이는 레이저가 보이는 시간(초)입니다.

  4. toolHandleendPosition이라는 두 개의 매개변수를 가진 LaserRenderer의 createLaser라는 함수를 만듭니다.

    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- 레이저가 보이는 시간
    -- 시작 위치에서 끝 위치로 레이저 빔 생성
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. startPosition이라는 변수를 선언하고 toolHandlePosition 속성을 값으로 설정합니다. 이는 플레이어의 레이저 블래스터 위치가 됩니다.

  6. laserDistance라는 변수를 선언하고 endPosition에서 startPosition을 빼서 두 벡터 간의 차이를 찾습니다. 이 차이의 Magnitude 속성을 사용하여 레이저 빔의 길이를 구합니다.

    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. 레이저 빔의 위치와 방향을 저장할 laserCFrame 변수를 선언합니다. 위치는 빔의 시작과 끝의 중간 지점이어야 합니다. CFrame.lookAt을 사용하여 startPosition에 위치하고 endPosition을 향하는 새로운 CFrame을 생성합니다. 이를 -laserDistance의 절반으로 Z축 값을 가진 새로운 CFrame과 곱하여 중간 지점을 얻습니다.

    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

레이저 파트 생성

이제 레이저 빔을 생성할 위치를 알았으므로, 빔 자체를 추가해야 합니다. 이는 네온 파트를 사용하여 쉽게 수행할 수 있습니다.

  1. laserPart라는 변수를 선언하고 새 Part 인스턴스를 할당합니다.

  2. 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) (강한 빨간색)
    6. Material: Enum.Material.Neon
  3. laserPartWorkspace에 부모로 설정합니다.

  4. Debris 서비스에 파트를 추가하여 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
    -- 레이저 빔을 Debris 서비스에 추가하여 제거 및 정리
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

이제 레이저 빔을 렌더링하는 함수가 완료되었습니다. 이를 ToolController에서 호출할 수 있습니다.

  1. ToolController 스크립트 상단에 LaserRenderer라는 변수를 선언하고 PlayerScripts에 있는 LaserRenderer ModuleScript를 require합니다.

    local UserInputService = game:GetService("UserInputService")
    local Players = game:GetService("Players")
    local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
    local tool = script.Parent
  2. fireWeapon 함수의 끝에서 레이저를 생성하기 위해 LaserRenderer의 createLaser 함수를 호출합니다. 인수로 도구 핸들과 hitPosition을 사용합니다.

    -- 최대 레이저 거리 기반으로 끝 위치 계산
    hitPosition = tool.Handle.Position + directionVector
    end
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  3. 플레이 버튼을 클릭하여 무기를 테스트합니다. 도구가 활성화될 때 무기와 마우스 사이에 레이저 빔이 보여야 합니다.

무기 발사 속도 제어

무기에는 짧은 시간에 너무 많은 피해를 주지 않도록 각 발사 사이에 지연이 필요합니다. 이는 플레이어가 마지막으로 발사한 이후 충분한 시간이 지났는지 확인하여 제어할 수 있습니다.

  1. ToolController의 상단에 FIRE_RATE라는 변수를 선언합니다. 이는 각 발사 사이의 최소 시간입니다. 원하는 값으로 설정합니다. 이 예제에서는 0.3초를 사용합니다.

  2. 그 아래에 timeOfPreviousShot라는 또 다른 변수를 선언하고 값을 0으로 설정합니다. 이는 플레이어가 마지막으로 발사한 시간을 저장하며 각 발사 시 업데이트됩니다.

    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 300
    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
  3. 매개변수가 없는 canShootWeapon이라는 함수를 만듭니다. 이 함수는 이전 발사 이후 얼마나 시간이 지났는지 확인하고 true 또는 false를 반환합니다.

    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- 이전 발사 이후 충분한 시간이 지났는지 확인
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. 함수 내부에서 currentTime이라는 변수를 선언하고 tick() 함수를 호출하여 할당합니다. 이는 1970년 1월 1일 이후 경과된 시간을 초 단위로 반환합니다(일반적으로 사용되는 임의의 날짜).

  5. currentTime에서 timeOfPreviousShot을 빼고 결과가 FIRE_RATE보다 작으면 false를 반환하고, 그렇지 않으면 true를 반환합니다.

    -- 이전 발사 이후 충분한 시간이 지났는지 확인
    local function canShootWeapon()
    local currentTime = tick()
    if currentTime - timeOfPreviousShot < FIRE_RATE then
    return false
    end
    return true
    end
  6. fireWeapon 함수의 끝에서 무기가 발사될 때마다 tick을 사용하여 timeOfPreviousShot을 업데이트합니다.

    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  7. toolActivated 함수 내부에서 if 문을 만들고 canShootWeapon을 호출하여 무기를 발사할 수 있는지 확인합니다.

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

블래스터를 테스트하면 클릭 속도에 관계없이 각 발사 사이에 항상 짧은 0.3초의 지연이 있어야 합니다.

플레이어에게 피해 주기

클라이언트는 다른 클라이언트를 직접적으로 피해를 줄 수 없습니다. 서버가 플레이어가 맞았을 때 피해를 부여하는 책임이 있습니다.

클라이언트는 RemoteEvent를 사용하여 서버에 캐릭터가 맞았음을 알릴 수 있습니다. 이러한 이벤트는 ReplicatedStorage에 저장되어 클라이언트와 서버 모두에서 볼 수 있습니다.

  1. ReplicatedStorage에 Events라는 이름의 Folder를 생성합니다.

  2. Events 폴더에 RemoteEvent를 삽입하고 이름을 DamageCharacter로 설정합니다.

  3. ToolController에서 스크립트 시작 부분에 ReplicatedStorage와 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. fireWeapon에서 "플레이어가 맞았습니다." 출력 문을 characterModel 변수를 인수로 사용하여 DamageCharacter 원격 이벤트를 발사하는 줄로 교체합니다.

    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
    -- 최대 레이저 거리 기반으로 끝 위치 계산
    hitPosition = tool.Handle.Position + directionVector
    end

서버는 이벤트가 발사될 때 맞은 플레이어에게 피해를 주어야 합니다.

  1. ServerScriptService에 ServerLaserManager라는 이름의 Script를 삽입합니다.

  2. LASER_DAMAGE라는 변수를 선언하고 10으로 설정합니다. 또는 원하는 값을 설정합니다.

  3. playerFiredcharacterToDamage라는 두 개의 매개변수를 가진 damageCharacter라는 함수를 만듭니다.

  4. 함수 내부에서 캐릭터의 Humanoid를 찾아 LASER_DAMAGE를 그 건강에서 빼줍니다.

  5. Events 폴더의 DamageCharacter 원격 이벤트에 damageCharacter 함수를 연결합니다.

    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
    -- 캐릭터의 건강을 제거합니다.
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- 적절한 함수에 이벤트 연결
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. 두 플레이어로 블래스터를 테스트합니다. 로컬 서버를 시작합니다. 다른 플레이어를 쏘면 그들의 건강이 LASER_DAMAGE에 할당된 값만큼 감소합니다.

다른 플레이어의 레이저 빔 렌더링

현재 레이저 빔은 무기를 발사하는 클라이언트에 의해 생성되므로, 오직 그 클라이언트만 레이저 빔을 볼 수 있습니다.

서버에서 레이저 빔을 생성하면 모든 클라이언트가 이를 볼 수 있습니다. 그러나 클라이언트가 무기를 발사하고 서버가 발사 정보를 수신하는 사이에 약간의 지연이 발생합니다. 이로 인해 무기를 발사하는 클라이언트는 무기를 활성화할 때와 레이저 빔이 보일 때 사이에 지연을 경험하게 됩니다. 결과적으로 무기가 느리게 느껴질 수 있습니다.

이 문제를 해결하기 위해 모든 클라이언트는 자신의 레이저 빔을 생성합니다. 이는 무기를 발사하는 클라이언트가 레이저 빔을 즉시 볼 수 있게 합니다. 다른 클라이언트는 다른 플레이어가 발사할 때 레이저가 나타나는 데 약간의 지연을 경험하게 됩니다. 이는 최상의 시나리오입니다. 한 클라이언트의 레이저를 다른 클라이언트에게 더 빠르게 전달할 방법은 없습니다.

발사자의 클라이언트

먼저 클라이언트는 서버에 레이저를 발사했음을 알리고 끝 위치를 제공해야 합니다.

  1. ReplicatedStorage의 Events 폴더에 LaserFired라는 이름의 RemoteEvent를 삽입합니다.

  2. ToolController 스크립트에서 fireWeapon 함수를 찾아서 함수의 끝에서 hitPosition을 인수로 사용하여 LaserFired 원격 이벤트를 발사합니다.

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

서버

서버는 이제 클라이언트가 발사했다는 이벤트를 수신하고 모든 클라이언트에게 레이저 빔의 시작 및 끝 위치를 알려야 하므로, 그들도 렌더링할 수 있습니다.

  1. ServerLaserManager 스크립트에서 playerFiredLaser라는 이름의 함수를 damageCharacter 위에 두 개의 매개변수인 playerFiredendPosition을 사용하여 만듭니다.

  2. 이 함수를 LaserFired 원격 이벤트에 연결합니다.

    -- 모든 클라이언트에게 레이저가 발사되었음을 알리기 위해 레이저를 표시할 수 있도록 합니다.
    local function playerFiredLaser(playerFired, endPosition)
    end
    -- 적절한 함수에 이벤트 연결
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

서버는 레이저의 시작 위치가 필요합니다. 이는 클라이언트에서 보낼 수 있지만, 가능한 한 클라이언트를 신뢰하지 않는 것이 좋습니다. 캐릭터의 무기 핸들 위치가 시작 위치가 되므로 서버는 그 위치를 찾을 수 있습니다.

  1. playerFiredLaser 함수 위에 player라는 매개변수를 가진 getPlayerToolHandle라는 함수를 만듭니다.

  2. 다음 코드를 사용하여 플레이어의 캐릭터에서 무기를 검색하고 핸들 객체를 반환합니다.

    local LASER_DAMAGE = 10
    -- 플레이어가 들고 있는 도구의 핸들을 찾습니다.
    local function getPlayerToolHandle(player)
    local weapon = player.Character:FindFirstChildOfClass("Tool")
    if weapon then
    return weapon:FindFirstChild("Handle")
    end
    end
    -- 모든 클라이언트에게 레이저가 발사되었음을 알리기 위해 레이저를 표시할 수 있도록 합니다.
    local function playerFiredLaser(playerFired, endPosition)

이제 서버는 LaserFired 원격 이벤트에서 FireAllClients를 호출하여 클라이언트가 레이저를 렌더링하는 데 필요한 정보를 보낼 수 있습니다. 여기에는 레이저를 발사한 플레이어(해당 플레이어의 클라이언트가 레이저를 두 번 렌더링하지 않도록 함), 레이저의 시작 위치 역할을 하는 핸들 및 레이저의 끝 위치가 포함됩니다.

  1. playerFiredLaser 함수에서 getPlayerToolHandle 함수를 호출하여 playerFired를 인수로 사용하고 값을 toolHandle이라는 변수에 할당합니다.

  2. toolHandle이 존재하면 playerFired, toolHandleendPosition을 인수로 사용하여 LaserFired 이벤트를 모든 클라이언트에 발사합니다.

    -- 모든 클라이언트에게 레이저가 발사되었음을 알리기 위해 레이저를 표시할 수 있도록 합니다.
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

클라이언트에서 렌더링

이제 FireAllClients가 호출되었으므로 각 클라이언트는 서버로부터 레이저 빔을 렌더링하라는 이벤트를 수신합니다. 각 클라이언트는 이전에 사용한 LaserRenderer 모듈을 재사용하여 서버에서 보낸 도구의 핸들 위치와 끝 위치 값을 사용하여 레이저 빔을 렌더링할 수 있습니다. 레이저 빔을 처음 발사한 플레이어는 이 이벤트를 무시해야 하므로 두 개의 레이저를 보지 않도록 해야 합니다.

  1. StarterPlayerScripts에 ClientLaserManager라는 이름의 LocalScript를 생성합니다.

  2. 스크립트 내부에서 LaserRenderer 모듈을 require합니다.

  3. playerWhoShot, toolHandleendPosition 매개변수를 가진 createPlayerLaser라는 함수를 만듭니다.

  4. Events 폴더의 LaserFired 원격 이벤트에 이 함수를 연결합니다.

  5. 함수 내부에서 if 문을 사용하여 playerWhoShotLocalPlayer와 같지 않은지 확인합니다.

  6. if 문 내부에서 toolHandleendPosition을 인수로 사용하여 LaserRenderer 모듈의 createLaser 함수를 호출합니다.

    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))
    local eventsFolder = ReplicatedStorage.Events
    -- 다른 플레이어의 레이저 표시
    local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
    if playerWhoShot ~= Players.LocalPlayer then
    LaserRenderer.createLaser(toolHandle, endPosition)
    end
    end
    eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)
  7. 두 플레이어로 블래스터를 테스트합니다. 로컬 서버를 시작합니다. 각 클라이언트를 모니터의 서로 다른 측면에 배치하여 두 창을 동시에 볼 수 있도록 합니다. 한 클라이언트에서 쏘면 다른 클라이언트에서 레이저가 보여야 합니다.

사운드 효과

현재 발사 사운드 효과는 발사하는 클라이언트에서만 재생됩니다. 다른 플레이어도 이를 들을 수 있도록 사운드를 재생하는 코드를 이동해야 합니다.

  1. ToolController 스크립트에서 toolActivated 함수로 이동하여 Activate 사운드를 재생하는 줄을 제거합니다.

    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. LaserRenderercreateLaser 함수 끝에 shootingSound라는 변수를 선언하고 toolHandleFindFirstChild() 메서드를 사용하여 Activate 사운드를 확인합니다.

  3. if 문을 사용하여 shootingSound가 존재하는지 확인합니다. 존재하면 Play() 함수를 호출합니다.

    laserPart.Parent = workspace
    -- 레이저 빔을 Debris 서비스에 추가하여 제거 및 정리
    Debris:AddItem(laserPart, SHOT_DURATION)
    -- 무기의 발사 사운드 재생
    local shootingSound = toolHandle:FindFirstChild("Activate")
    if shootingSound then
    shootingSound:Play()
    end
    end

검증을 통한 원격 보안

서버가 수신 요청의 데이터를 확인하지 않으면 해커가 원격 함수 및 이벤트를 악용하여 서버에 가짜 값을 보낼 수 있습니다. 이를 방지하기 위해 서버 측 검증을 사용하는 것이 중요합니다.

현재 형태의 DamageCharacter 원격 이벤트는 공격에 매우 취약합니다. 해커는 이 이벤트를 사용하여 자신을 쏘지 않고도 원하는 플레이어에게 피해를 줄 수 있습니다.

검증은 서버에 전송되는 값이 현실적인지 확인하는 과정입니다. 이 경우 서버는 다음을 확인해야 합니다:

  • 플레이어와 레이저에 의해 맞은 위치 간의 거리가 특정 경계 내에 있는지 확인합니다.
  • 레이저를 발사한 무기와 맞은 위치 사이에 레이캐스트를 수행하여 발사가 가능했는지, 벽을 통과하지 않았는지 확인합니다.

클라이언트

클라이언트는 서버에 레이캐스트에 의해 맞은 위치를 보내야 하므로 거리가 현실적인지 확인할 수 있습니다.

  1. ToolController에서 fireWeapon 함수의 DamageCharacter 원격 이벤트가 발사되는 줄로 이동합니다.

  2. hitPosition을 인수로 추가합니다.

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

서버

클라이언트가 DamageCharacter 원격 이벤트를 통해 추가 매개변수를 보내고 있으므로 ServerLaserManager를 조정하여 이를 수용해야 합니다.

  1. ServerLaserManager 스크립트에서 damageCharacter 함수에 hitPosition 매개변수를 추가합니다.

    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- 캐릭터의 건강을 제거합니다.
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. getPlayerToolHandle 함수 아래에 isHitValid라는 이름의 함수를 생성하고 세 개의 매개변수인 playerFired, characterToDamagehitPosition을 추가합니다.

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

첫 번째 검사는 맞은 위치와 맞은 캐릭터 간의 거리입니다.

  1. 스크립트 상단에 MAX_HIT_PROXIMITY라는 변수를 선언하고 값을 10으로 설정합니다. 이는 맞은 위치와 캐릭터 간의 최대 거리입니다. 캐릭터가 클라이언트가 이벤트를 발사한 이후에 약간 이동했을 수 있으므로 여유가 필요합니다.

    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    local MAX_HIT_PROXIMITY = 10
  2. isHitValid 함수에서 캐릭터와 맞은 위치 간의 거리를 계산합니다. 거리가 MAX_HIT_PROXIMITY보다 크면 false를 반환합니다.

    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- 맞은 캐릭터와 맞은 위치 간의 거리 검증
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > MAX_HIT_PROXIMITY then
    return false
    end
    end

두 번째 검사는 발사된 무기와 맞은 위치 사이의 레이캐스트를 포함합니다. 레이캐스트가 캐릭터가 아닌 물체를 반환하면, 무기가 유효하지 않았다고 가정할 수 있습니다. 무언가가 발사를 차단했기 때문입니다.

  1. 다음 코드를 복사하여 이 검사를 수행합니다. 함수의 끝에서 true를 반환합니다. 끝에 도달하면 모든 검사가 통과한 것입니다.

    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- 맞은 캐릭터와 맞은 위치 간의 거리 검증
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 then
    return false
    end
    -- 벽을 통과하여 쏘는지 확인
    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)
    -- 캐릭터가 아닌 인스턴스가 맞았다면 발사를 무시합니다.
    if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
    return false
    end
    end
    return true
    end
  2. damageCharacter 함수에서 validShot라는 변수를 선언합니다. isHitValid 함수를 호출하여 playerFired, characterToDamagehitPosition을 인수로 전달합니다.

  3. 아래 if 문에 and 연산자를 추가하여 validShottrue인지 확인합니다.

    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
    if humanoid and validShot then
    -- 캐릭터의 건강을 제거합니다.
    humanoid.Health -= LASER_DAMAGE
    end
    end

이제 damageCharacter 원격 이벤트가 더 안전해졌으며 대부분의 플레이어가 이를 악용하는 것을 방지할 수 있습니다. 일부 악의적인 플레이어는 검증을 우회하는 방법을 찾을 수 있으므로 원격 이벤트를 안전하게 유지하는 것은 지속적인 노력입니다.

이제 레이저 블래스터가 완성되었으며, 레이캐스팅을 사용한 기본 히트 감지 시스템이 구현되었습니다. 사용자 입력 감지 튜토리얼을 시도하여 레이저 블래스터에 재장전 동작을 추가하는 방법을 알아보거나 재미있는 맵을 만들어 다른 플레이어와 함께 레이저 블래스터를 사용해 보세요!

최종 코드

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
-- 이전 발사 이후 충분한 시간이 지났는지 확인
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()
-- 2D 마우스 위치에서 레이 생성
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- 최대 거리로 곱한 레이의 단위 방향 벡터
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- 레이의 원점에서 방향으로 레이캐스트
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- 3D 교차점 반환
return raycastResult.Position
else
-- 물체가 충돌하지 않았으므로 레이의 끝 위치 계산
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- 정규화된 방향 벡터를 계산하고 레이저 거리로 곱합니다.
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- 무기를 발사할 방향, 최대 거리로 곱한 값
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- 플레이어의 캐릭터를 무시하여 스스로를 다치지 않도록 합니다.
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- 시작 위치와 끝 위치 사이에 어떤 물체가 충돌했는지 확인
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- 맞은 인스턴스는 캐릭터 모델의 자식이 됩니다.
-- 모델에서 휴머노이드가 발견되면 플레이어의 캐릭터일 가능성이 높습니다.
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
-- 최대 레이저 거리 기반으로 끝 위치 계산
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 -- 레이저가 보이는 시간
-- 시작 위치에서 끝 위치로 레이저 빔 생성
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
-- 레이저 빔을 Debris 서비스에 추가하여 제거 및 정리
Debris:AddItem(laserPart, SHOT_DURATION)
-- 무기의 발사 사운드 재생
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
-- 플레이어가 들고 있는 도구의 핸들을 찾습니다.
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)
-- 맞은 캐릭터와 맞은 위치 간의 거리 검증
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- 벽을 통과하여 쏘는지 확인
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)
-- 캐릭터가 아닌 인스턴스가 맞았다면 발사를 무시합니다.
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- 모든 클라이언트에게 레이저가 발사되었음을 알리기 위해 레이저를 표시할 수 있도록 합니다.
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
-- 캐릭터의 건강을 제거합니다.
humanoid.Health -= LASER_DAMAGE
end
end
-- 적절한 함수에 이벤트 연결
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
-- 다른 플레이어의 레이저 표시
local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
if playerWhoShot ~= Players.LocalPlayer then
LaserRenderer.createLaser(toolHandle, endPosition)
end
end
eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)
©2026 Roblox Corporation. Roblox 및 Roblox 로고, 'Powering Imagination'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.