# 问题描述

在 Unity 开发中,将项目打包为 WebGL 并在浏览器中运行后,点击 TMP_InputField 或原生 InputField 时,玩家无法唤起中文输入法(IME),导致只能输入英文字母,无法输入中文。

# 原因分析

Unity WebGL 的渲染目标是一个 HTML5 <canvas> 元素。浏览器默认只会为标准的 HTML 输入标签(如 <input><textarea> )唤出系统的输入法(IME)面板。由于 <canvas> 只是一个绘图区域,它只能直接捕获键盘的按键事件(KeyDown/KeyUp),无法触发操作系统的输入法组合态逻辑。

# 解决方案:HTML Input 覆盖法

目前行业内通用的解决思路是 HTML DOM 覆盖层:当玩家点击 Unity 画面中的输入框时,在网页的最上层动态生成一个透明的 HTML <input> 元素,将其精确对齐到 Unity 输入框的位置。玩家实际上是在 HTML 输入框里打字(支持完美的中文 IME),插件再实时将网页中的文本同步回 Unity 的 UI 中。

# 具体实施步骤

# 1. 使用开源 WebGL 补充插件

由于自己编写和维护 JSlib(Unity 与浏览器的通信脚本)比较繁琐,推荐直接使用成熟的开源插件,例如 GitHub 上广泛使用的 Unity-WebGL-SupportWebGLInput

  • 前往 GitHub 搜索并下载对应的 .unitypackage
  • 导入项目后,插件通常会提供一个名为 WebGLInput 或类似名称的 C# 组件。
    ⚠️ 极其关键的一步:配置 Plugins 目录
    这种插件的核心并不是 C#,而是用于和浏览器 DOM 通信的 JavaScript 桥接文件。
    你必须确保插件包中后缀为 .jslib 的文件,被放置在 Unity 项目的 Assets/Plugins/WebGL 目录下。只有放置在这个极其特殊的目录中,Unity 打包时才会将其编译进最终的 WebGL 网页代码中。

# 2. 配置输入框

  1. 在场景中选中你的 TMP_InputField 对象。不能是子对象或者子 text 对象。
  2. 点击 Add Component,将插件提供的 WebGLInput (或 WebGLSupport )脚本挂载到该对象上。
  3. 该脚本会自动接管此输入框在 WebGL 环境下的焦点获取与跨平台文本同步。

# 3. 字体配置(关键兜底)

解决了 “输入” 问题后,必须确保 “显示” 问题。WebGL 打包后不会自动读取玩家电脑本地的系统字体。

  • 如果你使用的是 TextMeshPro,必须提前通过 Window -> TextMeshPro -> Font Asset Creator ,使用包含中文字符的 .ttf 字体文件(如思源黑体)生成 SDF 字体资源。
  • 确保 TMP_InputField 使用的字体资源包含了你需要的所有常用中文字符(通常需要包含 3500 常用字或更大字符集),否则输入的中文在 WebGL 中会变成方块(□)。

# 补充优化:解决拼音输入时的字符截断

在解决了 WebGL 的中文输入支持后,如果你还需要限制玩家的昵称长度(例如最多 5 个汉字),请千万不要直接在 Unity 面板中设置 Character Limit。因为原生的限制会把玩家在 HTML 输入框中敲击的拼音字母也算作字符,从而强制截断。

你需要将输入框的限制设为 0,并配合代码在输入完成后进行裁切(参考以下逻辑):

using UnityEngine;
using TMPro;
public class WebGLInputLimiter : MonoBehaviour
{
    public TMP_InputField targetInput;
    private const int MaxLength = 5;
    void Start()
    {
        // 关闭原生限制,防止打断拼音
        targetInput.characterLimit = 0;
        targetInput.onValueChanged.AddListener(OnInputChanged);
    }
    private void OnInputChanged(string text)
    {
        // 检查输入法组合态,如果不为空,说明玩家正在拼写,直接放行
        if (!string.IsNullOrEmpty(Input.compositionString)) return;
        // 选词落字后,进行长度截断
        if (text.Length > MaxLength)
        {
            targetInput.SetTextWithoutNotify(text.Substring(0, MaxLength));
        }
    }
}
更新于