[Unity][RoboCup] CrossView開発日記 #4

# CrossViewとは
こちらの記事をご覧ください。

https://qiita.com/suzuki_nene/items/cbdb701622a738f1b2ee

# はじめに
[前回](https://qiita.com/suzuki_nene/items/b920e3faf7aa27410005)、ログファイルを読み込み、記述されたログ（ボールやプレーヤーの動きなど）を100msec間隔で順次実行するLogPlayerの仕組みを構築するところまで実装しました。

実際にログを再生した時、ボールの動きまでは動作を確認することができたので、今回はプレーヤー(選手)の番です。

プレーヤーは、左右のチームごとに11名ずつ、合計22名の3Dモデルを実行時にインスタンス化しなければなりません。

事前に3Dモデルを22個用意しておくというやり方もありますが、ゲームで使う3Dモデルをユーザーが設定画面から選択できるようにもしたかったので、Prefabによるインスタンス化を採用します。

# Football_4
今回のテストプログラムでは、以下の項目を実装します。
- Prefabによるプレーヤー3Dモデルのインスタンス化
- プレーヤーごとの背番号を2DTextで描画する仕組み
- AnimatorControllerを実行時に切り替える
- インスタンス化したらアニメーションをデモ実行
- NightModeの仮実装

![Football_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/843818/2a78c64b-a672-bce1-aae3-3ccbe0d0f48b.png)

## プレーヤーのインスタンス化
プレーヤーのGameObject階層とアタッチするScriptは、次のような形にしました。
プレーヤーをPrefab化するにあたり、クラス構成も見直しを行いました。
```
Prefabs
 +-- Player_00        -> FieldPlayerBehaviour
   +-- {3DModel}      -> PlayerAnimBehaviour
   +-- ViewAngle      -> ViewAngleBehaviour
     +-- Spot Light
```
{3DModel}の部分に、BananaManやSpaceRobotKyle、Unityちゃんといった3Dモデルを入れ替えて、それぞれ Player_xxとしてプレハブ化しています。

アタッチするScriptのクラス継承、および各クラスの機能は、次の通りです。
```FieldPlayerBehaviour.cs
MonoBehaviour
 +-- MovingBehaviour           -> MoveTo()
   +-- PlayerBehaviour         -> TurnTo()
     +-- FieldPlayerBehaviour  -> TurnBodyNeckViewAngleTo(), Idle(), Dash(), Kick(), Tackle(), Catch()
```
```PlayerAnimBehaviour.cs
MonoBehaviour
 +-- PlayerNeckBehaviour       -> TurnNeckTo()
   +-- PlayerAnimBehaviour     -> Idle(), Dash(), Kick(), Tackle(), Catch()
```
```ViewAngleBehaviour.cs
MonoBehaviour
 +-- ViewAngleBehaviour        -> TurnTo(), ChangeAngle()
```
Prefabデータが用意出来たら、あとはInstantiate()でインスタンス化するだけです。
今回は6種類のPrefabを用意しました。

テストプログラムでは、画面下部で使用するPrefabの種類を選んでサッカーフィールド上をマウスクリックすると、インスタンスが生成されるようになっています。

## 背番号を2DTextで描画
背番号は、Canvas上に2DTextを描画することで実現します。
- 3Dモデルの頭部中央の3D座標を求める。
- 3D座標をスクリーン座標に変換する。
- スクリーン座標に2DTextを生成して、配置する。背番号の番号文字列も実行時に決定する。

3Dモデルの頭部中央の3D座標は、次のように求めています。
```sample.cs
    private Vector3 getPlayerHeadPosition(GameObject obj)
    {
        Bounds bounds = new Bounds(obj.transform.position, Vector3.zero);
        Renderer[] renderers = obj.GetComponentsInChildren<Renderer>();

        foreach (Renderer renderer in renderers)
        {
            bounds.Encapsulate(renderer.bounds);
        }
        Vector3 head = new Vector3(bounds.center.x, bounds.max.y, bounds.center.z);

        return head;
    }
```
テストプログラムでは、背番号は「画面上にインスタンス化されたプレーヤーの人数＋１」を新規に割り当てるようにしています。

プレーヤーは数秒経つと自動的に消滅するようにしているので、タイミングにより、背番号が重複することがありますが、まぁ、テストプログラムということで。。。汗)

## AnimatorControllerの切り替え
FieldPlayerとGoalKeeperで、アニメーションを一部変えています。
- Idle状態にて、GoalKeeperはボールをキャッチする構えをするアニメーションを使います。
- CatchはGoalKeeperのみ実行でき、Catch直後のKickは、ボールを下投げするアニメーションを実行します。

テストプログラムでは、デフォルトのAnimatorControllerはFieldPlayer用とし、背番号が11カウントアップする毎に、GoalKeeper用のAnimatorControllerに変更しています。

実装方法は、次のような感じです。
```
// FieldPlayer or GoalKeeper
if (unum % 11 == 1)
{
    obj.GetComponentInChildren<Animator>().runtimeAnimatorController = Resources.Load<RuntimeAnimatorController>("Animators/GoalKeeper");
}
```
ちなみに、Resources.Load()の利用はUnity的には【非推奨】のようですが、この場面でしか使わないので良しとしています。

https://light11.hatenadiary.com/entry/2020/07/29/202755

## アニメーションのデモ
プレーヤーをインスタンス化したら、アニメーションの動きも確認したいので、デモを実装してみることにしました。
- サッカーフィールド上の任意の位置をクリックする。
- センターサークル中央位置に、画面下のチェックボックスで選択したプレーヤーのインスタンスを１つ生成する。
- クリックした座標にプレーヤーの向きを変え、Dashで2秒かけて移動させる。この時、視界の向きも体の向きに対して右90度に変化させる。
- クリック位置に到着したら、センターサークルに向けて体の向きを変える。
- DashからIdle状態に変更し、Catchアニメーションを実行。
- 5秒後に、Kickアニメーションを実行。
- 2秒後に、Tackleアニメーションを実行。
- 再度、Dash状態を5秒間続けてからIdleに戻り、最後に、プレーヤーを自動消滅させる。

StartCoroutine()によって、次のデモシナリオを実行しています。
```
    IEnumerator MovePlayer(FieldPlayerBehaviour player, Vector3 clickPos, float delta)
    {
        float body = Mathf.Atan2(clickPos.x, clickPos.z) * Mathf.Rad2Deg;
        float neck = 90f;
        float angle = 120f;
        float range = 5f;

        // Instantiate() and call it's method soon, it occured NullReferenceException.
        yield return new WaitForSeconds(0.1f);

        // Turn to clicked position
        player.TurnBodyNeckViewAngleTo(body, 0f, 90f, range, 0f);

        // Animation
        player.Dash();

        // Move to clicked position with delta seconds.
        player.MoveTo(clickPos, delta);
        player.TurnBodyNeckViewAngleTo(body, neck, angle, range, delta);

        yield return new WaitForSeconds(delta);

        // Turn to center circle
        body = Mathf.Atan2(-clickPos.x, -clickPos.z) * Mathf.Rad2Deg;
        angle = 60f;
        range = 15f;
        player.TurnBodyNeckViewAngleTo(body, 0f, angle, range, 0f);

        player.Idle();
        player.Catch();

        yield return new WaitForSeconds(5f);

        player.Kick();

        yield return new WaitForSeconds(2f);

        player.Tackle();

        yield return new WaitForSeconds(1f);

        player.Dash();

        yield return new WaitForSeconds(5f);

        player.Idle();

        yield return new WaitForSeconds(1f);

        OnDestroyPlayer(player.gameObject);

        Destroy(player.gameObject);
    }
```
画面上を一気にクリックしていって、90個くらいインスタンスを生成してみました。

![Football_4_Instantiate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/843818/9a63c9ea-4373-263b-b1e9-e5178cf43286.png)

## NightMode
画面右下のNightModeチェックボックスをONにすると、画面を暗くして各プレーヤーの足元にあるSpotLightを点灯させます。
- 環境光(RenderSettings.ambientIntensity)とDirectionalLightを消す/暗くする。
- 各プレーヤーのSpotLightを点灯させる。

![Football_4_NightMode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/843818/5c885916-4bcc-bcc4-bed9-7fdcf04ed329.png)

# ソースコード
最新版のソースコードは、CrossViewのリポジトリにあります。
- [FieldPlayerBehaviour](https://github.com/SuzukiNene/CrossView3D/blob/master/Assets/Scripts/FieldPlayerBehaviour.cs)
- [PlayerAnimBehaviour](https://github.com/SuzukiNene/CrossView3D/blob/master/Assets/Scripts/PlayerAnimBehaviour.cs)
- [ViewAngleBehaviour](https://github.com/SuzukiNene/CrossView3D/blob/master/Assets/Scripts/ViewAngleBehaviour.cs)
- [HowToCreate_Player_00_Prefab](https://github.com/SuzukiNene/CrossView3D/blob/master/HowToCreate_Player_00_Prefab.md)

# 参考サイト
- https://qiita.com/2dgames_jp/items/8a28fd9cf625681faf87
- https://qiita.com/NNNiNiNNN/items/ab983db04bcb1a70dd8e
- https://nekojara.city/unity-object-ui-position

# WebGL version

https://suzukinene.github.io/FootballWeb/Football_4/

操作方法は、前述の通りです。

以上です。