数が変わるデータを扱おう:List【Unityスタートガイド6-13】

手に入れたアイテム、画面に出ている敵、クリアしたステージ・・・。ゲームには「いくつになるか最初は分からない」データがたくさんあります。6-5の記事で紹介した配列は、最初に決めた数から増やしたり減らしたり出来ません。今回は、あとから自由に増やしたり減らしたり出来るList(リスト)をご紹介します!

目次

この記事で学べること

  • 配列とListの違い
  • Listの作り方と、足す・消す・数える・取り出すの基本
  • foreach文とfor文でListを順番に処理する方法
  • 自分で作ったクラスをListに入れる方法(Inspectorで編集もできる)
  • JsonUtilityで保存するときの注意(Dictionaryは保存できない)

前提知識

配列とListの違い

配列(6-5の記事)List
書き方string[] namesList<string> names
数作るときに決めて、あとから変えられないあとから自由に増やしたり減らしたり出来る
数を調べるnames.Lengthnames.Count
使いどころステージ数、曜日など、数が決まっているもの持ち物、出ている敵、ログなど、数が変わるもの

「数が決まっているなら配列、変わるならList」と覚えておけばOKです。

Listの基本

Listを作る

using System.Collections.Generic;
using UnityEngine;

// List の基本(足す・消す・数える・取り出す)
public class ListBasics : MonoBehaviour
{
    private List<string> items = new List<string>();

    private void Start()
    {
        items.Add("やくそう");      // 最後に足す
        items.Add("どくけしそう");
        items.Add("やくそう");
        Debug.Log($"持ち物は{items.Count}個");            // 3

        Debug.Log($"1番目は{items[0]}");                  // やくそう(番号は0から)

        bool hasAntidote = items.Contains("どくけしそう"); // 入っているか
        Debug.Log($"どくけしそうを持っている:{hasAntidote}"); // True

        items.Remove("やくそう");   // 最初に見つかった1つを消す
        Debug.Log($"やくそうを使った。持ち物は{items.Count}個"); // 2

        items.RemoveAt(0);          // 0番目を消す
        items.Clear();              // 全部消す
        Debug.Log($"全部なくなった。持ち物は{items.Count}個");   // 0
    }
}
  • using System.Collections.Generic;
    • ListはC#の機能なので、一番上にこの1行が必要
  • List
    • < >の中に、入れるものの型を書く。List<int>なら整数のリスト、List<GameObject>ならGameObjectのリスト
  • new List()
    • 空のリストを作る。newを忘れると、使ったときにNullReferenceExceptionになる
書き方意味
Add(値)最後に足す
Insert(番号, 値)番号の位置に割り込ませる
Remove(値)最初に見つかった1つを消す(見つかればtrue)
RemoveAt(番号)番号の位置のものを消す
Clear()全部消す
Count入っている数
リスト[番号]番号の位置のものを取り出す・書き換える(0から)
Contains(値)入っていればtrue
IndexOf(値)何番目にあるか(なければ-1)

順番に取り出す(foreach・for)

配列と同じように、foreach文で全部を順番に取り出せます。

foreach (string item in items)
{
    Debug.Log(item);
}

番号も使いたいときは、for文でCountまで回します。

for (int i = 0; i < items.Count; i++)
{
    Debug.Log($"{i}番目:{items[i]}");
}
スライム

配列はLength、ListはCount。ここだけ名前が違うので、よく間違えます・・・

自分で作ったクラスをListに入れる

「アイテムの名前」と「持っている数」のように、1つの項目に情報がいくつかあるときは、クラスを作ってListに入れます。

using System;
using System.Collections.Generic;
using UnityEngine;

// 持ち物の一覧。「アイテムの名前」と「持っている数」を1組にして List に入れる
public class Inventory : MonoBehaviour
{
    [Serializable]
    public class Slot
    {
        public string itemName; // アイテムの名前
        public int count;       // 持っている数
    }

    [SerializeField] private List<Slot> slots = new List<Slot>();

    private void Start()
    {
        AddItem("やくそう", 3);
        AddItem("どくけしそう", 1);
        AddItem("やくそう", 2); // すでに持っているので、数が増える
        UseItem("どくけしそう"); // 0個になったら一覧から消える

        foreach (Slot slot in slots)
        {
            Debug.Log($"{slot.itemName}:{slot.count}個"); // やくそう:5個
        }
    }

    public void AddItem(string itemName, int amount)
    {
        Slot slot = FindSlot(itemName);
        if (slot == null)
        {
            // まだ持っていないアイテムなら、新しく足す
            slot = new Slot { itemName = itemName, count = 0 };
            slots.Add(slot);
        }
        slot.count += amount;
    }

    public bool UseItem(string itemName)
    {
        Slot slot = FindSlot(itemName);
        if (slot == null)
        {
            return false; // 持っていない
        }

        slot.count--;
        if (slot.count <= 0)
        {
            slots.Remove(slot); // 0個になったら一覧から消す
        }
        return true;
    }

    // 名前が同じ Slot を探す(なければ null)
    private Slot FindSlot(string itemName)
    {
        foreach (Slot slot in slots)
        {
            if (slot.itemName == itemName)
            {
                return slot;
            }
        }
        return null;
    }
}
  • [Serializable]を付けたクラス
    • [SerializeField] private List<Slot>にすると、Inspectorで中身が見える・編集できる
    • JsonUtilityで保存するときにも必要
  • FindSlot
    • foreachで1つずつ見て、名前が同じものを返す。見つからなければnull
  • AddItem
    • 持っていなければ新しいSlotを足し、持っていれば数を増やす
  • UseItem
    • 数を減らして、0個になったらRemoveで一覧から消す

InspectorのListは、右下の「+」「−」で項目を増やしたり減らしたりも出来ます。最初から持たせておくアイテムを、Inspectorで設定しておくことも出来ます。

JsonUtilityで保存するときの注意

12-6 データを保存しようで紹介したJsonUtilityは、Listを中身ごと保存できます。

[Serializable]
public class SaveData
{
    public List<Inventory.Slot> slots = new List<Inventory.Slot>();
}
{
    "slots": [
        { "itemName": "やくそう", "count": 5 }
    ]
}

C#には「名前から値を引ける」Dictionaryという便利なものもありますが、JsonUtilityはDictionaryを保存できません。保存したいときは、今回のSlotのように「名前と数を1組にしたクラス」のListにしておくのがオススメです。

フェアリー

「名前と数のセットをListに入れる」形は、持ち物・ショップ・図鑑など、いろんなところで使えるよ!

よくあるエラーと対処法

エラー:ArgumentOutOfRangeException: Index was out of range.

  • 原因:items[3]のように、入っている数より大きい番号を指定した(3個なら最後は[2])
  • 対処法:番号は0からCount - 1まで。for文はi < items.Countにする

エラー:NullReferenceException

  • 原因:new List<string>()を書き忘れて、Listが作られていない
  • 対処法:変数を作るときに= new List<string>()を付ける

エラー:InvalidOperationException: Collection was modified; enumeration operation may not execute.

  • 原因:foreach文で回している途中に、同じListでAddやRemoveをした
  • 対処法:消したいものを探すだけにして、foreach文が終わってから消す。または、for文で後ろから回してRemoveAtする
// 0個のものを全部消す(後ろから回すと、消しても番号がずれない)
for (int i = slots.Count - 1; i >= 0; i--)
{
    if (slots[i].count <= 0)
    {
        slots.RemoveAt(i);
    }
}

エラー:The type or namespace name ‘List<>’ could not be found

  • 原因:using System.Collections.Generic;を書いていない
  • 対処法:スクリプトの一番上に足す

まとめ

  • 数が決まっているなら配列、あとから増えたり減ったりするならList
  • using System.Collections.Generic;とnew List<型>()を忘れずに
  • Addで足す、Remove・RemoveAtで消す、Countで数える、[番号]で取り出す
  • 1つの項目に情報がいくつかあるときは、[Serializable]のクラスを作ってListに入れる
  • JsonUtilityはListを保存できるが、Dictionaryは保存できない

次のステップ

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次