【Unity】#1 Unityで、拾ったアイテムをアイテム欄に追加する処理。
見出し。
1.Unityのバージョン
今回使用したUnityのバージョンは2019.1.5f1 Personalです。
2.前置き。
ブログ2日目です。絵を描く機能、いいですね。個人的にめっちゃ気に入りました。今後、毎回書いていくと思います。
今回はUnityで「拾ったアイテムをアイテム欄に追加し、選択できるようにする」処理を実装します。
実装したいなぁって思ってから約1年ほど悩み続けていました。何やってんだか。
一応、この記事で紹介する内容で出来ること・できないことを挙げておきます。
簡単に言えば、ドラ○エの、アイテムの手持ち欄を作る感覚です。ふくろのアイテム欄まではできていません。
3.出来ること。
・アイテムを拾ったら、それをアイテム欄に追加する
・アイテム欄に追加されたアイテムを選択して使用する
4.出来ないこと。
・アイテム欄に「ページ」のような機能を付ける
5.実装方法。
- とりあえずプレイヤーが動けるようにする。
- とりあえずアイテム欄を作る。
b-1. UIの情報を更新するスクリプト。 - アイテムの情報を作る。
c-1. ScriptableObjectって何さ?
c-2. じゃあ、使ってみよー
c-3. 軽く説明。
c-4. じゃあアイテムを作ってみよー
c-5. アイテムのデータベースを作る。
c-6. アイテムの数を増減させる。 - アイテムに当たり判定を付け、拾えるようにする。
- 作ったスクリプトを指定のオブジェクトに付ける。
という感じです。では早速取り掛かっていきましょー
a.とりあえずプレイヤーが動けるようにする。
これを作らないと「アイテムを取得する」っていうイベントを発生させられないので、適当に作ります。
もう作ってあるのならば、この工程は必要ないです。
PlayerControllerスクリプトを作ります。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { public float speed; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { Move(); } private void Move() { if (Input.GetKey(KeyCode.W)) { transform.position += Vector3.forward * speed; } if (Input.GetKey(KeyCode.A)) { transform.position += Vector3.left * speed; } if (Input.GetKey(KeyCode.S)) { transform.position += Vector3.back * speed; } if (Input.GetKey(KeyCode.D)) { transform.position += Vector3.right * speed; } } }
あとは、適当にオブジェクトを配置(CubeをPlayerとしました)。
PlayerにPlayerControllerを付けて、speedを0.1くらいに設定。
プレイヤーはこれで終了。アイテム欄を作ります。
b.とりあえずアイテム欄を作る。
アイテム欄がないと、アイテムを作っても表示できません。ってことで適当に作ります。
こんな感じです。次に、このUIの情報を更新するスクリプトを書きます。
b-1.UIの情報を更新するスクリプト。
スクリプトから、ウィンドウを非表示にするときに楽なように、CreateEmptyして、名前をItem_windowとしてさっき作ったUIを全てそれの子にします。
では、スクリプトを作ります。これは、アイテム欄を開くスクリプトになります。コメントアウトしていますが、コメントアウト部分は「アイテムを拾った時にアイテム欄にアイテムの名前と所持数を追加する」という処理です。
名前はUIManagerです。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { //private ItemManager itemManager; //メニューウィンドウ [SerializeField] GameObject item_window; [SerializeField] GameObject item_window_cursor; //矢印. //アイテム欄表示時に表示されるテキスト群 [SerializeField] Text item_list_text; [SerializeField] Text item_name_text; [SerializeField] Text item_description_text; [SerializeField] Text item_num_text; int item_select_num = 0; //選択しているアイテムの場所. void Start() { //itemManager = GameObject.Find("GameManager").GetComponent<ItemManager>(); Update_item_list(); item_window.SetActive(false); } void Update() { Item_window_control(); Item_window_cursor_control(); } private void Item_window_control() { //item_windowが非アクティブの時にSpaceキーを押したとき、item_windowをアクティブに. if (Input.GetKeyDown(KeyCode.Space) && item_window.activeSelf == false) { item_window.SetActive(true); item_select_num = 0; } //item_windowがアクティブの時にQキーを押したとき、item_windowを非アクティブに. if (Input.GetKeyDown(KeyCode.Q) && item_window.activeSelf == true) { item_window.SetActive(false); } } private void Item_window_cursor_control() { /* //item_windowが非アクティブの時はこの後の処理を実行しない. if (item_window.activeSelf == false) return; //取得しているアイテムの数が0この場合も以降の処理をしない. if (itemManager.Get_item_list_count() <= 0) return; string now_select_item_name = itemManager.Get_item_name(item_select_num); if (Input.GetKeyDown(KeyCode.UpArrow)) { item_select_num -= 1; } if (Input.GetKeyDown(KeyCode.DownArrow)) { item_select_num += 1; } //Enterキーを押したとき. if (Input.GetKeyDown(KeyCode.Return)) { if (itemManager.Item_property(now_select_item_name).Get_item_type() == Item.ITEM_TYPE.Heal_hp) { print(now_select_item_name + "を使った!"); print("HPが" + itemManager.Item_property(now_select_item_name).Get_heal_hp_value() + "回復!"); itemManager.Subtract_item_num(now_select_item_name); } if (itemManager.Item_property(now_select_item_name).Get_item_type() == Item.ITEM_TYPE.Heal_food) { print(now_select_item_name + "を使った!"); print("食料値が" + itemManager.Item_property(now_select_item_name).Get_heal_food_value() + "回復"); itemManager.Subtract_item_num(now_select_item_name); } item_window.SetActive(false); return; } //カーソルを戻す処理. if (item_select_num < 0) item_select_num = itemManager.Get_item_list_count() - 1; if (item_select_num > 10 || item_select_num > itemManager.Get_item_list_count() - 1) item_select_num = 0; //今カーソルがさしているアイテムの説明と名前を持ってくる. item_description_text.text = itemManager.Get_item_infomation(item_select_num); item_name_text.text = itemManager.Get_item_name(item_select_num); item_window_cursor.transform.localPosition = new Vector3(0f, 129f - 29f * item_select_num); */ } /// <summary> /// アイテム欄の情報を更新する /// </summary> public void Update_item_list() { /* item_list_text.text = ""; item_num_text.text = ""; item_name_text.text = ""; item_description_text.text = ""; int list_count = itemManager.Get_item_list_count(); for (int i = 0; i < list_count; i++) { item_list_text.text += itemManager.Get_item_name(i) + "\n"; item_num_text.text += itemManager.Get_item_num(i) + "\n"; } */ } }
コメントアウトは「まだ」外さないでください。今外すとエラーが出ます。
今作ったこのUIManagerスクリプトを、Canvasに付けます。
代入する箇所はこんな感じ。
実行すると、「Spaceキーを押すとアイテム欄が開く」、「Qキーを押すとアイテム欄が閉じる」というところまで実装できています。
c.アイテムの情報を作る。
ScriptableObjectというものを使えば、わざわざExcelなどからデータを持ってこなくても簡単にアイテムの情報を作ることが出来ます!この機能やばい
c-1.ScriptableObjectって何さ?
クラスに継承して使います(クラス名 : MonoBehaviour の、MonoBehaviourをScriptableObjectというものに書き換えて使います)。
基本的にはステータスなどを管理することに使われるのが一般的っぽい?素人なんでわからないっす
c-2.じゃあ、使ってみよー
何はともあれ、とりあえず使ってみましょう。以下のようなスクリプトを書きます。
クラス名はItemとしました。
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(fileName = "Item", menuName = "CreateItem")] public class Item : ScriptableObject { public enum ITEM_TYPE { Heal_hp, //HPを回復する Heal_food, //食料値を回復する } [SerializeField] ITEM_TYPE item_type; [SerializeField] string item_name; [SerializeField] string item_description; [SerializeField] int heal_hp_value; [SerializeField] int heal_food_value; public string Get_item_description() { return item_description; } public string Get_item_name() { return item_name; } public ITEM_TYPE Get_item_type() { return item_type; } public int Get_heal_hp_value() { return heal_hp_value; } public int Get_heal_food_value() { return heal_food_value; } }
c-3.軽く説明。
一応先にちらっと説明しましたが、もう少しだけ解説。
[CreateAssetMenu(fileName = "Item", menuName = "CreateItem")]は、fileNameでデフォルトの名前を、menuNameでCreateメニューで表示される名前を設定しています。
ローグライクを作る時に作ったスクリプトの一部を使っているので、Heal_HPという種類のアイテムはHPを、Heal_foodという種類のアイテムは食料値を回復するっていうシステムです。ほかのアイテムを作りたい場合はITEM_TYPEの種類を増やしたり変数を増やしたりしてください。
c-4.じゃあアイテムを作ってみよー
Assets内で右クリックして、Create内を見てみましょう。
なんか増えています。これがアイテムのデータとなります。選択してみましょう。
色々設定できそうな画面が出てきます。値を入れましょう。
とりあえずこんな感じにしました。
値を設定する画面で、せっかくアイテムの種類を設定するのに、例えばHeal_HPの種類を選んだのにHeal_food_valueがあるのはキモチワルイ!という人は、inspector拡張というものを行わなければなりません。今回は解説しませんが、近いうちに解説するかも。
c-5.アイテムのデータベースを作る。
アイテムを設定したら、次はアイテムを全てまとめたものを作ります。スクリプトで、ここからアイテムの情報を引っ張ってくるっていう寸法です。
ItemDataBaseクラスを作ります。
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(fileName = "ItemDataBase", menuName = "CreateItemDataBase")] public class ItemDataBase : ScriptableObject { [SerializeField] List<Item> Items = new List<Item>(); public List<Item> Get_item_list() { return Items; } }
先程と同じく、Create内に新しくItemDataBaseが作られています。
こんな感じで設定。
次は、アイテムの数を増やしたり減らしたりする処理を書きます。
c-6.アイテムの数を増減させる。
具体的に言えば、新しく「所持しているアイテムを管理する」というクラスを作ります。
ItemManagerクラスを作ります。
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// アイテムを取得したときの操作 /// </summary> public class ItemManager : MonoBehaviour { [SerializeField] private ItemDataBase ItemDataBase; //アイテムの全データ private UIManager uiManager; [SerializeField] private List<Item_infomations> item_Infomations = new List<Item_infomations>(); // Start is called before the first frame update void Start() { uiManager = GameObject.Find("Canvas").GetComponent<UIManager>(); } // Update is called once per frame void Update() { } /// <summary> /// Listにアイテムを追加する /// </summary> /// <param name="item_name"></param> public void Add_item_to_list(string item_name, string item_info) { //もしすでに所持している場合は、アイテムの数を増やして終了 if (Is_have_item(item_name)) { Add_item_num(item_name); return; } item_Infomations.Add(new Item_infomations(item_name, 1, item_info)); uiManager.Update_item_list(); } /// <summary> /// 既にアイテムを持っているか(持っている場合はtrue) /// </summary> /// <param name="item_name"></param> /// <returns></returns> public bool Is_have_item(string item_name) { return item_Infomations.Exists(item => item.Get_item_name() == item_name); } /// <summary> /// item_nameで指定したアイテムの数を増やす /// </summary> /// <param name="item_name"></param> private void Add_item_num(string item_name) { //アイテムの名前を検索し、数を増やす Find_item(item_name).Add_item_num(); uiManager.Update_item_list(); } /// <summary> /// item_nameで指定したアイテムの数を増やす /// </summary> /// <param name="item_name"></param> private void Add_item_num(string item_name, int add_num) { //アイテムの名前を検索し、数を増やす Find_item(item_name).Add_item_num(add_num); uiManager.Update_item_list(); } /// <summary> /// item_nameで指定したアイテムの数を減らす /// </summary> /// <param name="item_name"></param> public void Subtract_item_num(string item_name) { Item_infomations item_info = Find_item(item_name); //アイテムの名前を検索し、アイテムの数を減らす item_info.Subtract_item_num(); if (item_info.Get_item_num() <= 0) item_Infomations.Remove(item_info); uiManager.Update_item_list(); } /// <summary> /// item_nameで指定したアイテムの数をsubtract_num分減らす /// </summary> /// <param name="item_name"></param> /// <param name="subtract_num"></param> public void Subtract_item_num(string item_name, int subtract_num) { Item_infomations item_info = Find_item(item_name); //アイテムの名前を検索し、アイテムの数をsubtract_num分減らす item_info.Subtract_item_num(subtract_num); if (item_info.Get_item_num() <= 0) item_Infomations.Remove(item_info); uiManager.Update_item_list(); } /// <summary> /// アイテムを、item_nameで検索する /// </summary> /// <param name="item_name"></param> /// <returns></returns> private Item_infomations Find_item(string item_name) { return item_Infomations.Find(item => item.Get_item_name() == item_name); } /// <summary> /// リスト内のindex番目のアイテム名を取得 /// </summary> /// <param name="index"></param> /// <returns></returns> public string Get_item_name(int index) { return item_Infomations[index].Get_item_name(); } /// <summary> /// リスト内のindex番目のアイテム数を取得 /// </summary> /// <param name="index"></param> /// <returns></returns> public string Get_item_num(int index) { return item_Infomations[index].Get_item_num().ToString(); } /// <summary> /// リスト内のindex番目のアイテムの説明を取得 /// </summary> /// <param name="index"></param> /// <returns></returns> public string Get_item_infomation(int index) { return item_Infomations[index].Get_item_infomation(); } public int Get_item_list_count() { return item_Infomations.Count; } /// <summary> /// アイテムを検索する /// </summary> /// <param name="item_name"></param> /// <returns></returns> public Item Item_property(string item_name) { return ItemDataBase.Get_item_list().Find(name_item => name_item.Get_item_name() == item_name); } } [System.Serializable] public class Item_infomations { [SerializeField] private string item_name; [SerializeField] private int item_num; [SerializeField] private string item_infomation; /// <summary> /// このクラスをリスト化して管理することで、アイテム欄を実装する /// </summary> /// <param name="name"></param> /// <param name="num"></param> /// <param name="item_info"></param> public Item_infomations(string name, int num, string item_info) { item_name = name; item_num = num; item_infomation = item_info; } /// <summary> /// itemの名前のゲッター /// </summary> /// <returns></returns> public string Get_item_name() { return item_name; } /// <summary> /// itemの数のゲッター /// </summary> /// <returns></returns> public int Get_item_num() { return item_num; } /// <summary> /// itemの数のセッター /// </summary> public void Add_item_num() { item_num += 1; } /// <summary> /// itemの数のセッター /// </summary> public void Add_item_num(int add_num) { item_num += add_num; } /// <summary> /// itemの数のセッター /// </summary> public void Subtract_item_num() { item_num -= 1; } /// <summary> /// itemの数のセッター /// </summary> public void Subtract_item_num(int subtract_num) { item_num -= subtract_num; } /// <summary> /// アイテムの説明を返す /// </summary> /// <returns></returns> public string Get_item_infomation() { return item_infomation; } }
プログラムが長い(きたない)ので全部は解説できませんが、Item_infomationsという、アイテムの数や説明などの情報を持つクラスを定義し、それをリスト化することで、リスト内検索で見つからなかったら「所持していない」、見つかれば「所持している」という処理を行っています。
名前でアイテムを持っているか?を検索できるのでいいかなーと。長いけど...
Item_infomarionを管理するクラスがItemManagerです。ここでアイテムを消費したり増やしたりする処理を行います。
d.アイテムに当たり判定を付け、拾えるようにする。
アイテムに当たった時に、先程作ったItemManager内の所持アイテムのリストであるitem_informationsにアクセスし、増やすという処理を書きます。
このスクリプトは取得されるアイテム側に付けます。
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// アイテムを拾った時の挙動(アイテム側につける) /// </summary> [RequireComponent(typeof(Rigidbody))] public class GetItemManager : MonoBehaviour { public Item item; //何を入手するのか private ItemManager itemManager; // Start is called before the first frame update void Start() { itemManager = GameObject.Find("GameManager").GetComponent<ItemManager>(); } private void OnTriggerEnter(Collider collision) { if (!(collision.gameObject.name == "Player")) return; itemManager.Add_item_to_list(item.Get_item_name(), item.Get_item_description()); print(item.Get_item_name() + "を拾った"); Destroy(this.gameObject); } }
次に、アイテムを作ります。今回はモデルをインポートしていないので、Sphereで代用します。
名前をItemとし、これにGetItemManagerを付けます。
※Is Triggerや、Is Kinematicのチェックを忘れないように!!※
e.作ったスクリプトを指定のオブジェクトに付ける。
あとはオブジェクトに残りのスクリプトを付けるだけです!!
GameManagerオブジェクトを作り(Create Emptyで)、ItemManagerスクリプトを付けてください。
最後に、UIManagerを開き、コメントアウトをすべて解除します。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { private ItemManager itemManager; //メニューウィンドウ [SerializeField] GameObject item_window; [SerializeField] GameObject item_window_cursor; //矢印. //アイテム欄表示時に表示されるテキスト群 [SerializeField] Text item_list_text; [SerializeField] Text item_name_text; [SerializeField] Text item_description_text; [SerializeField] Text item_num_text; int item_select_num = 0; //選択しているアイテムの場所. void Start() { itemManager = GameObject.Find("GameManager").GetComponent<ItemManager>(); Update_item_list(); item_window.SetActive(false); } void Update() { Item_window_control(); Item_window_cursor_control(); } private void Item_window_control() { //item_windowが非アクティブの時にSpaceキーを押したとき、item_windowをアクティブに. if (Input.GetKeyDown(KeyCode.Space) && item_window.activeSelf == false) { item_window.SetActive(true); item_select_num = 0; } //item_windowがアクティブの時にQキーを押したとき、item_windowを非アクティブに. if (Input.GetKeyDown(KeyCode.Q) && item_window.activeSelf == true) { item_window.SetActive(false); } } private void Item_window_cursor_control() { //item_windowが非アクティブの時はこの後の処理を実行しない. if (item_window.activeSelf == false) return; //取得しているアイテムの数が0この場合も以降の処理をしない. if (itemManager.Get_item_list_count() <= 0) return; string now_select_item_name = itemManager.Get_item_name(item_select_num); if (Input.GetKeyDown(KeyCode.UpArrow)) { item_select_num -= 1; } if (Input.GetKeyDown(KeyCode.DownArrow)) { item_select_num += 1; } //Enterキーを押したとき. if (Input.GetKeyDown(KeyCode.Return)) { if (itemManager.Item_property(now_select_item_name).Get_item_type() == Item.ITEM_TYPE.Heal_hp) { print(now_select_item_name + "を使った!"); print("HPが" + itemManager.Item_property(now_select_item_name).Get_heal_hp_value() + "回復!"); itemManager.Subtract_item_num(now_select_item_name); } if (itemManager.Item_property(now_select_item_name).Get_item_type() == Item.ITEM_TYPE.Heal_food) { print(now_select_item_name + "を使った!"); print("食料値が" + itemManager.Item_property(now_select_item_name).Get_heal_food_value() + "回復"); itemManager.Subtract_item_num(now_select_item_name); } item_window.SetActive(false); return; } //カーソルを戻す処理. if (item_select_num < 0) item_select_num = itemManager.Get_item_list_count() - 1; if (item_select_num > 10 || item_select_num > itemManager.Get_item_list_count() - 1) item_select_num = 0; //今カーソルがさしているアイテムの説明と名前を持ってくる. item_description_text.text = itemManager.Get_item_infomation(item_select_num); item_name_text.text = itemManager.Get_item_name(item_select_num); item_window_cursor.transform.localPosition = new Vector3(0f, 129f - 29f * item_select_num); } /// <summary> /// アイテム欄の情報を更新する /// </summary> public void Update_item_list() { item_list_text.text = ""; item_num_text.text = ""; item_name_text.text = ""; item_description_text.text = ""; int list_count = itemManager.Get_item_list_count(); for (int i = 0; i < list_count; i++) { item_list_text.text += itemManager.Get_item_name(i) + "\n"; item_num_text.text += itemManager.Get_item_num(i) + "\n"; } } }
そして、実行して、Itemに当たってみてください。これでアイテムが取得出来て、使用もできるはず!(WASDキー:移動、Spaceキー:アイテム欄を開く、Qキー:アイテム欄を閉じる、Enterキー:アイテムを使用する、↑↓キー:アイテムを選択する)
6.注意点。
アイテムを新しく作った時は、忘れずにItemDataBaseに追加してください!Null Reference Exceptionが出ます。
ItemManagerを取得する際にGameManagerオブジェクトを名前で検索しているので、GameManagerの名前は変えないでください。
(変える場合はプログラムを変更してください)
7.まとめとか。
プログラム汚いくせに公開するとかやべーんじゃないの?って思いました。すいません。長いし。
今投稿文字数を見たら(プログラム含めて)2万文字こえてました。これブログって言っていいの???
このタイプのアイテム欄の実装はあまり見当たらなかったので、もし「探していた」という方がいらっしゃったら幸いです。次のページいけないのがネック...
おまけ:ミスって3回同じ絵を保存してました。↓
以上です。お疲れさまでした。