今回はキーボードやゲームパッド(PS3コントローラー)で操作が出来るステータス画面を作成していきます。
マウスを使ったステータス画面は
で作成しましたが、キーボードやゲームパッドに対応していませんでしたので、
これらの機能をキーとゲームパッドでの操作でボタンの選択が出来る別のステータス画面を作成したいと思います。
今回の機能を作成すると、
↑のような感じのステータス画面が作成出来ます。
↑ではゲームパッドで操作しています。
本来はもう少し色合いを付けて作成していましたが、記事の修正の為、改めて作成して簡略化しました。
ステータスメイン画面の作成
ステータス画面のメイン画面を作成していきます。
ステータスメイン画面UIを作成していく
メイン画面のUIを作成していきます。
ヒエラルキー上で右クリック→UI→Canvasを選択し、名前をPropertyにします。
Propertyの子要素にUI→Panelでパネルを作成し、名前をBackgroundにします。
BackgroundのRectTransform左のAnchorで縦横をStretchにします。
Backgroundの子要素にUI→Panelでパネルを作成し、名前をMainWindowにします。
MainWindowのRectTransform左のAnchorで縦横をStretchにします。
これでBackground、MainWindowはPropertyの大きさで変動します。
MainWindowの子要素にUI→Panelで3つのパネルを作成し、名前をTitle、MenuArea、Informationとします。
TitleとInformationの子要素にUI→Textでテキストを作成し、タイトルと説明文の初期テキストを設定しておきます。
MenuAreaの子要素にUI→Buttonで3つのボタンを作成し、名前をEquip、Item、Exitとします。
子要素にはTextが自動で作成されるので
Equipには装備
Itemにはアイテム
Exitにはゲームに戻る
という初期テキストを設定します。
上の画像のように領域を作成します。
上の画像で確認出来ますが、パネルの領域の4隅に矢印のようなものが出るのでそれをドラッグして領域を変更します。
MenuAreaのインスペクタにAdd CompoenntからLayout→Horizontal Layout Groupを追加します。
Horizontal Layout Groupは設定したMenuAreaの子要素を横に整列させる為に設定しています。
今回の場合はEquip、Item、Exitボタンを横に整列させる為に設定しました。
設定項目のPaddingは親要素からの距離を指定し、Spacingは他要素との距離になります。
値を変えて好みの設定値を探ってください。
これでステータスメイン画面のUIが出来ました。
Exitボタン等のメニューボタンのイベント処理をするButtonEventスクリプトの作成
次に各画面のメニューボタンに設定するスクリプトを作成していきます。
Equipボタン、Itemボタン、Exitボタンを押した時は画面の遷移やゲームに戻るようにしなければいけません。
その為の処理を行うButtonEventスクリプトを新しく作成し設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | using UnityEngine; using System.Collections; using UnityEngine.UI; using UnityEngine.EventSystems; public class ButtonEvent : MonoBehaviour { // インフォメーションテキストに表示する文字列 [SerializeField] private string informationString; // インフォメーションテキスト [SerializeField] private Text informationText; // 自身の親のCanvasGroup private CanvasGroup canvasGroup; // 前の画面に戻るボタン private GameObject returnButton; void Start() { canvasGroup = GetComponentInParent <CanvasGroup> (); returnButton = transform.parent.Find ("Exit").gameObject; } void OnEnable() { // 装備アイテム選択中にステータス画面を抜けた時にボタンが無効化したままの場合もあるので立ち上げ時に有効化する GetComponent<Button>().interactable = true; } // ボタンの上にマウスが入った時、またはキー操作で移動してきた時 public void OnSelected() { if(canvasGroup == null || canvasGroup.interactable) { // イベントシステムのフォーカスが他のゲームオブジェクトにある時このゲームオブジェクトにフォーカス if(EventSystem.current.currentSelectedGameObject != gameObject) { EventSystem.current.SetSelectedGameObject(gameObject); } informationText.text = informationString; } } // ボタンから移動したら情報を削除 public void OnDeselected() { informationText.text = ""; } // ステータスウインドウを非アクティブにする public void DisableWindow() { if(canvasGroup == null || canvasGroup.interactable) { // ウインドウを非アクティブにする transform.root.gameObject.SetActive(false); } } // 他の画面を表示する public void WindowOnOff(GameObject window) { if(canvasGroup == null || canvasGroup.interactable) { Camera.main.GetComponent <OperationStatusWindow>().ChangeWindow(window); } } // 前の画面に戻るボタンを選択状態にする public void SelectReturnButton() { EventSystem.current.SetSelectedGameObject(returnButton); } } |
canvasGroupはMainWindowでは使用していませんが、他のウインドウで使用します。
CanvasGroupはその子要素のキャンバスを一括管理するもので、CanvasGroupのinteractableをオフにしてその子要素のボタンを操作出来ないようにします。
returnButtonはその画面での前の画面に戻るボタンやゲームに戻るボタンを設定します。
OnEnableメソッド(オブジェクトが有効になった時に呼ばれる)で自身のButtonのinteractableをtrueにし、画面が開かれた時は自身のボタンを作用するようにしておきます。
これはこの後作成する装備変更画面でアイテム変更中はメニューボタンを無効化しますが、変更途中でステータス画面自体を閉じた後、次にステータス画面を開くと無効のままになっている為、OnEnableメソッドでボタンを有効にしています。
OnSelectedメソッドはボタンが選択された時に実行するメソッドで、CanvasGroupがNullまたはinteractableが有効の場合は
1 2 3 | EventSystem.current.SetSelectedGameObject(gameObject); |
を使って、このボタンをフォーカスします。
OnDeselectedメソッドはボタンの選択が解除された時に呼び出すメソッドで、ボタンの情報を空文字にしています。
DisableWindowメソッドは自身のルートにあるPropertyゲームオブジェクトを非アクティブにしステータス画面を閉じます。
WindowOnOffメソッドはメニューボタンのボタンが押された時に呼び出すメソッドでメインカメラに取り付けたOperationStatusWindowスクリプトのChangeWindowメソッドを呼び出し、受け取った画面のゲームオブジェクトをオンにします。
OperationStatusWindowスクリプトは後で作成します。
ButtonEventスクリプトをEquipボタン、Itemボタン、Exitボタンに取り付けます。
MainWindowのExitボタンを押した時に実行するメソッドをEvent Triggerで設定する
次にExitボタンが押された時にさきほど作成したメソッドを呼び出すようにします。
その為にExitボタンのインスペクタでAdd Component→Event→Event Triggerを選択し追加します。
イベントに関しては
を参照してください。
Event TriggerコンポーネントのAdd New Event Typeをクリックしイベントが発生した時に呼び出す関数を指定します。
呼び出すメソッドが指定されているゲームオブジェクト(今回の場合Exitゲームオブジェクト)を指定した後、メソッドを指定します。
Selectはキー操作でボタンを選択した時のイベントでゲームオブジェクトにExitボタン、メソッドはOnSelectedを指定。
Pointer Enterはマウスがボタン上に入った時のイベントでゲームオブジェクトにExit、メソッドはOnSelectedを指定。
Pointer Downはマウスをボタン上で押した時のイベントでゲームオブジェクトにExit、関数はDisableWindowを指定。
Submitはキー操作でEventSystemのSubmit Buttonに指定されているキーが押された時のイベントで、ゲームオブジェクトにExit、関数はDisableWindowを指定。
Pointer Exitはマウス操作でボタン外に移動した時のイベントでゲームオブジェクトにExit、関数はButtonのOnDeselectを指定。
またButtonEventスクリプトのOnDeselectedメソッドを呼び出します。
マウスでボタン外に出たらボタンを選択している状態から解除させます。
Button EventスクリプトのインスペクタにInformationテキストと表示するテキストの内容を設定します。
このようにEvent Triggerでイベントが発生した時に実行する関数を指定する事でボタンに対する操作をする事が出来ます。
これでExitボタンの処理が完成しました。
Equipボタン、ItemボタンのEventTriggerはそれぞれの画面が出来たら設定する事にします。
ステータス画面を開くOperationStatusWindowスクリプトを作成
ステータス画面は最初は表示しない状態にしておくので、何らかのボタンを押したらステータス画面を表示するというスクリプトが必要になります。
そこで、ステータス画面を開くスクリプトOperationStatusWindowスクリプトを作成し、メインカメラに設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | using UnityEngine; using System.Collections; using UnityEngine.EventSystems; public class OperationStatusWindow : MonoBehaviour { [SerializeField] private GameObject propertyWindow; // ステータスウインドウの全部の画面 [SerializeField] private GameObject[] windowLists; void Update () { // ステータスウインドウのオン・オフ if(Input.GetButtonDown("Start")) { propertyWindow.SetActive(!propertyWindow.activeSelf); // MainWindowをセット ChangeWindow (windowLists [0]); } } // ステータス画面のウインドウのオン・オフメソッド public void ChangeWindow(GameObject window) { foreach (var item in windowLists) { if (item == window) { item.SetActive (true); EventSystem.current.SetSelectedGameObject (null); } else { item.SetActive (false); } // それぞれのウインドウのMenuAreaの最初の子要素をアクティブな状態にする EventSystem.current.SetSelectedGameObject (window.transform.Find("MenuArea").GetChild (0).gameObject); } } } |
インスペクタでpropertyWindowにPropertyゲームオブジェクト、windowListsのSizeを3にし、最初にMainWindow、次にItemWindow、次にEquipWindowを設定します。
今はまだMainWindowしか作成していないのでSizeを1にしMainWindowだけ設定します。
キー操作でStartボタンに設定されたキーを押した時にインスペクタで設定したPropertyをOn・Offします。
Start等の独自のボタン設定はUnityのメニューのEdit→Project Settings→Inputで作成出来ます。
ChangeWindowメソッドは受け取ったウインドウのゲームオブジェクトをアクティブにし、それ以外のウインドウを非アクティブにする処理をしています。
ウインドウ(MainWindow、ItemWindow、EquipWindow)の階層構造は大体同じに作成し、必ずMenuAreaの子要素にメニューボタンを配置します。
なので、アクティブにしたウインドウのMenuAreaの最初の子要素のゲームオブジェクトをフォーカスします。
最初にひとつだけゲームオブジェクトを選択状態にしておかなければならない
ステータス画面を開いたり・閉じたりするスクリプトが出来たので、ステータス画面のOn・Offの確認をする為、実行してみましょう。
マウス操作では問題はないですが、キー操作で問題が発生します。
先ほどChangeWindowメソッドでアクティブにした画面の最初の子要素のボタンをフォーカスしましたが、
キー操作をする場合はあらかじめ何らかのゲームオブジェクトが選択されていないと他のボタンをフォーカスする事が出来ないので、あらかじめ最初に設定するボタンをフォーカスしておきます。
今回はキー操作をする事をメインに考えているので、ステータスメイン画面が開かれたら何らかのゲームオブジェクトを選択状態にしておきます。
逆にマウス操作だけする場合は選択しておかない方がいいかもしれません。
ゲームオブジェクトを選択状態にするには、EventSystemのSetSelectedGameObject関数を使用します。
EventSystemのインスペクタのFirst Selectedで最初に選択するゲームオブジェクトを指定出来ますが、
今回のステータス画面のように何度も開いたり閉じたりする場合は最初の1回しか作用しないので、こちらは使いません。
ステータスメイン画面がアクティブになった時にEventSystemのSetSelectedGameObjectを使って選択状態にします。
アイテム画面の作成
メイン画面が出来たので、次はアイテム画面を作成していきます。
アイテム画面のUIを作成していく
まずはアイテム画面のUIを作成します。
Backgroundの子要素にUI→Panelでパネルを作成し、名前をItemWindowにします。
MainWindowとほとんど同じ項目を作成していきます。
ItemAreaTitleとItemAreaはPanelです。
MenuAreaにはMainWindowのMenuAreaと同じようにHorizontal Layout Groupコンポーネントを取り付け設定を同じにします。
これでアイテム画面UIが出来ました。
アイテム画面のUIが出来たのでMain Cameraに設定したOperationStatusWindowスクリプトのwindowListsのSizeを2にしItemWindowを追加します。
MainWindowのItemボタンを押した時に呼び出す処理をEvent Triggerで設定する
アイテム画面UIが出来たのでMainWindowのItemボタンを押した時のEventTriggerの設定をしていきます。
MainWindowのItemボタンを押した時にアイテム画面を開くように設定します。
MainWindowのItemボタンにEvent Triggerコンポーネントを追加し、Exitボタンに設定した時と同じように設定します。
SubmitとPointer DownにはButton EventのWindowOnOff関数を指定し、渡す引数にItemWindowを指定します。
Cancelの時にはSelectReturnButtonメソッドを呼び出します。
CancelはCancelのキーが押された時に発生するイベントです。デフォルトではEscキーが対応しています。
PS3コントローラーで操作する場合は×ボタンをCancelボタン等に設定するといいかもしれません。
例えばCancelボタンにPS3コントローラの×ボタンが設定されていて、今Equipボタンが選択状態の時に
×ボタンを1回押せばExitボタンが選択状態になり、もう1回押せばゲームに戻るというように、キャンセルボタンを押した時に画面を簡単に遷移させる為の処理です。
設定は自身のボタンに設定したButtonEventスクリプトの該当するメソッドを呼び出しているだけでExitボタンの設定とCancelイベント以外はほぼ同じです。
これでItemボタンを押した時にアイテム画面が開くようになりました。
これでステータスメイン画面→アイテム画面の遷移が出来ました。
ItemWindowのExitボタンにもイベントトリガーを追加し、アイテム画面→ステータスメイン画面へと遷移するように設定してください。
EventTriggerの設定ではボタンを押した時にMainWindowを開くようにするだけでMainWindowのExitボタン等と作り方は同じです。
アイテム画面にアイテムを表示していく機能を作成
次にアイテム画面が表示された時にアイテムを表示していく処理が必要になります。
アイテムはItemAreaの子要素に追加していきます。
ItemAreaのインスペクタでAdd ComponentでLayout→Grid Layout Groupを追加します。
Cell SizeのX、Yを50、SpacingをX、Yを5に設定します。
ItemAreaにレイアウトを設定したので、ItemAreaの子要素に要素を追加するとこのレイアウト設定に従って並びます。
ItemAreaの子要素に追加するアイテムボタンのプレハブを作成する前に、アイテムに関するスクリプトを作成していきます。
以前作成したアイテム管理スクリプトだと少々使い勝手がよくないのでこの記事用にアイテム関連のスクリプトを作成します。
アイテムデータを表すStatusWindowItemDataスクリプトの作成
まずはアイテムを表すクラスStatusWindowItemDataスクリプトを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | using UnityEngine; using System.Collections; public class StatusWindowItemData : object { // アイテムのImage画像 private Sprite itemSprite; // アイテムの名前 private string itemName; // アイテムのタイプ private StatusWindowItemDataBase.Item itemType; // アイテムの情報 private string itemInformation; public StatusWindowItemData(Sprite image, string itemName, StatusWindowItemDataBase.Item itemType, string information) { this.itemSprite = image; this.itemName = itemName; this.itemType = itemType; this.itemInformation = information; } public Sprite GetItemSprite() { return itemSprite; } public string GetItemName() { return itemName; } public StatusWindowItemDataBase.Item GetItemType() { return itemType; } public string GetItemInformation() { return itemInformation; } } |
このItemDataはアイテムの情報を保持するだけのクラスです。
アイテム情報を管理するStatusWindowItemDataBaseスクリプトの作成
次にゲーム内で使用するアイテムのデータを管理するItemDataBaseスクリプトを作成します。
このStatusWindowItemDataBaseではStatusWindowItemDataクラスにアイテムの情報を設定し、全てのアイテム情報を作成して保持しておくクラスです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | using UnityEngine; using System.Collections; public class StatusWindowItemDataBase : MonoBehaviour { public enum Item { Sword, HandGun, ShotGun, UseItem }; private StatusWindowItemData[] itemLists = new StatusWindowItemData[4]; void Awake () { // アイテムの全情報を作成 itemLists[0] = new StatusWindowItemData(Resources.Load("FlashLight", typeof(Sprite)) as Sprite, "懐中電灯", Item.Sword, "あれば便利な辺りを照らすライト"); itemLists[1] = new StatusWindowItemData(Resources.Load("Sword", typeof(Sprite)) as Sprite, "ナイフ", Item.Sword, "切れ味するどいナイフ"); itemLists[2] = new StatusWindowItemData(Resources.Load("Sword", typeof(Sprite)) as Sprite, "ブロードソード", Item.Sword, "大剣"); itemLists[3] = new StatusWindowItemData(Resources.Load("Gun", typeof(Sprite)) as Sprite, "ハンドガン", Item.HandGun, "威力抜群ハンドガン"); } public StatusWindowItemData[] GetItemData() { return itemLists; } public int GetItemTotal() { return itemLists.Length; } } |
どのような情報を持ったアイテムなのか調べる場合はこのスクリプトにアクセスして情報を取得します。
1 2 3 | Resources.Load("sword", Sprite) |
Resources.Loadを使うとAssetsフォルダに作成したResourcesフォルダの第1引数で指定した名前の物をロード出来ます。
アイテムを表すアイコンのSpriteはResourcesフォルダを作成しその中に入れておきます。
(今回使用するアイコンはAsset Storeのテクスチャ&マテリアルのカテゴリのRPG inventory iconsを使用させて頂いてます)
Resourcesフォルダに入れたファイルの名前はスクリプトで指定した名前と同じにする必要があります。
Resourcesと名前を付けたフォルダは特殊フォルダになりますので、他の名前には出来ません。
Resourcesフォルダに入れたゲームオブジェクトはインスタンス化しなくてもResources.Loadで読み出せます。
これでアイテムクラスとアイテムデータベースの作成が終わりました。
StatusWindowItemDataBaseスクリプトをMain Cameraに設定します。
主人公のステータスを保持するStatusWindowStatusスクリプトの作成
次は主人公がアイテムを持っているかどうかもわからなければならないので、主人公にステータススクリプトを設定します。
新しくStatusWindowStatusというスクリプトを作りMain Cameraに設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | using UnityEngine; using System.Collections; public class StatusWindowStatus : MonoBehaviour { // アイテムを持っているかどうかのフラグ [SerializeField] private bool[] itemFlags = new bool[6]; private StatusWindowItemDataBase statusWindowItemDataBase; void Start() { statusWindowItemDataBase = GetComponent<StatusWindowItemDataBase> (); SetItemData ("懐中電灯"); SetItemData ("ハンドガン"); } // アイテムを持っているかどうか public bool GetItemFlag(int num) { return itemFlags[num]; } // アイテムをセット public void SetItemData(string name) { var itemDatas = statusWindowItemDataBase.GetItemData (); for (int i = 0; i < itemDatas.Length; i++) { if (itemDatas [i].GetItemName () == name) { itemFlags [i] = true; } } } } |
Startメソッドではあらかじめアイテムを取得しておきます。
それをしているのがSetItemDataメソッドでアイテムを取得した時にアイテム名を指定して呼び出しitemFlagsの該当する番号のアイテムをtrueにし所持したとします。
今回はアイテム1種類に付き1つしか持たないという仕様にしています。
itemFlagsは全アイテム個数分用意し、StatusWindowItemDataBaseスクリプトで作成したアイテムの順番と対応しています。
その為アイテムデータベースに登録されたアイテムの名前と現在取得したアイテムの名前が一致したらその番号のitemFlagsをtrueにしアイテムを取得したとしています。
アイテム一つ一つを表すItemButtonプレハブを作成する
次はアイテム1つ1つを表現するプレハブを作成していきます。
アイテム1つは1つのパネルとボタンで表し、ボタンのアイコンに2Dのスプライトを表示するようにします。
まずはItemAreaの子要素にUI→Panelを選択しパネルを作成したらItemという名前にします。
Itemの子要素にUI→Buttonを選択しボタンを作成したらItemButtonという名前にします。
↑のような階層になります。
ItemAreaにはGrid Layout Groupを設定したので
↑のようなサイズで表示されます。
ItemButtonのイベント処理を行うItemButtonスクリプトの作成
子要素のItemButtonにAdd Componentから新しいスクリプトItemButtonを作り設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | using UnityEngine; using System.Collections; using UnityEngine.UI; public class ItemButton : MonoBehaviour { private Text informationText; private StatusWindowItemDataBase itemDataBase; private int itemNum; void Start () { itemDataBase = Camera.main.GetComponent<StatusWindowItemDataBase>(); informationText = transform.parent.parent.parent.Find ("Information/Text").GetComponent <Text> (); } // アイテムボタンが選択されたら情報を表示 public void OnSelected() { informationText.text = itemDataBase.GetItemData () [itemNum].GetItemInformation (); } // アイテムボタンから移動したら情報を削除 public void OnDeselected() { informationText.text = ""; } public void SetItemNum(int number) { itemNum = number; } public int GetItemNum() { return itemNum; } } |
Startメソッドでメインカメラに設定したStatusWindowItemDataBaseスクリプトを取得したり、情報表示用のTextを取得しています。
ItemButtonスクリプトはプレハブにしたボタンに設定する為、自身の階層から該当するコンポーネントを取得するようにしています。
このボタンが選択された時にOnSelectedメソッドを実行し、このボタンに設定されているアイテム情報をアイテムデータベースから取得し、InformationTextのTextに表示します。
アイテムボタンにはアイテムの番号を設定しておき、ボタンが選択された時にアイテムの情報を設定出来るようにしておきます。
ItemButtonのイベントが発生した時に実行するメソッドを設定する
ItemButtonにAdd Component→Event→Event Triggerを追加し、イベントが発生した時に呼び出すメソッドを設定します。
上の画像のように設定します。
ボタンのイベントが発生したら実行する関数を指定しています。
これでアイテムの1つ1つの入れ物が完成しました。
出来上がったらItemをAssetsフォルダにドラッグ&ドロップしプレハブにし、ItemAreaの子要素のItemは削除します。
ItemAreaにアイテムの総数分Itemを表示するSetItemButtonスクリプトの作成
アイテム画面が表示される時に、ItemButtonをアイテムデータベース分だけ作成し、ItemAreaの子要素に表示するようにします。
ItemAreaに新しいスクリプトCreateItemButtonを作成し、設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | using UnityEngine; using System.Collections; using UnityEngine.UI; public class CreateItemButton : MonoBehaviour { // 主人公キャラクターのステータス private StatusWindowStatus statusWindowStatus; // アイテムデータベース private StatusWindowItemDataBase statusWindowItemDataBase; // アイテムボタンのプレハブ public GameObject itemPrefab; // アイテムボタンを入れておくゲームオブジェクト private GameObject[] item; // ゲームオブジェクトがアクティブになった時実行 void OnEnable() { statusWindowStatus = Camera.main.GetComponent<StatusWindowStatus>(); statusWindowItemDataBase = Camera.main.GetComponent<StatusWindowItemDataBase>(); item = new GameObject[statusWindowItemDataBase.GetItemTotal()]; // アイテム総数分アイテムボタンを作成 for(var i = 0; i < statusWindowItemDataBase.GetItemTotal(); i++) { item[i] = GameObject.Instantiate(itemPrefab) as GameObject; item [i].name = "Item" + i; // アイテムボタンの親要素をこのスクリプトが設定されているゲームオブジェクトにする item[i].transform.SetParent(transform); // アイテムを持っているかどうか if(statusWindowStatus.GetItemFlag(i)) { // アイテムデータベースの情報からスプライトを取得しアイテムボタンのスプライトに設定 item[i].transform.GetChild(0).GetComponent<Image>().sprite = statusWindowItemDataBase.GetItemData()[i].GetItemSprite (); } else { // アイテムボタンのUI.Imageを不可視にし、マウスやキー操作で移動しないようにする item[i].transform.GetChild(0).GetComponent<Image>().enabled = false; item[i].transform.GetChild(0).GetComponent<Button>().interactable = false; } // ボタンにユニークな番号を設定(アイテムデータベース番号と対応) item[i].transform.GetChild(0).GetComponent<ItemButton>().SetItemNum(i); } } void OnDisable() { // ゲームオブジェクトが非アクティブになる時に作成したアイテムボタンインスタンスを削除する for(var i = 0; i < statusWindowItemDataBase.GetItemTotal(); i++) { Destroy(item[i]); } } } |
インスペクタでitemPrefabにさきほど作成したItemプレハブを設定します。
OnEnable関数はこのスクリプトが設定されているItemAreaがアクティブになった時に自動で実行されます。
アイテムデータベースに登録されているアイテムの数分だけItemインスタンスを作成し、親要素をItemAreaに設定します。
ItemAreaにはGridLayoutが設定されているので、子要素であるItemはそれに従って並んでいきます。
主人公のステータスを確認し、そのアイテムを持っている場合は子要素のItemButtonのImageにアイテムのアイコンを設定します。
そのアイテムを持っていない時はImageコンポーネントを使用不可にし、ボタンの状態変更を無効化しておきます。
最後にアイテムボタン毎にユニークな番号を設定します。
このユニークな番号とアイテムデータベースの登録番号は一致します。
OnDisable関数はItemAreaが非アクティブになった時に自動で呼ばれます。
作成したアイテムボタンインスタンスを全部削除します。
ステータスメイン画面からアイテム画面を開いて確認する
これでアイテム画面が完成したので、ステータスメイン画面からアイテム画面を表示してアイテムが表示されるか確認してみます。
アイテムを所持していない場合はアイテム一覧のアイコンが表示されず、キー操作やマウス操作にも反応しません。
アイテムの上にキー操作やマウス操作で選択状態を移動するとInformationのText部分にアイテムの情報が表示されます。
これでアイテム画面の動作確認は終了です。
うん、なかなかよく出来てる・・・(^_^)v
装備画面の作成
最後に装備画面を作成していきます。
装備画面のUIを作成していく
ここまででずいぶん長くなりましたが、実はここからが一番面倒くさい処理です。(+_+)
装備スロットに武器を設定する装備画面の作成をしていきます。
では装備画面を作成していきましょう。
アイテム画面を作成する時と同じように装備画面を作ります。
MenuAreaには
↑のようにAdd ComponentのLayoutからHorizontal Layout GroupコンポーネントとCanvas Groupを取り付けます。
Horizontal Layout Groupは今まで通りボタンを横に並べる為で、Canvas Groupは子要素のメニューボタンを一括で有効化と無効化を切り替える為に使用します。
EquipAreaにもCanvas Groupを取り付けInteractableのチェックを外しておきます。
EquipItemAreaにはGrid Layout GroupとCanvas Groupを取り付けCanvas GroupのInteractableのチェックを外しておきます。
Interactableのチェックを外すのは最初は子要素のボタンを無効化しておきたい為です。
最初にチェックが入った状態でスクリプトでチェックを外してもいいんですが画面上で一瞬だけボタンが有効な状態が見えてしまう為オフにしました。
Equip0~4はPanelでその子要素にButtonを作成し名前をEquipButtonとします。
装備画面の作成は色々面倒くさい・・・
マウス操作でドラッグ&ドロップする処理も色々面倒な処理をしなければいけませんでしたが、残念なことにキー操作やマウスで押して装備を変更するのもなかなか面倒な処理が絡んできます。
面倒な処理というのは装備スロットの一つをキー操作のSubmitやマウスを押した時に装備できる武器のアイテムボタンだけを選択出来るようにして、それが終わったらまた装備スロットやメニューボタンの選択が出来るようにしなければいけません。
つまり、装備スロットの選択と装備するアイテムの選択でキー操作で移動出来る項目に制限を設ける必要が出てくるんです。
キー操作する場合はこれが面倒ですね・・・・(^_^;)
現在押したボタンを保持するSelectedEquipButtonスクリプトの作成
まずはEquipAreaにSelectedEquipButtonスクリプトを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using UnityEngine; using System.Collections; public class SelectedEquipButton : MonoBehaviour { // 押した装備ボタン番号 public int selectedEquipButton; void Start () { selectedEquipButton = -1; } public void SetSelectedEquipButton(int select) { selectedEquipButton = select; } public int GetSelectedEquipButton() { return selectedEquipButton; } } |
このスクリプトはEquipAreaの子要素のボタンでどれが押されたかを保持しておくだけのスクリプトです。
なぜこのスクリプトが必要かと言うと、EquipAreaの子要素のボタンが押された時、装備一覧のパネルへ遷移し、スロットに設定する装備を選び、再びEquipAreaに戻ります。
その時に選んだスロットに選択したアイテムの画面を表示する為、押したボタンの番号を記憶しておきます。
EquipButtonのイベント処理をするEquipButtonスクリプトの作成
EquipButtonにEquipButtonスクリプトを作成し取り付けます。
EquipButtonは装備スロット分の4つあるのでそれぞれ設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | using UnityEngine; using System.Collections; using UnityEngine.EventSystems; using UnityEngine.UI; public class EquipButton : MonoBehaviour { // EquipButtonに保持するアイテムデータ private StatusWindowItemData statusWindowItemData; private Transform equipArea; private Transform menuArea; private Transform equipItemArea; private Text informationText; private SelectedEquipButton selectedEquipButton; private CanvasGroup canvasGroup; // この装備の番号 [SerializeField] private int equipNum; // 戻るボタン private GameObject returnButton; void Start() { equipArea = transform.parent.parent; menuArea = transform.parent.parent.parent.Find ("MenuArea"); equipItemArea = transform.parent.parent.parent.Find ("EquipItemArea"); informationText = transform.parent.parent.parent.Find ("Information/Text").GetComponent <Text> (); selectedEquipButton = GetComponentInParent<SelectedEquipButton> (); canvasGroup = equipArea.GetComponent <CanvasGroup> (); returnButton = menuArea.Find("Exit").gameObject; } // EquipButtonを押した時 public void OnClick() { if(canvasGroup.interactable) { // 選択中の装備スロットであることをわかるように背景色を変更する transform.parent.GetComponent<Image>().color = new Color(0.1f, 0.1f, 0.1f, 1f); // イベントシステムのセレクトをオフ EventSystem.current.SetSelectedGameObject(null); // EquipAreaを無効化 equipArea.GetComponent <CanvasGroup> ().interactable = false; // MenuAreaを無効化 menuArea.GetComponent <CanvasGroup> ().interactable = false; // アイテムボタンを有効化 equipItemArea.GetComponent<CanvasGroup>().interactable = true; // 現在選択中の装備ボタンの番号をセットする selectedEquipButton.SetSelectedEquipButton (equipNum); // EquipItemAreaの最初のボタンをフォーカスする EventSystem.current.SetSelectedGameObject (equipItemArea.GetChild (0).GetChild (0).gameObject); } } // EquipButtonが選択された時 public void OnSelected() { if(canvasGroup.interactable) { if(statusWindowItemData != null) { informationText.text = statusWindowItemData.GetItemInformation (); } transform.parent.GetComponent<Image>().color = new Color(1f, 1f, 1f, 1f); } } // EquipButtonから移動したら情報を削除 public void OnDeselected() { informationText.text = ""; } // EquipItemButtonが押され装備スロットにアイテムをセット public void SetStatusWindowItemData(StatusWindowItemData itemData) { statusWindowItemData = itemData; GetComponent <Image> ().sprite = statusWindowItemData.GetItemSprite (); transform.parent.GetComponent<Image>().color = new Color(1f, 1f, 1f, 100f / 255f); } void OnEnable() { GetComponent <Button>().interactable = true; } // キー操作でステータス画面を閉じた時は選択中のボタンを元に戻す public void OnDisable() { transform.parent.GetComponent<Image>().color = new Color(1f, 1f, 1f, 100f / 255f); } // 前の画面に戻るボタンを選択状態にする public void SelectReturnButton() { EventSystem.current.SetSelectedGameObject(returnButton); } } |
equipNumはインスペクタでその装備スロットの番号を設定します。
Equip0の子要素のEquipButtonゲームオブジェクトのEquipButtonスクリプトのequipNumは0と親のゲームオブジェクトの番号と同じ数をしていしておきます。
StartメソッドではEquipWindowのパネルを取得しています。
自身の階層から該当するパネルを取得しているのでparentがいっぱい出てきていますが・・・・・(^_^;)
OnClickメソッドはボタンが押された時に実行させるメソッドで、押したボタンの背景色を変えたり、MenuArea、EquipAreaのCanvasGroupのInteractableを操作し無効化、
EquipItemAreaのCanvasGroupの有効化をしています。
EquipAreaのボタンを押したらそのスロットに設定する装備アイテム一覧の中のアイテムを選択するのでMenuAreaとEquipAreaを操作出来ないようにし、EquipItemAreaを操作出来るようにしています。
EquipItemAreaを操作する時にキー操作だとあらかじめどれかのボタンが選択されていないと操作出来ないので、EquipItemAreaの子要素のボタンをフォーカスしています。
SetStatusWindowItemDataメソッドはEquipItemAreaの装備アイテムを選択したら呼び出すメソッドでアイテム情報をEquipAreaのEquipButtonスクリプト自体で保持します。
他の処理はコメントを見て頂ければわかると思います。
EquipButtonのEventTriggerの設定
EquipButtonにはEventTriggerコンポーネントを取り付け以下のように設定します。
先ほど作成したメソッドを呼び出すように設定するだけですね。
EquipItemプレハブの作成
次にEquipItemArea(装備アイテム一覧エリア)に表示する装備品を表示するボタンのプレハブを作成します。
EquipItemAreaの子要素にPanelを作成し名前をEquipItem、その子要素にButtonを作成し名前をEquipItemButtonとします。
EquipItemButtonに設定するEquipItemButtonスクリプトの作成
EquipItemButtonのイベント処理スクリプトEquipItemButtonスクリプトを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | using UnityEngine; using System.Collections; using UnityEngine.EventSystems; using UnityEngine.UI; public class EquipItemButton : MonoBehaviour { // EquipItemButtonに保持するアイテムデータ private StatusWindowItemData statusWindowItemData; private Transform equipArea; private Transform menuArea; private Transform equipItemArea; private Text informationText; // 現在選択している装備ボタンを保持するスクリプト private SelectedEquipButton selectedEquipButton; void Start() { equipArea = transform.parent.parent.parent.Find ("EquipArea"); menuArea = transform.parent.parent.parent.Find ("MenuArea"); equipItemArea = transform.parent.parent.parent.Find ("EquipItemArea"); informationText = transform.parent.parent.parent.Find ("Information/Text").GetComponent <Text> (); selectedEquipButton = equipArea.GetComponent<SelectedEquipButton> (); } // EquipButtonを押した時 public void OnClick() { if(GetComponentInParent<CanvasGroup>().interactable) { // イベントシステムのセレクトをオフ EventSystem.current.SetSelectedGameObject(null); // EquipAreaを有効化 equipArea.GetComponent <CanvasGroup> ().interactable = true; // MenuAreaを有効化 menuArea.GetComponent <CanvasGroup> ().interactable = true; // アイテムボタンを無効化 equipItemArea.GetComponent<CanvasGroup>().interactable = false; var equipButton = equipArea.transform.GetChild (selectedEquipButton.GetSelectedEquipButton ()).GetComponentInChildren<EquipButton> (); equipButton.SetStatusWindowItemData (statusWindowItemData); // EquipAreaの最初のボタンをフォーカスする EventSystem.current.SetSelectedGameObject (equipArea.GetChild (selectedEquipButton.GetSelectedEquipButton ()).GetChild (0).gameObject); } } // EquipButtonが選択された時 public void OnSelected() { if(GetComponent<Button>().interactable) { if(GetComponent<Image>().sprite != null) { informationText.text = statusWindowItemData.GetItemInformation (); } transform.parent.GetComponent<Image>().color = new Color(1f, 1f, 1f, 1f); } } // EquipItemButtonから移動したら情報を削除 public void OnDeselected() { informationText.text = ""; } public void SetStatusWindowItemData(StatusWindowItemData itemData) { statusWindowItemData = itemData; } } |
内容は以前出てきた処理とほとんど同じなので割愛します・・・・(-_-)
EquipItemButtonのEventTriggerの設定
EquipItemButtonにEventTriggerコンポーネントを取り付け、先ほど作成したスクリプトのメソッドを呼び出すようにします。
設定も以前と同じなので割愛します。
EquipItemAreaにEquipItemを表示するCreateEquipButtonスクリプトの作成
EquipItemが完成したので、EquipItemAreaにEquipItemを設置するスクリプトを作成していきます。
EquipItemAreaに新しくSetEquipItemButtonというスクリプトを作成し追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | using UnityEngine; using System.Collections; using UnityEngine.UI; public class CreateEquipButton : MonoBehaviour { // 主人公キャラクターのステータス private StatusWindowStatus statusWindowStatus; // アイテムデータベース private StatusWindowItemDataBase statusWindowItemDataBase; // Equipボタンのプレハブ public GameObject equipButtonPrefab; // アイテムボタンを入れておくゲームオブジェクト private GameObject[] item; // ゲームオブジェクトがアクティブになった時実行 void OnEnable() { // EquipItemAreaを無効化 GetComponent <CanvasGroup>().interactable = false; statusWindowStatus = Camera.main.GetComponent<StatusWindowStatus>(); statusWindowItemDataBase = Camera.main.GetComponent<StatusWindowItemDataBase>(); item = new GameObject[statusWindowItemDataBase.GetItemTotal()]; // アイテム総数分アイテムボタンを作成 for(var i = 0; i < statusWindowItemDataBase.GetItemTotal(); i++) { // アイテムタイプが使用アイテム、またはアイテムを持っていない時は次に進む if (statusWindowItemDataBase.GetItemData () [i].GetItemType () == StatusWindowItemDataBase.Item.UseItem || !statusWindowStatus.GetItemFlag (i) ) { continue; } item[i] = GameObject.Instantiate(equipButtonPrefab) as GameObject; item [i].name = "EquipItem" + i; // アイテムボタンの親要素をこのスクリプトが設定されているゲームオブジェクトにする item[i].transform.SetParent(transform); // アイテムデータベースの情報からスプライトを取得しアイテムボタンのスプライトに設定 item[i].transform.GetChild(0).GetComponent<Image>().sprite = statusWindowItemDataBase.GetItemData()[i].GetItemSprite (); // EquipItemAreaを無効化してからEquipButtonを有効化(ボタンが点滅しているように見えてしまう為) item [i].transform.GetChild (0).GetComponent<Button> ().interactable = true; item [i].transform.GetChild (0).GetComponent<EquipItemButton> ().SetStatusWindowItemData (statusWindowItemDataBase.GetItemData () [i]); } } void OnDisable() { // ゲームオブジェクトが非アクティブになる時に作成したアイテムボタンインスタンスを削除する for(var i = 0; i < statusWindowItemDataBase.GetItemTotal(); i++) { Destroy(item[i]); } } } |
アイテム画面でアイテム数分アイテムボタンを表示したのとほとんど同じです。
違う点としてはアイテムの種類が装備する武器出ない時やアイテムを所持していない時はcontinueを使って処理を飛ばし次のアイテムの確認をしているところです。
装備アイテム一覧には装備出来る武器で持っている物だけを表示します。
これでEquipItemが出来たのでAssetsフォルダにドラッグ&ドロップしてプレハブにし、EquipItemAreaに作ったEquipItemは削除します。
CreateEquipButtonのインスペクタでequipButtonPrefabに今プレハブ化したEquipItemを設定します。
装備選択中にステータス画面を抜けた時の為の処理
ゲームパッドのStartボタンに対応するボタンを押すとステータス画面を閉じます。
装備選択中にステータス画面を抜けてしまった場合はEquipWindowのMenuAreaやEquipAreaが無効で、EquipItemAreaが有効のままになっています。
なので、EquipWindowがアクティブになった時に初期化をするスクリプトを作成しEquipWindowゲームオブジェクトに設定しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using UnityEngine; using System.Collections; using UnityEngine.UI; public class ActivateEquipWindow : MonoBehaviour { // 装備画面を操作中にステータス画面を閉じた時用に装備画面がアクティブになった時に初期化処理をする void OnEnable() { // EquipAreaを有効化 transform.Find ("EquipArea").GetComponent <CanvasGroup> ().interactable = true; // MenuAreaを有効化 transform.Find("MenuArea").GetComponent <CanvasGroup> ().interactable = true; // アイテムボタンを無効化 transform.Find ("EquipItemArea").GetComponent<CanvasGroup>().interactable = false; } } |
OnEnableメソッドはゲームオブジェクトがアクティブになった時に呼ばれるので、その時に該当するエリアの有効化と無効化をします。
ステータス画面が出来たので確認してみる
これでステータス画面が完成しましたので、確認してみてください。
Propertyゲームオブジェクトのインスペクタで名前の横のチェックを外し、必ず最初は非アクティブな状態でないとエラーが出ます。
上の画像が装備スロットを選択している時の状態です。
上のスロットを選択している状態なので剣が赤くなっています。
上の画像が装備スロットを選択した後の画像です。
装備アイテム選択エリアに操作が移動し、装備アイテム選択エリア外のボタンは無効化され移動出来なくなります。
最後に動画で紹介します。
↑のようになります。
ふぅ・・・・ようやくステータス画面が完成しました。
100記事到達とアクセスアップに感謝
この記事ボリュームありすぎですね・・・・・、分割してもよかったんですが、マウス操作の持ち物画面のように分割すると、色々ページを飛ばなければいけなくなるので面倒くさいと思って全部詰め込みました。
さすがにこれだけ長いと見る気が失せますね・・・・自分自身でも確認が大変です。
一度見直して文章を見なおしましたが大変なんてもんじゃなかったです・・・、プログラムの影響もあるけど文字数が35000文字超えてる記事って・・・(^_^;)
だいぶ削減して26000文字ほどになりました(2018/01/20)
ゲーム画面に表示する装備スロットの機能等は削除しました・・・・。
この記事がちょうど100記事目なのでメモリアルという意味も込めてちょうどいい長さなのかもしれません。
4ヶ月目+数日でようやく100記事です。
機能をまず完成させてからでないと記事に出来ないので、記事を書く事より機能を実現するのが大変です((+_+))
更新頻度はどんどん落ちていきそう・・・・(-.-)
ありがたい事に少しづつこのブログのアクセス数も増えてきました。
さらに多くの方に記事を見てもらう機会が増えるとなると過去記事を見直して読みやすくしていく必要がありそうです。
更新はRSSご登録頂いたり、ツイッターで更新情報を流しているのでフォローして頂けると、
更新してないじゃないか!
といった事もなくなるかと思います。
ツイッターは更新情報をツイートする時に使ってるだけです・・・・・・・(-.-)
最近は関係ない事もつぶやく事が多くなりました。
ただツイッターで更新情報をツイートしたけどわたくしのツイート検索出来なかったんですよね・・・
URLが似たようなものだとフィルターにひっかかるのかな?
それでは今後ともよろしく(仲魔?謎)お願いします。