レーザーでヒット検出

*このコンテンツは、ベータ版のAI(人工知能)を使用して翻訳されており、エラーが含まれている可能性があります。このページを英語で表示するには、 こちら をクリックしてください。

このチュートリアルでは、プレイヤーツールを作成 でブラスターからレーザーをキャストし、プレイヤーにヒットしたかどうかを検出する方法を学びます。

衝突を見つけるレイキャスト

レイキャスト は、定義された長さで開始位置から指定された方向に向かって、見えないレイを作成します。レイがそのパス上のオブジェクトや地形と衝突すると、位置や衝突したオブジェクトなどの衝突情報が返されます。

A から B へのレイキャストが壁と衝突する

マウスの位置を見つける

レーザーを撃つ前に、プレイヤーが狙っている場所を最初に知らなければなりません。これは、カメラからゲームワールドへ直接スクリーン上のプレイヤーの 2D マウスロケーションをレイキャストして見つけることができます。レイは、プレイヤーがマウスで狙っているものと衝突します。

  1. ブラスターツールから ツールコントローラー スクリプトを開き、プレイヤーツールを作成 。まだそのチュートリアルを完了していない場合は、ブラスター モデルをダウンロードしてスターターパックに挿入できます。

  2. スクリプトの最上部で、値 1000 の常数 MAX_MOUSE_DISTANCE を宣言します。

  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. Use the GetMouseLocation function of UserInputService を使用して、画面上のプレイヤーの 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 マウスの位置が知られているので、その X および Y プロパティは、画面から 3D ゲームワールドに移動する 機能のパラメータとして使用できます。

  1. 使用する X および Y プロパティの mouseLocation は、ViewportPointToRay() 関数の引数として。これを screenToWorldRay という変数に割り当てます。


    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- 2D マウスの位置からレイを作成
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    end

Raycast 関数を使用して、レイがオブジェクトにヒットするかどうかをチェックする時が来ました。これには開始位置と方向ベクトルが必要です:この例では、screenToWorldRay の起源と方向プロパティを使用します。

方向ベクトルの長さは、レイがどこまで移動するかを決定します。レイは MAX_MOUSE_DISTANCE の長さである必要があるので、方向ベクトルを MAX_MOUSE_DISTANCE で乗算する必要があります。

  1. 変数 directionVector を宣言し、値を 乗算して割り当てます。


    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. ワークスペースの 関数を呼び出し、 起源 プロパティを最初の引数として、 を 2番目の引数として、呼び出します。これを 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 プロパティ説明
インス턴スレイが交差した BasePart または Terrain セル。
地位交差点が発生した場所; 通常、部品または地形の表面上のポイントです。
材料衝突ポイントの材料。
ノーマル交差した顔の普通のベクトル。これを使用して、顔がどの方向を指しているかを判断できます。

位置 プロパティは、マウスがホバリングしているオブジェクトの位置になります。マウスが MAX_MOUSE_DISTANCE 距離内のオブジェクトにホバリングしていない場合、raycastResultnil になります。

  1. if 文を作成して、raycastResult が存在するかどうかをチェックします。

  2. If raycastResult に値がある場合、その 位置 プロパティを返します。

  3. If 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 マウスの位置が知られたので、レーザーを発射するための ターゲットポジション として使用できます。2番目のレイは、 レイキャスト 関数を使用して、プレイヤーの武器とターゲットの位置の間に投射できます。

  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. Create a function called fireWeapon under the getWorldMousePosition function.を呼び出す関数を作成します。

  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. その ユニット プロパティを使用してベクトルを正常化します。これにより、後で長さで乗算するのが簡単になり、ベクトルの大きさが 1 になります。


    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    -- 正常化された方向ベクトルを計算し、レーザー距離で乗算する
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. 変数名を directionVector として宣言し、targetDirection を掛けて MAX_LASER_DISTANCE に割り当てます。


    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    -- 武器を発射する方向、最大距離で掛け算する
    local directionVector = targetDirection * MAX_LASER_DISTANCE
    end

A RaycastParams オブジェクトは、レイキャスト関数の追加パラメータを保存するために使用できます。レーザーブラスターで使用され、レイキャストが武器を発射しているプレイヤーと偶然に衝突しないようにします。レイキャストパラメータオブジェクトの FilterDescendantsInstances プロパティに含まれるパーツは、 無視されます レイキャストで。

  1. fireWeapon 関数を続行し、 weaponRaycastParams という変数を宣言します。新しい RaycastParams オブジェクトを割り当てます。

  2. プレイヤーのローカル 文字 を含むテーブルを作成し、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. Use an if 文を使用して、weaponRaycastResult が値を持っているかどうかを確認します。オブジェクトがヒットした場合は、weaponRaycastResult.PositionhitPosition に割り当てます。


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- 開始から終了までの間にオブジェクトがヒットしたかどうかをチェックする
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. If 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. ナビゲート to the toolActivated 機能と fireWeapon 機能を呼び出して、レーザーがツールが有効になるたびに発射するようにします。


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

オブジェクトヒットをチェック

レーザーによってヒットされたオブジェクトがプレイヤーのキャラクターの一部か、ただの景観の一部かどうかを見つけるには、すべてのキャラクターに Humanoid を検索する必要があります。

まず、 キャラクターモデル を見つける必要があります。キャラクターの一部がヒットした場合、ヒットしたオブジェクトの親がキャラクターであるとは推定できません。レーザーは、体の部分、アクセサリ、またはツールを打つことができましたが、すべてはキャラクタの階層の異なる部分に位置しています。

FindFirstAncestorOfClass を使用して、レーザーによってヒットされたオブジェクトのキャラクターモデルの祖先を見つけることができます(存在する場合)。モデルを見つけて、それにヒューマノイドが含まれている場合、ほとんどの場合、それがキャラクターであると想定できます。

  1. ハイライトされたコードを以下の 文に追加して、キャラクターがヒットしたかどうかをチェックします。


    -- 開始から終了までの間にオブジェクトがヒットしたかどうかをチェックする
    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("Player hit")
    end
    end
    else
    -- 最大レーザー距離に基づいて最終位置を計算する
    hitPosition = tool.Handle.Position + directionVector
    end

今、レーザーブラスターはレイキャスト操作が別のプレイヤーにヒットするたびに出力ウィンドウに Player hit を印刷する必要があります。

複数のプレイヤーでテスト

武器レイキャストが他のプレイヤーを見つけているかをテストするには、2人のプレイヤーが必要であるため、ローカルサーバーを開始する必要があります。

  1. Studio で テスト タブを選択します。

  2. プレイヤーのドロップダウンが「2人」に設定されていることを確認し、スタートボタンをクリックして ローカルサーバーを 2 クライアントで開始 します。3つのウィンドウが表示されます。最初のウィンドウはローカルサーバー、他のウィンドウは Player1 と Player2 のクライアントになります。

  3. 1つのクライアントで、武器をクリックして他のプレイヤーをテスト撮影します。「プレイヤーヒット」は、プレイヤーが撃たれるたびに出力に表示されるべきです。

詳しくは、 テスト タブ ここ で見つけることができます。

レーザーの位置を見つける

ブラスターはターゲットに赤い光ビームを発射すべきです。これの機能は ModuleScript 内にあるので、後で他のスクリプトで再利用できます。まず、スクリプトはレーザービームがレンダリングされる位置を見つける必要があります。

  1. Create a モジュールスクリプト named LaserRenderer , parented to StarterPlayerScripts under StarterPlayer.

  2. スクリプトを開き、モジュールテーブルの名前をスクリプト名に変更します LaserRenderer

  3. 変数 SHOT_DURATION を値 0.15 で宣言します。これはレーザーが見える時間(秒)の量になります。

  4. LaserRenderer の機能を createLaser という名前で作成し、2つのパラメータ toolHandleendPosition を持って作成します。


    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- レーザーが見える時間
    -- 開始位置から終了位置へレーザービームを作成する
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. 変数 startPosition を宣言し、 位置 プロパティを toolHandle の値として設定します。これがプレイヤーのレーザーブラスターの位置になります。

  6. 変数 laserDistance を宣言し、endPosition から startPosition を差し引いて、2つのベクトルの違いを見つけます。この 大きさ プロパティを使用して、レーザービームの長さを取得します。


    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. laserCFrame 変数を宣言して、レーザービームの位置と方向を保存する位置はビームの始点と終点の中間になければなりません。Use CFrame.lookAt を使用して、新しい CFramestartPosition に配置し、endPosition に向かって作成します。これをマイナスの 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. サイズ : Vector3.new(0.2, 0.2, laserDistance)
    2. CFrame : laserCFrame
    3. 固定済み : 真
    4. 可能な衝突 : false
    5. : Color3.fromRGB(225, 0, 0) (強い赤色)
    6. マテリアル : Enum.Material.Neon
  3. laserPartワークスペース に。

  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:AddItem(laserPart, SHOT_DURATION)
    end

今、レーザービームをレンダリングする機能が完了したので、 ツールコントローラー に呼び出されることができます。

  1. ツールコントローラー スクリプトの最上部で、 LaserRenderer という名前の変数を宣言し、PlayerScripts 内の LaserRenderer モジュールスクリプトを要求する


    local UserInputService = game:GetService("UserInputService")
    local Players = game:GetService("Players")
    local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
    local tool = script.Parent
  2. fireWeapon 関数の底で、ツールハンドルと createLaser を引数として使用して LaserRenderer hitPosition 関数を呼び出します。


    -- 最大レーザー距離に基づいて最終位置を計算する
    hitPosition = tool.Handle.Position + directionVector
    end
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  3. プレイボタンをクリックして武器をテストします。ツールが有効になったときに、武器とマウスの間にレーザービームが表示されるべきです。

武器の発射評価するを制御

武器は、各ショットの間に遅延が必要で、プレイヤーが短時間に多くのダメージを与えるのを止めます。これは、プレイヤーが最後に発射した時から十分な時間が経過したかどうかをチェックすることで制御できます。

  1. ツールコントローラ のトップに変数を宣言し、呼び出された FIRE_RATE 。これは、各ショットの間の最小時間になります。好きな値を与えてください; この例では 0.3 秒を使用しています。

  2. 別の変数を下に宣言し、値は 0timeOfPreviousShot と呼ばれます。これはプレイヤーが最後に発砲した時を保存し、毎回のショットで更新されます。


    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 300
    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
  3. パラメータなしで canShootWeapon という機能を作成します。この機能は、前のショットからどれくらい時間が経過したかを見て、真または偽を返します。


    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- 前のショットが発射されてから十分な時間が経過したかどうかをチェック
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. 機能内で、変数 currentTime を宣言し、tick() 関数を呼び出す結果を割り当てます。これは、1970年1月1日から、秒単位でどれくらい時間が経過したかを返します (時間を計算するために広く使用されている任意の日付)。

  5. から を差し引き、結果が より小さい場合は、 より小さいと返し、そうでない場合は 真 を返します。


    -- 前のショットが発射されてから十分な時間が経過したかどうかをチェック
    local function canShootWeapon()
    local currentTime = tick()
    if currentTime - timeOfPreviousShot < FIRE_RATE then
    return false
    end
    return true
    end
  6. 関数の終わりで、武器が を使用して発射されるたびに更新します。


    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  7. toolActivated 関数内で、 如果 文を作成し、武器が発射できるかどうかを確認するために canShootWeapon を呼び出します。


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

ブラスターをテストすると、どれほど早くクリックしても、各ショットの間に常に短い 0.3秒の遅延が発生することがわかります。

プレイヤーにダメージを与える

クライアントは他のクライアントに直接ダメージを与えることはできません;サーバーは、プレイヤーが攻撃されたときにダメージを発行する責任がある必要があります。

クライアントは、RemoteEvent を使用して、サーバーにキャラクターがヒットしたことを告知できます。これらは ReplicatedStorage に保存され、クライアントとサーバーの両方に表示される必要があります。

  1. ReplicatedStorage に名前を イベント という ReplicatedStorage にフォルダを作成します。

  2. リモートイベントをイベントフォルダに挿入し、名前を ダメージキャラクター とします。

  3. In ツールコントローラ で、スクリプトの開始時に ReplicatedStorage とイベントフォルダの変数を作成します。


    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. "Player hit" 印刷文を fireWeapon に Luau の行で置き換えて、 ダメージキャラクター リモートイベントを characterModel 変数を引数として発射します。


    local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    eventsFolder.DamageCharacter:FireServer(characterModel)
    end
    end
    else
    -- 最大レーザー距離に基づいて最終位置を計算する
    hitPosition = tool.Handle.Position + directionVector
    end

サーバーは、イベントが発動したときにヒットしたプレイヤーにダメージを与える必要があります。

  1. ServerScriptService に スクリプト を挿入し、名前を ServerLaserManager とします。

  2. 変数 LASER_DAMAGE という名前を宣言し、 10 または好みの値に設定します。

  3. 2つのパラメータ playerFiredcharacterToDamage を持つ機能名 damageCharacter を作成し、2つのパラメータを使用して呼び出します。

  4. 機能内で、キャラクターのヒューマノイドを見つけて、健康から LASER_DAMAGE を差し引きます。

  5. 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. ローカルサーバーを開始して、ブラスターを 2 人のプレイヤーでテストします。他のプレイヤーを撃つと、そのプレイヤーの体力が LASER_DAMAGE に割り当てられた数で減少します。

他のプレイヤーのレーザービームをレンダリング

現在、レーザービームは武器を発射するクライアントによって作成されるため、彼らだけがレーザービームを見ることができます。

サーバー上でレーザービームが作成された場合、誰もがそれを見ることができます。しかし、クライアントが武器を発射してサーバーがショットに関する情報を受信する間に、小さな 遅延 が発生する可能性があります。これは、クライアントが武器を発射すると、武器を有効にしてからレーザービームを見るまでの遅延が発生することを意味します;その結果、武器は遅れて感じられます。

この問題を解決するために、各クライアントは独自のレーザービームを作成します。これは、武器を撃つクライアントがすぐにレーザービームを見ることを意味します。他のクライアントは、別のプレイヤーがシュートしてビームが表示される間に少しの遅延を経験します。これは最良のシナリオです:別のクライアントにレーザーをコミュニケートする方法は、より速くはありません。

シューターのクライアント

まず、クライアントはサーバーにレーザーを発射し、終了位置を提供したことを伝える必要があります。

  1. ReplicatedStorage のイベントフォルダに リモートイベント を挿入し、名前を LaserFired とします。

  2. fireWeapon 機能を ツールコントローラー スクリプトで見つけます。機能の終わりに、 LaserFired リモートイベントを hitPosition を引数として使用して発射します。


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

サーバー

サーバーは今、クライアントが発射したイベントを受信し、すべてのクライアントにレーザービームの開始および終了位置を伝えて、彼らもレンダリングできるようにする必要があります。

  1. In the ServerLaserManager スクリプト, create a function named playerFiredLaser above damageCharacter with two parameters called playerFiredendPosition .

  2. 機能を LaserFired リモートイベントに接続します。


    -- レーザーが発射されたことをすべてのクライアントに通知して、レーザーを表示できるようにする
    local function playerFiredLaser(playerFired, endPosition)
    end

    -- イベントを適切な機能に接続する
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

サーバーはレーザーの開始位置が必要です。これはクライアントから送信できますが、可能な限りクライアントを信頼しないことが最善です。キャラクターの武器ハンドルの位置は、スタートポジションになるので、サーバーはそこから見つけることができます。

  1. パラメータ playerFiredLaser を持つ上記の player 機能を作成し、 **** 機能の上に配置します。

  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)

サーバーは今、 FireAllClientsLaserFired リモートイベントに呼び出し、レーザーをクライアントにレンダリングするために必要な情報を送信できます。これには、レーザーを発射した プレイヤー 、ブラスターの ハンドル 、およびレーザーの終わりの位置が含まれます。

  1. playerFiredLaser 関数では、getPlayerToolHandle 関数を引数として呼び出し、playerFired を値として変数名 toolHandle に割り当てます。

  2. ツールハンドル が存在する場合、playerFiredtoolHandle、および endPosition を引数として使用するすべてのクライアントに対して、LaserFired イベントを発射します。


    -- レーザーが発射されたことをすべてのクライアントに通知して、レーザーを表示できるようにする
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

クライアントでレンダリング

今、 FireAllClients が呼び出されたので、各クライアントはサーバーからレーザービームをレンダリングするイベントを受け取ります。各クライアントは、 LaserRenderer モジュールを以前から再使用して、サーバーによって送信されたツールのハンドルポジションと終了ポジション値を使用してレーザービームをレンダリングできます。レーザービームを最初に発射したプレイヤーは、このイベントを無視する必要があります。そうしないと、2つのレーザーを見ることになります。

  1. スタータープレイヤースクリプトに ローカルスクリプト を作成し、名前は ClientLaserManager

  2. スクリプト内で、 LaserRenderer モジュールが必要です。

  3. パラメータ createPlayerLaserplayerWhoShottoolHandleendPosition で名前の付いた機能を作成します。

  4. 機能を LaserFired リモートイベントにイベントフォルダに接続します。

  5. 機能では、 if 文を使用して、 がローカルプレイヤーと等しくないかどうかをチェックします。

  6. if 文内で、createLasertoolHandleendPosition を引数として使用して、レーザーレンダラーモジュールから 関数を呼び出します。


    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. ローカルサーバーを開始して、ブラスターを 2 人のプレイヤーでテストします。各クライアントをモニターの異なる側に配置して、両方のウィンドウを同時に見ることができます。1つのクライアントに撃つとき、他のクライアントにレーザーが表示されるはずです。

サウンド効果

現在、発射サウンド効果は、弾丸を発射しているクライアント上でのみ再生します。コードを移動してサウンドを再生する必要があり、他のプレイヤーも聞くことができます。

  1. ツールコントローラー スクリプトで、 ツールが有効化 機能に移動し、アクティベートサウンドを再生する行を削除します。


    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. LaserRenderer の 底 機能で、shootingSound という名前の変数を宣言し、 メソッドの Activate サウンドをチェックするために メソッドを使用します。

  3. Use an if 文を使用して、shootingSound が存在するかどうかをチェックします; 存在する場合は、その プレイ 機能を呼び出します。


    laserPart.Parent = workspace
    -- 削除してクリーンアップするデブリスサービスにレーザービームを追加する
    Debris:AddItem(laserPart, SHOT_DURATION)
    -- 武器の射撃音を再生する
    local shootingSound = toolHandle:FindFirstChild("Activate")
    if shootingSound then
    shootingSound:Play()
    end
    end

検証を使用してリモートを安全にする

サーバーが受信したリクエストからデータをチェックしていない場合、ハッカーはリモート機能とイベントを悪用し、サーバーに偽の値を送信することができます。これを防ぐためには、 サーバー側の検証 を使用することが重要です。

現在の形で、 ダメージキャラクター リモートイベントは、攻撃に非常に弱いです。ハッカーは、このイベントを使用して、撃つことなくゲームで望むプレイヤーにダメージを与えることができます。

検証は、サーバーに送信される値が現実的であるかどうかをチェックするプロセスです。この場合、サーバーは次のことが必要になります:

  • プレイヤーとレーザーによって命中した位置の距離が特定の境界内にあるかどうかをチェックします。
  • レーザーを発射した武器とヒット位置の間でレイキャストして、ショットが可能であり、壁を通り抜けなかったことを確認します。

クライバー

クライアントは、レイキャストによってヒットされた位置をサーバーに送信して、距離が現実的かどうかをチェックする必要があります。

  1. In ツールコントローラー , navigate to the line where the DamageCharacter リモートイベントが fireWeapon 関数で発動されるラインに移動します。

  2. hitPosition を引数として追加。


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

サーバー

クライアントは現在、DamageCharacter リモートイベントを通じて追加パラメータを送信しているので、 ServerLaserManager を調整して受け入れる必要があります。

  1. サーバーレーザーマネージャー スクリプトで、hitPosition パラメータを damageCharacter 関数に追加します。


    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- キャラクターから体力を削除
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. getPlayerToolHandle 関数の下で、3つのパラメータ isHitValid を持つ機能を作成します: playerFiredcharacterToDamagehitPosition


    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

2番目のチェックでは、発射された武器とヒットポジションの間のレイキャストが関与します。レイキャストがキャラクタではないオブジェクトを返した場合、ショットがブロックされていたため、ショットが無効であったと推測できます。

  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 で宣言します。それには、3つの引数で呼び出された isHitValid 関数の結果を割り当てます:playerFiredcharacterToDamage、そして hitPosition

  3. 以下の if 文では、 オペレータを追加して、validShot真実 かどうかをチェックします。


    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

今、ダメージキャラクターリモートイベントはより安全であり、ほとんどのプレイヤーが悪用するのを防ぐでしょう。悪意のあるプレイヤーの一部は、検証を回避する方法を見つけることが多いことに注意してください;リモートイベントを安全に保つことは連続的な努力です。

レーザーブラスターが完了し、レイキャストを使用した基本的なヒット検出システムが完了しました。ユーザーの入力を検出するチュートリアルを試して、レーザーブラスターにリロードアクションを追加するか、楽しいゲームマップを作成して他のプレイヤーとレーザーブラスターを試してみましょう!

最終コード

ツールコントローラ


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)

レーザーレンダラー


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:AddItem(laserPart, SHOT_DURATION)
-- 武器の射撃音を再生する
local shootingSound = toolHandle:FindFirstChild("Activate")
if shootingSound then
shootingSound:Play()
end
end
return LaserRenderer

サーバーレーザーマネージャ


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)

客先レーザーマネージャー


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)