今回は前回作成したアイテムコマンドのアイテム一覧表示でキーボードやゲームコントローラー操作でアイテム一覧をスクロールする機能を作成していきます。
前回はコマンド画面にアイテムコマンドを作成しました。
ユニティちゃんのRPGを作ってみようの他の記事は
から見ることが出来ます。
前回作成したアイテムコマンドのアイテム一覧でマウス操作でスクロールバーを操作しアイテム一覧をスクロールする事が出来ました。
しかしボタンの選択をキーボードやゲームコントローラーで操作した時はスクロールバーを選択してスクロールしない限りはアイテム一覧をスクロール出来ませんでした。
そこで今回はキーボードやゲームコントローラーで選択するボタンを変更した時にアイテム一覧をスクロール出来るようにしたいと思います。
ただその機能を取り付ける場合はマウスでのスクロールが出来なくなります。(^_^;)
出来ないというよりはキーボードとゲームコントローラーによる操作とマウスでの操作を混在させて処理するのが大変なのでスクロールバーを直接操作出来なくしたといった方が正しいです。
両方の操作に対応したい!という方は色々試行錯誤してみてください。
現時点ではゲームコントローラーとキーボードでの操作に限定しておきます。
2020/07/17にスクロール機能を全面的に変更しました。
ItemPanel子要素のScrollbarをマウスで操作させない
まずはアイテム一覧(ItemPanel)のスクロールバーをマウスで操作出来ないようにします。
ItemPanel子要素のScrollbarを選択しScrollbarコンポーネントのinteractableのチェックを外します。
これでアイテム一覧のスクロールバーをマウス操作出来なくなりました。
特定のアイテムを選択、選択解除した時の処理を追加
前回、アイテム一覧で見えるアイテムの種類は14個までにしました。
そこでキーボードやゲームコントローラーで13、14個目のアイテムパネルボタンを選択した時にItemPanel子要素のContentを下にスクロールし、
15、16個目のアイテムパネルボタンを選択した時にContentを上にスクロールするようにします。
Assets/RPG/Scriptsに新しくScrollManagerというスクリプトを作成し、ItemPanelの子要素にあるContentに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ScrollManager : MonoBehaviour { // アイテムボタン表示用コンテンツ private Transform content; // スクロール中かどうか private bool changeScrollValue; // スクロールの目的の値 private float destinationValue; // スクロールスピード [SerializeField] private float scrollSpeed = 1000f; // 一回でスクロールする値 [SerializeField] private float scrollValue = 415f; // アイテム一覧のスクロールのデフォルト値 private Vector3 defaultScrollValue; // 前に選択していたボタン public static GameObject PreSelectedButton { get; set; } void Awake() { content = transform; defaultScrollValue = content.transform.position; } void Update() { if (!changeScrollValue) { return; } // 徐々に目的の値に変化させる content.transform.localPosition = new Vector3(content.transform.localPosition.x, Mathf.MoveTowards(content.transform.localPosition.y, destinationValue, scrollSpeed * Time.deltaTime), content.transform.localPosition.z); // ある程度移動したら目的地に設定 if (Mathf.Abs(content.transform.localPosition.y - destinationValue) < 0.2f) { changeScrollValue = false; content.transform.localPosition = new Vector3(0f, destinationValue, 0f); } } // 下にスクロール public void ScrollDown(Transform button) { if (changeScrollValue) { changeScrollValue = false; content.transform.localPosition = new Vector3(content.transform.localPosition.x, destinationValue, content.transform.localPosition.z); } if (ScrollManager.PreSelectedButton != null && button.position.y > ScrollManager.PreSelectedButton.transform.position.y) { destinationValue = content.transform.localPosition.y - scrollValue; changeScrollValue = true; } } // 上にスクロール public void ScrollUp(Transform button) { if (changeScrollValue) { content.transform.localPosition = new Vector3(content.transform.localPosition.x, destinationValue, content.transform.localPosition.z); changeScrollValue = false; } if (ScrollManager.PreSelectedButton != null && button.position.y < ScrollManager.PreSelectedButton.transform.position.y) { destinationValue = content.transform.localPosition.y + scrollValue; changeScrollValue = true; } } public void Reset() { PreSelectedButton = null; transform.position = defaultScrollValue; } } |
contentは自身のTransformを取得し設定します。
changeScrollValueは現在スクロールバーがスクロール中かどうか、destinationValueはスクロールの目的地、scrollSpeedはスクロールのスピード、scrollValueは1回でスクロールする値です。
PreSelectedButtonプロパティは前に選択していたアイテムパネルボタンを入れておく為に用意しました。
AwakeメソッドではcontentとdefaultScrollValueを設定しています。
UpdateメソッドではchangeScrollValueがfalseの時(スクロール中でない)はその後の処理を実行しません。
スクロール中の時はcontentのローカル位置にMathf.MoveTowardsを使って現在の値から目的の値へと徐々に変化させた値を設定します。
Mathf.Absで現在のY軸の位置と目的地の引き算の絶対値を求め、ある程度目的地に移動していたら現在の位置を目的地に設定し、スクロールを終えてます。
ScrollDownメソッドはアイテムパネルボタンに取り付けたスクリプトから呼び出し、スクロール中であれば現在の位置に目的地を設定し、スクロールを終了します。
これはスクロール中にさらにアイテムパネルボタンを移動した時にすぐに目的地に設定し、次のスクロールに備える為です。
ScrollManager.PreSelectedButtonがnullでなく引数で受け取ったアイテムパネルボタンが前に選択していたボタンよりYの位置が大きい時(現在のボタンが前に選択していたボタンより上にある時)にContentのYの位置をscrollValue分引きます。
ScrollUpメソッドはScrollDownの逆で現在のボタンが前に選択していたボタンより下にある時にContentのYの位置をscrollValue分足します。
Resetメソッドはアイテムパネル外をマウスで押した時等の初期化をする時に呼び出し、前に選択していたボタンのリセットとスクロールのリセットを行います。
ここで設定したscrollValueの値は調べる必要があり、そのやり方を記載します。
Unityを実行しアイテム一覧を表示したらContentゲームオブジェクトを選択し、15個目、16個目のアイテムが一番上に表示される位置にMove Toolを選択し、シーンビューのY軸の矢印をドラッグします。
シーンビューでは以下のように15、16個目のアイテムパネルボタンが一番上に来るようにドラッグします。
15、16個目のアイテムは『普通の剣』と『痺れ回復薬』となっており、ゲームビューで確認すると以下のように一番上に表示されるようにContentを移動させます。
移動させた状態でContentゲームオブジェクトのインスペクタのRect TransformのPos Yの値を確認し、これをCommandScriptのscrollValueに設定するようにします。
scrollValueの値によってはスクロールする毎に徐々にアイテムパネルボタンの表示位置がずれてしまう可能性もあります。
アイテムパネルボタンの高さ、アイテムパネルボタン同士の隙間等を厳密に計算しscrollValueを設定した方がいいかもしれません。
ボタンに取り付けるスクリプトの作成
ScrollManagerスクリプトでアイテム一覧のスクロールをしますが、その処理を呼び出すスクリプトを作成します。
Assets/RPG/Scriptsフォルダに新しくScrollDownScriptとScrollUpScriptを作成します。
どちらのスクリプトもCommandScript内から該当するボタンに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class ScrollDownScript : MonoBehaviour, ISelectHandler, IDeselectHandler { private ScrollManager scrollManager; void Start() { scrollManager = GetComponentInParent<ScrollManager>(); } // ボタンが選択された時に実行 public void OnSelect(BaseEventData eventData) { scrollManager.ScrollDown(transform); ScrollManager.PreSelectedButton = gameObject; } // ボタンが選択解除された時に実行 public void OnDeselect(BaseEventData eventData) { } } |
ScrollDownScriptはMonoBehaviour、ISelectHandler、IDeselectHandlerを継承して作成し、ボタンを選択した時、ボタンの選択を解除した時の処理を実装します。
ScrollDownScriptはScrollManagerスクリプトを自身の親の階層から探して取得します。
OnSelectメソッドはボタンが選択された時に呼び出されるので、そこでScrollManagerのScrollDownメソッドを自身のTransformを渡して呼び出します。
またScrollManager.PreSelectedButtonに自身のゲームオブジェクトを設定し前に選択していたボタンに自身を設定します。
ボタン選択解除時の処理は今回何もしていませんが、何か必要なことがあるかもしれないので作っています。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class ScrollUpScript : MonoBehaviour, ISelectHandler, IDeselectHandler { private ScrollManager scrollManager; void Start() { scrollManager = GetComponentInParent<ScrollManager>(); } // ボタンが選択された時に実行 public void OnSelect(BaseEventData eventData) { scrollManager.ScrollUp(transform); ScrollManager.PreSelectedButton = gameObject; } // ボタンが選択解除された時に実行 public void OnDeselect(BaseEventData eventData) { } } |
ScrollUpScriptスクリプトはScrollDownScriptとほぼ同じでScrollUpメソッドを呼び出す処理に変えています。
CommandScriptでScrollDownScriptとScrollUpScriptの取り付け
アイテム一覧をスクロールする処理はScrollManagerに記述しました。
アイテム一覧をスクロールさせる為に一画面の最後のボタン二つにScrollDownScriptを取り付け、スクロール後の最初のボタン二つにScrollUpScriptを取り付ける必要があります。
なのでItemPanelButtonPrefabプレハブをインスタンス化した時、12、13個目のボタンだった時にScrollDownScript、14、15個目のボタンだった時にScrollUpScriptを取り付けます。
CommandScriptスクリプトに処理を追加します。
まずはフィールドです。
1 2 3 4 5 6 7 8 9 10 | // アイテムパネルボタンでどの番号のボタンから上にスクロールするか [SerializeField] private int scrollDownButtonNum = 12; // アイテムパネルボタンでどの番号のボタンから下にスクロールするか [SerializeField] private int scrollUpButtonNum = 14; // ScrollManager private ScrollManager scrollManager; |
scrollDownButtonNumは何個目のボタンからスクロールするかの指定で、数値は0から始まるので12を設定すると12、13個目のボタンが選択された時にContentを下にスクロールします。
scrollUpButtonNumも同じでこちらの場合は14、15個目のボタンが選択された時にContentを上にスクロールします。
Awakeメソッドに処理を追加します。
1 2 3 | scrollManager = content.GetComponent<ScrollManager>(); |
contentを取得した後に処理を追加し、contentに取り付けたScrollManagerスクリプトを取得します。
CommandScriptのCreateItemPanelButtonメソッドに処理を追加します。
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 | // キャラクターが持っているアイテムのボタン表示 public void CreateItemPanelButton(AllyStatus allyStatus) { itemInformationPanel.SetActive(true); selectCharacterPanelCanvasGroup.interactable = false; // アイテム一覧のスクロール値の初期化 scrollManager.Reset(); // アイテムパネルボタンを何個作成したかどうか int itemPanelButtonNum = 0; GameObject itemButtonIns; // 選択したキャラクターのアイテム数分アイテムパネルボタンを作成 // 持っているアイテム分のボタンの作成とクリック時の実行メソッドの設定 foreach (var item in allyStatus.GetItemDictionary().Keys) { itemButtonIns = Instantiate<GameObject>(itemPanelButtonPrefab, content.transform); itemButtonIns.transform.Find("ItemName").GetComponent<Text>().text = item.GetKanjiName(); itemButtonIns.GetComponent<Button>().onClick.AddListener(() => SelectItem(allyStatus, item)); itemButtonIns.GetComponent<ItemPanelButtonScript>().SetParam(item); // 指定した番号のアイテムパネルボタンにアイテムスクロール用スクリプトを取り付ける if (itemPanelButtonNum != 0 && (itemPanelButtonNum % scrollDownButtonNum == 0 || itemPanelButtonNum % (scrollDownButtonNum + 1) == 0) ) { itemButtonIns.AddComponent<ScrollDownScript>(); } else if (itemPanelButtonNum != 0 && (itemPanelButtonNum % scrollUpButtonNum == 0 || itemPanelButtonNum % (scrollUpButtonNum + 1) == 0) ) { // アイテムスクロールスクリプトの取り付けて設定値のセット itemButtonIns.AddComponent<ScrollUpScript>(); } |
CreateItemPanelButtonメソッドが呼ばれたらScrollManagerのResetメソッドを呼んでリセットします。
これは最初にユニティちゃんのアイテムを表示した時にアイテム一覧をスクロールした状態で、大鳥ゆうじのアイテム一覧を表示するとスクロールしたままになってしまうので、毎回リセットをする為です。
itemPanelButtonNumは1画面中でアイテムパネルボタンの番号を保持します。
持っているアイテム数分のitemPanelButtonPrefabをインスタンス化している処理で、インスタンス化したボタンが0番目、itemPanelButtonNumをscrollDownButtonNumで割って余りが0の時、scrollDownButtonNum + 1で割って余りが0の時にそのアイテムパネルボタンのインスタンスにScrollDownScriptを取り付けます。
AddComponentで該当するコンポーネントをゲームオブジェクトに取り付けることが出来ます。
ScrollUpScriptを取り付ける処理も同じように行います。
itemPanelButtonNumをインクリメントした後にitemPanelButtonNumの値を変更する処理を追加します。
1 2 3 4 5 6 7 8 9 | // アイテムパネルボタン番号を更新 itemPanelButtonNum++; if(itemPanelButtonNum == scrollUpButtonNum + 2) { Debug.Log(itemPanelButtonNum); itemPanelButtonNum = 2; } |
スクリプトを取り付けるアイテムパネルボタンの番号を変更し、スクロールした後のボタンも同じようにスクリプトを取り付けられるようにします。
アイテムパネル外をマウスで押した時の処理に追加
アイテムパネル以外をマウスで押すと選択がItemPanelの最初の子要素を強制的に選択させるという処理をアイテムコマンドを作成した時に取り付けましたが、今回のアイテムスクロールをした後にパネル外をマウスで押すとスクロールをした状態のまま最初のボタンが選択されるので、スクロール値も最初の位置に戻す処理を追加します。
CommandScriptのUpdateメソッド内の強制的にボタンを選択させる処理にItemPanelのスクロール値を初期位置に戻す処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 選択解除された時(マウスでUI外をクリックした)は現在のモードによって無理やり選択させる if (EventSystem.current.currentSelectedGameObject == null) { if (currentCommand == CommandMode.CommandPanel) { EventSystem.current.SetSelectedGameObject(commandPanel.transform.GetChild(0).gameObject); } else if (currentCommand == CommandMode.ItemPanel) { EventSystem.current.SetSelectedGameObject(content.transform.GetChild(0).gameObject); scrollManager.Reset(); } else if (currentCommand == CommandMode.ItemPanelSelectCharacter) { EventSystem.current.SetSelectedGameObject(selectCharacterPanel.transform.GetChild(0).gameObject); } else if (currentCommand == CommandMode.StatusPanel) { EventSystem.current.SetSelectedGameObject(selectCharacterPanel.transform.GetChild(0).gameObject); } else if (currentCommand == CommandMode.StatusPanelSelectCharacter) { EventSystem.current.SetSelectedGameObject(selectCharacterPanel.transform.GetChild(0).gameObject); } else if (currentCommand == CommandMode.UseItemPanel) { EventSystem.current.SetSelectedGameObject(useItemPanel.transform.GetChild(0).gameObject); } else if (currentCommand == CommandMode.UseItemSelectCharacterPanel) { EventSystem.current.SetSelectedGameObject(useItemSelectCharacterPanel.transform.GetChild(0).gameObject); } } |
currentCommandがCommandMode.ItemPanelの時に
1 2 3 | scrollManager.Reset(); |
を追加します。
これで機能が完成しました。
アイテム一覧をゲームコントローラーでスクロールしてみる
機能が出来たのでキーボードのキーかゲームコントローラーを使ってアイテム一覧がスクロールされるか確認してみましょう。
上のように15、16個目のボタンを選択、選択解除した時にアイテム一覧がスクロールするようになりました。
終わりに
これでアイテムコマンドが完成しました。
ただアイテムの選択をする時の移動が少し遅いですね。
というわけで次回はアイコンの移動(次のボタンの選択処理)が早くなるように設定を変更してみます。
この作品はユニティちゃんライセンス条項の元に提供されています