Unity WebGLの日本語入問題と対策


Unityはゲーム制作について、とても素晴らしいツールであり、更なるアップデートで進化を続けています。


しかし、そんな完璧に見えるツールでもまだ改善する所はあります。
今日の話は入力フィールドのUnicode入力です。


Unityで提供している入力フィールド(UnityEngine.UI.InputField)

プラットフォームによって、デバイスが提供しているIME(入力装置)を完全に対応する(モバイルなど)プラットフォームもありますが、Webプラットフォームについては、そうではありません。

WebGLの場合、基本提供しているInputFieldで日本語入力ができないことが分かります。
(英文字なら問題なく入力可能)

その理由は、「ブラウザIMEのアクセス権限をUnity側が持ってない」 からです。それで、特殊なIME(日本語のromaji入力や組み合わせ入力など)の入力が、思い通りに動かないです。


これはゲームの中でプレイヤーの名前を入れたり、チャット機能などを実装する時、ややこしい問題になります。



WebGL用入力問題を解決する方法について、色々検索してみましたところ、「WebGLNativeInputField」 というパッケージを見つけました。




こちらのデモで動作確認ができます。



このパッケージを使うと、WebGLでも日本語入力が問題なくできます。
(僕が調べた限り、これがUnityのWebGLで日本語入力できる唯一な対策ではないかと思われます。)

ただ1つ問題があるとすれば、(製作者の方には申し訳ないですが)入力するとき、別途のUIが出てくることです。


入力フィールドを触るとこのポップが出る


オーバーレイタイプも選択可能(こっちの方が少し自然に見えます。)

そもそも、WebGLNativeInputFieldで使われた方法は、「Unity上の入力フィールドを使うことではなく、ブラウザ上に仮想の入力フォームを作り、そこて入力された値をUnityに渡す」 ことです。確かに、この方法ならブラウザのIMEを自由に使用することができます。


でも入力の度にポップが飛び出るのも嫌だし、この入力フォームとゲーム画面との違和感が、どうしても気になりました。

それで、入力フォームをゲーム画面と一致させるよう、コードを修正してみました。


基本アイデアは、これです。
  1. 入力ObjectのTransform情報(座標や大きさなど)をブラウザーに渡す
  2. 指定された領域に入力フォームを生成

では、このパッケージに、少し修正をかけます。

  • 入力フィールドのTransform情報を渡すため、Interface部分を修正
  • 格納場所:WebGLNativeInputField/Scripts/WebNativeDialog.cs


    SetupOverlayDialogHtml関数を修正します。引数に入力フィールドのRect情報と実際テキスト領域とのPadding情報、テキストのFontサイズを追加します。

    private static extern string SetupOverlayDialogHtml(string title, string defaultValue, string okBtnText, string cancelBtnText);
    修正前

    private static extern bool SetupOverlayDialogHtml(string defaultValue, int x, int y, int width, int height, int paddingTop, int paddingRight, int paddingBottom, int paddingLeft, int fontSize);
    修正後


  • InputFieldコンポーネントで、入力フィールドのRect情報を計算して渡す処理を追加
  • 格納場所:WebGLNativeInputField/Scripts/WebGLNativeInputField.cs
    (UnityEngine.UI.InputFieldを継承したコンポーネントです。)

    ここで注意する点は 「Unityの座標情報はブラウザの座標と一致しないこと」 です。これはRetina画面を使ってるパソコンで発生している問題で、 「Unityは画面上のpixel情報に基づいた座標を使っている反面、ブラウザは目に見えるpointサイズに基づいた座標を使っている」 からです。(つまり、retina displayでウェブサイトを開いても、普通のディスプレイと同じ大きさで見えるようにpixel座標を補正しています。)

    Unity2019.2まではエンジンで指定されたサイズに固定されましたが、2019.3以降からブラウザーのPixelサイズによってScreen sizeが変わることになりました。

    正確な座標計算のために、Canvasのスケールモードを 「Scale With Screen Size」 にします。

    (これによって、画面サイズが変わってもCanvas内部のUI座標は崩れません。)

    Screnn sizeをInputFieldに渡し、それでpixel情報を計算し直して、その結果をブラウザに渡すように修正します。

    public class WebGLNativeInputField : InputField
    {
        ...
        [SerializeField] Vector2 screenSize = Vector2.zero; // 追加
        ...
    強調部分をWebGLNativeInputFieldクラスに追加

    追加したPropertyがインスペクターに見えるように、WebGLNativeInputFieldEditorも修正します。
    格納場所:WebGLNativeInputField/Editor/WebGLNativeInputFieldEditor.cs

    public class WebGLNativeInputFieldEditor : InputFieldEditor
    {
        ...
        private SerializedProperty screenSize; // 追加

        protected override void OnEnable()
        {
            base.OnEnable();
            ...
            screenSize = serializedObject.FindProperty("screenSize"); //追加
        }

        public override void OnInspectorGUI()
        {
            ...
            serializedObject.ApplyModifiedProperties(); //追加
            base.OnInspectorGUI();
        }
    }


    「Start()」 関数で、Transform情報を計算するコードを追加します。

    private int _x, _y, _width, _height;
    private int _paddingTop, _paddingRight, _paddingBottom, _paddingLeft;
    private int _fontSize;

    protected override void Start()
    {
        if (screenSize == Vector2.zero) screenSize = new Vector2(Screen.width, Screen.height);

        RectTransform inputTransform = transform as RectTransform;
        Vector3[] corners = new Vector3[4];
        inputTransform.GetWorldCorners(corners);
        Transform canvas = FindObjectOfType<Canvas>().transform;
        Vector3 center = new Vector3(screenSize.x / 2f, screenSize.y / 2f, 0);
        for (int i = 0; i < 4; i++)
        {
            corners[i] = corners[i] / canvas.localScale.x + center;
            corners[i].y = screenSize.y - corners[i].y;
        }
        var pos = new Vector3(Mathf.Min(corners[0].x, corners[2].x), Mathf.Min(corners[0].y, corners[2].y), 0);
        var size = new Vector3(Mathf.Abs(corners[0].x - corners[2].x), Mathf.Abs(corners[0].y - corners[2].y), 0);

        RectTransform textTransform = textComponent.transform as RectTransform;
        var offsetMin = textTransform.offsetMin;
        var offsetMax = textTransform.offsetMax;

        _x = Mathf.RoundToInt(pos.x);
        _y = Mathf.RoundToInt(pos.y);
        _paddingTop = Mathf.RoundToInt(-offsetMax.y);
        _paddingRight = Mathf.RoundToInt(-offsetMax.x);
        _paddingBottom = Mathf.RoundToInt(offsetMin.y);
        _paddingLeft = Mathf.RoundToInt(offsetMin.x);
        _width = Mathf.RoundToInt(size.x) - _paddingLeft - _paddingRight;
        _height = Mathf.RoundToInt(size.y) - _paddingTop - _paddingBottom;
        _fontSize = textComponent.fontSize;
    }


    SetUpOverlayDialogを呼び出す部分を修正します。

    public override void OnSelect(BaseEventData eventData)
    {     ...
        WebNativeDialog.SetUpOverlayDialog(m_DialogTitle, this.text , m_DialogOkBtn , m_DialogCancelBtn );
        ...
    修正前

    public override void OnSelect(BaseEventData eventData)
    {     ...
        WebNativeDialog.SetUpOverlayDialog(this.text, _x, _y, _width, _height, _paddingTop, _paddingRight, _paddingBottom, _paddingLeft, _fontSize );
        ...
    修正後


  • ブラウザで入力フォームを作るコードを修正
  • 格納場所:WebGLNativeInputField/Plugins/WebGL/WebNativeDialog.jslib
    (中身はjavascriptです。)



    渡されたTransform情報を、使って入力フォームを配置するように、修正かけます。

    // setup html
    var html = '<div id="nativeInputDialog" style="background:#000000;opacity:0.9;width:100%;height:100%;position:fixed;top:0%;z-index:2147483647;">' +
        '   <div style="position:relative;top:30%;" align="center" vertical-align="middle">' +
        '     <div id="nativeInputDialogTitle" style="color:#ffffff;">Here is title</div>' +
        '     <div>' +
        '       <input id="nativeInputDialogInput" type="text" size="40" onsubmit="">' +
        '     </div>' +
        '     <div style="margin-top:10px">' +
        '       <input id="nativeInputDialogOkBtn" type="button" value="OK" onclick="" >' +
        '       <input id="nativeInputDialogCancelBtn" type="button" value="Cancel" onclick ="">' +
        '       <input id="nativeInputDialogCheck" type="checkBox" style="display:none;">' +
        '     </div>' +
        '   </div>' +
        '</div>';
    修正前


    // setup html
    var inputStyle = 'background:rgba(255, 255, 255, 0);' +
        'font-size:' + fontSize.toString() + 'px;' +
        'position:absolute;' +
        'top:' + textY.toString() + 'px;left:' + textX.toString() + 'px;height:' + textH.toString() + 'px;width:' + textW.toString() + 'px;' +
        'padding: ' + paddingT.toString() + 'px ' + paddingR.toString() + 'px ' + paddingB.toString() + 'px ' + paddingL.toString() + 'px;' +
        'margin:0px;border:0px;';
    var html = '<div id="nativeInputDialog" style="width:100%;height:100%;position:absolute;top:0px;left:0px;z-index:2147483647;">' +
        '   <input id="nativeInputDialogCheck" type="checkBox" style="display:none;">' +
        '   <div id="nativeInputBg" style="background:#000000;opacity:0;position:absolute;top:0px;left:0px;width:100%;height:100%;margin:0px"></div>' +
        '   <form id="nativeInputForm" onsubmit="">' +
        '     <input id="nativeInputDialogInput" type="text" style="' + inputStyle + '">' +
        '     <input type="submit" style="display:none;">' +
        '   </form>' +
        '</div>';
    修正後

    入力Objectを生成するコードを見ると、親がRootになっています。そうするとフォームの位置がブラウザ基準になるので、親をゲーム画面に変えます。それで入力フォームの位置はゲーム画面にくっ付けられます。

    ...
     // write to html
     document.appendChild( element );
    修正前

    ...
     // write to html
     document.getElementById("unityContainer").appendChild( element );
    修正後:Unity客体はunityContainerというidが付けられています。


    これで必要なコード修正は終わりました。

    ※ 実際は他の部分も色々修正されましたが、ここでは見た目の修正分だけ説明し、他の部分は飛ばしました。


  • テスト
  • コード修正が終わったら画面にWebGLNativeInputFieldしてUIを構成してみます。

    したの部分がWebGLNativeInputFieldです。


    Screen Size欄に画面サイズを入れます。



    Unity上では確認ができないので、WebGLでビルドして確認します。

    いい感じで入力フィールドがゲームに溶け込んでいます!


    まだ課題は幾つか残っていますが、(改行の対応、色情報の追加など…)これでゲームにチャット機能を入れることができて嬉しいです。


    ※ この入力フィールドはハバネロサイトに掲載されたゲームで使われています。入力フィールドの動作が気になる方は是非お試しください。







    ※ 元ブログから移転された記事です。

    コメント