【Unity】アイテムを拾って管理する

投稿者: | 2020-12-21

拾ったアイテムをインベントリに置いて管理してみました。

まずこちらのサイトを参考にアイテムデータベースを作りました。

アイテム管理クラスでは、スタートでシーン上のインスタンスを静的フィールドに入れて、静的メソッドでそれを返すようにしました。また、アイテムがデータベースに存在するか調べるメソッドも作りました。

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

public class ItemManager : MonoBehaviour
{
    static ItemManager instance;

    public static ItemManager GetInstance()
    {
        return instance;
    }

    // ...

    // Use this for initialization
    void Start()
    {
        instance = this;

        // ...
    }

    // ...

    public bool HasItem(string searchName)
    {
        return itemDataBase.GetItemLists().Exists(item => item.GetItemName() == searchName);
    }
}

次に取得したアイテムの画像を表示するスクロールビューを作りました。Contentの子として取得したアイテムの画像が設定されるImageオブジェクトを作ります。

ContentにGrid Layout Groupコンポーネントを追加すると綺麗に配置されます。Spacingを少し上げてImage同士に隙間を作りました。

このImageオブジェクトをプレハブ化して、シーン上のものは削除します。空のゲームオブジェクトを作って取得したアイテムを管理するスクリプトを新規作成し、シーン上のContentとProjectウィンドウのImageのプレハブをアタッチします。

アイテムを取得したいときは、まず取得可能クラスのオブジェクトをそのアイテムのクラスに持たせて、取得可能クラスのメソッドを呼んでいます。

// アイテムのクラス
using UnityEngine;

public class ObtainableItem : MonoBehaviour, ITouchable
{
    [SerializeField] Obtainable obt; // 取得可能クラスのオブジェクト

    // アイテムに触った時
    public void Touch(GameObject sender, Vector3 point)
    {
        obt.Obtain(gameObject); // 取得メソッドを呼ぶ
    }

    public void Release(GameObject sender)
    {

    }

}
// 取得可能クラス
using UnityEngine;
using System;

[Serializable]
public class Obtainable
{
    [SerializeField] string itemName;
    GameObject gameObject;

    internal void Obtain(GameObject item)
    {
        gameObject = item;
        Inventory.GetInstance().Obtain(this);
        
    }

    internal string GetItemName()
    {
        return itemName;
    }

    internal GameObject GetGameObject()
    {
        return gameObject;
    }
}

取得可能クラスには[Serializable]属性が付いているので、アイテムクラスを付けたプレハブのインスペクタで取得可能クラスで宣言されているフィールドの値を変えられます。

アイテムクラスが複数あっても、この取得可能クラスを書き換えれば取得方法を変えられます。

取得メソッドでは、取得可能クラスのフィールドにアイテムのゲームオブジェクトを入れて、取得したアイテムを管理するクラスのメソッドを呼びます。このときに自分自身を引数に渡しています。

取得したアイテムを管理するクラスではインスタンスを取得する静的メソッドと、アイテムを取得するメソッドが定義されています。

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

public class Inventory : MonoBehaviour
{
    [SerializeField] Transform content;
    [SerializeField] GameObject imagePrefab;
    List<Goods> inventory = new List<Goods>();

    static Inventory instance;
    public static Inventory GetInstance()
    {
        return instance;
    }

    // アイテムを取得するメソッド
    public void Obtain(Obtainable item)
    {
        // アイテムの存在を確認
        if (ItemManager.GetInstance().HasItem(item.GetItemName()))
        {
            GameObject goodsObj = Instantiate(imagePrefab, content); // Imageインスタンスを作る
            Goods goods = goodsObj.GetComponent<Goods>(); // スクリプトを取得
            goods.SetUp(item); // 画像などを設定
            item.GetGameObject().SetActive(false); // アイテムを非アクティブにする
            inventory.Add(goods); // リストに入れる

            TextManager.GetInstance().SetText(item.GetItemName() + "を取得した", 2);
        }
        else
        {
            TextManager.GetInstance().SetText("アイテム名が無効", 2);
        }
    }
    private void Awake()
    {
        instance = this;
    }

}

この取得メソッドに渡されたオブジェクトにはアイテム名とアイテムのゲームオブジェクトのGetメソッドがあるので、アイテム管理のメソッドにアイテム名を渡して、アイテムがデータベースに存在するかを調べられます。

あれば、Contentの子として、Imageオブジェクトを生成します。そして、このImageオブジェクトにつけるスクリプトを取得して、それをリストに入れて置きました。

Imageオブジェクトのスクリプトのクラスでは、アイテム名でデータベースを検索し、アイテムの画像を取得してImageコンポーネントに設定します。そして、アイテム名とアイテムのゲームオブジェクトを記憶しておきました。

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

public class Goods :MonoBehaviour
{
    GameObject itemObject;
    string itemName;
    Image image;
       
    private void Awake()
    {
        image = GetComponent<Image>();
    }

    public void SetUp(Obtainable item)
    {
        image = GetComponent<Image>(); // Imageコンポーネント

        this.itemName = item.GetItemName(); // アイテム名を取得

        // 画像を取得してImageコンポーネントに入れる
        image.sprite = ItemManager.GetInstance().GetItem(this.itemName).GetIcon(); 
        this.itemObject = item.GetGameObject(); // オブジェクトを取得
    }
    
}

これで拾ったアイテムをインベントリに置いて管理できました。掴めるアイテムも掴めないアイテムも同じ様に取得できます。

【Unity】アイテムを拾って管理する」への14件のフィードバック

    1. nekoromorph 投稿作成者

      スクリーンショット画像をスクリプタブルオブジェクトのインスタンスにアタッチして、
      そのインスタンスのメソッドを呼んでいます。

      using UnityEngine;
      using System;
      
      [Serializable]
      [CreateAssetMenu(fileName = "Item", menuName = "CreateItem")]
      public class Item : ScriptableObject
      {
          [SerializeField] string itemName; // アイテムの名前
          [SerializeField] Sprite icon; // アイテムの画像
          
          // ...
      
          public string GetItemName()
          {
              return itemName;
          }
      
          // アイコン画像を取得する
          public Sprite GetIcon()
          {
              return icon;
          }
      
          // ...
      
      }
      返信
  1. 匿名

    「ITouchable」の部分でエラーが起きてしまいます。
    正しく打ってるはずなのですがなぜなのか教えて下さい
    コンパイルエラーはcs0246です

    返信
      1. 匿名

        定義したら今度はcs0535が出てきました
        どこが定義されてないかわかりません

        返信
        1. nekoromorph 投稿作成者

          インターフェースを継承したクラスで、インターフェースで宣言したメソッドを実装すると大丈夫かと思います。

          public interface ITouchable
          {
              void Touch(GameObject sender, Vector3 point);
              void Release(GameObject sender);
          }
          
          // インターフェースを継承したクラス
          public class Class1 : MonoBehaviour, ITouchable
          {
              
              // メソッドを実装
              public void Touch(GameObject sender, Vector3 point)
              {
                  
              } 
          
              // メソッドを実装
              public void Release(GameObject sender)
              {
          
              }
          }

          わかりにくくて申し訳ございません。m(__)m

          返信
  2. aiuo

    goodsクラスの24行目に関してなんですが、ItemManagerにGetItemという関数は定義されていないと思うのですが、GetItemの内容を教えていただけますでしょうか。よろしくお願いします。

    返信
  3. 匿名

    始めのアイテム管理クラスは参考にされたサイトのItemManagerを使用していない別のものですか?

    返信
  4. 匿名

    アイテムを取得する際、Rayを使用してますか?
    それとも別の記事のスクリプトを使用されていますか?

    返信
  5. aiuo

    同じアイテムを拾った際に×個数と表示させようとしているのですが、Inventoryクラスのinventoryリストを使ってリスト内に入っているgoodsがあれば、×個数と表示させるためにif分を使ったり、containsメソッドをつかったりしているのですがうまくいきません。大雑把でもいいのでこんな風にやれば、できるかもしれないなどのヒントがあればぜひ教えてほしいです( ノД`)。

    返信
    1. nekoromorph 投稿作成者

      LinqのWhereメソッドを使って条件に合った要素をカウントできます。

      using System.Collections.Generic;
      using UnityEngine;
      using System.Linq;
      
      public class LinqTest : MonoBehaviour
      {
          // Start is called before the first frame update
          void Start()
          {
              var list = new List() { 0, 0, 1, 2, 2, 2, 3, 3 };
      
              Debug.Log(list.Count(x => x == 0)); // 2
          }
      }

      参考:https://csharp.sql55.com/cookbook/csharp-how-to-count-number-of-objects-in-list-satisfying-a-condition.php

      返信

aiuo へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。