このチュートリアルでは、プレイヤーツールを作成する において、ブラスターからレーザーを発射し、プレイヤーに当たるかどうかを検出する方法を学びます。
衝突を見つけるためのレイキャスト
レイキャストは、開始位置から特定の方向に対して定義された長さの見えない光線を作成します。もしその光線が進む途中でオブジェクトや地形に衝突した場合、その衝突に関する情報(位置や衝突したオブジェクト)を返します。

マウスの位置を見つける
レーザーを発射する前に、まずプレイヤーがどこを狙っているのかを知る必要があります。これは、プレイヤーの2Dマウス位置からカメラを通じて3D空間に直接レイキャストすることで見つけることができます。この光線は、プレイヤーがマウスで狙っているものに衝突します。
プレイヤーツールを作成するからブラスターのToolControllerスクリプトを開きます。まだこのチュートリアルを完了していない場合は、Blasterモデルをダウンロードし、StarterPackに挿入してください。

スクリプトの先頭に、値が1000の定数を名付けてMAX_MOUSE_DISTANCEと宣言します。
getWorldMousePositionという関数を作成します。
local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()endlocal function toolActivated()tool.Handle.Activate:Play()end-- イベントを適切な関数に接続しますtool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)UserInputServiceのGetMouseLocation関数を使用して、画面上のプレイヤーの2Dマウス位置を取得します。これをmouseLocationという変数に代入します。
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
これで2Dマウス位置が分かったので、そのXとYプロパティを、Camera:ViewportPointToRay()関数の引数として使用できます。この関数は、画面から3D空間に向かうRayを作成します。
mouseLocationのXおよびYプロパティを、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を掛ける必要があります。
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_DISTANCEWorldRoot:Raycast()関数を呼び出し、最初の引数にscreenToWorldRayのOriginプロパティを渡し、2番目に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の距離内でオブジェクトにホバーしていない場合、raycastResultはnilになります。
raycastResultが存在するかどうかを確認するif文を作成します。
raycastResultに値がある場合、そのPositionプロパティを返します。
raycastResultがnilである場合、レイキャストの終点を見つけます。screenToWorldRay.OriginとdirectionVectorを加算することで、マウスの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関数を使用して2番目の光線をキャストできます。
スクリプトの先頭にMAX_LASER_DISTANCEという定数を宣言し、値500または選択したレーザーブラスターの範囲を代入します。
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500getWorldMousePosition関数の下にfireWeaponという関数を作成します。
getWorldMousePositionを呼び出し、その結果をmousePositionという変数に代入します。これがレイキャストのターゲット位置になります。
-- オブジェクトに衝突しないので、レイの終点を計算しますreturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()end
今度は、レイキャスト関数の方向ベクトルが、プレイヤーのツール位置からターゲット位置へ向かう方向を表します。
targetDirectionという名前の変数を宣言し、mouseLocationからツール位置を引いて方向ベクトルを計算します。
ベクトルを正規化するために、そのUnitプロパティを使用します。これにより、後で長さを掛けるのが容易なマグニチュード1のベクトルが得られます。
local function fireWeapon()local mouseLocation = getWorldMousePosition()-- 正規化された方向ベクトルを計算し、レーザー距離で掛けますlocal targetDirection = (mouseLocation - tool.Handle.Position).UnitenddirectionVectorという名前の変数を宣言し、targetDirectionにMAX_LASER_DISTANCEを掛けた値を代入します。
local targetDirection = (mouseLocation - tool.Handle.Position).Unit-- レーザーを発射する方向、最大距離で掛けますlocal directionVector = targetDirection * MAX_LASER_DISTANCEend
RaycastParamsオブジェクトを使用して、レイキャスト関数の追加のパラメータを格納できます。このオブジェクトは、レーザーブラスターで光線が弾を発射しているプレイヤーと衝突しないようにします。FilterDescendantsInstancesプロパティに含まれるパーツは、レイキャストで無視されます。
fireWeapon関数を続けて、weaponRaycastParamsという変数を宣言します。新しいRaycastParamsオブジェクトを代入します。
プレイヤーのローカルcharacterを含むテーブルを作成し、それをweaponRaycastParams.FilterDescendantsInstancesプロパティに代入します。
プレイヤーのツールハンドル位置から、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
最後に、レイキャスト操作が値を返したかどうかを確認する必要があります。値が返されている場合、オブジェクトが光線に当たったことになるので、武器と当たった位置の間でレーザーを作成できます。何も返されていない場合、レーザーを作成するためにレイキャストの終点を計算する必要があります。
hitPositionという名前の空の変数を宣言します。
if文を使用してweaponRaycastResultに値があるかどうかを確認します。オブジェクトが当たった場合、weaponRaycastResult.PositionをhitPositionに代入します。
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- 開始位置と終了位置の間でオブジェクトが当たったか確認しますlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.PositionendweaponRaycastResultに値がない場合、ツールハンドルの位置とdirectionVectorを加算して、レイキャストの終点の位置を計算します。これをhitPositionに代入します。
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- 開始位置と終了位置の間でオブジェクトが当たったか確認しますlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- 最大レーザー距離に基づいて終点を計算しますhitPosition = tool.Handle.Position + directionVectorendendtoolActivated関数に移動し、ツールをアクティブにするたびにレーザーを発射するためにfireWeapon関数を呼び出します。
local function toolActivated()tool.Handle.Activate:Play()fireWeapon()end
ヒットしたオブジェクトを確認
レーザーによって当たったオブジェクトがプレイヤーのキャラクターの一部か、単なる風景かを見つけるには、すべてのキャラクターにはHumanoidがあるため、これを探す必要があります。
まず、character modelを見つける必要があります。もしキャラクターの一部が当たった場合、当たったオブジェクトの親がキャラクターであるとは限りません。レーザーは、体の部位、アクセサリー、またはツールに当たるかもしれず、これらはすべてキャラクターの階層の異なる部分にあります。

FindFirstAncestorOfClassを使用して、レーザーによって当たったオブジェクトのキャラクターモデルの祖先を見つけることができます。モデルを見つけ、それにヒューマノイドが含まれている場合、ほとんどの場合、それはキャラクターであると見なすことができます。
weaponRaycastResultのif文に、キャラクターがヒットしたか確認するために下記のハイライトされたコードを追加します。
-- 開始位置と終了位置の間でオブジェクトが当たったか確認しますlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Position-- 当たったインスタンスはキャラクターモデルの子供になります-- モデル内にヒューマノイドが見つかれば、それはプレイヤーのキャラクターの可能性が高いlocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("プレイヤーがヒットしました")endendelse-- 最大レーザー距離に基づいて終点を計算しますhitPosition = tool.Handle.Position + directionVectorend
これで、レーザーブラスターはレイキャスト操作が他のプレイヤーをヒットするたびにOutputウィンドウに「プレイヤーがヒットしました」と表示するようになります。
複数のプレイヤーでテスト
武器のレイキャストが他のプレイヤーを見つけているかをテストするには、2人のプレイヤーが必要なので、ローカルサーバーを開始する必要があります。手順についてはmulti‑client simulationを参照してください。
一方のクライアントで、他のプレイヤーをクリックして武器で撃つテストを行います。「プレイヤーがヒットしました」と表示されるはずです。
レーザーの位置を見つける
ブラスターは、そのターゲットに赤い光線を発射する必要があります。この機能は、後で他のスクリプトで再利用できるように、ModuleScript内に置きます。まず、レーザー光線がレンダリングされる位置を見つける必要があります。
ModuleScriptをLaserRendererと名付け、StarterPlayerのStarterPlayerScriptsに親子化します。

スクリプトを開き、モジュールテーブルをスクリプトの名前であるLaserRendererにリネームします。
SHOT_DURATIONという名前の変数を宣言し、その値を0.15に設定します。これは、レーザーが表示される時間(秒)です。
LaserRenderer内でcreateLaserという名前の関数を作成し、2つのパラメーターにtoolHandleとendPositionを指定します。
local LaserRenderer = {}local SHOT_DURATION = 0.15 -- レーザーが表示される時間-- 開始位置から終端位置に向けたレーザー光線を作成しますfunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererstartPositionという名前の変数を宣言し、toolHandleのPositionプロパティをその値に設定します。これがプレイヤーのレーザーブラスターの位置になります。
laserDistanceという名前の変数を宣言し、startPositionからendPositionを引いて、2つのベクトルの差を見つけます。この長さを求めるためにMagnitudeプロパティを使用します。
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudeendレーザー光線の位置と向きを格納するlaserCFrame変数を宣言します。位置は光線の開始と終了の中間地点である必要があります。CFrame.lookAtを使用して、startPositionにあり、endPositionに向いている新しいCFrameを作成します。これを負のlaserDistanceの半分で新しいCFrameと掛け算して中間地点を取得します。
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)end
レーザーパートを作成する
レーザービームを作成する位置がわかったので、実際のビームを追加する必要があります。これは、ネオンパートを使うことで簡単に実現できます。
laserPartという変数を宣言し、新しいPartインスタンスを代入します。
laserPartの以下のプロパティを設定します。
- Size: Vector3.new(0.2, 0.2, laserDistance)
- CFrame: laserCFrame
- Anchored: true
- CanCollide: false
- Color: Color3.fromRGB(225, 0, 0) (強い赤色)
- Material: Enum.Material.Neon
laserPartをWorkspaceに親子化します。
Debrisサービスにパートを追加して、SHOT_DURATION変数の秒数後に削除されてクリーンアップされるようにします。
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal 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 = laserCFramelaserPart.Anchored = truelaserPart.CanCollide = falselaserPart.Color = Color3.fromRGB(225, 0, 0)laserPart.Material = Enum.Material.NeonlaserPart.Parent = workspace-- レーザービームをDebrisサービスに追加して削除及びクリーンアップしますDebris:AddItem(laserPart, SHOT_DURATION)end
これでレーザービームをレンダリングする関数が完成しました。次にToolControllerで呼び出せるようにします。
ToolControllerスクリプトの先頭で、LaserRendererという変数を宣言し、PlayerScripts内のLaserRenderer ModuleScriptを参照します。
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.ParentfireWeapon関数の最後で、hitPositionを引数にしてLaserRendererのcreateLaser関数を呼び出します。
-- 最大レーザー距離に基づいて終点を計算しますhitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endPlayボタンをクリックして武器をテストします。ツールがアクティブなとき、レーザー光線が武器からマウスの間で表示されるはずです。
武器の発射レートを制御する
武器には、短時間であまりにも多くのダメージを与えないように、各ショットの間に遅延が必要です。これは、プレイヤーが最後に発射してからの時間をチェックすることで制御できます。
ToolControllerの先頭にFIRE_RATEという名前の変数を宣言します。これは、各ショットの間に必要な最小時間です。適当な値を設定します。この例では0.3秒を使用します。
その下にtimeOfPreviousShotという別の変数を宣言し、値を0にします。これは、プレイヤーが最後に発射した時間を保存し、各ショットで更新されます。
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0パラメータなしでcanShootWeaponという関数を作成します。この関数は、前回のショットからどれだけの時間が経過したかを調べ、真または偽を返します。
local FIRE_RATE = 0.3local timeOfPreviousShot = 0-- 前回のショットが発射されてから十分な時間が経過したか確認しますlocal function canShootWeapon()endlocal function getWorldMousePosition()関数内で、currentTimeという名前の変数を宣言し、tick()関数を呼び出してその結果を代入します。これにより、1970年1月1日から経過した時間(秒)が返されます。
timeOfPreviousShotをcurrentTimeから引き、その結果がFIRE_RATEより小さい場合はfalseを返し、それ以外の場合はtrueを返します。
-- 前回のショットが発射されてから十分な時間が経過したか確認しますlocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendfireWeapon関数の最後で、武器が発射されるたびにtimeOfPreviousShotをtickを使用して更新します。
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endtoolActivated関数内でif文を作成し、canShootWeaponを呼び出して、武器を撃つことができるかどうかを確認します。
local function toolActivated()if canShootWeapon() thentool.Handle.Activate:Play()fireWeapon()endend
ブラスターをテストすると、どれだけ素早くクリックしても、各ショットの間に常に短い0.3秒の遅延があることが分かります。
プレイヤーにダメージを与える
クライアントは他のクライアントに直接ダメージを与えることはできません。サーバーがプレイヤーがヒットしたときにダメージを発行する責任を持つ必要があります。
クライアントは、RemoteEventを使用してサーバーにキャラクターがヒットしたことを通知できます。これらはReplicatedStorageに保存され、クライアントとサーバーの両方から見ることができます。
ReplicatedStorage内にEventsという名前のフォルダーを作成します。

EventsフォルダーにRemoteEventを挿入し、名前をDamageCharacterにします。

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.Parentlocal eventsFolder = ReplicatedStorage.Eventslocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500fireWeapon内の「プレイヤーがヒットしました」と表示するprint文を、characterModel変数を引数にしてDamageCharacterリモートイベントを発火するLuauの行に置き換えます。
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endendelse-- 最大レーザー距離に基づいて終点を計算しますhitPosition = tool.Handle.Position + directionVectorend
サーバーは、イベントが発火されたときにヒットしたプレイヤーにダメージを与える必要があります。
ServerScriptServiceにScriptを挿入し、名前をServerLaserManagerにします。

LASER_DAMAGEという名前の変数を宣言し、10またはお好みの値に設定します。
playerFiredおよびcharacterToDamageという2つのパラメータを持つdamageCharacterという名前の関数を作成します。
関数内で、キャラクターのHumanoidを見つけ、その健康をLASER_DAMAGEから引きます。
Eventsフォルダー内のDamageCharacterリモートイベントにdamageCharacter関数を接続します。
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- キャラクターから健康を削除しますhumanoid.Health -= LASER_DAMAGEendend-- 適切な関数にイベントを接続しますeventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)ローカルサーバーを開始して2人のプレイヤーでブラスターをテストします。他のプレイヤーを撃つと、その健康がLASER_DAMAGEで割り当てられた値だけ減少します。
他のプレイヤーのレーザービームをレンダリングする
現在、レーザービームは武器を発射しているクライアントによって作成されているため、彼らだけがそのレーザービームを見ることができます。
サーバーでレーザービームを作成すれば、全員がそれを見ることができるようになります。ただし、クライアントが武器を撃つときとサーバーがショットの情報を受信する間にごく小さな遅延が生じるでしょう。これにより、武器を撃っているクライアントは、武器をアクティブにしてからレーザービームを見るまでの間に遅延を感じることになります。その結果、武器はラグがかかったように感じられます。
この問題を解決するために、すべてのクライアントは自分自身のレーザービームを作成します。これにより、武器を撃つクライアントはレーザービームをすぐに見ることができます。他のクライアントは、別のプレイヤーが撃ったときにビームが表示される間、わずかな遅延を経験することになります。これは最善のシナリオです。クライアントが他のクライアントに自身のレーザーをより早く伝える方法はありません。
シューターのクライアント
まず、クライアントはサーバーにレーザーを撃ったことを通知し、終点を指定する必要があります。
ReplicatedStorageのEventsフォルダー内にRemoteEventを挿入し、名前をLaserFiredにします。

ToolControllerスクリプト内のfireWeapon関数を見つけます。関数の最後で、LaserFiredリモートイベントを発火し、hitPositionを引数として渡します。
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()eventsFolder.LaserFired:FireServer(hitPosition)LaserRenderer.createLaser(tool.Handle, hitPosition)end
サーバー
サーバーは、クライアントが発火したイベントを受信し、すべてのクライアントにレーザービームの開始位置と終了位置を伝える必要があります。これにより、それらはレーザーをレンダリングできます。
ServerLaserManagerスクリプト内に、2つのパラメータであるplayerFiredとendPositionを持つplayerFiredLaserという関数を作成します。
関数をLaserFiredリモートイベントに接続します。
-- ライザーが発射されたことをすべてのクライアントに通知し、レーザーを表示させますlocal function playerFiredLaser(playerFired, endPosition)end-- 適切な関数にイベントを接続しますeventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
サーバーは、レーザーの開始位置を知る必要があります。これはクライアントから送信することも可能ですが、可能な限りクライアントを信頼しないことが最善です。キャラクターの武器ハンドル位置がその開始位置となるため、サーバーはそこから見つけることができます。
getPlayerToolHandleという関数をplayerFiredLaser関数の上に作成し、引数にplayerを指定します。
プレイヤーのキャラクター内の武器を検索し、ハンドルオブジェクトを返す以下のコードを使用します。
local LASER_DAMAGE = 10-- プレイヤーが持っている武器のハンドルを見つけますlocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- ライザーが発射されたことをすべてのクライアントに通知し、レーザーを表示させますlocal function playerFiredLaser(playerFired, endPosition)
サーバーは、LaserFiredリモートイベントのFireAllClientsを呼び出して、レーザーをレンダリングするために必要な情報をクライアントに伝えます。これには、レーザーを発射したplayer(そのプレイヤーのクライアントがレーザーを二重にレンダリングしないようにします)、ブラスターのhandle(レーザーの開始位置として機能する)、およびレーザーの終了位置が含まれます。
playerFiredLaser関数内で、引数にplayerFiredを指定してgetPlayerToolHandle関数を呼び出し、その値をtoolHandleという変数に代入します。
toolHandleが存在する場合、playerFired、toolHandle、endPositionを引数として使用してLaserFiredイベントをすべてのクライアントに発火します。
-- ライザーが発射されたことをすべてのクライアントに通知し、レーザーを表示させますlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
クライアントでのレンダリング
FireAllClientsが呼び出されたため、各クライアントはサーバーからレーザービームをレンダリングするためのイベントを受信します。各クライアントは、サーバーから送信されたツールのハンドル位置と終了位置を使用してレーザービームをレンダリングするために、先に使ったLaserRendererモジュールを再利用できます。レーザービームを撃ったプレイヤーはこのイベントを無視すべきです。そうしないと、2つのレーザーを見ることになります。
StarterPlayerScriptsにLocalScriptを作成し、ClientLaserManagerと名付けます。

スクリプト内で、LaserRendererモジュールを参照します。
playerWhoShot、toolHandle、endPositionのパラメーターを持つcreatePlayerLaserという関数を作成します。
Eventsフォルダー内のLaserFiredリモートイベントにその関数を接続します。
関数内で、if文を使用してplayerWhoShotがLocalPlayerと等しくないかどうかを確認します。
if文内で、toolHandleとendPositionを引数として使用して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 thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)2人でローカルサーバーを開始し、ブラスターをテストします。各クライアントをモニターの異なる側に配置して、両方のウィンドウが同時に見えるようにします。一方のクライアントで撃つと、もう一方のクライアントにレーザーが表示されるべきです。
サウンドエフェクト
現在、発射音効果はプロジェクタイルを撃っているクライアントのみで再生されています。他のプレイヤーも聞こえるように、音を再生するコードを移動する必要があります。
ToolControllerスクリプト内で、toolActivated関数に移動し、Activateサウンドを再生する行を削除します。
local function toolActivated()if canShootWeapon() thenfireWeapon()endendLaserRenderer内のcreateLaser関数の最後で、shootingSoundという名前の変数を宣言し、toolHandleのActivateサウンドを確認するためにFindFirstChild()メソッドを使用します。
if文を使用して、shootingSoundが存在するかどうかを確認し、存在する場合はそのPlay()関数を呼び出します。
laserPart.Parent = workspace-- レーザービームをDebrisサービスに追加して削除及びクリーンアップしますDebris:AddItem(laserPart, SHOT_DURATION)-- 武器の発射音を再生しますlocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
検証を使用してリモートを安全にする
もしサーバーが受信したリクエストのデータを確認していなければ、ハッカーはリモート関数やイベントを悪用し、サーバーに偽の値を送信することができます。これは、サーバー側の検証を使用して防止するのが重要です。
現在の状態では、DamageCharacterリモートイベントは攻撃に非常に脆弱です。ハッカーはこのイベントを使用して、彼らが撃たれることなく任意のプレイヤーにダメージを与えることができます。
検証は、サーバーに送信される値が現実的であるかどうかを確認するプロセスです。この場合、サーバーは以下を実施する必要があります。
- プレイヤーとレーザーでヒットした位置との距離が特定の境界内にあるかどうかを確認します。
- 武器が撃たれた位置とヒット位置の間でレイキャストを行い、ショットが可能であり、壁を通過しなかったことを確認します。
クライアント
クライアントは、サーバーにヒットした位置を送信する必要があります。サーバーは距離を現実的であるかどうかを確認できます。
ToolController内で、fireWeapon関数内のDamageCharacterリモートイベントが発火される行に移動します。
hitPositionを引数として追加します。
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
サーバー
クライアントは、DamageCharacterリモートイベントを介して追加のパラメータを送信しているため、ServerLaserManagerもこれを受け入れるために調整する必要があります。
ServerLaserManagerスクリプト内で、damageCharacter関数にhitPositionパラメータを追加します。
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- キャラクターから健康を削除しますhumanoid.Health -= LASER_DAMAGEendendgetPlayerToolHandle関数の下に、isHitValidという名前の関数を作成し、3つのパラメータ:playerFired、characterToDamage、hitPositionを指定します。
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
最初のチェックは、ヒットした位置とヒットしたキャラクターとの間の距離になります。
スクリプトの先頭でMAX_HIT_PROXIMITYという名前の変数を宣言し、その値を10に設定します。これは、ヒット位置とキャラクター間の最大距離になります。許容範囲が必要であるのは、クライアントがイベントを発火している間にキャラクターがわずかに動いている可能性があるからです。
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10isHitValid関数内で、キャラクターとヒット位置の間の距離を計算します。もしその距離がMAX_HIT_PROXIMITYを超えた場合、falseを返します。
local function isHitValid(playerFired, characterToDamage, hitPosition)-- ヒットされたキャラクターとヒット位置の間の距離を検証しますlocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
2番目のチェックは、撃たれた武器とヒット位置の間でレイキャストを行うことになります。もし、キャラクターでないオブジェクトが当たっている場合、ショットが無効であったことを示します。
次のコードをコピーして、チェックを実行します。関数の最後にtrueを返します。末尾に達した場合、すべてのチェックが合格したことを意味します。
local function isHitValid(playerFired, characterToDamage, hitPosition)-- ヒットされたキャラクターとヒット位置の間の距離を検証しますlocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseend-- 壁を通して撃つか確認しますlocal toolHandle = getPlayerToolHandle(playerFired)if toolHandle thenlocal rayLength = (hitPosition - toolHandle.Position).Magnitudelocal rayDirection = (hitPosition - toolHandle.Position).Unitlocal raycastParams = RaycastParams.new()raycastParams.FilterDescendantsInstances = {playerFired.Character}local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)-- もしキャラクターでないインスタンスがヒットした場合、そのショットを無視しますif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueenddamageCharacter関数内にvalidShotという名前の変数を宣言します。この変数にisHitValid関数を引数playerFired、characterToDamage、hitPositionを指定して代入します。
その下のif文内に、and演算子を追加してvalidShotがtrueであるかを確認します。
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")local validShot = isHitValid(playerFired, characterToDamage, hitPosition)if humanoid and validShot then-- キャラクターから健康を削除しますhumanoid.Health -= LASER_DAMAGEendend
これで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)