【Unity】Cinemachineを使わず3D自作カメラを作成する

### はじめに
ゲーム開発を始めてからほとんど2Dゲームしか作ってきておらず、そろそろ3Dゲームを作ろうかなと思いクオータニオンなどの勉強も兼ねてCinemachineを使わずに自作カメラを作成しました。
また、カメラオブジェクトは一つのみで子要素を用いずにわかりやすいものになるように作りました。

---

作成した機能一覧と説明する目次です。

１　<b>プレイヤー追尾</b>
　∟　プレイヤーを中心に回転するカメラワーク
２　<b>ロックオン機能</b>
　∟　ロックオン時はオート角度調整
３　<b>壁判定</b>
　∟　俗にいうめり込まないようにする機能

---

参考にしたカメラワークとしてニーアオートマタとゼルダの伝説を参考にして、
スタイリッシュでユーザーがゲームに集中しやすいものをコンセプトとして作成しました。

細かい内容はGitを公開するのでそちらを拝見すればわかると思います。
スクリプトのコードを用いて説明するので、宜しければそちらを見ながら記事を読んでいただければ、わかりやすいのかなと思います。

https://github.com/KensukeStudent/3DCameraWork

### １：プレイヤー追尾機能

今回作成した機能はFixedUpdateとLateUpdateに分けて処理を行っています。

<b>使い分けの説明</b>

FixedUpdateについて
プレイヤー側の移動処理にはRigidBodyを使っており、Unity側の物理挙動の処理順の都合上FixedUpdateでプレイヤー移動座標の取得と情報を加工するようにしております。
物理挙動の関わる処理は非常に重いと言われており、Unity側で処理するフレーム数が毎フレームではありません。

LateUpdateについて
マウスの入力処理やキーボード入力処理を行っております。
FixedUpdateで定義しない理由はUnity側から呼ばれるFixedUpdateのタイミングが常に一定ではないため、入力しても処理が通らないことがあるためです。

<br>
<font color="Red"> 

Update関係のわかりやすい記事は[こちら](https://qiita.com/tsukimi_neko/items/acf2d1dce01ea97885cf "UpdateやFixedUpdateの処理について")
</font>

---

続いてはプレイヤーの追尾機能とプレイヤーを中心に回転するカメラワーク処理の説明です。

PlayerCameraController.csの374行目
```C#
currentCamera.transform.position = VT + q * Vector3.forward * cameraDistance;
```

変数VT：変数vとtを足し合わせたものでLerp化させてスムージングなカメラ移動をできるようにした変数
vにはプレイヤーの座標
tにはプレイヤー座標からのオフセット

変数q：マウスの入力方向から得た角度をクオータニオン座標に変換したものでLerp化させてスムージングなカメラ角度変更をできるようにした変数

定数Vector3.forward：クオータニオンをVector3座標に変換するために定義
置き換えるVector3の値に応じてどの角度からカメラがプレイヤーを正面(基準)とするかを指定可能
※　基本的にはVector3.forward(0,0,1)をいれてプレイヤーの背後を正面(基準)とするようです。

<br>
<font color="Red"> 

クオータニオンのわかりやすい動画は[こちら](https://www.youtube.com/watch?v=HCTQNJu8OhE "クオータニオンのわかりやすいyoutube動画")
</font>

<br>
変数cameraDistance：プレイヤーからどのくらい離れた位置にカメラを配置するかに使用

----
カメラが配置される座標としては下記の画像のようになります。

![スクリーンショット_2 2023-07-17 125849.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3128572/bbcd2981-c1e7-ff85-85c7-b993b3caa1d6.png)

----
#### Lerp化したものを最後に足し算でcurrentCamera.transform.positionに代入している理由

初めのうちは下記のコードのようにまとめてLerpするようにしておりました。
この時、VT、q、cameraDistanceはLerp化させていません。
```C#
currentCamera.transform.position = Vector3.Lerp(currentCamera.transfrom.position, VT + q * Vector3.forward * cameraDistance, Time.deltaTime);
```

このコードだと、マウス思いっきり左右に移動させるとプレイヤーに急に近づいたりなど非常に不可解な挙動をするようになりました。
深くまでは調べていませんが、恐らく「プレイヤーの座標(VT)」、「角度(q)」、「プレイヤーまでの距離(cameraDistance)」が指定のLerpパーセント分の値ではないのが理由なのかなと思います。
![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3128572/1e2dc0ed-e459-0a1b-2f17-919dcc41d9fd.png)


### ２：ロックオン機能

ロックオン機能は多種多様なものがありますが今回作成したものは下記の機能が入っております。

１．プレイヤーと敵の中点を焦点とする
２．プレイヤーがカメラ画面領域から外れると範囲内に入るようにオート角度調整を行う
３．敵・プレイヤー・カメラが指定の角度になるようにオート角度調整を行う
　　∟　１．特定の距離まではプレイヤーの正面に敵が来るようにオート角度調整
　　∟　２．特定の距離から敵・プレイヤー・カメラが二等辺三角形になるようにオート角度調整

![スクリーンショット 2023-07-03 230726.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3128572/a253e41d-1771-ac80-7ce9-bb3af0296621.png)

基本的にはプレイヤーを基準に敵とカメラの座標からなす角度を求めて右か左に指定角度になるまで足してるだけです。

詳しいコード内容はPlayerCameraController.csのGetLockOnAutoAngle関数より確認が可能です。

### ３：壁判定

カメラの当たり判定はよく別の記事にあるRayCast(今回は幅も考慮したいのでPhysics.SphereCast)を用いて作成しました。壁に当たった座標をカメラ座標にすると不都合が起こるで壁に当たった座標からプレイヤーまでの距離と半径を引いた値をカメラ座標に入れるだけで作成しました。

左右と下判定はうまく取れるけど、上の判定は少し調整が必要にあります。
上の判定を取るときってあんまりみないような・・・・

![スクリーンショット 2023-07-17 140420.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3128572/d168bbb8-0c5b-904f-f589-8fe98328b205.png)

詳しいコード内容はPlayerCameraContorller.csのClacNextDistance関数より確認が可能です。

---

### おわりに

今回作成した機能の動画になります。
<iframe width="560" height="315" src="https://www.youtube.com/embed/oTxPyCtFaY0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>


<br>
<br>
初めて3Dカメラワークを作成したのですが、最初に想定していた機能も含めて全て実現できたこともあり、個人的には満足のいくものができたと思います。
このリポジトリを参考にして何かゲームを作ったなどありましたら宜しければ教えていただければ、是非遊びたいと思います。

<br>
<br>
いやぁ、それにしてもこの機能を作るのに2か月もかかってしまったなぁー<br>
さて、どんなゲームをつくろうかなー(; ･`д･´)