Unity:如何实现TextMeshPro UI单个单词可拖拽?求解决方案
实现TextMeshPro单个单词可拖拽的解决方案
核心思路
不拆分单个单词为独立UI对象,通过文本解析+碰撞检测+富文本动态偏移实现单单词交互,完全适配不同屏幕尺寸。
具体实现步骤
1. 解析文本,定位单个单词的屏幕区域
利用TextMeshPro内置API,获取每个单词的字符范围与对应的屏幕矩形区域,存储为字典供后续交互判断:
using TMPro; using UnityEngine; using System.Collections.Generic; public class DraggableWordHandler : MonoBehaviour { private TMP_Text _tmpText; private Dictionary<TMP_WordInfo, Rect> _wordRectMap = new Dictionary<TMP_WordInfo, Rect>(); void Start() { _tmpText = GetComponent<TMP_Text>(); RefreshWordRects(); } // 刷新单词区域(屏幕尺寸变化/文本更新时调用) public void RefreshWordRects() { _wordRectMap.Clear(); TMP_TextInfo textInfo = _tmpText.textInfo; for (int i = 0; i < textInfo.wordCount; i++) { TMP_WordInfo wordInfo = textInfo.wordInfo[i]; // 获取单词首尾字符的屏幕坐标,计算包围矩形 Vector3 bottomLeft = _tmpText.transform.TransformPoint(textInfo.characterInfo[wordInfo.firstCharacterIndex].bottomLeft); Vector3 topRight = _tmpText.transform.TransformPoint(textInfo.characterInfo[wordInfo.lastCharacterIndex].topRight); Rect wordRect = new Rect(bottomLeft.x, bottomLeft.y, topRight.x - bottomLeft.x, topRight.y - bottomLeft.y); _wordRectMap[wordInfo] = wordRect; } } }
2. 处理点击/触摸事件,识别选中单词
在Update中检测输入,判断点击位置落在哪个单词区域内,记录选中状态与拖拽偏移:
private TMP_WordInfo _selectedWord; private Vector2 _dragOffset; private Vector2 _originalWordLocalPos; void Update() { if (Input.GetMouseButtonDown(0)) { Vector2 clickScreenPos = Input.mousePosition; _selectedWord = GetWordAtScreenPos(clickScreenPos); if (_selectedWord != null) { // 计算点击点与单词中心的偏移,避免拖拽跳变 Rect wordRect = _wordRectMap[_selectedWord]; _dragOffset = clickScreenPos - new Vector2(wordRect.center.x, wordRect.center.y); // 记录单词原始本地位置,用于复位 Vector3 originalWorldPos = _tmpText.transform.TransformPoint(_tmpText.textInfo.characterInfo[_selectedWord.firstCharacterIndex].bottomLeft); _originalWordLocalPos = _tmpText.transform.InverseTransformPoint(originalWorldPos); } } // 执行拖拽逻辑 if (Input.GetMouseButton(0) && _selectedWord != null) { Vector2 currentScreenPos = Input.mousePosition; Vector2 targetCenter = currentScreenPos - _dragOffset; // 转换为文本本地坐标,计算偏移量 Vector3 targetWorldPos = new Vector3(targetCenter.x, targetCenter.y, 0); Vector2 targetLocalPos = _tmpText.transform.InverseTransformPoint(targetWorldPos); Vector2 offset = targetLocalPos - _originalWordLocalPos; // 更新文本显示 UpdateDraggedWordOffset(offset); } // 释放拖拽,判断是否通关 if (Input.GetMouseButtonUp(0)) { if (_selectedWord != null) { if (IsWordInTargetArea()) { // 触发通关逻辑 Debug.Log("关卡通关!"); // 可移除该单词或标记为已完成 RemoveWordFromText(_selectedWord.GetWord()); } else { // 单词复位 ResetDraggedWord(); } _selectedWord = null; } } } // 根据屏幕坐标获取对应的单词 private TMP_WordInfo GetWordAtScreenPos(Vector2 screenPos) { foreach (var pair in _wordRectMap) { if (pair.Value.Contains(screenPos)) { return pair.Key; } } return null; } // 判断单词是否进入目标区域 private bool IsWordInTargetArea() { Rect targetScreenRect = GameObject.Find("TargetArea").GetComponent<RectTransform>().GetScreenRect(); return targetScreenRect.Overlaps(_wordRectMap[_selectedWord]); }
3. 用富文本实现单词拖拽视觉效果
通过TMP的<pos>富文本标签,动态修改选中单词的位置偏移,无需拆分UI:
private string _originalText; void Awake() { _originalText = _tmpText.text; } // 更新拖拽单词的位置偏移 private void UpdateDraggedWordOffset(Vector2 offset) { string word = _selectedWord.GetWord(); // 替换原单词为带位置偏移的富文本格式 string formattedWord = $"<pos=({offset.x},{offset.y})>{word}</pos>"; // 先清除之前的富文本标签,再替换 string cleanText = RemoveAllPosTags(_originalText); _tmpText.text = cleanText.Replace(word, formattedWord); } // 复位拖拽单词 private void ResetDraggedWord() { _tmpText.text = RemoveAllPosTags(_originalText); } // 移除所有<pos>标签,还原原始文本 private string RemoveAllPosTags(string text) { return System.Text.RegularExpressions.Regex.Replace(text, @"<pos=\([\d\.-]+,[\d\.-]+\)>|<\/pos>", ""); } // 移除已完成的单词 private void RemoveWordFromText(string word) { _originalText = _originalText.Replace(word, ""); _tmpText.text = _originalText; RefreshWordRects(); }
4. 屏幕适配优化
- 监听
Screen.resize事件,在屏幕尺寸变化时调用RefreshWordRects()重新计算单词区域。 - 使用
RectTransformUtility.ScreenPointToLocalPointInRectangle处理UI坐标转换,避免分辨率差异导致的定位偏差:
// 替代直接坐标转换,更适配UI系统 RectTransform rt = _tmpText.GetComponent<RectTransform>(); Vector2 localPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, Input.mousePosition, Camera.main, out localPoint);
额外提示
- 可添加
<color>或<material>富文本标签,给选中的单词添加高亮、阴影等视觉反馈。 - 针对重复单词场景,可通过字符索引而非单词内容来定位,避免替换错误。
内容的提问来源于stack exchange,提问作者Saeed Rahbari
相关产品推荐
相关产品推荐

