【UE4】ゲームパッドの斜め45度の値が気に食わない／ぷちコン15進捗 - 2021/03/12

[1]:https://historia.co.jp/ue4petitcon15

[株式会社ヒストリアさん主催の第15回UE4ぷちコン][1]の作業進捗。
ちょっと脇道にそれて、ゲームパッドのスティック入力の調査。

# イマココ

ここまで作ってきたControl Rigで足のIKを地面の法線に合わせるやつを使って、キャラクタを歩かせようとしていたのだが、斜め入力をしたときに妙に歩く速度が落ちる現象が妙に不満に思った。
…なので、まじめに調査してみた。

> いつものことと言えば、いつものことではある。
> スティックの斜め方向(45度、135度、225度、315度)付近は、スティックを一杯に入力しても、長さは１にはならない。
> おそらく皆さん、現象としては良くご存じかと。

# やったこと

- スティックを一杯に切って、フチにグリグリ押し付けながら、360度回す。
    - ゆっくり回したり、速く回したり、右回りしたり、左回りしたり。
    - これをひたすら繰り返す。
- そのデータを配列に保存。
    - 4096個の配列に`Add Unique`でいっぱいになるまで続けた。（結構しんどかった）
- そのデータを解析。
    - `Atan2(Y,X)`で得た角度を四捨五入(`Round`)して0～359の整数に。要するに、整数で0度から359度。これを配列のインデックスとする。（例 `DegreeIndex`）
    - １度分ずつ`Averages[DegreeIndex]`みたいな配列に平均値を保存。
    - １度分ずつ`StandardDeviations[DegreeIndex]`みたいな配列に標準偏差を保存。

# 結果

おおむね感じていた通り。スティックを丸く回しても、データは円形にならない。
斜め45度(+90×n度)近辺で顕著。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/b7e5379e-c74a-d94e-5ae3-2eff1082fe56.png)

- 緑の座標軸がY軸（スティック入力の上方向）、赤の座標軸がX軸（スティック入力の右方向）
- 白～緑にグラデーションしているギザギザのラインが入力の平均値。
- 黄色の図形は、正八角形。

…ということで、スティックをフチに沿って丸く回すと、おおむね正八角形な入力データが得られることが分かった！
つまり、スティック入力ベクトルは円で取り扱うより、正八角形で取り扱う方が良い。

- 単純に`Vector2DLength(X,Y)`しても、斜め近辺で長さ１にならないということ。
- 正八角形にクランプして使うべき… 作るの面倒くさいなぁ（~~←まだやってない~~ 作った。一番下にBPを載せました）

ちなみに、標準偏差を見ると、こういう(↓)感じになる。（赤い棒グラフ的なやつ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/96b86c55-c3fe-b54a-8274-99fd6d5021e8.png)
斜め45(+90×n)度付近で、標準偏差が極端に大きい！
これを偏差値という言葉が苦手な人にもわかるように説明しようとすると：

- 直角付近(90×n度)では、スティック値のばらつきが少ない。
    - クラス全員が押しなべて似たような成績である…という感じ。
    - みんな平均点付近 → 平和。
    - このクラスの生徒は押しなべて学力が揃っている。みんな同じくらい授業を聞いている。
    - このクラスの平均点が他のクラスよりも低いならば、それは先生のせいだ！
- 斜め付近(45+90×n度)では、スティック値のばらつきが大きい。
    - クラス内の成績が大きくばらついている…という感じ。
    - 平均点付近の人が比較的少なく、偏差値が超高い生徒と、超低い生徒がいる感じ。
    - 授業を超真面目に聞く生徒と、サボりまくっている生徒がいる。
    - このクラスの平均点が他のクラスより低いならば、不真面目なコを指導すべきだ！

…ということで（どういうことだろう？）：

- 斜め付近の入力は、一定のところに収まりにくい。
- 毎回きっちり斜めに入れたつもりでも、スティック入力データはばらつき易い。
- 斜め付近の入力は、ブルブル震えやすい。

…とか、そういうイメージだろうか。

> ちなみに、斜め付近の平均値ラインだけ、ひどく凸凹なラインになっていることも分かる。

では、今から、正八角形にクランプするコードを考えます。

# 【追記】正八角形に正規化してみた

キャラを操作してみたところ、操作感が全方位に渡って均一になった(…と思う)
Blueprintグラフは以下(↓)のような形に。(PlayerControllerに実装)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/c776a304-8ed4-4e45-ad3b-de2da018806d.png)
※クリックで拡大

Unreal C++版 (2025/03/26追記)
```c++
FVector2D AMyPlayerController::NormalizeStickVector(
    double InStickX, double InStickY, double InTolerance)
{
	if (FMath::IsNearlyZero(InStickX, InTolerance) 
    && FMath::IsNearlyZero(InStickY, InTolerance))
	{
		return FVector2D::ZeroVector;
	}

	////

	constexpr double Radian45 = UE_PI / 4.0;
	constexpr double Cos225 = 0.92387953251128675612818318939679; // Cos(22.5度)
	
	const double InputRadians = FMath::Atan2(InStickY, InStickX) + UE_PI; // Atan2は-180～+180度、これを0～360度にする
	const double DirectionIndex = FMath::RoundHalfToEven(InputRadians / Radian45);
	const double CenterRadians = DirectionIndex * Radian45;
	const double MaxRadius = Cos225 / FMath::Cos(InputRadians - CenterRadians);

	const FVector2D StickVector(InStickX, InStickY);
	FVector2D Direction;
	double Length;
	StickVector.ToDirectionAndLength(/*out*/Direction, /*out*/Length);

	return Direction * FMath::Clamp(Length / MaxRadius, 0.0, 1.0);
}
```