【Unity】new演算子を使ったインスタンスの作成(Unity編共通インスタンス)

2023年10月17日

インスタンスの作成を作成するクラス自身で行うパターンを使ったランキング情報の管理の方法を学びます

プロジェクトの作成

サンプルでは、RankingSampleとして新しくUnityプロジェクトを作成しています

基本パターン

シーンの構成

空のゲームオブジェクトを作成して、Sampleスクリプトをアタッチします

Sampleスクリプト

インスタンスを作成、取得するクラス

作成手順1(基本)

Rankingクラスのインスタンス(実体)を作成します(newキーワードを使います)
Rankingクラスにランカーの情報を追加します
RAnkingクラスには、ランカーのリストがありAddメソッドでは、Rankerクラスから新しくインスタンスを作ってリストに追加しています

注)RankingRankerスクリプトはこの次に掲載しています

using System.Collections.Generic;
using UnityEngine;

public class Sample : MonoBehaviour
{
    void Start()
    {
        Ranking ranking = new Ranking();
        ranking.Add("太郎", 70);
        ranking.Add("次郎", 60);
        ranking.Add("三郎", 50);

        ranking.Remove("次郎");

        ranking.Add("四郎", 65);
}

作成手順2(ランキング一覧の確認)

RankingクラスのRankersプロパティでランキング一覧を取得します
その情報をコンソール画面に表示させています

using System.Collections.Generic;
using UnityEngine;

public class Sample : MonoBehaviour
{
    void Start()
    {
        Ranking ranking = new Ranking();
        ranking.Add("太郎", 70);
        ranking.Add("次郎", 60);
        ranking.Add("三郎", 50);

        ranking.Remove("次郎");

        ranking.Add("四郎", 65);

        List<Ranker> rankers = ranking.Rankers;

        foreach (var ranker in rankers)
        {
            Debug.Log($"名前:{ranker.name}  ポイント:{ranker.score}");
        }
    }
}

Rankerスクリプト

ランキングの個々のメンバーの属性(情報)です

作成手順1(基本)

インスタンスを作るクラスです
ランキングを叩き出した人の名前とポイント情報がメンバーとして記録しています

public class Ranker
{
    public string name;
    public int score;
}

作成手順2(初期値設定機能)

初期値を設定するために引数付きコンストラクタを追加します

public class Ranker
{
    public string name;
    public int score;

    public Ranker(string name, int score)
    {
        this.name = name;
        this.score = score;
    }
}

Rankingスクリプト

ランカーをまとめたクラスになります

作成手順1(基本)

ランキング情報をリストにしたものをメンバーとして持っています

using System.Collections.Generic;

public class Ranking
{
   List<Ranker> rankers = new List<Ranker>();
}

作成手順2(スコア情報追加機能)

Addメソッドを追加します
このメソッドが引数付きで呼ばれると、Rankerクラスからインスタンスを作成してListに追加していきます

using System.Collections.Generic;

public class Ranking
{
    List<Ranker> rankers = new List<Ranker>();

    public void Add(string name, int score)
    {
        Ranker ranker = new Ranker(name, score);
        rankers.Add(ranker);
    }
}

作成手順3(スコア情報削除機能)

Removeメソッドを追加します
呼ばれるとスコアのリストから該当するスコア情報を削除します

using System.Collections.Generic;

public class Ranking
{
    List<Ranker> rankers = new List<Ranker>();

    public void Add(string name, int score)
    {
        Ranker ranker = new Ranker(name, score);
        rankers.Add(ranker);
    }

    public void Remove(string name)
    {
        rankers.Remove(rankers.FirstOrDefault(ranker => ranker.name == name));
    }
}
rankers.Remove(rankers.FirstOrDefault(ranker => ranker.name == name));

ラムダ記号を分解していきます(インテリセンスがやってくれます)

rankers.Remove(rankers.FirstOrDefault(ranker =>
{
    return ranker.name == name;
}));

わざわざ、メソッドにして名前をつけます(実はRemoveメソッドの引数はメソッドの型ということ)

スコアリストの中から調べたい名前で探して見つかれば戻すということをやっているのがわかります

public void Remove(string name)
{
    rankers.Remove(FindRanker(name));
}

Ranker FindRanker(string name)
{
    foreach (var ranker in rankers)
    {
        if (ranker.name == name)
        {
            return ranker;
        }
    }
    return null;
}

作成手順4(スコアリスト取得機能)

プロパティを窓口として、ランキング一覧を返すようにします
getアクセサのみにすることで書き込みはできないようにします
public修飾子であることも確認してください

=>(ラムダ記号、アロー演算子とも呼ばれています)は、プロパティのgetを意味しています

using System.Collections.Generic;

public class Ranking
{
    List<Ranker> rankers = new List<Ranker>();

    public List<Ranker> Rankers => rankers;

    public void Add(string name, int score)
    {
        Ranker ranker = new Ranker(name, score);
        rankers.Add(ranker);
    }

    public void Remove(string name)
    {
        rankers.Remove(rankers.Find(ranker => ranker.name == name));
    }
}
public List<Ranker> Rankers => rankers;

ラムダ記号を分解すると次のようになります(インテリセンスがやってくれます)

public List<Ranker> Rankers
{
    get
    {
        return rankers;
    }
}

作成手順5(スコアリスト取得機能(スコアの高い順で並び替えて取得))

これまでのサンプルでは、追加順に取得していますが、実際ではスコアが高い順に欲しいですよね

using System.Collections.Generic;
using System.Linq;

public class Ranking
{
    List<Ranker> rankers = new List<Ranker>();

    public List<Ranker> Rankers => rankers.OrderByDescending(ranker => ranker.score).ToList();

    public void Add(string name, int score)
    {
        Ranker ranker = new Ranker(name, score);
        rankers.Add(ranker);
    }

    public void Remove(string name)
    {
        rankers.Remove(rankers.Find(ranker => ranker.name == name));
    }
}
rankers.OrderByDescending(ranker => ranker.score).ToList();

ラムダ記号を分解します(インテリセンスがやってくれます)

public List<Ranker> Rankers => rankers.OrderByDescending(ranker =>
{
    return ranker.score;
}).ToList();

OrderByDescendingメソッドな、LINQのメソッドです
降順(大きい順)に並び替えられます
引数に並び替えの基準を記述します
今回は、スコア順としたいのでscoreを渡します

実行結果

点数の高い順に並び替えられたのを取得できています

名前:太郎  ポイント:70
名前:四郎  ポイント:65
名前:三郎  ポイント:50

Rankingクラス内にRankerクラスを取り込む

シーンの構成

RankerクラスをRankingクラスに取り込んだため、プロジェクトビューのファイルが1つ減っています

Rankingスクリプト

RankerkingクラスにRankerクラスを内包します
Rankerクラスを使うのがRankingクラスといった発想がぴったりくる場合、このような選択もありますね

using System.Collections.Generic;
using System.Linq;

public class Ranking
{
    public class Ranker
    {
        public string name;
        public int score;

        public Ranker(string name, int score)
        {
            this.name = name;
            this.score = score;
        }
    }

    List<Ranker> rankers = new List<Ranker>();

    public List<Ranker> Rankers => rankers.OrderByDescending(ranker =>
    {
        return ranker.score;
    }).ToList();

    public void Add(string name, int score)
    {
        Ranker ranker = new Ranker(name, score);
        rankers.Add(ranker);
    }

    public void Remove(string name)
    {
        rankers.Remove(rankers.Find(ranker => ranker.name == name));
    }
}

Sampleスクリプト

RankingクラスのインナークラスとしてRankerクラスが内包されても影響はありません

using System.Collections.Generic;
using UnityEngine;

public class Sample : MonoBehaviour
{
    void Start()
    {
        Ranking ranking = new Ranking();
        ranking.Add("太郎", 70);
        ranking.Add("次郎", 60);
        ranking.Add("三郎", 50);

        ranking.Remove("次郎");

        ranking.Add("四郎", 65);

        List<Ranking.Ranker> rankers = ranking.Rankers;

        foreach (var ranker in rankers)
        {
            Debug.Log($"名前:{ranker.name}  ポイント:{ranker.score}");
        }
    }
}

Rankingクラスが単純にインスタンス化できるとまずい?

例えば、UIの表示の時など様々なところでRinkingが必要になることがあります
その都度、new Ranking()としてしまうと新しいインスタンスが作られ情報を共有することができません

そこで、何回呼ばれても同じインスタンスを戻すようにします

これをシングルトンパターンといいます

シーンの構成

Rankingスクリプト

外部から、new Ranking()でインスタンスを作成するのではなく、GetInstanceメソッドを呼び出してもらいます
戻り値がこのクラスのインスタンスになります

2回目からは1回目で作成されたインスタンスが返されます

追加するコードイメージ

シングルトンを実現するためのコード部分を抜粋しています

// プライベートコンストラクタ
// public 修飾子でないので、他のクラスからnewできません(エラーになります)
Ranking()
{
}

static Ranking instance;

// staticなので、他のクラスからは、クラス名.プロパティ名でアクセスします
// Ranking.GetInstanceで読み取ります
public static Ranking GetInstance
{
    get
    {
        if (instance == null)
        {
            instance = new Ranking();
        }
        return instance;
    }
}

シングルトンを使ったRankingクラス

上記パターンをクラス内に追記します

using System.Collections.Generic;
using System.Linq;

public class Ranking
{
    Ranking()
    {
    }

    static Ranking instance;

    public static Ranking GetInstance
    {
        get
        {
            if (instance == null)
            {
                instance = new Ranking();
            }
            return instance;
        }
    }

    public class Ranker
    {
        public string name;
        public int score;

        public Ranker(string name, int score)
        {
            this.name = name;
            this.score = score;
        }
    }

    List<Ranker> rankers = new List<Ranker>();

    public List<Ranker> Rankers => rankers.OrderByDescending(ranker =>
    {
        return ranker.score;
    }).ToList();

    public void Add(string name, int score)
    {
        Ranker ranker = new Ranker(name, score);
        rankers.Add(ranker);
    }

    public void Remove(string name)
    {
        rankers.Remove(rankers.Find(ranker => ranker.name == name));
    }
}

Sampleスクリプト

作成手順1(基本)

シングルトンで作成されてインスタンスの呼び出しなので、
new Ranking();のところをRanking.GetInstance;に変えています

using UnityEngine;

public class Sample : MonoBehaviour
{
    void Start()
    {
        Ranking ranking = Ranking.GetInstance;
        ranking.Add("太郎", 70);
        ranking.Add("次郎", 60);
        ranking.Add("三郎", 50);

        ranking.Remove("次郎");

        ranking.Add("四郎", 65);

        foreach (var ranker in ranking.Rankers)
        {
            Debug.Log($"名前:{ranker.name}  ポイント:{ranker.score}");
        }
    }
}

作成手順2:新しくスクリプトを作成(インスタンスを取得するクラス(UIを表示するクラスシミュレーション))

これまではコンソールにテスト表示していましたが、UI(Canvas)に表示するようにするためUISampleスクリプトを追加します

using UnityEngine;

public class UISample : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Ranking ranking = Ranking.GetInstance;
            ranking.Add("五郎", 75);
            ranking.Add("六郎", 88);
            ranking.Add("七郎", 20);

            ranking.Remove("七郎");

            ranking.Add("八郎", 10);

            foreach (var ranker in ranking.Rankers)
            {
                Debug.Log($"名前:{ranker.name}  ポイント:{ranker.score}");
            }
        }
    }
}

実行結果の再確認

点数の高い順に並び替えられたのを取得できています

スペースキーを押す前

名前:太郎  ポイント:70
名前:四郎  ポイント:65
名前:三郎  ポイント:50

スペースキーを押した後(コンソールウィンドウでClearボタンをクリックすると上記は消えるので見やすいです)

名前:六郎  ポイント:88
名前:五郎  ポイント:75
名前:太郎  ポイント:70
名前:四郎  ポイント:65
名前:三郎  ポイント:50
名前:八郎  ポイント:10

テスト結果を確認するためのブレークポイント

Macでのブレークポイント設定ですが、基本Windowsでも同じです

Unity

Posted by hidepon