[UE] CharacterMovementComponentをちょっと詳しくみてみる（Velocity基本編） 

# はじめに #
UEではおなじみのCharacterアクター(ACharacterクラス)。
よく使っているという方も多いと思います。
Characterアクターを動かす際には**CharacterMovementComponent**に対して操作を行います。
そんなCharacterMovementComponentですが、**いろいろ機能がありパラメータも多くていまいちわからない！** という方もいらっしゃるのではないでしょうか。
今回はその移動の基本の仕組み（Velocityの変化）について見てみたいと思います。

確認したUEのバージョンは、UE4.26～UE5.0.3となります。
これ以降では大幅な変更や新機能が入っているかもしれません。
ちなみに、UE5でもUE4からCharacterMovementComponent自体に大きな変化はないようなので同様に使うことができそうです。

# 対象読者 #
この記事は、CharacterMovementComponentを使ったことがある方向けになっています。
初めて使う、CharacterMovementComponentってなに？という方はまずはそちらについて把握していただくのが良いと思います。
CharacterMovementComponent自体について解説している公式記事もなさそうなのですが、最初に見るのは [Character Movement (キャラクターの動き) を設定する(UE公式ページ)](https://docs.unrealengine.com/4.27/ja/InteractiveExperiences/HowTo/CharacterMovement/) が良さそうです。
また、動作を詳しく見る都合上、C++コードを基準に話を進めていきます。
ただ、そこまで深くC++コードの話はしないのと、紹介する関数のいくつかはBPでも呼び出しできるので、C++のところは流しで読んでいただいても大丈夫かと思います。

# Characterを移動させるには #
+ ACharacterはCharacterMovementComponentの**Velocity**変数の値によって移動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1886417/e029e3ee-4570-7636-7c87-e07973b7d505.png)
+ Velocityとは秒速の移動量と移動方向をもつベクトルです（UEの単位はcmなので）
+ 簡潔にいうとVelocityにDeltaTimeをかけたものがそのフレームの移動量になります。
+ Velocityの定義自体は **UMovementComponent** クラス（親クラス）にあります。
+ Velocityはpublic変数なのでどこからでも書き換え、読み取りができます。
（UEはアップデートごとにpublic変数をよくprotectedやprivateに置き換えているので、いずれVelocityもそうなるかも？）

Velocityが決まる方法や要素についてこのあと深掘りしていきます。

:::note info
**Velocityの計算をしているC++コードの場所**
+ **UCharacterMovementComponent::CalcVelocity関数** にて計算しているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1886417/1dcefbae-3df1-a444-e4bc-3f13b078ee4e.png)
+ 歩き、飛行などのMovementModeに対応した **Phys~(歩きならPhysWalking)** という関数があり、その関数から共通で呼び出されています。
+ virtual関数になっていて、CharacterMovementComponentを継承したクラスでオーバーライドすれば、独自処理を加えることができそうです。
+ 落下時の重力については、別の関数で計算されます（後述）
:::

:::note warn
**SetActorLocationを安易に使うのはオススメできません**
Actorを移動させる共通の方法として、SetActorLocationによる移動があると思います。
ただ、Characterをこの方法で移動させるのはオススメできません。

理由として例えば、
CharacterMovementComponentには地形などとのコリジョンチェック機能が用意されており、Velocityを変動させて移動させることでその恩恵を得られることができます。
SetActorLocationにて移動させてしまった場合、そのコリジョンチェックの恩恵を得られず自力で行う必要がある、といったデメリットもあります。
:::

# Velocityの値を確認するには #
コンソールコマンドに **p.VisualizeMovement 1 (0で非表示)** と入力することで可視化できるデバッグ機能がありますので、こちらを使うと便利です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1886417/bf6a69e8-ec11-1c85-32f0-02c71f862de0.png)


# 初速の与え方 #
UCharacterMovementComponentの関数を呼び出すことで移動を開始させます

### ●ゲームパッドなどからの入力による移動 ###
<b>UPawnMovementComponent::AddInputVector</b>関数にて移動方向と移動量のベクトル[毎秒]を与えます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1886417/6058b305-b29c-bcc9-0d8f-ce23bf333367.png)
 CharacterMovementComponentの親クラスの関数を使います。
 APawnクラスにも、呼び出すための関数**APawn::AddMovementInput**があるので、直接呼び出しはしはせずにこちらを使うのが良さそうです。

### ●直接初速を与えて移動させる ###
目的に応じて移動量を与える関数が何種類か用意されているようです。
よく使いそうなやつをピックアップしました。

+ <b>UCharacterMovementComponent::AddImpulse</b>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1886417/e9b2eb2f-6b6b-0e15-38ce-361a9d405b63.png)
吹き飛ばしのような一撃の移動量を設定するための関数のようです。
毎フレームかかるような移動量は、**AddForce**という関数を使うようです。

+ **UCharacterMovementComponent::Launch**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1886417/91b6969b-e9f9-ab7e-342c-9fe24d8a77f2.png)
あまり詳しいコメントが書いてないですが、Launchという名前の通り打ち出すという感じの力を与える処理のようです。
コードを見るとこの関数で力を与えるとMovementModeがFallingに遷移するようです。
キャラクターをジャンプさせるような挙動に使えそうです。
こちらも **ACharacter::LaunchCharacter** という関数がありますので、直接は呼び出さずこちらの関数を使うのが良さそうです。
:::note info
ちなみに、**UCharacterMovementComponent::DoJump**というジャンプ用処理もあります。
この関数では内部でLaunchを呼んでいるわけではなく、直接VelocityのZ方向のみ値を入れて移動させています。（Fallingにするのはこちらも同じでした）
:::


# Velocityを更新する基本的な値 #
C++ソースを見てみるといろいろなオプション変数などがあり複雑に見えますが、紐解いてみると以下の要素が基本になっているようです。

### ●Acceleration（アクセル） ###
+ ゲームパッドなどの入力から設定される想定のVelocityへの変化量です。
+ AddMovementInputから指定された値はAccelerationに設定されます。
+ 最大値を設定することができます（MaxAcceleration）
+ CharacterMovementComponentのTickが回るたびに計算され直すため、フレームが更新されるたびに設定する必要があります。

### ●Braking (ブレーキ） ###
+ 特定の状況で働く**減速力**
+ 歩き・飛行などMovementModeに応じて個別に設定可能です。
（例えば歩きならBrakingDecelerationWalking）
+ 最大速度(MaxSpeed)を超えた場合にブレーキが作動して速度を維持しようとします。
+ Accelerationに値がある場合は無視されてしまうようです。

### ●Friction (摩擦) ###
+ 常にかかる減速力です。
+ 歩き・飛行などMovementModeに応じて個別に設定可能。
+ 飛行中はAPhysicsVolumeからも受け取ったりするなど、Character外の要因からも影響を受けます。
+ 地形や空間における移動しやすさを設定するためのパラメータっぽい感じです。
  + ツルツルした床の上は滑りやすい、など。
  + 物理マテリアルにもFrictionというパラメータがあったります。

:::note info
**例えば、Braking と Friction の設定をどちらも0にしてみると・・・**
この2つの値を0にして、地上待機状態でキャラクターに一度初速を与えると、自動で止まらず移動し続けます。
減速させる要素は基本的にはこの2つのようです。
:::

### Gravity (重力) ###
+ MovementModeがFalling（落下）の際に作用します。
+ **Z方向**に対して影響します。
　(Z方向以外に重力をかけることはできないようです。）


# Velocityの毎フレームの変化 #
初速を与えた場合にフレームが進むごとに各値がどうVelocityに影響していくかをみてみます。

### ●Accelerationで変化させた場合 ###
+ 基本的にはAcceleration*DeltaTime分加速されて移動するようです
+ ただし、最大速度を超えないように丸める処理が働きます

### ●AddImplusleなどで初速を与えた場合 ###
+ FrictionとGravityを除くと、実はシンプルな**等加速度(減速)直線運動式**でVelocityは減速していきます
  (毎フレームVelocityに対してBrakingが計算されて減速していく）
+ FrictionやGravityに値を入れると、その値がさらに作用します。
 （常に減速や重力による落下）

:::note info
**パラメータ設定例１** 
**指定時間[秒]で指定距離[cm]を吹き飛ばしたい** 場合どうするか？
+ 等加速度直線運動式を使って、距離と時間から初速(Velocityの初期値)と減速度(Braking)を求める
+ Frictionは0にする
+ AddImpulse関数などで初速を与える前にVelocityを0にしておく
(値が入っていると加算されてより遠くに吹き飛びます）
+ ただし、補間移動ではないので指定距離ぴったりにはならなず誤差が多少出ます。

等加速度直線運動式を使って初速とブレーキを求める関数例
```c++
void CalcInitialSpeedAndBraking(float dist, float time, float& speed, float& breaking)
{
	// 飛び出してほしい距離から初速とブレーキを計算.
	// シンプルな等加速運動計算を使う
	// 
	// 距離x = (初速(cm)v0 * 時間(s)t) + (減速なら-) (1/2 * 加速度(cm)a * 時間(s)tの二乗)
	// 
	// 初速v0 = (吹き飛び距離(cm)x / (1/2 * 時間(s)tの二乗)) * 時間(s)t.
	// 加速度(減速度)a = 吹き飛び距離(cm)x / (1/2 * 時間(s)tの二乗).
	//
	const float x = dist;
	const float t = time;
	const float tSquare = (t * t);
	const float v0 = ((x / (0.5f * tSquare)) * t);
	const float a = (x / (0.5f * tSquare));

	speed = v0;
	breaking = a;
}
:::

:::note info
**パラメータ設定例２** 
歩きと飛行で **同じように移動させたい** 場合どうするか

実は初期設定では同じように移動させることができず、移動距離や速度に差がでます。
以下がその解説。

+ 最大速度、Friction、Brakingなどを同じ値にする必要がありますが、Frictionの値が飛行中はちょっとややこしいです。
+ CharacterMovementComponentでは、飛行中はAPhysicsVolumeから **摩擦値(FluidFriction)** を受け取って計算しています。
+ しかも0.5倍されてCalcVelocityに渡されています。
（UCharacterMovementComponent::PhysFlying関数にて行っています）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1886417/f855d3e7-911a-7cdb-fd08-f031865e217e.png)
+ 手動で配置したPhysVolumeに入ってない場合でもDefaultPhysicsVolumeというエンジンのデフォルトのVolume内にいることになっています。
+ このDefaultPhysicsVolumeのFluidFrictionの初期値が0ではないため、CharacterMovementCoponentの設定だけでは同じにできません。

★★ **じゃあ同じにするにはどうするか？** ★★
方法１：自分でレベルに配置したAPhysicsVolume内に常にキャラクターがいるようにする
方法２：エンジン初期値の流体摩擦値を変更する
　プロジェクト設定→エンジン→物理→定数にて変更できます。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1886417/d9fc7587-3379-87d1-8634-ba099db7ca67.png)
方法３：C++にてCalcVelocity関数をオーバーライドして同じになるよう改修を加える
:::


# 落下 #
+ 落下による重力の適用は、CalcVelocityが呼ばれたあとに **UCharacterMovementComponent::NewFallVelocity** で重力落下計算が行われるようです。
（この流れはUCharacterMovementComponent::PhysFalling関数を見るとわかります）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1886417/7bc40cb4-e2b6-2ded-8714-e49c02470427.png)
+ CalcVelocityで計算されたVelocityのZ方向の移動量は無視され、NewFallVelocityで求めた値で上書きされるようです。
+ virtual関数になっていて、CharacterMovementComponentを継承したクラスでオーバーライドすれば、独自処理を加えることができそうです。
（Z以外の方向にも重力をかけたりといったカスタマイズもできそう）
+ 重力の値はPhysicsVolumeから設定がされるようです。
+ CharacterMovementComponentのGravityScale変数で重力に対して倍率を設定できます。



# 最後に #
基本的な部分にしぼって解説してみましたがいかがでしょうか。
CharacterMovementComponentは初期値そのままでもそれなりにいい感じの動きをしてくれますが、制作を進めていくうちにやりたいことが出てくると、仕組みを知らないとできないことがでてくると思います。

CharacterMovementComponentは、他にもルートモーションやパス・ナビゲーション移動、さらにはネットワーク機能によっても挙動が変化しますのでなかなか複雑ですが、初期値状態からすこしづつ挙動をみていけば動作を把握しやすいと思います。

この記事がUEを使ったゲーム制作のヒントにでもなれば幸いです。