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

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

スクリプトの先頭に、MAX_MOUSE_DISTANCEという名前の定数を宣言し、値を1000に設定します。
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のoriginおよびdirectionプロパティを使用します。
方向ベクトルの長さは、レイがどれだけ移動するかを決定します。レイは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()関数をworkspaceのWorldRootで呼び出し、最初の引数に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マウス位置がわかったので、レーザーを発射するためのターゲット位置として使用できます。プレイヤーの武器とターゲット位置の間に2番目のレイを発射するためにRaycast関数を使用します。
スクリプトの先頭に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プロパティを使用して正規化します。これにより、後で長さを掛けやすくなります。
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を探す必要があります。すべてのキャラクターには1つがあります。
まず、キャラクターモデルを見つける必要があります。キャラクターの一部がヒットした場合、ヒットしたオブジェクトの親がキャラクターであるとは限りません。レーザーは体の一部、アクセサリー、またはツールに当たる可能性があり、これらはすべてキャラクターの階層内の異なる部分にあります。

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
これで、レーザーブラスターは、レイキャスト操作が他のプレイヤーにヒットするたびにプレイヤーがヒットしましたと出力ウィンドウに表示されるはずです。
複数のプレイヤーでテスト
武器のレイキャストが他のプレイヤーを見つけているかどうかをテストするには、2人のプレイヤーが必要です。ローカルサーバーを開始する必要があります。手順についてはmulti‑client simulationを参照してください。
1つのクライアントで、他のプレイヤーをクリックして武器で撃つテストを行います。プレイヤーが撃たれるたびに、出力に「プレイヤーがヒットしました」と表示されるはずです。
レーザー位置を見つける
ブラスターは、ターゲットに向かって赤い光のビームを発射する必要があります。これを行う関数は、後で他のスクリプトで再利用できるように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という名前の変数を宣言し、endPositionからstartPositionを引いて2つのベクトルの差を見つけます。この値のMagnitudeプロパティを使用してレーザービームの長さを取得します。
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).MagnitudeendlaserCFrame変数を宣言して、レーザービームの位置と向きを保存します。位置はビームの開始と終了の中点である必要があります。CFrame.lookAtを使用して、startPositionにあり、endPositionに向かっている新しいCFrameを作成します。これを負のlaserDistanceの半分のZ軸値を持つ新しい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をrequireします。
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)endプレイボタンをクリックして武器をテストします。ツールがアクティブ化されると、武器とマウスの間にレーザービームが表示されるはずです。
武器の発射レートを制御
武器には、短時間で過剰なダメージを与えないように、各ショットの間に遅延が必要です。これは、プレイヤーが最後に発射してから十分な時間が経過したかどうかを確認することで制御できます。
ToolControllerの先頭にFIRE_RATEという名前の変数を宣言します。これは、各ショットの間の最小時間になります。お好みの値を設定します。この例では0.3秒を使用します。
その下にtimeOfPreviousShotという名前の別の変数を宣言し、値を0に設定します。これは、プレイヤーが最後に発射した時間を保存し、各ショットで更新されます。
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0canShootWeaponという名前の関数を作成し、パラメータはありません。この関数は、前のショットからどれだけの時間が経過したかを確認し、trueまたはfalseを返します。
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という名前のFolderを作成します。

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に設定された値だけ減少します。
他のプレイヤーのレーザービームを描画
現在、レーザービームは武器を発射するクライアントによって作成されているため、そのクライアントだけがレーザービームを見ることができます。
レーザービームがサーバーで作成されれば、全員がそれを見ることができます。しかし、クライアントが武器を撃ってからサーバーがその情報を受け取るまでに小さな遅延が発生します。これにより、武器を撃つクライアントは、武器をアクティブ化したときとレーザービームが表示されるときの間に遅延を感じることになります。その結果、武器が遅延しているように感じられます。
この問題を解決するために、各クライアントは自分のレーザービームを作成します。これにより、武器を撃つクライアントはレーザービームを即座に見ることができます。他のクライアントは、別のプレイヤーが撃ったときにビームが表示されるまでに小さな遅延を経験します。これは最良のシナリオです。1つのクライアントのレーザーを他のクライアントに伝える方法は、これ以上早くはありません。
撃つクライアント
まず、クライアントはサーバーにレーザーを発射したことを通知し、終点を提供する必要があります。
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スクリプト内で、damageCharacterの上にplayerFiredLaserという名前の関数を作成し、2つのパラメータplayerFiredとendPositionを持たせます。
関数を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(そのプレイヤーのクライアントがレーザーを2回描画しないようにするため)、ブラスターのhandle(レーザーの開始位置として機能)、およびレーザーの終了位置が含まれます。
playerFiredLaser関数内で、getPlayerToolHandle関数をplayerFiredを引数として呼び出し、その値を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モジュールをrequireします。
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人のプレイヤーでブラスターをテストします。ローカルサーバーを開始します。各クライアントをモニターの異なる側に配置して、両方のウィンドウを同時に見ることができるようにします。1つのクライアントで撃つと、もう1つのクライアントでレーザーが表示されるはずです。
音声効果
現在、発射音効果は、弾丸を撃つクライアントでのみ再生されます。他のプレイヤーも聞こえるように、音を再生するコードを移動する必要があります。
ToolControllerスクリプト内で、toolActivated関数に移動し、Activate音を再生する行を削除します。
local function toolActivated()if canShootWeapon() thenfireWeapon()endendLaserRendererのcreateLaser関数の最後に、shootingSoundという名前の変数を宣言し、toolHandleのFindFirstChild()メソッドを使用してActivate音を確認します。
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 > 10 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の3つの引数を渡します。
下の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 LaserRendererServerLaserManager
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)