Unityで配管工風3Dアクションゲームを作った知見

# はじめに
1か月ほどで某配管工のような3Dアクションゲームを作りました。
以下のサイトにて遊べます。
[SpaceJumpGame | unityroom](https://unityroom.com/games/spacejumpgame)

今後のゲーム制作にも役立ちそうな知見をまとめました。

(参考にならない)コードはGitHubにあげてます
https://github.com/Papyrustaro/Unity3DActionGame

# バージョン
Unity2019.4.0f1

# Cinemachineを使ったカメラの追従
プレイヤーが常に画面の中心になるようにカメラを移動すると、どの方向にプレイヤーが進んでいるのかわかりにくい。
ワンテンポ遅れて追従するようなカメラをCinemachineを利用することで簡単に実装できる。
CinemachineVirtualCameraのFollowにプレイヤーの(中心の)Transformを代入する。
あとはBodyとAimのタイプと、各種パラメータを設定するだけでそれっぽくなる。

![cinema1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276180/c1efbf3b-1b0e-82d8-9c17-990b779a4f9f.png)
![cinema0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276180/69f7d522-50fb-49ee-68fd-ba9bc5c32fbe.png)


今回はBodyをFraming Transposer、AimをDo nothingと置き、カメラの回転は別途スクリプトで書いた。
特に操作したパラメータは、画面のどこに対象物が映るようにするのか(Screen X/Y)、対象物とカメラの距離(Camera Distance)

# Cinemachineを使った特定経路のカメラの移動
今回メニュー画面で、カメラがステージの周りを回り続けるようにしている。
これもCinemachineを利用して簡単に実装している。
CinemachineSmoothPathでカメラの通過点を設定。
スクリプトからCinemachineTrackedDollyのm_PathPositionの値を変えることで通路上のどこにいるのかを変えている。

![cinema2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276180/28b6834c-7546-16a6-6e70-3197977215bd.png)


# エディタ上から特定の処理を呼ぶ
今回はNaughtyAttributesというエディタ拡張を利用した。
https://github.com/dbrizov/NaughtyAttributes

これによって手作業でやっていたInspectorの操作などを大幅に削減できる。
以下はシーンに存在するスイッチと、スイッチで切り替わるステージを代入するスクリプトとInspector上の見え方。

```cs
[SerializeField, ReadOnly] private List<SwitchOnOffStageInHitHead> switches;
[SerializeField, ReadOnly] private List<SwitchOnOffStage> switchStages;

/*~~~~~~~~~~*/

/// <summary>
/// シーンに存在するswitch、onOffStageを代入・初期化。Inspector上から
/// </summary>
[Button(enabledMode: EButtonEnableMode.Editor)]
private void SetAllSwitchesAndOnOffStages()
{
    this.switches = new List<SwitchOnOffStageInHitHead>();
    foreach(GameObject obj in GameObject.FindGameObjectsWithTag("SwitchOnOff"))
    {
        this.switches.Add(obj.GetComponent<SwitchOnOffStageInHitHead>());
    }

    this.switchStages = new List<SwitchOnOffStage>();
    foreach(GameObject obj in GameObject.FindGameObjectsWithTag("SwitchOnOffStage"))
    {
        this.switchStages.Add(obj.GetComponent<SwitchOnOffStage>());
    }
}
```

![extendEditer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276180/e881e6a3-1ccc-6f3f-ec02-eeb5c7d129da.png)

自前で用意したい場合はこちらの記事などを参考にしてみては。
https://kan-kikuchi.hatenablog.com/entry/CustomEditor_Button

# DoTweenはいいぞ(いいぞ)
今回初めてDoTweenを利用してみましたが、とにかく便利。
Unity全利用者使ってみるべき。

入門ブログを以前書きましたが、簡単なUIの処理にも重宝した。
これからもかなりお世話になりそう。
https://qiita.com/papyrustaro/items/2435369d13373417e1fd

# WebGLでのJoy-Con操作
Unity上とWebGLビルド上ではどうもJoy-Conの入力が違うらしい。
アナログスティックは検知できない、という記事も見かけたが、全入力検知できた。
正直全く詳しくないので、環境次第では上手くいかないかもしれない。

以下のサイトにて、unityroomでのパッド入力の確認ができるようにしています。
https://unityroom.com/games/inputchecker

# 衝突したColliderの法線ベクトルが知りたい
まず、調べた限りTrigger(OnTrigger...)ではできない。
OnCollision...の中で、collision.contacts[0].normalでできる(1つでいいなら)

https://docs.unity3d.com/ja/current/ScriptReference/Collision-contacts.html
https://docs.unity3d.com/ja/current/ScriptReference/Collision.html

# スクリプトからEventTriggerを設定する
ButtonなどのGUIでは、選択されたときやカーソルが上に乗ったときに特定の処理をさせたいことが頻繁に起こる。
そんなときにEventTriggerを利用しているのだが、Inspector上で毎回処理を代入するのが非常に面倒。
スクリプトでawakeなどで代入するのが楽。
https://light11.hatenadiary.com/entry/2018/01/31/234716

しかし、この場合だと処理を登録してもInspector上から見えないのが欠点。
見えないが登録はされている。
このせいで、予めスクリプトによる代入ができないのが悩みどころ。

# 床の移動にあわせてプレイヤーも移動させる
Rigidbodyによる摩擦計算を利用すれば、乗っている床が移動してもプレイヤーは乗ったままだが、
CharacterControllerを利用してプレイヤーを動かしていた場合、そうはいかない。
何もしないと、プレイヤーの座標はそのままで床だけ移動してしまう(しまいには落ちる)

一番手っ取り早い方法はプレイヤーgameObjectを移動床の子オブジェクトにすることだが、できることが制限されるので今回は別の方法を検討。

今回は移動する可能性のある床に接地したら、床のTransformを記憶し、前フレームからの移動分プレイヤーも移動させることで、移動リフトの上に乗ることを実装した。
移動リフトはいろんなゲームで登場するのでもっと賢い実装がありそうだ...

# その他
## 壁キック実装
ブログを別で書きました。
https://papyrustaro.hatenablog.jp/entry/2020/06/17/121100

## アニメーションによって、gameObjectが移動しないようにする
(怒涛のURL貼り付け)
http://tsubakit1.hateblo.jp/entry/2017/11/03/235913
http://aluminum-pepe.hatenablog.com/entry/2019/01/07/064816
https://gametukurikata.com/program/scriptisgrounded

## アニメーションを瞬時に変える
has exit time をfalseに
transration duration を0に

時にはanimationclip自体の編集も

## CharacterControllerを使用したときにキャラが地面から浮く
SkinWidthが原因。リファレンスではradisの10%。

https://docs.unity3d.com/ja/2018.4/Manual/class-CharacterController.html

## ライティング
これ以上わかりやすい資料があるでしょうか、というほどわかりやすい資料
https://learning.unity3d.jp/2224/

## 線を描く
LineRendererを使う。
Inspector上からメソッドを呼ぶことで、特定のVector配列で線を引くことも可能

### 移動リフトの線を引く例

```cs
    /// <summary>
    /// Inspectorからリフトの軌道上の線を作成
    /// </summary>
    [ContextMenu("GenerateLineOfOrbit")]
    public void GenericLineOfOrbit()
    {
        List<Vector3> paths = new List<Vector3>();
        paths.Add(this.transform.position);
        for(int i = 0; i < this.path.Length; i++)
        {
            paths.Add(this.path[i] + paths[i]);
        }
        LineRenderer lineRenderer = this.gameObject.AddComponent<LineRenderer>();
        lineRenderer.positionCount = paths.Count;
        lineRenderer.SetPositions(paths.ToArray());
        lineRenderer.startWidth = 0.1f;
        lineRenderer.endWidth = 0.1f;
    }
```

## プロパティをinspector上から変更する
https://baba-s.hatenablog.com/entry/2018/12/19/085000

## 自動でいい感じのColliderを作成する
https://kan-kikuchi.hatenablog.com/entry/SAColliderBuilder

## SDユニティちゃんにライトが当たらない
shaderのせいみたいだが、変えると見栄えが...おとなしく初期設定に戻した。

## Particleが場所や観る角度によって見え方が変わる
影がついているのが原因
ShaderをParticles/Standard Surfaceなどから、Particles/Standard Unlitなどに変えることで解決(壁張り付き時の煙)