mcpField
R
roblox-physics

roblox-physics

github.com/el4cteo開発61574 回インストール2 週間前に更新
リポジトリ

roblox-physics について

Roblox の物理、衝突処理、空間クエリ、ネットワーク所有権を扱うゲーム開発者向けスキルです。現行 API とサーバー権威シミュレーションの設計に加え、安定性と性能の注意点も解説します。

主な機能

  1. 01レイキャストと空間範囲クエリ
  2. 02衝突サマリーと衝突グループ
  3. 03アセンブリ・ジョイント・駆動制約
  4. 04ネットワーク所有権の制御
  5. 05サーバー権威シミュレーションの設計
  6. 06物理の安定性に関する指針

活用シーン

  1. 01ヒット判定やゲームゾーンの実装
  2. 02車両や飛翔体の動きの作成
  3. 03衝突時の影響やエフェクトの処理
  4. 04サーバーシミュレーション型の対戦ゲーム設計

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-physics

インストール後、Claude Code で/roblox-physicsを入力して読み込まれたか確認します。

同梱ファイルreferences/recipes.mdreferences/server-authority.md

詳細

Roblox の物理演算と空間クエリ

空間クエリ(ゲームプレイの判定には、Touched ではなくこちらを使用)

--!strict
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {} -- e.g. { character } of the shooter
params.IgnoreWater = true

local function castRay(origin: Vector3, direction: Vector3): RaycastResult?
	-- direction's length is the max distance
	return workspace:Raycast(origin, direction, params)
end

local result = castRay(Vector3.new(0, 50, 0), Vector3.new(0, -100, 0))
if result then
	print(result.Instance, result.Position, result.Normal, result.Material, result.Distance)
end

-- Swept volumes: thick bullets, melee arcs, ground checks with width.
local sphereHit = workspace:Spherecast(Vector3.new(0, 10, 0), 2, Vector3.new(0, 0, 50), params)
local boxHit = workspace:Blockcast(CFrame.new(0, 10, 0), Vector3.new(4, 4, 4), Vector3.new(0, 0, 50), params)

-- Volume overlap: explosions, hitboxes, zone checks.
local overlap = OverlapParams.new()
overlap.FilterType = Enum.RaycastFilterType.Exclude
overlap.MaxParts = 50
local partsInBox = workspace:GetPartBoundsInBox(CFrame.new(0, 5, 0), Vector3.new(10, 10, 10), overlap)
local partsInSphere = workspace:GetPartBoundsInRadius(Vector3.zero, 15, overlap)
print(sphereHit, boxHit, #partsInBox, #partsInSphere)
  • 再利用:パラメーターオブジェクトを再利用し、呼び出しごとに割り当てるのではなく、FilterDescendantsInstances(または params:AddToFilter(instance))を更新します。
  • 非衝突パーツを無視するには RespectCanCollide = true を、グループとしてクエリするには CollisionGroup = "Name" を指定します。
  • GetPartsInPart(part, params) は正確なジオメトリの重なりを調べます(負荷が高くなります)。GetPartBoundsIn* はバウンディングボックスを使用します。
  • 非推奨:FindPartOnRay*、Ray.new、FindPartsInRegion3* → 上記の API を使用してください。
  • クライアントのクエリで検出できるのはストリーミング済みのパーツだけです。権威ある判定はサーバーで行ってください。

Touched:制限を把握する

Touched は物理エンジンによって発火し、物理的に接触したパーツに対してのみ発火します。パーツをシミュレートする側(所有するパーツについては、クライアントが発火を偽装または抑制できます)によって発火し、1 回の接触で何度も発火することがあります。デバウンスとサーバー側の検証を組み合わせて、トリガー(パッド、アイテムの取得)に使用してください。ヒット検出やゾーンには空間クエリを使用してください。

--!strict
local Players = game:GetService("Players")

local pad = Instance.new("Part")
pad.Anchored = true
pad.Parent = workspace

local recently: { [Player]: boolean } = {}

pad.Touched:Connect(function(hit)
	local character = hit:FindFirstAncestorOfClass("Model")
	local player = character and Players:GetPlayerFromCharacter(character)
	if not player or recently[player] then
		return
	end
	recently[player] = true
	print(`{player.Name} stepped on the pad`)
	task.delay(1, function()
		recently[player] = nil
	end)
end)

衝突サマリー(衝撃データ)

パーツの衝突の「仕方」(衝撃エフェクト、衝撃音、衝撃速度に応じたダメージ)を取得するには、Touched の代わりに BasePart:BindToCollisionSummaries(callback) を使用します。物理ステップ後、毎フレーム 1 回、そのパーツのすべての接触についてコールバックが呼び出されます。各接触には Part0(バインドされたパーツ)、Part1、Point、Normal(Part0 から離れる向き)、RelativeVelocity(衝突を解決する前の相対速度)、Area、Time が含まれます。

  • パーツがシミュレートされる場所(ネットワーク所有者)でのみ発火し、何もレプリケートされません。他のクライアントに必要な情報は、独自のリモートを通じて送信し、サーバーで検証してください。
  • CanCollide = false のパーツや、スリープ状態にある 2 つのパーツ間ではサマリーは取得されません。非凸パーツでは、パーツのペアごとに複数のサマリーが生成されることがあります。負荷は衝突の総数ではなく、バインドされたパーツの数に応じて増加します。
--!strict
local function bindImpacts(part: BasePart, onImpact: (point: Vector3, speed: number) -> ()): RBXScriptConnection
	return part:BindToCollisionSummaries(function(summaries: { any })
		for _, summary in summaries do
			local speed = math.abs(summary.RelativeVelocity:Dot(summary.Normal)) -- head-on impact speed
			if speed > 10 then
				onImpact(summary.Point, speed)
			end
		end
	end)
end

return bindImpacts

衝突グループ

--!strict
-- Collision group APIs now live on WorldRoot (workspace); the PhysicsService versions are superseded.
workspace:RegisterCollisionGroup("Players")
workspace:RegisterCollisionGroup("Ghosts")
workspace:CollisionGroupSetCollidable("Players", "Ghosts", false)

local ghost = Instance.new("Part")
ghost.CollisionGroup = "Ghosts" -- assign by name (CollisionGroupId/SetPartCollisionGroup are deprecated)
ghost.Parent = workspace

グループはサーバーの起動時に一度だけ登録してください(最大 32。workspace:GetMaxCollisionGroups() を参照)。CanCollide = false にするとすべての衝突が無効になります。CanQuery = false にするとレイキャストでパーツが検出されなくなります。CanTouch = false にすると Touched が無効になります(CPU 負荷を軽減)。NoCollisionConstraint は、特定の 2 つのパーツ間の衝突を無効にします。

MeshPart の衝突形状には、Box/Hull(低負荷)、Default、PreciseConvexDecomposition(高負荷)、または CollisionPrecision スライダーを備えた Tunable があります。メッシュごとの精度とパフォーマンスのバランスを調整できます。スライダーは Studio で設定します。スクリプトからは書き込めません。

アセンブリとジョイント

  • アセンブリ とは、溶接、WeldConstraint、Motor6D、剛体拘束によって剛結合されたパーツの集合です。1 つの物体としてシミュレートされ、ルートパーツ(part.AssemblyRootPart)によってネットワーク所有権が決まります。
  • 静的なジオメトリはアンカーします。アンカーされていないパーツをアンカー済みパーツに溶接すると、アンカー済みアセンブリになります。
  • 編集時に WeldConstraint で溶接すると、現在のオフセットが維持されます。コードでは Part0/Part1 を設定し、親を指定します。
  • モデル全体を移動するには model:PivotTo(cframe) を使用します。多数のパーツを一度に移動するには workspace:BulkMoveTo を使用します。
  • AssemblyLinearVelocity/AssemblyAngularVelocity、part:ApplyImpulse()、ApplyAngularImpulse()、AssemblyMass で物理プロパティを読み取り、設定します。Velocity/RotVelocity は非推奨です。
  • CustomPhysicalProperties = PhysicalProperties.new(density, friction, elasticity, frictionWeight, elasticityWeight)。
  • 単位:1 stud ≈ 0.28 m、workspace.Gravity のデフォルト値は 196.2 studs/s² です。

Moverの制約(BodyMoversを置き換えるもの)

目的制約置き換え対象
速度を維持/操作するLinearVelocity(VelocityConstraintMode Vector/Plane/Line)BodyVelocity
位置に向かって移動するAlignPosition(Mode = OneAttachment + Position、Responsiveness、MaxForce)BodyPosition
向きを合わせて回転するAlignOrientation(CFrame/LookAtPosition)BodyGyro
一定の力を加えるVectorForce(RelativeTo = World)BodyForce、BodyThrust
回転させるAngularVelocityBodyAngularVelocity
点に向かって引くLineForceRocketPropulsion
トルクを加えるTorque—

すべてにAttachment(Attachment0)が必要です。MaxForce/MaxTorqueは現実的な値に設定してください(意図的な場合を除き、math.hugeは使わないでください)。そうしないと、オブジェクトが吹き飛ぶことがあります。機械的な制約:HingeConstraint(車輪やドア用のMotor/Servoアクチュエーター)、PrismaticConstraint(スライダー、エレベーター)、SpringConstraint(サスペンション)、RopeConstraint、RodConstraint、BallSocketConstraint、CylindricalConstraint(車輪+ステアリング)、UniversalConstraint、RigidConstraint。

乗り物と飛翔体のレシピ、CFrameの手引き:references/recipes.md。

ネットワークオーナーシップ

  • サーバーは、固定されていないアセンブリのオーナーシップを近くのクライアントに自動割り当てします(そのクライアントでは滑らかに動作しますが、悪用される可能性があります)。キャラクターはそのプレイヤーが所有します。
  • part:SetNetworkOwner(nil)=サーバーがシミュレーションします(安全ですが、プレイヤーには遅延が大きくなります)。乗り物の運転者にはSetNetworkOwner(player)を使い、元に戻すにはSetNetworkOwnershipAuto()を使います。アセンブリがWorkspaceに入った後、サーバー側でルートパーツに対して呼び出してください。固定されたパーツにはオーナーがいません。
  • オーナーシップを切り替えると引っかかりが発生するため、毎フレーム切り替えないでください。ビューポートの可視化オプション → ネットワークオーナーの切り替えで確認できます。

サーバー権限(対戦ゲーム)

WorkspaceのAuthorityMode = Serverを設定すると、サーバーがすべてをシミュレーションし、クライアントは数フレーム先を予測し、予測が外れた場合はロールバック/再シミュレーションします。必要な設定:NextGenerationReplication、PlayerScriptsUseInputActionSystem、SignalBehavior = Deferred、UseFixedSimulation、StreamingEnabled(AuthorityModeを設定すると、これらも設定されます)。基本ルール:

  • ゲームロジックは、サーバーとクライアントの両方でrequireされるModuleScript内で、RunService:BindToSimulation(fn)にバインドした関数に記述します。
  • クライアントがゲームに影響を与える方法は入力アクション(Playerを親とするInputContexts)のみです。UserInputServiceのイベントは使いません。
  • 予測対象のインスタンスのカスタム状態は、シミュレーションのコールバック内でのみ書き込む属性を通じて同期します。
  • AnimationTrackをキャッシュせず、各ステップでAnimator:GetTrackByAnimationId()を呼び出します。
  • サーバー上でプレイヤーのカメラを読み取るにはplayer:GetCameraState()(CFrame、FieldOfView、ViewportSize)を使います。以前のカメラ用InputActionのレプリケーションでは同期されなくなりました。

2026年7月以降、オプトイン方式で正式リリース済みです。詳細と実装パターン:references/server-authority.md。

安定性に関するヒント

  • ジッター:オーナーシップの競合、制約同士の干渉、大きな質量比(> 1:100)、または固定されていないパーツのCFrameを毎フレーム設定すること(代わりに制約を使います)。
  • 吹き飛び:軽いパーツにMaxForce = math.hugeを設定すること、スポーン時に衝突が重なること、NaN/極端に大きな速度。
  • 休止中のアセンブリは、外部から作用を受けるまでシミュレーションが停止します。必要に応じて小さなインパルスを加えて再始動します。
  • 適応型タイムステップ(Workspace.PhysicsSteppingMethod = Adaptive)はCPU負荷を抑えます。精密な機構には固定タイムステップを使います。

関連スキル

roblox-security(オーナーシップの悪用)、roblox-characters-animation、roblox-networking、roblox-performance、roblox-input。

コメント