ラグドール(Ragdoll)物理は、キャラクターの被弾や爆発、落下時に迫力あるアクション体験を生み出します。しかし、粗雑な実装は四肢の激しいバタつき、位置ズレ(ラバーバンド現象)、地面抜けなどの致命的なバグを引き起こします。
商用レベルのラグドールシステムには、BallSocketConstraintによる人体の可動域制限、NoCollisionConstraintを用いた自己衝突防止、NetworkOwnershipの適切な設定、そして倒れた姿勢から滑らかに復帰するステートマシンが不可欠です。
1. Motor6Dの切り替えとBallSocketConstraintの角度制限
アニメーション用関節をリアルタイムに物理制約関節へと置き換える基礎:
- Motor6Dの無効化:Motor6Dを破棄(Destroy)せず、Enabled = falseに設定することで、後からの自然な起き上がりを可能にします。
- 球面回転の制限:BallSocketConstraintのLimitsEnabledとTwistLimitsEnabledを有効化し、関節が不自然に逆関節に曲がったり360度ねじれたりするのを防ぎます。
- Attachmentの事前配置:キャラクター生成時にあらかじめAttachment0と1を仕込んでおくことで、ラグドール化瞬間の処理落ちを防ぎます。
- Humanoid状態管理:Enum.HumanoidStateType.Physicsへと移行させ、落下中はGettingUpステートを一時的に停止します。
2. 衝突グループとNoCollisionConstraintによる干渉防止
四肢同士の衝突によるキャラクターの吹き飛び現象を防止する設計:
- 自己衝突(Self-Collision)の爆発:四肢パーツ同士が重なると物理エンジンが強力な反発力を生み出し、キャラクターが空高く吹き飛びます。
- NoCollisionConstraintの連結:隣接する関節パーツ間にNoCollisionConstraintを設定し、相互の当たり判定計算を完全に除外します。
- CollisionFilteringServiceの活用:ラグドールパーツを専用の衝突グループに隔離し、静的な地形とのみ接触するように設定します。
- CanCollideの適正化:HumanoidRootPartのCanCollideを無効化し、Torsoのみ有効化することで滑らかな転がりを実現します。
--!strict
-- 起き上がり処理付きLuauラグドールマネージャー
local PhysicsService = game:GetService("PhysicsService")
3. ネットワーク所有権(Network Ownership)と同期
物理シミュレーションをどこで計算させるかが動作のスムーズさを左右します:
- ローカルプレイヤー:`SetNetworkOwner(player)`を設定し、クライアント側で遅延ゼロで物理演算させてサーバーへ送信します。
- NPCおよび死亡者の死体:`SetNetworkOwner(nil)`でサーバーが物理権限を保持し、不正改ざんや同期ズレを防ぎます。
- 通信パケットの抑制:角速度が低下して静止したパーツは自動的にスリープ状態へ移行させます。
- テレポート防止:Motor6Dを無効化した同一フレームでネットワーク所有権を変更せず、最低1ティックの猶予を設けます。
4. 起き上がりアニメーションとブレンド処理
倒れた位置から不自然なワープなしに立ち上がる復帰プロセス:
- 地面傾斜への適合:Torsoから下方にレイキャストを放ち、地面の法線に合わせてHumanoidRootPartの向きを補正します。
- CFrameの引き戻し防止:Motor6Dを再有効化する直前に、HumanoidRootPartの位置を現在のTorsoの位置へ同期させます。
- アニメーションブレンド:起き上がりモーションを再生しながら、0.3秒かけて段階的に関節をアニメーション制御へ復帰させます。
- 無敵時間(i-frame)の付与:立ち上がる最中に連続被弾して無限ラグドール化するのを防ぐため、短時間のアーマーを付与します。
5. モバイル最適化と運用ベストプラクティス
低スペックなスマートフォン端末でCPU負荷を抑えるルール:
- 制約数の限定:BallSocketは主要な四肢の関節のみに適用し、装飾品や帽子は静的なパーツとして扱います。
- 物理スリープの促進:地面に静止したラグドールは物理エンジンの自動スリープ機能に委ねてCPU消費をゼロにします。
- メモリ回収:死亡後10秒のタイマーを設けて、不要になったラグドールと制約オブジェクトを完全に破棄します。
- MicroProfilerでの測定:Ctrl+F6でプロファイラを開き、`physics::step`がフレームあたり3ms以内に収まっているか確認します。
Frequently Asked Questions
ラグドールになった瞬間、キャラクターが空へ吹き飛んでしまいます。なぜですか?
四肢パーツ同士の重なりによる自己衝突反発が原因です。隣接パーツ間にNoCollisionConstraintを設定し、HumanoidRootPartのCanCollideを無効化してください。
ラグドールの物理計算はクライアントとサーバーのどちらで行うべきですか?
操作中のプレイヤーは`SetNetworkOwner(player)`でクライアント演算、死亡した死体やNPCは`SetNetworkOwner(nil)`でサーバー演算を行います。
起き上がる時に最初に転んだ場所へワープしてしまう現象はどう直しますか?
Motor6Dを再有効化する直前に、HumanoidRootPartのCFrameを現在のTorsoの位置へ更新してください。
BallSocketConstraintのTwistLimitsはどうして必要なのですか?
これがないと手足がコルク抜きのように自身の軸で360度回転してしまい、人体構造として破綻するためです。