レースゲームで順位をつけるシステムをつける（簡単なもの）

<h1>概要</h1>
レースゲームに必要な順位をつけ，画面に表示するシステムを構築しました。
とはいっても，リアルタイムに，すぐに順位を切り替えるのは難しいため，一周ごとに順位を切り替えることにした。
プレイヤーとCPUの車は，UnityStandardAssetsのカーオブジェクトを使用しました。

Assets/Standard Assets/Vehicles/Car/Prefabs/Car.prefab
CPUは，
Assets/Standard Assets/Vehicles/Car/Prefabs/CarWaypointBased.prefab

モデルの部分は，Blenderで作成したものを使用しました。

<h1>流れ</h1>
スタートライン/ゴールラインにBoxColliderを置き，isTriggerのチェックをONにする。車側にOnTriggerStayの関数を用意し，スタートライン/ゴールラインのBoxColliderをすり抜けた時，周回数を更新できるようにしました。
そして，プレイヤーの車がスタートライン/ゴールラインにBoxColliderをすり抜けた場合は，プレイヤーの周回数と同じか，それ以上に周回している車が何台いるか数え，順位の表示を更新する形にしました。
また，今回は，コースが壁に囲まれてるため不可能ですが，ショートカット防止のため，BoxColliderの中継地点をつくり，そこを通らない場合は，スタートライン/ゴールラインを通過しても周回が加算されず，順位の表示の更新もされない仕様にしました。
また，図のようにプレイヤーが使用する車は，BoxColliderを通過する時，他のCPUと区別するため，ColliderBottomをMyColliderBottomという名前に変更しておきます。

![MyColliderBottom.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1312991/69179de8-6fe5-11a7-30b2-62afc04bf7a2.png)


<h1>ポイントの設置</h1>
checkPointというゲームオブジェクトを用意し，その子要素としてスタートライン・ゴールラインにPoint，コースを半周程した場所にPoint2を用意する。
下の画像のように，Point，Point2ともに，Add ComponentでBoxColliderを加え，isTriggerのチェックをONにする。

![Point.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1312991/65f0a185-a6b8-e809-81fa-cb9859e25de1.png)

![point2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1312991/4b6d0532-2086-a648-1b02-238e3417dec7.png)

<h1>プログラム（車側）</h1>
プレイヤーの車の周回数取得するプログラムをmyPos.csという名前で，Carオブジェクト内に組み込みました。mylapという変数を使用し，スタート時は，0とします。最初は，スタートラインより，手前から，スタートしているため，0周からスタートし，Pointオブジェクト（スタートライン・ゴールライン）のBoxColliderを越えて，1周目に入ることにしました。
この時点で，半周後のPoint2のチェックポイントに向かうため，mylapの変数を5とします。半周のため0.5としたかったのですが，intの型の変数の方が扱いが容易なので5としました。Point2オブジェクトのBoxColliderを通り抜けるとき，mylapの値が5だった場合，値を10とし，PointオブジェクトのBoxColliderに向かうようにします。
そして，PointオブジェクトのBoxColliderを通り抜けるとき，mylapの値が10だった場合，値を15とし，Point2オブジェクトのBoxColliderに向かうようにします。

このように，
Pointオブジェクト（スタートライン・ゴールライン）でmylapの値が0だった場合は，5に
↓
Point2オブジェクト（中間地点）でmylapの値が5だった場合は，10に
↓
Pointオブジェクトでmylapの値が10だった場合は，15に
↓
Point2オブジェクトでmylapの値が15だった場合は，20に

と半周ごとに値を大きくしていきます。

```myPos.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class myPos : MonoBehaviour
{
    public int mylap = 0;
    
    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void OnTriggerStay(Collider col)
    {

        if (col.gameObject.name == "Point")
        {
            if (mylap == 0) //0 = スタート
            {
                mylap = 5; //Point2を目指す
            }

            else if (mylap == 10) //1周目
            {
                mylap = 15; //Point2を目指す
            }

            else if (mylap == 20) //2周目
            {
                mylap = 25; //Point2を目指す
            }

            else if (mylap == 30) //3周目
            {
                mylap = 35; //Point2を目指す
            }

            else if (mylap == 40) //4周目
            {
                mylap = 45; //Point2を目指す
            }

            else if (mylap == 50) //5周目
            {
                mylap = 55; //フィニッシュ
            }
        }

        else if (col.gameObject.name == "Point2")
        {
            if (mylap == 5) //5 = 半周
            {
                mylap = 10; //Pointを目指す
            }

            else if (mylap == 15) //1周半
            {
                mylap = 20; //Pointを目指す
            }

            else if (mylap == 25) //2周半
            {
                mylap = 30; //Pointを目指す
            }

            else if (mylap == 35) //3周半
            {
                mylap = 40; //Pointを目指す
            }

            else if (mylap == 45) //4周半
            {
                mylap = 50; //Point（ゴール）を目指す
            }
        }
    }
}
```
CPUのスクリプトも，myPos.csと内容は変わりませんが，mylapという半周ごとの周回を取得する変数をrivalLapという変数名に変更しています。
また，CPUの1台目のプログラム名はrivalPosだが，2台目はrivalPos1，3台目はrivalPos2とし，CPUの車のオブジェクトに組み込むことにしました。

```rivalPos.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class rivalPos : MonoBehaviour
{
    public int rivalLap = 0;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
    }

    public void OnTriggerStay(Collider col)
    {

        if (col.gameObject.name == "Point")
        {
            if (rivalLap == 0) //0 = スタート
            {
                rivalLap = 5; //Point2を目指す
            }

            else if (rivalLap == 10) //1周目
            {
                rivalLap = 15; //Point2を目指す
            }

            else if (rivalLap == 20) //2周目
            {
                rivalLap = 25; //Point2を目指す
            }

            else if (rivalLap == 30) //3周目
            {
                rivalLap = 35; //Point2を目指す
            }

            else if (rivalLap == 40) //4周目
            {
                rivalLap = 45; //Point2を目指す
            }

            else if (rivalLap == 50) //5周目
            {
                rivalLap = 55; //フィニッシュ
            }
        }
        else if (col.gameObject.name == "Point2")
        {
            if (rivalLap == 5) //5 = 半周
            {
                rivalLap = 10; //Pointを目指す
            }

            else if (rivalLap == 15) //1周半
            {
                rivalLap = 20; //Pointを目指す
            }

            else if (rivalLap == 25) //2周半
            {
                rivalLap = 30; //Pointを目指す
            }

            else if (rivalLap == 35) //3周半
            {
                rivalLap = 40; //Pointを目指す
            }

            else if (rivalLap == 45) //4周半
            {
                rivalLap = 50; //Point（ゴール）を目指す
            }
        }
    }
}
```

<h1>プログラム（Pointオブジェクト側）</h1>
順位の表示や周回数の表示は，Pointオブジェクト内のプログラム（LapControl.cs）で制御することにしました。Updateメソッド内で，GetComponentを使用し，プレイヤーの車のオブジェクトやCPUの車のオブジェクトから，mylapの変数やrivalLapの変数の値を取得します。
Pointオブジェクト内のプログラムでは，mylap変数の値は，LapMyという変数に代入し，rivalLap変数は，各CPUの車のオブジェクトによって，変わってくるため，RivalPosというint型の配列の要素として，それぞれの値を代入することにしました。
プレイヤーの車のMyColliderBottomがBoxColliderと接触した時，countPosというメソッドを使用し，順位の更新が始まります。この時，あまりする必要はなかったかもしれませんが，countPosメソッドが実行されている最中に，RivalPosの各要素の値が変更するしても問題がない状態にするため，RivalPosNowというint型の配列を新規で作成し，RivalPosNowにRivalPosの配列の値を代入します。そして，RivalPosNowを引数に入れてからcountPosメソッドを実行します。
countPosメソッド内では，count変数を使用し，LapMyの値よりも大きい値のRivalPosNowの要素がある場合，count変数の値を+1し，PosNowという順位の表示に使用する変数に代入します。
例として，0周目（スタートしたばかり）の時にPointオブジェクトのBoxColliderに接触し，countPosメソッドが実行された場合，mylapの値は更新されていない（OnTriggerEnterで接触を検知している）ため，LapMyの値は0のままで，LapMyが0より大きいRivalPosNowの要素の値がある場合，count変数の値に+1される形になります。

順位と周回数の表示はOnGUIメソッドでおこないます。コースを5周するレースのため，LapMyの値が45か50（5周目）の時は，順位の表示を"FINAL LAP"としました。また，LapMyの値が55（50以上の場合）は，レースが終了しているため，順位の表示を"FINISH"としました。また，順位の表示については，先ほど，countPosメソッド実行時に，count変数の値を代入したPosNowの値を表示します。


```LapControl.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LapControl : MonoBehaviour
{
    int PosNow = 12; //11台のCPUがいるため（初期の順位）
    int LapMy;
    int count;
    bool finish = false;
    int[] RivalPos = new int[11];
    public GUIStyle style;

    // Start is called before the first frame update
    void Start()
    {
        style = new GUIStyle();
    }

    // Update is called once per frame
    void Update()
    {
        //オブジェクトの読み込み
        GameObject obj = GameObject.Find("CarWaypointBased(0)"); //CPU（ライバルカー）の1台目
        GameObject obj1 = GameObject.Find("CarWaypointBased (1)"); //CPUの2台目
        GameObject obj2 = GameObject.Find("CarWaypointBased (2)"); //CPUの3台目
        GameObject obj3 = GameObject.Find("CarWaypointBased (3)"); //CPUの4台目
        GameObject obj4 = GameObject.Find("CarWaypointBased (4)"); //CPUの5台目
        GameObject obj5 = GameObject.Find("CarWaypointBased (5)"); //CPUの6台目
        GameObject obj6 = GameObject.Find("CarWaypointBased (6)"); //CPUの7台目
        GameObject obj7 = GameObject.Find("CarWaypointBased (7)"); //CPUの8台目
        GameObject obj8 = GameObject.Find("CarWaypointBased (8)"); //CPUの9台目
        GameObject obj9 = GameObject.Find("CarWaypointBased (9)"); //CPUの10台目
        GameObject obj10 = GameObject.Find("CarWaypointBased (10)"); //CPUの11台目
        GameObject myobj = GameObject.Find("Car"); //プレイヤーの車

        //mylap，rivalLap変数の読み込み
        rivalPos myRival;
        myRival = obj.GetComponent<rivalPos>();
        RivalPos[0] = myRival.rivalLap;

        rivalPos1 myRival1;
        myRival1 = obj1.GetComponent<rivalPos1>();
        RivalPos[1] = myRival1.rivalLap;

        rivalPos2 myRival2;
        myRival2 = obj2.GetComponent<rivalPos2>();
        RivalPos[2] = myRival2.rivalLap;

        rivalPos3 myRival3;
        myRival3 = obj3.GetComponent<rivalPos3>();
        RivalPos[3] = myRival3.rivalLap;

        rivalPos4 myRival4;
        myRival4 = obj4.GetComponent<rivalPos4>();
        RivalPos[4] = myRival4.rivalLap;

        rivalPos5 myRival5;
        myRival5 = obj5.GetComponent<rivalPos5>();
        RivalPos[5] = myRival5.rivalLap;

        rivalPos6 myRival6;
        myRival6 = obj6.GetComponent<rivalPos6>();
        RivalPos[6] = myRival6.rivalLap;

        rivalPos7 myRival7;
        myRival7 = obj7.GetComponent<rivalPos7>();
        RivalPos[7] = myRival7.rivalLap;

        rivalPos8 myRival8;
        myRival8 = obj8.GetComponent<rivalPos8>();
        RivalPos[8] = myRival8.rivalLap;

        rivalPos9 myRival9;
        myRival9 = obj9.GetComponent<rivalPos9>();
        RivalPos[9] = myRival9.rivalLap;

        rivalPos10 myRival10;
        myRival10 = obj10.GetComponent<rivalPos10>();
        RivalPos[10] = myRival10.rivalLap;

        myPos my;
        my = myobj.GetComponent<myPos>();
        LapMy = my.mylap;

    }

    //順位，周回数の表示
    void OnGUI()
    {
        style.fontSize = 30;

        if (finish == false)
        {
            if (LapMy == 0)
            {
                GUI.Box(new Rect(800, 220, 80, 80), "Lap: 0/5", style);
            }

            else if (LapMy == 5 || LapMy == 10)
            {
                GUI.Box(new Rect(800, 220, 80, 80), "Lap: 1/5", style);
            }

            else if (LapMy == 15 || LapMy == 20)
            {
                GUI.Box(new Rect(800, 220, 80, 80), "Lap: 2/5", style);
            }

            else if (LapMy == 25 || LapMy == 30)
            {
                GUI.Box(new Rect(800, 220, 80, 80), "Lap: 3/5", style);
            }

            else if (LapMy == 35 || LapMy == 40)
            {
                GUI.Box(new Rect(800, 220, 80, 80), "Lap: 4/5", style);
            }

            else if (LapMy >= 45 || LapMy == 50)
            {
                GUI.Box(new Rect(800, 220, 80, 80), "FINAL LAP", style);
            }
        }

        else //LapMy（mylap）の値が50以上（ゴールしている）の場合
        {
            GUI.Box(new Rect(800, 220, 80, 80), "FINISH", style);
        }
        GUI.Box(new Rect(400, 220, 80, 80), "Position: " + PosNow.ToString(), style);
    }


    public void OnTriggerEnter(Collider col)
    {
        //プレイヤーの車がBoxColliderを通過した場合
        if (col.gameObject.name == "MyColliderBottom")
        {
            int[] RivalPosNow = new int[11];
            RivalPosNow = RivalPos; //RivalPosの値を代入
            countPos(RivalPosNow); //順位を数えるメソッドを実行
        }

    }

    //順位を数えるメソッド
    void countPos(int[] RivalPosNow)
    {
        if (LapMy == 0)
        {
            count = 1;
            for (int i = 0; i < RivalPosNow.Length; i++)
            {
                //プレイヤーより前をはしっているCPUをカウント
                if (0 < RivalPosNow[i]) //LapMyより大きいRivalPosNowの要素をカウント
                {
                    count += 1;
                }
            }
            PosNow = count; //カウントした値を代入
        }

        //その後も同じ作業
        else if (LapMy == 10)
        {
            count = 1;
            for (int i = 0; i < RivalPosNow.Length; i++)
            {
                if (10 < RivalPosNow[i])
                {
                    count += 1;
                }
            }
            PosNow = count;
        }

        else if (LapMy == 20)
        {
            count = 1;
            for (int i = 0; i < RivalPosNow.Length; i++)
            {
                if (20 < RivalPosNow[i])
                {
                    count += 1;
                }
            }
            PosNow = count;
        }

        else if (LapMy == 30)
        {
            count = 1;
            for (int i = 0; i < RivalPosNow.Length; i++)
            {
                if (30 < RivalPosNow[i])
                {
                    count += 1;
                }
            }
            PosNow = count;
        }

        else if (LapMy == 40)
        {
            count = 1;
            for (int i = 0; i < RivalPosNow.Length; i++)
            {
                if (40 < RivalPosNow[i])
                {
                    count += 1;
                }
            }
            PosNow = count;
        }

        else if (LapMy == 50)
        {
            count = 1;
            for (int i = 0; i < RivalPosNow.Length; i++)
            {
                if (50 < RivalPosNow[i])
                {
                    count += 1;
                }
            }
            PosNow = count;
            finish = true;
        }
        Debug.Log("LapMy: " + LapMy);
        Debug.Log("RivalPosNow: " + string.Join(", ", RivalPosNow));
        Debug.Log("PosNow: " + PosNow);
    }
}
```

<h1>テスト動画</h1>
テストをUnity上でおこないました。最後尾からスタートして，CPUを追い越した分，順位が上がっていき，スタートライン・ゴールラインで順位と周回数がどちらも切り替わっていたため，問題なく動作していると考えられます。また，今回は，ゴールした後も走れるようになっているが，レース終了後，周回を重ねても，ゴール後から表示が変わることはありませんでした。

<iframe width="560" height="315" src="https://www.youtube.com/embed/GS897lGMqps" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>


また，下の画像は，スタートライン・ゴールラインを越えて2周目から3周目に入ったところのもので，緑の枠の中に，この時のLapMy変数，RivalPosNowの要素，PosNowのそれぞれの値が出力されています。LapMyの値は20となっていて，それよりも大きい値のRivalPosNowの要素が4つあります。したがって，PosNowの値は，5となり，ゲーム画面のPosition（順位）の値も同じものが出力されているため，このことからも，構築したシステムが問題なく動いていることがわかります。

![テスト写真.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1312991/57737e2e-905a-e85d-a8d1-ad666d1c30d8.png)


<h1>おわりに</h1>
今回は，初めてということで，スタートライン・ゴールラインで，順位の表示が変更するシステムの構築をおこないました。もっとBoxColliderの数を増やし，そこで，今回のプログラムのように，順位を数えるようにすれば，1周待たなくても，途中で順位が変更するようになると思います。
しかし，これでは，CPUを追い抜いたり，抜かされたりした時に，瞬時に変わらないため，また，別の方法で，順位をリアルタイムに切換えられる方法を探していきます。
長い文でしたが，読んでいただき，ありがとうございます。

<h1>参考にさせていただいたWebサイト</h1>
[Unity]他のオブジェクトについているスクリプトの変数を参照したり関数を実行したりする。 
https://qiita.com/tsukasa_wear_parker/items/09d4bcc5af3556b9bb3a