🎮

【Roblox】Character Controller について

に公開

はじめに

今回は、RobloxのCharacter Controllerについて紹介します。

Robloxバージョン: 0.665.0.6650685

Character Controllerとは

Character Controllerとはキャラクターの物理挙動を管理するシステムです。
Humanoidでキャラクターを動きを管理するのに対して、Character Controllerは物理挙動(例:走る、跳ぶ、歩くなど)を管理・カスタマイズすることができます。

物理挙動システムを自作したい場合や、乗り物の挙動を作成したい場合などに使用できます。

準備

今回はCreator Storeから取得したModelをCharacter Controllerを用いて移動できるようにします。

今回は試しにCreator Storeから「Pony」を取得します。

名称を「StarterCharacter」に変更しStarterPlayer直下に配置することで、ゲームプレイ中に操作できます。

このPony改めStarterCharacter以下にはHumanoidが存在しているので、この状態ですでに移動やジャンプが行えます。

Humanoid.EvaluateStateMachine

ここまでの準備の段階では、通常のキャラクターと同じでHumanoidをベースとして移動しています。
Character Controllerで物理挙動をカスタマイズするにはHumanoidの物理挙動をオフにしなければいけません。

Humanoidでは、物理挙動やステートマシンを無効化するHumanoid.EvaluateStateMachineというプロパティが用意されています。

これを変更するLocalScriptを作成し、StarterCharacterScriptsに配置します。

CharacterSample
-- Service
local Players = game:GetService("Players")

local LocalPlayer = Players.LocalPlayer
local Character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
local humanoid = Character:FindFirstChildWhichIsA("Humanoid")

humanoid.EvaluateStateMachine = false

Humanoid.EvaluateStateMachineをオフにした場合は以下の効果があります。

  • 力が加わらないので、移動ができなくなる。

  • 床や梯子などを検知しなくなる。

  • Humanoidがキャラクターのパーツの衝突状態を変更しなくなる。

  • HumanoidStateが自動的に変更されなくなる。(スクリプト経由で変更は可能)

  • HumanoidStateをスクリプト経由で変更した場合でも、ChagedイベントとStateイベントは呼ばれる。

    • → アニメーションを管理するAnimateスクリプトは引き続き動作する。
  • HumanoidDescription、衣服、アクセサリーやその他Humanoidのレンダリング動作は無効になる。

  • Humanoidとカメラのリンクはそのままなので、カメラ挙動は引き続き動作する。

  • Humanoid.MoveDirectionは引き続き入力に応じて変更されるので、移動などで使用できる。

ざっとこのような効果があります。
Character Controllerを使用する際は物理挙動をカスタマイズしたい場合が多いと思うので、Humanoid.EvaluateStateMachineはオフにしましょう。

今回はHumanoidがすでにエクスプローラーにあるので、プロパティから変更することもできます。

ControllerManager

ControllerManagerはCharacter Controllerを構築するために必要なインスタンスです。

ControllerManagerはRootPartを指定する必要があります。
このRootPartを基準にシミュレーションを管理するのでPartを作成しRootPartに設定します。

今回は分かりやすいように、胴体のTorsoパーツと同じ大きさ、座標の物を用意しTorsoにWeldします。

RootPartを作成しWeldする


ControllerManagerのRootPartプロパティに作成したRootPartを設定

ControllerPartSensor

ControllerPartSensorとは、Humanoidが床やはしご(TrussPart)を検出するように、他のBasePartでも検出を可能とするインスタンスです。
検出した結果をCharacter Controllerに送信する目的で使用します。

これを用いて、接地判定とはしごを検出するセンサーを作成します。

ControllerPartSensorをRootPart下に作成し、名前を分かりやすいように"GroundSensor"と"ClimbSensor"に変更します。

ControllerPartSensor.SensorModeプロパティはFloorとLadderの2種類があり、LadderはHumanoidのはしごを検出する仕組みを実行し、FloorはHumanoidの床を検出する仕組みを実行します。
GroundSensorはFloorに、ClimbSensorはLadderに変更します。

ControllerPartSensorのSearchDistanceプロパティは、ControllerPartSensorインスタンスの親のBasePartが他のBasePartを検出する距離を設定できます。
今回はRootPartの高さや大きさを考慮して、GroundSensor・ClimbSensor両方のSearchDistanceを2に設定しておきます。

Controllerの設定

先ほど設定したControllerPartSensorの親パーツとワールドのやり取りを指示するには、GroundControllerClimbControllerのようなControllerの設定が必要です。

ControllerManagerの子オブジェクトとして、GroundControllerClimbControllerを作成します。

GroundControllerのプロパティでは摩擦や入力終了時から停止するまでの時間など、物理挙動における各種パラメータを細かく設定できます。

今回はGroundOffsetの値をRootPartの高さを考慮して1.5に設定します。

GroundControllerのプロパティ

ControllerManagerにリンクさせる

ここまでで、RootPart、ControllerPartSensor、GroundController、ClimbControllerを作成しました。
これらをControllerManagerのプロパティにリンクさせる必要があります。

ActiveControllerプロパティをGroundControllerインスタンスにリンクします。
RootPartプロパティをRootPartという名前のパーツにリンクします。
ClimbSensorプロパティをClimbSensorインスタンスにリンクします。
GroundSensorプロパティをGroundSensorインスタンスにリンクします。

BaseMoveSpeedプロパティとBaseTurnSpeedは移動時の速度や方向切り替えの速度を変更できます。
必要があれば変更しましょう。

実際に移動させてみる

ここまでで、CharacterControllerのおおまかな設定が完了しました。
この時点では移動はできないので、移動処理を作成します。

ControllerManagerにはMovingDirectionというプロパティがあります。
この値をHumanoid.MoveDirectionと同じにすることで、入力方向に対してControllerManager.BaseMoveSpeedの速度で移動させることができます。

先ほど作成した、LocalScriptに追加でコードを記載します。

CharacterSample
-- Service
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local LocalPlayer = Players.LocalPlayer
local Character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
local humanoid = Character:FindFirstChildWhichIsA("Humanoid")

local ControllerManager = Character:FindFirstChildWhichIsA("ControllerManager")

-- Humanoidの初期化
humanoid.EvaluateStateMachine = false
humanoid:ChangeState(Enum.HumanoidStateType.Running)


local function onUpdate(deltaTime: number)
	
	local desiredDirection = humanoid.MoveDirection
	ControllerManager.MovingDirection = desiredDirection

	if desiredDirection.Magnitude > 0 then
		ControllerManager.FacingDirection = desiredDirection
	else
		ControllerManager.FacingDirection = ControllerManager.RootPart.CFrame.LookVector
	end
	
	humanoid:ChangeState(Enum.HumanoidStateType.Running)
end


RunService.RenderStepped:Connect(onUpdate)

RunServiceのRenderSteppedでControllerManager.MovingDirectionHumanoid.MoveDirectionと同期させています。
また、HumanoidStateをRunningに変更することで、もともとあるアニメーションを再生するようにしています。

はしご(TrussPart)の検知

このままでは、はしご(TrussPart)の検知はできません。
理由は、CharacterManager.ActiveControllerがGroundControllerのままだからです。

CharacterManager.ActiveControllerは現在使用してるControllerになります。
検知されたBasePartは、ControllerPartSensor.SensedPartから読み取ることができるので、ClimbSensor.SensedPartが存在する場合は、ActiveControllerをClimbControllerに切り替えることではしごの昇り降りが可能になります。

さきほどのコードを、Controllerの切り替えを考慮した形に修正します。

CharacterSample
-- Service
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local LocalPlayer = Players.LocalPlayer
local Character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
local humanoid = Character:FindFirstChildWhichIsA("Humanoid")

local ControllerManager = Character:FindFirstChildWhichIsA("ControllerManager")
local GroundController = ControllerManager:FindFirstChildWhichIsA("GroundController")
local ClimbController = ControllerManager:FindFirstChildWhichIsA("ClimbController")

-- Humanoidの初期化
humanoid.EvaluateStateMachine = false
humanoid:ChangeState(Enum.HumanoidStateType.Running)


local function checkGround()
	
	return ControllerManager and
		ControllerManager.GroundSensor.SensedPart ~= nil and 
		not (ControllerManager.ActiveController == GroundController and GroundController.Active)
end

local function checkClimbing()
	
	return ControllerManager and 
		ControllerManager.ClimbSensor.SensedPart ~= nil and 
		not (ControllerManager.ActiveController == ClimbController and ClimbController.Active)
end

local function updateActiveController()
	
	if checkClimbing() then
		ControllerManager.ActiveController = ClimbController
		humanoid:ChangeState(Enum.HumanoidStateType.Climbing)
	elseif checkGround() then
		ControllerManager.ActiveController = GroundController
		humanoid:ChangeState(Enum.HumanoidStateType.Running)
	end
end

local function onUpdate(deltaTime: number)
	
	local desiredDirection = humanoid.MoveDirection
	ControllerManager.MovingDirection = desiredDirection
	
	if desiredDirection.Magnitude > 0 then
		ControllerManager.FacingDirection = desiredDirection
	else
		ControllerManager.FacingDirection = ControllerManager.RootPart.CFrame.LookVector
	end
	
	updateActiveController()
end


RunService.RenderStepped:Connect(onUpdate)


これにより、はしごの昇り降りが可能になりました。
(昇降のアニメーションは人用のアニメーションになっているので変になっていますが、正常に動作しています。)

今回はサンプルなので省きましたが、上記のコードにはまだ問題点があります。
接地判定とはしご判定が同時に存在する場合に毎フレームControllerが変更されたり、はしごを登り切った後に上に行けないなどです。

修正点もありますが、こういった詳細な点もカスタマイズ可能なのがCharacter Controllerの良い点だと思います。

さらに機能を追加する

今回は長くなるので割愛しましたが、GroundControllerのようにAirControllerSwimControllerも存在します。

ジャンプをした際の空中にいる状態や、水に落ちた時の水中の状態などを設定する際は同様に作成可能です。

また、ControllerPartSensorにはUpdateTypeというプロパティが存在します。
今回の例やデフォルトではOnReadになっており、センサーで内部的にプロパティが上書きされています。

このUpdateTypeをManualにすることでセンサーによる内部的な感知を停止させることができます。
接地判定などをRaycastなどで独自に判断したい場合はManualにしましょう。

まとめ

  • Character Controllerとはキャラクターの物理挙動を管理・カスタマイズできるシステムである。

  • Humanoid.EvaluateStateMachineをオフにすることで、Humanoidの物理挙動をオフにでき移動も停止させることができる。

  • ControllerManagerはCharacter Controllerを構築するために必要で、ControllerManager.MovingDirectionにVector3を入れることで、ControllerManager.BaseMoveSpeedを基準にした移動を行うことができる。

  • ControllerPartSensorは床やはしご(TrussPart)を検知するのに使用する。

  • ControllerPartSensor.SensedPartから検知しているBasePartを取得することが可能。

  • GroundControllerClimbControllerAirControllerSwimControllerはControllerPartSensorの親パーツとワールドのやり取りを指示するのに必要。

  • ControllerManagerのプロパティにてRootPartやSensorなどをリンクさせる必要がある。

  • ControllerPartSensor.UpdateTypeでは内部的にセンサーを検知するOnReadと、自作のRaycastなどで検知するManualを設定することができる。

以上がCharacter Controllerについての紹介でした。
もともと移動やジャンプなどは自動で行えますが、自作する場合は非常に詳細にカスタマイズができ便利だと思います。
ぜひCharacter Controllerを使用してみてください。

参考

https://create.roblox.com/docs/physics/character-controllers
https://create.roblox.com/docs/reference/engine/classes/ControllerManager
https://create.roblox.com/docs/reference/engine/classes/GroundController
https://create.roblox.com/docs/reference/engine/classes/AirController
https://create.roblox.com/docs/reference/engine/classes/ClimbController
https://create.roblox.com/docs/reference/engine/classes/SwimController
https://create.roblox.com/docs/reference/engine/classes/ControllerPartSensor
https://create.roblox.com/docs/reference/engine/classes/Humanoid#EvaluateStateMachine

ランド・ホー Roblox開発チーム

Discussion