0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

CrossViewとは

こちらの記事をご覧ください。

はじめに

前回、ログファイルを読み込み、記述されたログ(ボールやプレーヤーの動きなど)を100msec間隔で順次実行するLogPlayerの仕組みを構築するところまで実装しました。

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

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

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

Football_4

今回のテストプログラムでは、以下の項目を実装します。

  • Prefabによるプレーヤー3Dモデルのインスタンス化
  • プレーヤーごとの背番号を2DTextで描画する仕組み
  • AnimatorControllerを実行時に切り替える
  • インスタンス化したらアニメーションをデモ実行
  • NightModeの仮実装

Football_4.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;
    }

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

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

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的には【非推奨】のようですが、この場面でしか使わないので良しとしています。

アニメーションのデモ

プレーヤーをインスタンス化したら、アニメーションの動きも確認したいので、デモを実装してみることにしました。

  • サッカーフィールド上の任意の位置をクリックする。
  • センターサークル中央位置に、画面下のチェックボックスで選択したプレーヤーのインスタンスを1つ生成する。
  • クリックした座標にプレーヤーの向きを変え、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

NightMode

画面右下のNightModeチェックボックスをONにすると、画面を暗くして各プレーヤーの足元にあるSpotLightを点灯させます。

  • 環境光(RenderSettings.ambientIntensity)とDirectionalLightを消す/暗くする。
  • 各プレーヤーのSpotLightを点灯させる。

Football_4_NightMode.png

ソースコード

最新版のソースコードは、CrossViewのリポジトリにあります。

参考サイト

WebGL version

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

以上です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?