시작하기
중급 튜토리얼

레이저로 히트 감지

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

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

충돌 감지를 위한 레이캐스팅

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

A에서 B로 레이캐스트 하여 벽과 충돌

마우스 위치 찾기

레이저를 쏘기 전에 먼저 플레이어가 어디를 겨냥하고 있는지 알아야 합니다. 이는 카메라에서 3D 세계로 직접 앞으로 향하도록 화면에서 플레이어의 2D 마우스 위치를 레이캐스팅하여 찾을 수 있습니다. 이 레이는 플레이어가 마우스로 겨냥하고 있는 대상을 감지합니다.

  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 마우스 위치가 알려졌으므로 XY 속성을 Camera:ViewportPointToRay() 함수의 매개변수로 사용할 수 있습니다. 이 함수는 화면에서 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

이제 레이캐스트를 사용하여 레이가 객체에 충돌하는지 확인할 차례입니다. 이에는 시작 위치와 방향 벡터가 필요합니다: 이 예에서는 screenToWorldRay의 origin 및 direction 속성을 사용합니다.

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

  1. directionVector라는 이름의 변수를 선언하고 screenToWorldRay.Direction의 값에 MAX_MOUSE_DISTANCE를 곱하여 할당합니다.


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


    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)

충돌 정보

레이캐스트 작업이 레이로 인해 맞은 객체를 찾으면, 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 마우스 위치에서 레이 생성
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 마우스 위치가 알려졌으므로 레이저를 쏘기 위한 목표 위치로 사용할 수 있습니다. 두 번째 레이를 플레이어의 무기와 목표 위치 사이에 쏘기 위해 Raycast 함수를 사용할 수 있습니다.

  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. if 문을 사용하여 weaponRaycastResult에 값이 있는지 확인합니다. 객체가 맞으면 weaponRaycastResult.PositionhitPosition에 할당합니다.


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- 시작 위치와 끝 위치 사이에 어떤 객체가 맞았는지 확인합니다
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. weaponRaycastResult에 값이 없으면 도구 핸들의 위치directionVector를 더하여 레이캐스트의 끝 위치를 계산합니다. 이를 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. ModuleScript를 생성하고 LaserRenderer라고 명명하며, StarterPlayer 하위의 StarterPlayerScripts에 배치합니다.

  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을 빼서 두 벡터 간의 차이를 찾습니다. 이를 사용하여 레이저 빔의 길이를 구합니다.


    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. SHOT_DURATION 변수에 있는 초 수가 지난 후 제거되도록 레이저 파트를 Debris 서비스에 추가합니다.


    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 모듈 스크립트를 require 합니다.


    local UserInputService = game:GetService("UserInputService")
    local Players = game:GetService("Players")
    local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
    local tool = script.Parent
  2. fireWeapon 함수의 맨 아래에서 도구 핸들과 hitPosition을 인수로 사용하여 LaserRenderer의 createLaser 함수를 호출합니다.


    -- 최대 레이저 거리로 끝 위치 계산
    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 함수가 끝날 때마다 무기가 발사될 때마다 timeOfPreviousShot을 업데이트하여 tick을 사용합니다.


    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라는 이름의 폴더를 생성합니다.

  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에서 "플레이어 맞음"을 출력하는 줄을 DamageCharacter 원격 이벤트를 발사하는 줄로 대체합니다. characterModel 변수를 인수로 추가합니다.


    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에 Script를 삽입하고 이름을 ServerLaserManager로 설정합니다.

  2. LASER_DAMAGE라는 이름의 변수를 선언하고 값을 10으로 설정합니다. 원하시는 값을 선택하셔도 됩니다.

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

  4. 함수 안에서 해당 캐릭터의 Humanoid를 찾아 LASER_DAMAGE를 건강에서 빼줍니다.

  5. 이벤트 폴더의 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 폴더에 RemoteEvent를 삽입하고 이름을 LaserFired로 설정합니다.

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


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

서버

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

  1. ServerLaserManager 스크립트에서, 매개변수로 playerFiredendPosition를 가진 playerFiredLaser라는 이름의 함수를 만듭니다.

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


    -- 모든 클라이언트에게 레이저가 발사되었음을 통지하여 레이저를 표시할 수 있도록 합니다
    local function playerFiredLaser(playerFired, endPosition)
    end

    -- 적절한 함수에 이벤트 연결
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

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

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

  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)

이제 서버가 FireAllClients를 호출하여 모든 클라이언트에게 레이저를 렌더링하는 데 필요한 정보를 보내야 합니다. 여기에는 레이저를 발사한 player와 레이저를 시작하는 handle와 레이저의 끝 위치가 포함됩니다.

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

  2. toolHandle이 존재하면 playerFired, toolHandle, endPosition을 인수로 하여 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 내에 LocalScript를 만들고 이름을 ClientLaserManager로 설정합니다.

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

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

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

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

  6. if 문 안에서 LaserRenderer 모듈의 createLaser 함수를 호출하여 toolHandleendPosition을 인수로 제공합니다.


    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라는 변수를 선언하고 toolHandleActivate 소리를 찾기 위해 FindFirstChild() 메서드를 사용합니다.

  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, characterToDamage, hitPosition을 가집니다.


    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 함수의 결과로 설정하는 호출 결과로 할당합니다.

  3. 아래의 if 문에서 validShottrue인지 확인하기 위해 and 연산자를 추가합니다.


    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'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.