using System; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; public enum KeyboardLayout { Default, Numeric, Hex, Pinyin // 拼音键盘(支持中文输入) } /// /// 动态生成自定义键盘并将按键事件转发给 InputDialog /// 用法:把该脚本挂在 CustomKeyboardPanel 上,面板内部应包含一个用于放按键的容器(可不设置则使用自身)。 /// public class CustomKeyboard : MonoBehaviour { public Transform KeysContainer; // 按键放置容器 [Tooltip("如果设置了 KeyPrefab,则在运行时使用该 prefab 来生成按键;如果为空,脚本会创建简单的 GameObject(仅在运行时)。")] public GameObject KeyPrefab; public Vector2 KeySize = new Vector2(60, 60); public Vector2 KeySpacing = new Vector2(6, 6); private object _target; // 可以是 InputDialog 或 KeyboardInputAdapter private KeyboardLayout _layout = KeyboardLayout.Default; private bool _isPasswordMode = false; private bool _isUpperCase = false; // 拼音键盘大小写切换 private GridLayoutGroup _grid; // 拼音输入相关 private string _pinyinBuffer = ""; // 拼音缓冲区 private Transform _candidateContainer; // 候选字容器 public Action OnKeyboardSizeChanged; // 键盘大小变化回调 // 翻页相关 private int _currentPage = 0; // 当前页码(0基索引) private const int _candidatesPerPage = 7; // 每页显示的候选字数量 private string[] _allCandidates = null; // 当前拼音的所有候选字 void Awake() { // 如果 KeysContainer 未设置,尝试查找;否则 InputDialog.CreateDynamicKeyboard() 已设置 if (KeysContainer == null) { var child = this.transform.Find("KeysContainer"); if (child != null) KeysContainer = child; else KeysContainer = this.transform; } _grid = KeysContainer.GetComponent(); // 如果容器本身没有 GridLayoutGroup 则添加一个(仅在这里需要,动态创建时已添加) if (_grid == null) { _grid = KeysContainer.gameObject.AddComponent(); _grid.cellSize = KeySize; _grid.spacing = KeySpacing; } } public void SetTarget(object target, KeyboardLayout layout, bool isPassword) { _target = target; _layout = layout; _isPasswordMode = isPassword; _isUpperCase = false; _pinyinBuffer = ""; RebuildKeys(); } public void RebuildKeys() { // 注意: 修改 Prefab Asset(例如在 Project 视图中打开的 Prefab)时, // 直接对其 Transform 做 SetParent/Destroy 会导致 Unity 抛出错误: // "Setting the parent of a transform which resides in a Prefab Asset is disabled to prevent data corruption" // 因此此方法只会在运行时(Play mode)修改场景实例的子对象。 if (!Application.isPlaying) { Debug.Log("RebuildKeys skipped: only runs in Play mode to avoid editing prefab assets. For editing prefab assets use the Editor -> Prefab editing workflow."); return; } // 清除已有按键(运行时安全) for (int i = KeysContainer.childCount - 1; i >= 0; --i) { var child = KeysContainer.GetChild(i); if (Application.isPlaying) Destroy(child.gameObject); else DestroyImmediate(child.gameObject); } switch (_layout) { case KeyboardLayout.Numeric: BuildNumeric(); break; case KeyboardLayout.Hex: BuildHex(); break; case KeyboardLayout.Pinyin: BuildPinyin(); break; default: BuildDefault(); break; } // add control row: Clear Backspace Confirm BuildControlRow(); } private void BuildKey(string label, Action onClick) { // 优先使用 KeyPrefab(需在 Inspector 中赋值),在运行时用 Instantiate 创建实例并设置父物体——这是安全的。 GameObject go = null; if (KeyPrefab != null) { go = Instantiate(KeyPrefab, KeysContainer, false); go.name = "Key_" + label; // 如果 prefab 没有自动设置文本,需要查找并设置 var tmp = go.GetComponentInChildren(); if (tmp != null) tmp.text = label; var btn = go.GetComponent