//tips
//時間経過でのアイテム配布
時間経過でアイテムをランダムに配布する仕組みを考える。
まず、ゲーム開始からの時間をTimeを使用して、画面に経過時間を表示する。
Time.timeはゲーム開始から経過した時間を簡単に出力できるが、読み取り専用となる。一方で、Time.deltaTimeを使用すると計測開始、計測停止位置を指定できるという違いがある。
今回はTime.deltaTimeを使用する。画面に時:分:秒で表示する下記スクリプトをcanvas下に作ったテキストにアタッチ。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TimerText : MonoBehaviour
{
private Text timerText;
private float second;
private int minute;
private int hour;
void Start()
{
timerText = GetComponent<Text>();
}
void Update()
{
second += Time.deltaTime;
if (minute > 60)
{
hour++;
minute = 0;
}
if (second > 60f)
{
minute += 1;
second = 0;
}
timerText.text = hour.ToString() + ":" + minute.ToString("00") + ":" + second.ToString("f2");
}
}
この表示を元にし、経過時間で別のアクションを実行するスクリプトを作成した。
Invokeがシンプルなので、invokeで実行した場合の時間がdeltatimeで表示する時間とどの程度差が出ているのかを確認する。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TimeController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Invoke("Hoge", 10);
}
void Hoge()
{
Debug.Log("10秒後に実行された");
}
}
このスクリプトを実行したところ、10秒後に実行されたの表示がDebug.Log(timerText.text);で表示させた経過時間の10.02秒と10.03秒の間に出てきたので、invokeを使用するのは悪くなさそうに思える。
念のため下記の別のdeltatimeを使用して10秒後に実行されたの表示をだし、時間差を確認する。9.99と10.00の間に表示されたのでこちらの方が正確であると言える。
float seconds;
void Update()
{
seconds += Time.deltaTime;
if (seconds >= 10)
{
Debug.Log("10秒後に実行された");
}
}
ただ、updateの中にあり、boolではないため=が使えず、>=にしなければならないので、できればinvokeを使いたい。一分経過時も、60.02で表示が行われていたので使用に問題はないと思われinvokeを採用。
リストを絡めてランダムなスクリプトを重複なしに一つずつ取り出すスクリプトを作成。itemを3つ時間を分けて配ることとし、itemはリストにaddで追加。Random.Range(0, numbers.Count)でリストの項目に紐づくindexを取得。numbers.RemoveAt(index)で取り出したものをindexからリスト項目を参照し削除。短くなったリストで再度試行していく流れとなっている。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TimeController : MonoBehaviour
{
List<string> numbers = new List<string>();
void Start()
{
numbers.Add("ichigo");
numbers.Add("ringo");
numbers.Add("mikan");
Invoke("Item1", 3);
Invoke("Item2", 4);
Invoke("Item3", 5);
}
void Item1()
{
int index = Random.Range(0, numbers.Count); //int型なので最大値を含まない
string item1 = numbers[index];
Debug.Log(numbers.Count);
Debug.Log("3秒後に実行されたitem1"+item1);
numbers.RemoveAt(index);
}
void Item2()
{
int index = Random.Range(0, numbers.Count);
string item2 = numbers[index];
Debug.Log("4秒後に実行されたitem2" + item2);
numbers.RemoveAt(index);
}
void Item3()
{
int index = Random.Range(0, numbers.Count);
string item3 = numbers[index];
Debug.Log("5秒後に実行されたitem3" + item3);
numbers.RemoveAt(index);
}
}
うまく機能したのでこれをUIと紐付けて考えていく。
現在のUIではvoid OnTriggerEnter(Collider other)でcubeに接触したらcubeにアタッチされたスクリプトで
InventoryTest inventorytest = canvas.GetComponentInChildren<InventoryTest>();
inventorytest.Add(item);
を実行し、インベントリーへ追加している。
インベントリーのアクセスはInventoryTestコンポーネントに対して行い、下記のaddを使用している。
public void Add(Item item)
{
items.Add(item);
InventoryUITest inventoryui = GetComponent<InventoryUITest>();
inventoryui.UpdateUI();
}
ここでitemの方がItemというクラスの方になっていることに気を付け、inventoryUItextにて、slots[i].AddItem(inventoryTest.items[I]);という、Inventoryに表示される各slotに対して、inventoryTest.items[I]の中身を格納させるように指示している。
.AddItemはslotスクリプトが持つもので、new Itemを型としてして取るものである。
ここで扱われるItemはScriptableObjectと呼ばれるものを継承しており、名前とiconをセットで保有する性質を持つ。
public class Item : ScriptableObject
{
new public string name = "New Item";
public Sprite icon = null;
public void Use()
{
Debug.Log(name + "を使用");
}
}
要するに、拾ったものが、Itemで登録されているもの(Itemスクリプト)であればリストに追加し(Inventoryスクリプト)、そのリストの内容を橋渡し(InventoryUIスクリプト)し、inventoryのslotに反映させる(slotスクリプト)操作を行っている。
少しややこしい気がする。Itemスクリプトとslotスクリプトはinventoryに表示させる上で必要だが、他のところではもう少し簡略化できるようにする。
その前に、ScriptableObjectについての理解を深めてから進むことにする。
ScriptableObjectとは、特定のゲームオブジェクトに紐付かない共有データを格納するのに使われるクラス、およびそのインスタンスとして作成されるアセットのことを指すが、その背景には、特定のオブジェクトにComponentクラスなどゲームオブジェクトに機能をアタッチするという流れがあり、その機能の中で、ゲームオブジェクトにアタッチしない方が良いもの、しなくても良いものを扱うのに使われる。
例えば、複数のオブジェクトで使用したいデータの場合は、ゲームオブジェクトにアタッチするよりアセットとして切り出しておいた方が使いいので、ScriptableObjectを継承したItemクラスが書かれている。
各インスタンスで個別にパラメータを持つと、負荷がインスタンス分だけ増加するが、同じ機能を持つのであれば、ScriptableObjectのアセットファイルにしておけば、個々のインスタンスからScriptableObjectへの参照を行う負荷のみで事足りる。一枚の画像ですむか、100枚の画像で済むかを考えればインパクトがわかる。