今回はUnityのメニュー画面や持ち物画面等のUIの操作をゲームパッドやキー操作でアイコンを移動しそのアイコンを使って行えるようにしていきたいと思います。
メニュー画面等の操作は一般的にはマウス操作でマウスカーソルを移動させボタンの上に移動させたり、押したりします。
その操作に関しては
をご覧ください。
またゲームパッドやキー操作でメニュー画面等のボタンのフォーカスを移動させるやり方は
や
をご覧ください。
今回の機能を作成すると
↑のようなアイコンを使ったメニュー操作が出来るようになります。
今回作成するUIのメニュー操作
今回作成する機能は今までのとどう違うのか?と言うと
ゲームパッドやキーの移動ボタンを押した時に『ボタン等のUIのフォーカスを順番に移動させたり』、『マウスポインタを移動させたり』するのではなく、
移動キーを押した時にアイコンとして作成したゲームオブジェクトを動かし、アイコンがボタン上にある時に決定ボタンを押したらボタンが押されたことにします。
UIメニューの画面の作成と動かすアイコンゲームオブジェクトを作成する
UIメニューの外枠を作成
まずはボタンやアイコンを設置するCanvasを作成します。
Canvasの子要素にはPanelを作成します。
動かすアイコンの作成
次にゲームパッドやキーで移動キーが押された時に動かすゲームオブジェクトを作成します。
先ほど作成したPanelの子要素にUI→Imageを作成し名前をIconと変更します。
IconのImageコンポーネントのSource Imageに動かしたいアイコン用のスプライトを設定します。
今回はAsset Storeのカテゴリから『テクスチャ&マテリアル』から『アイコン&UI』を選択し値段でソートしたら出てくる『RPG inventory icons』をインポートして
使用させていただきます。
IconのSource ImageにUI_Touchを設定します。
↑のようにIconのWidthとHeightのサイズを30にします。
IconのタグにはIconというタグを作成し設定しておきます。
アイコンを動かすスクリプトMoveIconの作成
アイコンをゲームパッドやキーで移動できるようにするスクリプトMoveIconを作成し取りつけます。
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 | public class MoveIcon : MonoBehaviour { // アイコンが1秒間に何ピクセル移動するか [SerializeField] private float iconSpeed = Screen.width; // アイコンのサイズ取得で使用 private RectTransform rect; // アイコンが画面内に収まる為のオフセット値 private Vector2 offset; void Start () { rect = GetComponent<RectTransform>(); // オフセット値をアイコンのサイズの半分で設定 offset = new Vector2(rect.sizeDelta.x / 2f, rect.sizeDelta.y / 2f); } void Update () { // 移動キーを押していなければ何もしない if (Mathf.Approximately(Input.GetAxis ("Horizontal"), 0f) && Mathf.Approximately(Input.GetAxis ("Vertical"), 0f)) { return; } // 移動先を計算 var pos = rect.anchoredPosition + new Vector2 (Input.GetAxis ("Horizontal") * iconSpeed, Input.GetAxis ("Vertical") * iconSpeed) * Time.deltaTime; // アイコンが画面外に出ないようにする pos.x = Mathf.Clamp(pos.x, -Screen.width * 0.5f + offset.x, Screen.width * 0.5f - offset.x); pos.y = Mathf.Clamp(pos.y, -Screen.height * 0.5f + offset.y, Screen.height * 0.5f - offset.y); // アイコン位置を設定 rect.anchoredPosition = pos; } } |
アイコンは移動キーが押された時に移動させるのでUpdateメソッドで
移動キー × 1秒間での移動ピクセル値
でXとY方向の移動値を設定します。
ピクセル値は1秒間でスクリーンの横幅分移動すると設定しています。
その後、現在の位置に移動値を加えた位置にアイコンを移動させます。
このままだとアイコンが画面外にも移動してしまうので、Mathf.Clampを使ってアイコンのサイズ、画面サイズを使って画面内だけの移動が出来るようにしています。
ここまでを実行して確認してみましょう。
↑のように画面内のみアイコンのゲームオブジェクトが移動してします。
これでアイコンゲームオブジェクトを移動させる事が出来ました。
アイテム表示ボタンをアイコンで操作
次はアイテム表示用のボタンを作成し、アイコンがボタンの上に来たらアイテム情報をテキストUIに表示するようにしています。
アイテム表示用ボタンの作成
まずはアイテム毎にボタンを作成し、アイテムのImageを表示させます。
Panelの子要素にUI→Buttonを選択し、2つのボタンを作成し名前をAxeとArmorとします。
Buttonの子要素にあるTextは使わないので削除してください。
↑のようにAxeとArmorボタンが作成されます。
AxeとArmorのインスペクタでWidth、Heightを50に設定します。
それぞれのImageのSource Imageには先ほどインポートしたアイコンの中からaxeとarmorを選択し設定します。
またButtonコンポーネントのNavigationをNoneにします。
これは一度何らかのButtonや他のUIが選択状態になった時、他のNavigation項目に設定されていると移動キーが押された時に他のUIへフォーカスが移動してしまう為です。
今回の場合はアイコンを動かしボタンの操作を行う為Navigationによってフォーカスが移動してしまうと困るのでNoneに設定しています。
ButtonのHighlight Colorは赤色に設定し、ボタンの上にアイコンが来たらアイテムボタンのImageを赤くします。
デフォルトのアイテムボタンは↑のようになりました。
アイテム情報表示用テキストUIの作成
次にPanelの子要素にUI→Textでアイテム情報表示用のテキストを作成します。
ただ情報を表示する為だけのテキストなのでWidthとHeightをお好みの大きさで作成し、配置してください。
今回は↑のように作成しました。
アイコンとアイテムボタンの接触を判定する
次にアイコンとアイテムボタンが接触している事を検知出来なければボタン上にアイコンがあるのかどうかわかりません。
その為、アイコンにはRigidbody2DとBox Collider2D、アイテムボタンにはBox Collider2Dを取りつけます。
今回使用するRigidobodyとColliderは2Dのものを使用します。
これはUIは2Dで表示されるからです。
ちなみに3D用のColliderと2D用のColliderは別のシステムを使って衝突判定を行うので互いに衝突判定や検知は出来ません。
アイコンゲームオブジェクトのBox Collider2DのIs Triggerにチェックを入れ、コライダのサイズをアイコンのサイズと合わせます。
Rigidbody2DのSleeping ModeをNever Sleepにします。
Rigidbody2DのSleeping Modeはデフォルトの設定では動かしていない時にRigidbodyを無効化しています。
これでは常時接触を確認出来ないのでNever Sleepに設定します。
また連続衝突を検知出来るようにする為、Collision DetectionをContinuousにします。
今回の場合相手方で検知するだけなのでこの設定は意味ないかも?
Axe、Armorのアイテムボタンにはアイコン検知エリアであるBox Collider2Dを設定し、コライダのサイズをボタンのサイズと合わせます。
アイコンを検知する為のスクリプトIconEventスクリプトを作成し取り付けています。
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 | using UnityEngine; using System.Collections; using UnityEngine.UI; using UnityEngine.EventSystems; public class IconEvent : MonoBehaviour { // このスロットのアイテム名 [SerializeField] private string itemName; // アイテム情報を出力するテキスト [SerializeField] private Text text; private Image image; void Start() { image = GetComponent<Image>(); } // マウスアイコンが自分のアイコン上に入った時 void OnTriggerEnter2D(Collider2D col) { CheckEvent(col); } // マウスアイコンが自分のアイコン上にいる間 void OnTriggerStay2D(Collider2D col) { CheckEvent(col); } void CheckEvent(Collider2D col) { // アイコンを検知する if(col.tag == "Icon") { if(EventSystem.current.currentSelectedGameObject == null) { // このUIをフォーカスする EventSystem.current.SetSelectedGameObject(gameObject); // アイテムに応じて表示する情報を変える if(itemName == "axe") { text.text = "斧の情報です"; } else if(itemName == "armor") { text.text = "鎧の情報です"; } } // 選択状態の時にSubmitボタンを押した時、Input.GetButtonDownだと同じアイコン上にあるタイミングで動作出来ない事がある // if(Input.GetButtonDown("Submit")) { if(Input.GetButton("Submit")) { // マウスアイコンの左上に自分のアイコンを表示する col.gameObject.GetComponent<MoveIcon>().SetMoveItem(image.sprite); } } } // マウスアイコンが自分のアイコン上から出て行った時 void OnTriggerExit2D(Collider2D col) { if(col.tag == "Icon") { // フォーカスを解除する EventSystem.current.SetSelectedGameObject(null); text.text = "アイテム情報を表示する場所"; } } } |
↑がアイコン検知スクリプトです。
1 2 3 4 5 6 7 8 9 10 11 | // マウスアイコンが自分のアイコン上に入った時 void OnTriggerEnter2D(Collider2D col) { CheckEvent(col); } // マウスアイコンが自分のアイコン上にいる間 void OnTriggerStay2D(Collider2D col) { CheckEvent(col); } |
コライダは2D用のコライダなのでメソッド名や受け取る引数の型も2Dを付けなくてはいけません。
ここを3D用と同じ関数名にするとイベントが発生しないので何も起こりません。
今回はアイテムボタンの上にアイコンが侵入した時と上にいる間CheckEventメソッドを呼び出します。
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 | void CheckEvent(Collider2D col) { // アイコンを検知する if(col.tag == "Icon") { if(EventSystem.current.currentSelectedGameObject == null) { // このUIをフォーカスする EventSystem.current.SetSelectedGameObject(gameObject); // アイテムに応じて表示する情報を変える if(itemName == "axe") { text.text = "斧の情報です"; } else if(itemName == "armor") { text.text = "鎧の情報です"; } } // 選択状態の時にSubmitボタンを押した時、Input.GetButtonDownだと同じアイコン上にあるタイミングで動作出来ない事がある // if(Input.GetButtonDown("Submit")) { if(Input.GetButton("Submit")) { // マウスアイコンの左上に自分のアイコンを表示する col.gameObject.GetComponent<MoveIcon>().SetMoveItem(image.sprite); } } } |
CheckEventメソッドでは接触したゲームオブジェクトのタグがIconタグだった時でEventSystemがどのUIもフォーカスしていない時にこのゲームオブジェクト(アイテムボタン)をフォーカスします。
インスペクタで設定したitemNameの名前に応じてテキストUIにアイテムの情報を表示しています。
今回はサンプルなので文字列をその場で入れていますが、アイテム情報クラス等を作ってそこでアイテムに関する情報を入れておき、そこから情報を取り出すように
した方がいいと思います。
またSubmitに設定されているボタンを押した時にMoveIconスクリプトのSetMoveItemメソッドを呼び出し、このアイテムのSpriteを渡します。
ボタンの入力設定については
を参照してください。
↑のようにInputManagerで新しくSubmitを作り、ゲームパッドの決定ボタン(わたくしの場合PS3コントローラの○ボタン)を設定します。
アイテムを装備する機能を作成する時にアイテムを選択し装備スロットに移動させます。
その時アイコンと一緒にそのアイテムを表示させ、装備スロットの上でSubmitボタンを押した時にそのスロットにそのアイテムを表示させるようにします。
この時に装備するアイテムのSprite情報が必要な為SetMoveItem関数で設定しています。
Submitボタンが押された時はInput.GetButtonDownで取得する事が出来ますが、GetButtonDownはそのフレームで1回しか処理されないので、
マウスアイコンがアイコン上にきてすぐにSubmitボタンを押しても反応しない事があります。
その為Input.GetButtonを使って次のフレームに移動しても押している事を検知するようにしています。
SetMoveItemメソッドの記述は後で行います。
OnTriggerExit2D関数ではアイコンがアイテムボタン上から離れた時にフォーカスを外し、アイテム情報を表示するテキストUIを元の文字列に変更しています。
Iconの左上に選択したアイテムを表示する
さきほどアイテムボタンの上でSubmitボタンを押したらそのアイテムをアイコンと一緒に移動させると言いましたが、
今回はあらかじめアイコンの左上にImageを作成しておき、アイテムが選択された時にアイテムのImageの設定と表示を行うようにします。
Iconの子要素にUI→Imageを選択し作成します。
↑のようにアイコンの左上に移動し、アイコンのサイズより少し小さくします。
またImageコンポーネントのチェックを外し、Source ImageはNoneにしておきます。
↑はImageコンポーネントを有効化して一時的に表示した画像です。
Iconより少し小さいサイズで左上に配置しているのがわかると思います。
MoveIconスクリプトの修正
MoveIconスクリプトにアイテムボタンを押した時にアイテムのSpriteを設定する処理等を追加しましょう。
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 | // アイコンと一緒に動かしているアイテムのImage private Image moveItem; void Start () { moveItem = transform.GetChild (0).GetComponent <Image>(); } // 一緒に動かすアイテムを有効化 public void SetMoveItem(Sprite sprite) { moveItem.GetComponent<Image>().enabled = true; moveItem.sprite = sprite; } // 一緒に動かすアイテムのImageを返す public Image GetMoveItem() { return moveItem; } // 一緒に動かすアイテムの非表示 public void ResetMoveItem() { moveItem.GetComponent<Image>().enabled = false; moveItem.sprite = null; } |
↑のようにアイテムボタンを押した時(アイテムボタン上でSubmitボタンを押した時)は受け取ったSpriteを左上のImageスプライトに設定し、Imageを表示しています(SetMoveItem)。
moveItemにはStartメソッドでIconの子要素であるImageを設定しておきます。
GetMoveItemはアイテムのImageを返し、ResetMoveItemは装備スロットにアイテムを移動した時にIconの左上のImageを非表示にしImageスプライトをnullに設定しています。
装備スロットの作成
アイコンでボタンアイテムを押した後、装備スロットの上でSubmitボタンを押した時に装備スロットにアイテムのSpriteを表示出来るようにします。
まずは装備スロットを作成していきましょう。
↑のようにPanelの子要素にUI→Buttonを作成し名前をDropDownSlotとします。
その子要素にUI→Imageを作成します。
子要素のImageのSource Imageをアイテムのスプライトにし、ボタンのスプライトは白い背景のまま残します。
↑がDropDownSlotボタンのインスペクタです。
Width、Heightを50、NavigationをNoneにしておきます。
↑が子要素のImageです。
DropDownSlotのサイズより少し小さくしWidth、Heightを40とします。
Source ImageはNoneにします。
DropDownSlot範囲内でのイベントを処理するDropDownItemスクリプトの作成
DropDownSlotにはアイコンがボタン上にきてSubmitボタンを押した時に処理をするスクリプトDropDownItemスクリプトを作成し取りつけます。
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 DropDownItem : MonoBehaviour { private Image image; void Start () { // 自身の子要素のImageコンポーネントを取得 image = transform.GetChild(0).GetComponent<Image>(); } void OnTriggerStay2D(Collider2D col) { if(col.tag == "Icon") { // アイテムをドロップした時 if(Input.GetButton("Submit")) { MoveIcon moveIcon = col.gameObject.GetComponent<MoveIcon>(); Image item = moveIcon.GetMoveItem(); // 一緒に動かしているアイテムがあればアイテムスロットにアイテムを表示 if(item.sprite != null) { image.sprite = item.sprite; // 一緒に動かしているアイテムを非表示 moveIcon.ResetMoveItem(); } } } } } |
処理は今まで見てきたスクリプトとほとんど変わりません。
Iconにアイテムが設定されている時はDrowDownSlotの子要素のImageに同じSpriteを設定し、Iconで設定していたアイテムを解除しています。
これで機能が完成しました。
出来たUIの階層は
↑のようになりました。
Iconゲームオブジェクトは他のUI要素よりも手前側に表示したい為、一番下にドラッグして重なり順を変えておきます。
Unityを実行して確認する
それではUnityの実行ボタンを押して機能を確認してみましょう。
↑のようにアイテムを選択するとアイコンの左上にそのアイテムが表示され、それを装備スロットの上に持っていきSubmitボタンを押すと
アイコンの左上のアイテムが消え、装備スロットにアイテムが表示されました。
↑のサンプルはマウスアイコンがアイコン上にある時の処理でInput.GetButtonDownを使用して判断しているので、ボタンを押してもマウスアイコン左上のアイコンが変わらない事があります。
この記事ではInput.GetButtonDownをコメント化しInput.GetButtonを使用しているのでこの問題は出ないようになっています。
終わりに
今回の機能を作成したのは、家庭用据え置きゲームでコントローラーを使ったUIの操作に似たものを作成したかったからです。
単純にUIの要素のフォーカス移動でアイテムを選択するものが多いですが、ユーザーがUI内で自由に操作アイコンを移動出来る機能もほしかったからです。
んー・・でもフォーカスを単純に移動する方がステータス画面の操作としては簡単かも!?(^_^;)