【Unity】unity-simple-ranking にキャラクターアイコンを表示させる


## やったこと
unity-simple-ranking を改造して、ランキングデータベースに各プレイヤーのキャラクターアイコンを登録できるようにしました。
更に、それをランキング画面に表示できるようにしました。

([Unity1Week の参加作品](https://unityroom.com/games/accumulate_jump_run) にて、これを実装してみました。) 

:::note warn
※ unity-simple-ranking の中身を編集しているので気を付けてください。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/9afaaccb-d195-858a-17c5-6b901fde5d58.png)


## 環境
・Unity 2021.3.8f1
・unity-simple-ranking v2.2 
・NCMB v4.5.0

## 準備

https://blog.naichilab.com/entry/webgl-simple-ranking を参考に、NCMB と unity-simple-ranking をプロジェクトにインポートしてください


## 方針
「ランキング画面呼び出し時」、「データベース登録時」、「ランキング表示時」にそれぞれ処理を加えます。

### ランキング画面呼び出し時
unity-simple-ranking では、ランキング画面呼び出し時に、自分のスコアを引数で渡しています。 (`RankingLoader.Instance.SendScoreAndShowRanking()`で呼んでいます。) 
ここに、`キャラクターアイコンID` も引数で渡すようにします。

### データベース登録時
unity-simple-ranking では、`RankingSceneManager.cs` 内の `SendScoreEnumerator()` でレコード登録処理をしています。
「名前」と「スコア」の2つのデータを含んだレコードを、データベースに登録しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/3c6d018c-afc2-71ce-c1cc-5906469f71a2.png)
ランキング表示時、この2つのデータからでは「キャラクターアイコン」の情報を得ることが出来ません。
従って、ランキング登録時に `キャラクターアイコンID` と `名前` と `スコア` の3つのデータを含んだレコードを、データベースに登録する必要があります。

キャラクターアイコンは、アイコン画像そのものを登録するのではなく、「どのキャラクターアイコンか」を判別するための ID を登録します。ここでは整数値で登録します。
↑ の対応により、ランキング画面呼び出し時の引数に `キャラクターアイコンID` を追加したので、それを登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/db581772-7da1-d9fd-70c1-fe6bfa3c1661.png)


### ランキング表示時
↑ の対応により、各レコードには `キャラクターアイコンID` のデータが含まれているので、表示側では ID に対応する画像を表示します。

ランキング表示時の各ノードは `naichilab\unity-simple-ranking\Prefabs\RankingNode.prefab` を使用しています。
このプレハブには、キャラクターアイコンを表示する場所が用意されていません。従って、キャラクターアイコン表示用のオブジェクト、コンポーネントを追加しておく必要がります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/dd9921ad-a349-c62c-c061-9f17d0ba2644.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/fba927bc-d0d2-59c3-edb8-3cb557c7e3fe.png)
(それと、当然ですがキャラクターアイコン画像を用意しておきましょう。) 

## 実装
### スクリプト
##### ランキング画面呼び出し

・`RankingLorder.cs` に、フィールドを追加します。
```RankingLorder.cs
        [NonSerialized] public int LastIcon = 0; // ランキング呼び出し側で指定したキャラクターアイコンを一時保存する場所
```

・`RankingLorder.cs` に、「キャラクターアイコンID を引数に入れられるランキング画面呼び出し関数」を追加します。
```RankingLorder.cs
        public void SendScoreAndShowRankingWithIcon(TimeSpan time, int icon = 0, int boardId = 0)
        {
            var board = RankingBoards.GetRankingInfo(boardId);
            var sc = new TimeScore(time, board.CustomFormat);
            SendScoreAndShowRankingWithIcon(sc, icon, board);
        }

        public void SendScoreAndShowRankingWithIcon(double score, int icon = 0, int boardId = 0)
        {
            var board = RankingBoards.GetRankingInfo(boardId);
            var sc = new NumberScore(score, board.CustomFormat);
            SendScoreAndShowRankingWithIcon(sc, icon, board);
        }

        private void SendScoreAndShowRankingWithIcon(IScore score, int icon = 0, RankingInfo board)
        {
            if (board.Type != score.Type)
            {
                throw new ArgumentException("スコアの型が違います。");
            }

            CurrentRanking = board;
            LastScore = score;
            LastIcon = icon; // ここで引数のキャラクターアイコンIDを保存。ランキングシーンで参照されます
            SceneManager.LoadScene("Ranking", LoadSceneMode.Additive);
        }
```

##### データベース登録
・`RankingSceneManager.cs` に以下の定数、フィールド、シリアライズフィールドを追加します。
```RankingSceneManager.cs
    private const string COLUMN_ICON = "icon"; // レコード内の項目名
    private int _lastIcon; // 自分のキャラクターアイコンID。自分のランキングを送信するときに使用
    [SerializeField] private Sprite[] icons; // キャラクターアイコン画像の配列。ここに入っている画像を表示します
```
`RankingSceneManager.cs`の `Start()` にて、 `RankingLoader.cs` の `LastIcon` フィールド値を読み込みます。

```RankingSceneManager.cs
        void Start()
        {
            sendScoreButton.interactable = false;
            _board = RankingLoader.Instance.CurrentRanking;
            _lastScore = RankingLoader.Instance.LastScore;
            _lastIcon = RankingLoader.Instance.LastIcon; // ← ここ追加
            Debug.Log(BoardIdPlayerPrefsKey + "=" + PlayerPrefs.GetString(BoardIdPlayerPrefsKey, null));

            StartCoroutine(GetHighScoreAndRankingBoard());
        }
```

・`RankingSceneManager.cs`の `SendScoreEnumerator()` にて、送信用データに `キャラクターアイコンID` を追加で登録します。
```RankingSceneManager.cs
        private IEnumerator SendScoreEnumerator()
        {
            sendScoreButton.interactable = false;
            highScoreLabel.text = "送信中...";

            //ハイスコア送信
            if (_ncmbRecord == null)
            {
                _ncmbRecord = new NCMBObject(_board.ClassName);
                _ncmbRecord.ObjectId = ObjectID;
            }

            _ncmbRecord[COLUMN_NAME] = InputtedNameForSave;
            _ncmbRecord[COLUMN_SCORE] = _lastScore.Value;
            _ncmbRecord[COLUMN_ICON] = _lastIcon; // ← ここ追加

            ...以下略...

        }
```
##### ランキング表示

・ `RankingNode.cs` にキャラクターアイコン表示用のフィールド`Icon`を追加します
```RankingNode.cs
	public class RankingNode : MonoBehaviour
	{
		public Text NoText;
		public Text NameText;
		public Text ScoreText;
		public Image Icon; // ← ここ追加
	}
```

・`RankingSceneManager.cs`の`LoadRankingBoard()`にて、生成したインスタンスに `キャラクターアイコンID`を入れます。

```RankingSceneManager.cs
        private IEnumerator LoadRankingBoard()
        {

           ...以前略...

                foreach (var r in so.Result)
                {
                    var n = Instantiate(rankingNodePrefab, scrollViewContent);
                    var rankNode = n.GetComponent<RankingNode>();
                    rankNode.NoText.text = (++rank).ToString();
                    rankNode.NameText.text = r[COLUMN_NAME].ToString();

                    var iconID = int.Parse(r[COLUMN_ICON].ToString()); // ← ここ追加
                    rankNode.Icon.sprite = icons[iconID]; // ← ここ追加
                    
                    var s = _board.BuildScore(r[COLUMN_SCORE].ToString());
                    rankNode.ScoreText.text = s != null ? s.TextForDisplay : "エラー";

//                    Debug.Log(r[COLUMN_SCORE].ToString());
                }

            ...以下略...

        }
```


### プレハブ、シーン
・`RankingNode.prefab`に空のオブジェクトを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/fba927bc-d0d2-59c3-edb8-3cb557c7e3fe.png)
・↑ のオブジェクトに `Image` コンポーネントをアタッチします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/8d60b8cf-75c6-d21e-1406-c8d8005a0b37.png)

・さらに、`RankingNode`コンポーネントの Icon 項目に、先ほどの`Image`をアタッチします。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/b5aaae04-f958-b100-bf37-b3b591920026.png)

・`Ranking.unity`シーンを開き、`RankingSceneManager`コンポーネントの Icons 項目に、キャラクターアイコン画像を入れておきます。 (キャラクターアイコンIDと画像の順番を合わせておいてください。) 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/b015c7a7-1dfc-359d-7f95-5e51704ab7a7.png)

## ランキングを開く

こんな感じで開きます。
```
    {
        double score = 100d; // ← スコア。ゲーム内のスクリプトから適宜拾ってくる
        int icon = 1; // ← 現在使用しているキャラクターのID。ゲーム内のスクリプトから適宜拾ってくる
        int board = 0; // ← ランキング番号。ランキングの種類が1つだけなら0

        RankingLoader.Instance.SendScoreAndShowRankingWithIcon(score, icon, board);
    }
```

実行するとこんな感じで出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/19991e66-7c66-5000-1a23-c20ea3c03278.png)


## その他、応用
キャラクターアイコン画像のサイズは、 `RankingNode.prefab` の Height よりも小さくする必要があります (はみ出さないようにするために)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246559/093ebbc2-9a20-e8b4-51dc-ba8bdacda9e1.png)
 
おそらく、デフォルトの Height のままキャラクターアイコンを表示しようとすると、小さい印象を持つと思います。アイコンを大きく表示するためには、`RankingNode.prefab` の Height 値を大きくすると良いでしょう。

## 参考
・[【Unity、WebGL】なるべく簡単にオンラインランキング機能をつけるサンプル](https://blog.naichilab.com/entry/webgl-simple-ranking)
・[Unity1Week 参加作品「Accumulate Jump Run」](https://unityroom.com/games/accumulate_jump_run)