Roblox標準のHumanoidは手軽ですが、高精度なアクションゲームやパルクールでは操作遅延や移動床からの吹き飛びなど、致命的な問題に直面します。
独自の物理コントローラーを構築すれば、挙動を100%予測可能に制御できます。VectorForceによる仮想サスペンションでカプセルを浮かせ、傾斜面の法線ベクトルに合わせて移動方向を補正することで、極上の操作感が手に入ります。
1. 標準Humanoidの限界と自作の必然性
高速アクションゲームで標準システムが抱える課題:
- ステートマシンの遅延:固定された状態遷移が機敏な方向転換を阻害。
- 移動床での吹き飛び:動く電車やエレベーターの上で同期ズレによる弾き飛ばしが発生。
- 段差での微小なカクつき:階段を上る際の急激な高度変化が画面揺れを誘発。
- 完全な決定論的制御:Heartbeat内で毎フレーム物理計算を行い、常に安定した挙動を保証。
2. 浮遊カプセルとVectorForceサスペンション
地形の凹凸に引っかからないための非接触移動メカニズム:
- 仮想スプリング:下向きのレイキャストで高さを測定し、バネの力で空中に浮遊維持。
- 摩擦ゼロのカプセル:外側に滑らかな円筒パーツを配置し、角や隙間への挟まりを防止。
- AlignOrientationによる直立保持:キャラクターの転倒を防ぎつつ、水平旋回のみを自由に許可。
- LinearVelocityの即時応答:入力と同時に最高速度へ到達させ、もたつき感を排除。
Luauカスタムキャラクター物理コントローラー
--!strict
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local CustomController = {}
local player = Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local hrp = char:WaitForChild("HumanoidRootPart") :: BasePart
local TARGET_HEIGHT = 4.2
local SPRING_STIFFNESS = 1800
local SPRING_DAMPING = 160
local WALK_SPEED = 24
local RAY_PARAMS = RaycastParams.new()
RAY_PARAMS.FilterType = RaycastFilterType.Exclude
local linearVelocity = Instance.new("LinearVelocity")
linearVelocity.MaxForce = 40000
linearVelocity.VelocityConstraintMode = Enum.VelocityConstraintMode.Vector
linearVelocity.RelativeTo = Enum.ActuatorRelativeTo.World
local attachment = Instance.new("Attachment")
attachment.Parent = hrp
linearVelocity.Attachment0 = attachment
linearVelocity.Parent = hrp
function CustomController.Update(dt: number)
local moveDir = Vector3.zero
local cam = Workspace.CurrentCamera
if UserInputService:IsKeyDown(Enum.KeyCode.W) then moveDir += cam.CFrame.LookVector end
if UserInputService:IsKeyDown(Enum.KeyCode.S) then moveDir -= cam.CFrame.LookVector end
if UserInputService:IsKeyDown(Enum.KeyCode.D) then moveDir += cam.CFrame.RightVector end
if UserInputService:IsKeyDown(Enum.KeyCode.A) then moveDir -= cam.CFrame.RightVector end
moveDir = Vector3.new(moveDir.X, 0, moveDir.Z)
if moveDir.Magnitude > 0.01 then moveDir = moveDir.Unit end
RAY_PARAMS.FilterDescendantsInstances = { char }
local ray = Workspace:Raycast(hrp.Position, Vector3.new(0, -TARGET_HEIGHT * 1.5, 0), RAY_PARAMS)
local vForce = 0
local targetVel = moveDir * WALK_SPEED
if ray and ray.Instance and ray.Instance.CanCollide then
local dist = (hrp.Position - ray.Position).Magnitude
targetVel = moveDir - (ray.Normal * moveDir:Dot(ray.Normal))
targetVel = targetVel.Unit * WALK_SPEED
local err = TARGET_HEIGHT - dist
vForce = (err * SPRING_STIFFNESS) - (hrp.AssemblyLinearVelocity.Y * SPRING_DAMPING)
end
linearVelocity.VectorVelocity = Vector3.new(targetVel.X, targetVel.Y + (vForce * dt), targetVel.Z)
end
RunService.Heartbeat:Connect(CustomController.Update)
return CustomController
3. レイキャスト接地判定と傾斜面法線投影
坂道での減速や下り坂での跳ね上がりを防ぐ数学的手法:
- 法線ベクトル(Normal)取得:RaycastResultから地面の傾きを正確に把握。
- ベクトル投影:前進ベクトルを斜面に沿うよう再計算し、推進力を無駄なく伝達。
- 登坂限界角:45度を超える急斜面では摩擦を失わせて滑落させる処理。
- 崖際検知:外周への補助レイキャストで落下前の足場消失を検知。
4. 慣性モメンタムとスライディング挙動
スピード感あふれるアクションを実現する移動ギミック:
- ダッシュスライディング:しゃがみ入力時に一時的に摩擦を減らし前方へ滑走。
- 坂道での重力加速:下り坂で重力成分を加算して急加速を演出。
- 慣性の維持:スライディング終了時に急停止せず滑らかに通常歩行へ移行。
- 空中制御(Air Control):ジャンプ中も進行方向の微調整を可能にしつつ無限加速を防止。
5. プロシージャルモーションと足元の接地表現
物理移動速度と3Dモデルアニメーションの完全同期:
- 足滑りの解消:実際の物理移動速度を計測し、アニメーション再生速度を動的変更。
- 8方向ブレンド:移動方向の内積計算で自然な歩行モーションを合成。
- IKControlによる足配置:浮遊状態でも足先を階段や岩場に自然に着地。
- クライアント処理完結:物理演算をローカルで完結させて通信負荷を削減。
Frequently Asked Questions
カプセルを床から浮かせるメリットは何ですか?
地面に直接触れるとポリゴンの継ぎ目や微細な隙間に引っかかるため、浮遊サスペンションが最も滑らかに動けます。
傾斜面のベクトル投影でなぜ坂道の減速がなくなるのですか?
前進エネルギーを坂の斜面に沿ったベクトルへ変換するため、地面にぶつかって力が逃げないからです。
動く床から吹き飛ぶバグは防止できますか?
はい。レイキャストで検知した床の移動速度を直接キャラクターに合算できるためです。