質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.59%

InputField内で文字列が変換中か確定済みかわかりづらい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 3,078

score 12

InputFieldを実装したゲームを実行し、日本語を入力しようと
入力文字を変換すると下記の画像のようになります。
画像では見えにくいですが、入力文字が変換中なのか確定済みなのかが判別できません。
変換中の場合は文字に波線または下線を加えることは可能でしょうか。

イメージ説明

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

確かに確定済みと変換中の区別がつかないのは気になりますね。調べてみたものの、インスペクタから設定をいじって手軽にできるような方法はないかもしれません。何とかならないかと思い実験してみましたが、ちょっとトリッキー感のあるものになってしまいました。

下記スクリプトを作成し、InputFieldにアタッチして実行してみたところ、

using System.Collections;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(InputField))]
public class InputFieldCompositionStringColorizer : MonoBehaviour
{
    // 変換中文字列を何色で表示するか?
    [SerializeField] private Color compositionStringColor = Color.blue;

    private IEnumerator Start()
    {
        // 文字列の整形時に位置の手がかりとなる各種フィールドは、アクセシビリティレベルが
        // protectedなのでむりやり探し出さないといけない
        var caretPositionField = typeof(InputField).GetField(
            "m_CaretPosition",
            BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic);
        var drawStartField = typeof(InputField).GetField(
            "m_DrawStart",
            BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic);
        var drawEndField = typeof(InputField).GetField(
            "m_DrawEnd",
            BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic);

        // 非公開のメンバーはUnityのバージョンアップで予告なしに使えなくなるかもしれないので...
        if ((caretPositionField == null) || (drawStartField == null) || (drawEndField == null))
        {
            // フィールドの取得に失敗したら、このスクリプトコンポーネントは削除する
            Debug.LogError("Couldn't find non-public members.");
            Destroy(this);
            yield break;
        }

        // InputFieldに参照されているTextはInputFieldのコントロール下にあるので
        // リッチテキスト機能が使えないため、Textを複製してそれを表示用に使う
        var field = this.GetComponent<InputField>();
        var text = field.textComponent;
        var richText = Instantiate(text, text.transform.parent);
        richText.name = "RichText";
        richText.supportRichText = true;

        // 入力用Textは文字色を透明にする
        text.color = Color.clear;

        // 入力用Textの文字色を変えるとキャレットの色まで変わってしまうので、InputFieldは
        // カスタムキャレット色を使うようにして、元のテキストの色をセットする
        field.customCaretColor = true;
        field.caretColor = richText.color;

        // 前のフレームの入力用Textの内容を覚えておくための変数
        var previousTextString = text.text;

        // Textの兄弟関係調整(後述)が終わったかどうかを示すフラグ
        var hierarchyAdjusted = false;

        // 「変換中」から「非変換中」への変わり目を検出するためのフラグ
        var compositionStringEmptied = false;

        // 「変換中」から「非変換中」へ変わった後、さらにもう1回だけ表示用Textの文字列設定を走らせるためのフラグ
        var needsReassignTextString = false;

        // 準備が整ったら、あとは毎フレーム文字列を監視して色付け処理を行う
        while (true)
        {
            // 表示用Textが入力用Textよりも手前に来るようにヒエラルキー上の兄弟関係を調整する
            if (!hierarchyAdjusted)
            {
                var textSiblingIndex = text.transform.GetSiblingIndex();
                var richTextSiblingIndex = richText.transform.GetSiblingIndex();
                if ((textSiblingIndex + 1) != richTextSiblingIndex)
                {
                    richText.transform.SetSiblingIndex(textSiblingIndex + 1);
                    hierarchyAdjusted = true;
                }
            }

            // 変換中の文字列を取得
            var compositionString = Input.compositionString;
            var compositionStringLength = compositionString.Length;

            // 入力用Textの表示内容を取得
            var textString = text.text;

            // 変換中であれば(変換中文字列が空でなければ)...
            if ((compositionStringLength > 0) || needsReassignTextString)
            {
                // まず現在のInputFieldの文字列(まだ変換中文字列が挿入されていない)を取得
                var fieldText = field.text;
                var fieldTextLength = fieldText.Length;

                // 変換中文字列を挿入するべき位置を調べる
                // ※field.caretPositionというプロパティもありますが、それとはちょっと位置が違うようです
                var insertAt = (int)caretPositionField.GetValue(field);

                // 挿入後文字列から表示用に切り出す範囲を調べる
                var clipFrom = (int)drawStartField.GetValue(field);
                var clipTo = Mathf.Min(
                    (int)drawEndField.GetValue(field),
                    fieldTextLength + compositionStringLength);
                var clipLength = clipTo - clipFrom;

                // 元の文字列に変換中文字列を挿入する
                var concatenatedString = fieldText.Insert(insertAt, compositionString);

                // 挿入後文字列の中のタグを付けるべき範囲を求める
                var tagFrom = Mathf.Max(insertAt, clipFrom);
                var tagTo = Mathf.Min(insertAt + compositionStringLength, clipTo);
                var tagLength = Mathf.Max(tagTo - tagFrom, 0);

                // 目的の範囲の前後にタグを挿入する
                var openTagLength = 0;
                var closeTagLength = 0;
                if (tagLength > 0)
                {
                    var openTag = $"<color=#{ColorUtility.ToHtmlStringRGBA(this.compositionStringColor)}>";
                    const string closeTag = "</color>";
                    concatenatedString = concatenatedString.Insert(tagTo, closeTag);
                    concatenatedString = concatenatedString.Insert(tagFrom, openTag);
                    openTagLength = openTag.Length;
                    closeTagLength = closeTag.Length;
                }

                // タグ付きの挿入後文字列から表示範囲を切り出し、表示用Textにセットする
                var newTextString = concatenatedString.Substring(clipFrom, openTagLength + clipLength + closeTagLength);
                richText.text = newTextString;

                // 未確定文字を削除することによって「非変換中」に戻った場合に、最後の未確定文字の残骸が残ってしまうようなので
                // その対策として「非変換中」に戻った後にもう一回だけ表示用Text更新を行わせることにした
                // 何も特別なスクリプトを付けていないInputFieldでもこの現象が起こるようだが、InputField自体の不具合だろうか...?
                if (needsReassignTextString)
                {
                    text.text = newTextString;
                    needsReassignTextString = false;
                }
                else
                {
                    compositionStringEmptied = true;
                }
            }
            else if (compositionStringEmptied)
            {
                compositionStringEmptied = false;
                needsReassignTextString = true;
            }
            // 変換中でなくとも、入力用Textの内容に変化(スクロールした、削除した、直接入力したなど)があったら...
            else if (textString != previousTextString)
            {
                // 表示用Textの内容を更新する
                richText.text = textString;
                previousTextString = textString;
            }

            yield return null;
        }
    }
}

下図のように変換中の部分が青色で表示されるようになりました(Unity 2018.3.6f1、Windows 10 Home バージョン1803上でプレイモードで動かし、Microsoft IME 10.0.17134.1で入力)。

動作の様子

protectedフィールドを勝手に覗いていますが、UnityのアップデートでInputFieldの実装が変われば急に動かなくなる可能性があります。また、動きの確認もプレイモードでちょこっとやっただけですので、どの環境でも大丈夫かというと保証しかねます。

また、ご所望の下線ではなく色付けになってしまいすみません。リッチテキストの機能で下線ぐらい付けられるだろう、と思っていたらダメでして(リッチテキスト - Unity マニュアルに使えるタグの一覧がありますが、下線は載っていませんでした)、もしやるとしたら文字の幅やらを勘案して座標を求め、自前で線を引くことになりそうだったため妥協しました...
そして変換中の文字は一律青色です。変換済み文節と未変換文節を塗り分けるのはもっと難しそうです(そもそも可能なのかも未調査です)。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/28 19:39

    返信が遅くなり大変申し訳ございません。
    .NET 4.x上で確認できました!
    ただ、現状では.NET 3.5を使用しているので、下記のエラーが発生します。
    バージョンアップすると別機能が動かなくなってしまうので、バージョンアップ対応も検討しようと思います。
    InputFieldCompositionStringColorizer.cs(116,36): error CS1644: Feature `interpolated strings' cannot be used because it is not part of the C# 4.0 language specification

    キャンセル

  • 2019/02/28 20:49

    文字列補間が使えませんでしたか、すみません。
    .NET 3.5非対応なのは多分そこのcolorタグを作っている部分だけかと思いますので、そこを...

    var openTag = string.Format("<color=#{0}>", ColorUtility.ToHtmlStringRGBA(this.compositionStringColor));

    の形にすれば、アップグレードしなくても動かせるかもしれません。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.59%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る