読者です 読者をやめる 読者になる 読者になる

Tutti Lab

元シリコンバレー在住のおっさん技術者、モバイルVRアプリ開発に挑戦中

【Unity開発Tips】ButtonでInputField的なGUIコンポーネントを作る

InputField iOS Android Unity開発Tips

前回書いた通り、iOS/Androidでは、uGUIのInputFieldを選択すると、OS固有のタッチスクリーンキーボードが立ち上がってしまいます。このため、iOS/Androidでは、uGUIのInputFieldと連動したバーチャルキーボードの実現は困難です。
今回は、InputFieldの使用を諦め、uGUIのButtonコンポーネントを改造して、InputField風に仕上げる方法についてご紹介します。かなりマニアックな用途ですが。。

ButtonとInputField

ButtonはInputField同様、uGUI用のGUIコンポーネントであり、その名前の通り「ボタン」の機能を提供します。Canvas上に貼り付けて使用し、見た目は凸型・文字列で「どのように機能するか」が書かれており、クリックするとOnClick()イベントが発生、文字列の機能の通りの実施(するように実装)します。
一方で、InputFieldは、テキスト入力を受け付ける機能を提供するものです。Canvas上に貼り付けて使用するのはButtonと一緒ですが、見た目は凹型・ユーザがキーボード等で入力した文字列が表示され、文字列が変化した時はOnValueChanged()イベントが、文字列の入力が完了した際はOnEndEdit()イベントがそれぞれ発生します。
f:id:tuti107:20160627023215p:plain
このように、ButtonとInputFieldは異なる見た目・機能を持つコンポーネントですが、見た目に関しては、

  • 枠のイメージをUISpriteからInputFieldBackgroundに変更
  • 文字列が見入力かつ未選択時はEnter text...を表示

すると、ButtonとInputFieldの見た目はほぼ一緒になります。
f:id:tuti107:20160627023353p:plain

Buttonなら、当然選択時にOS固有のタッチスクリーンキーボードが立ち上がることもありません。そこで、Buttonクラスを拡張して、InputField風のコンポーネントを作ってみました。

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;

public class VRInputField : Button {

	public Text textComponent;
	public string text;

	public Text placeFolder;

	public void NotifyKeyPressed(string keyStr) {

		text += keyStr;
		UpdateLabel ();
	}

	private void UpdateLabel() {

		if (placeFolder != null) {
			placeFolder.gameObject.SetActive (text == "" && this != selectedInputField);
		}

		textComponent.text = text;
	}

	public KeyboardTranform keyboardTransform = new KeyboardTranform();

	public void NotifyKeyboardHidden() {
		OnDeselect ();
	}

	private static VRInputField selectedInputField = null;

	public override void OnSelect(BaseEventData eventData) {

		base.OnSelect (eventData);

		Board.isShown = true;

		if (this != selectedInputField && selectedInputField != null) {
			selectedInputField.OnDeselect ();
			Board.ResetKeyboardTransform (this);
		}
		selectedInputField = this;

		UpdateLabel ();
	}

	public void OnDeselect() {
		selectedInputField = null;
		UpdateLabel ();
	}
}

適当なButtonコンポーネントを作成ののち、Button(Script)を削除して、代わりにこのスクリプトVRInputFieldをAdd Componentします。
合わせて、エディタ拡張を適用し、上記スクリプトの内容に応じたインスペクタ表示を行えるようにします(下記のスクリプトVRInputFieldInspectorをEditor配下におきます)。

using UnityEngine;
using UnityEditor;
using System.Collections;
using UnityEngine.UI;

[CustomEditor(typeof(VRInputField))]
public class VRInputFieldInspector : UnityEditor.UI.ButtonEditor {

	public override void OnInspectorGUI() {

		VRInputField f = (VRInputField)target;

		EditorGUILayout.LabelField ("Virtual Keyboard transform:");

		EditorGUI.BeginChangeCheck ();

		EditorGUI.indentLevel++;

		f.keyboardTransform.keyboardPosition = EditorGUILayout.Vector3Field ("LocalPosition", f.keyboardTransform.keyboardPosition);

		EditorGUILayout.BeginHorizontal ();
		EditorGUILayout.PrefixLabel ("Scale");
		f.keyboardTransform.keyboardScale = EditorGUILayout.FloatField (f.keyboardTransform.keyboardScale);
		EditorGUILayout.EndHorizontal ();

		EditorGUI.indentLevel--;

		EditorGUILayout.Separator ();

		f.textComponent = EditorGUILayout.ObjectField ("TextComponent", f.textComponent, typeof(Text), true) as Text;
		f.text = EditorGUILayout.TextField ("Text", f.text);

		EditorGUILayout.Separator ();

		f.placeFolder = EditorGUILayout.ObjectField ("PlaceFolder", f.placeFolder, typeof(Text), true) as Text;

		if (EditorGUI.EndChangeCheck ()) {
			EditorUtility.SetDirty (target);
		}

		base.OnInspectorGUI ();
	}
}

これにより、スクリプトVRInputFieldを貼り付けたButtonのインスペクタは下記の通りとなります。
f:id:tuti107:20160627030012p:plain

PlaceFolderはEnter Text..の文字列を持つTextです。このText、自作しても良いのですが、InputField生成時にInputFieldの子要素として自動生成されるもの(下図)をコピーし、Button配下にペーストする方が楽です。
f:id:tuti107:20160627025217p:plain
ペースト後、PlaceFolderのText(Script)を、VRInputFieldのPlaceFolderへドラッグ&ドロップして設定します。合わせて、Button配下のTextのText(Script)をText Componentへドラッグ&ドロップします。
VRInputFieldのUpdateLabel()メソッドは、これらPlaceFolder及びTextComponentを利用して、テキスト未入力・入力中ではない場合はPlaceFolderを、それ以外(テキスト入力中・入力済)ならTextComponentを表示します。これにより、InputFieldのような見た目が実現されます。

Boardクラスはバーチャルキーボードの本体機能の実装であり、Board.isShownをtrue/falseとして表示/非表示の切り替え、Board.ResetKeyboardTransform ()にて、バーチャルキーボードの表示位置・サイズを切り替え(前々回ご紹介の通り、keyboardTransformの設定内容に応じて表示)ます。
ボタン押下時にはOnSelect()が呼び出されるので、そこでバーチャルキーボードを表示します。なお、すでに他のボタンがバーチャルキーボードを表示している場合は、バーチャルキーボードの表示位置だけ切り替えます。
f:id:tuti107:20160627031148p:plain

まだ色々足りない

今回は、Buttonクラスを拡張し、InputField風のGUIコンポーネントを作ってみました。一見InputFieldと同じ感じなのですが、実はまだ色々と見た目・機能的に足りていません。
列挙してみると、

  • キャレット(入力位置に点滅表示されるカーソル)が表示されない。当然入力位置の変更もできず、現状文字列末尾の文字追加・削除のみ
  • 表示内容がスクロールしない。例えば複数行の文字列を入力しても最初の行しか表示されない等
  • OnValueChanged()/OnEndEdit()イベントが発生しない

その他色々・・。完全なInputFieldの実現まで行かないとしても、最低限上記は実装したいと考えています。
次回はこの辺りを実装しようと思います。