我在Unity中有一个设置大小的文本框,我想用文本填充它直到它已满,然后等待用户翻页然后用剩余的文本重新填充它,重复直到所有文字已被查看。我们的想法是创建一个iBooks / Kindle类型的应用程序。无论用户选择哪种字体大小,我都希望它能够正常工作(例如,视力障碍者可以选择更大的字体大小)。 iBooks和Kindle中也存在类似的功能。
我遇到的挑战是:我怎么知道文本框何时满了?
我已经阅读了很多有关堆栈溢出和answers.unity3d.com的帖子,并提出了以下方法,但效果不佳。
基本上我的想法是使用Text Mesh对象(对用户不可见)并一次在Text Mesh中放置一个单词,允许Unity渲染它,然后查询边界框以找出有多宽文字有多高。使用这些信息,我可以知道在我的文本框中添加一个单词是否会导致我超出文本框的宽度(因此是新行的时间)和/或超出文本框的高度(因此它是等待用户翻页的时间。)
然而,这种方法存在一些问题。
1)My Text Mesh不会创建与我的文本框大小相同的屏幕文本,即使我确保它们都具有相同的位置(相对于相机),相同的缩放比例(1,1, 1),相同的字体大小和相同的字体类型。文本网格有一个名为字符大小的属性,我必须手动微调试错,直到两个看起来大小相同。
2)查看Text Mesh的边界框,可以看出Unity世界单位的尺寸。我必须将这些单位转换为像素,这需要了解相机的位置和方向。我使用WorldToScreenPoint做到了这一点,但我认为它不是很准确。
3)当我在文本框中添加行时,我正在通过查看文本网格大小来累积我添加的行的总高度。但是,这并没有包含行之间包含的空间量,因此我对文本块高度的计算是关闭的。我尝试使用mesh.font.lineheight
,但这似乎没有给我一个准确的高度,或者我可能不知道它正在使用的单位是什么。
有没有人有更好的想法来实现我在这里尝试做的事情?看起来填充文本框直到它已满是一件相当简单的事情,但我发现这个主题的答案很稀疏。
这是我到目前为止的代码。在这个例子中,我想出了一个用于测试目的的文本字符串。但是,在典型用法中,此文本在编译时不会知道,而是在运行时从文件中读取文本时确定。
private string myText =
"A-tisket, a-tasket\nA green and yellow basket\n" +
"I wrote a letter to my love\nAnd on the way I dropped it\n" +
"I dropped it\nI dropped it\nYes, on the way I dropped it\n" +
"A little boy he picked it up \nand put it in his pocket.\n\n" +
"Baa, baa, black sheep\nHave you any wool?\nYes sir, yes sir\n" +
"Three bags full.\nOne for my master\nAnd one for the dame\n" +
"One for the little boy\nWho lives down the lane.";
public Text textBox;
public TextMesh mesh;
public Button nextButton;
private int wordnum;
private List<string> words;
private Camera myCamera;
void Start ()
{
// divide the text up into words
words = new List<string> (
myText.Split (new string[] { " ", "\n", "\t" },
System.StringSplitOptions.RemoveEmptyEntries));
// store a reference to the camera
myCamera = GetComponent<Camera>();
// don't allow the user to click Next Page button unless there's more text to view
nextButton.enabled = false;
// turn off rendering of the text mesh because the user shouldn't see it
mesh.GetComponent<Renderer>().enabled = false;
wordnum = 0;
setContents ();
}
void setContents ()
{
float heightSoFar = 0;
float linewidth = 0;
float maxheight = 0;
float currheight = 0;
int wordCount = 0;
float unitsPerPixel = Vector3.Distance(myCamera.ScreenToWorldPoint(Vector3.zero), myCamera.ScreenToWorldPoint(Vector3.right));
float pixelsPerUnit = 1 / unitsPerPixel;
textBox.text = "";
while (heightSoFar < textBox.rectTransform.rect.height && wordnum < words.Count) {
mesh.text = words[wordnum] + " ";
Bounds bounds = mesh.GetComponent<Renderer>().bounds;
float width = bounds.size.x * pixelsPerUnit;
currheight = bounds.size.y * pixelsPerUnit + mesh.font.lineHeight;
if (currheight > maxheight) maxheight = currheight;
if (linewidth + width > textBox.rectTransform.rect.width) {
// time for new line
textBox.text += "\n" + words [wordnum];
heightSoFar += maxheight;
linewidth = width;
} else {
// add it to this line
if (wordCount == 0) textBox.text += words[wordnum];
else textBox.text += " " + words [wordnum];
linewidth += width;
wordCount++;
}
wordnum++;
}
if (wordnum < words.Count)
{
nextButton.enabled = true;
} else {
nextButton.enabled = false;
}
}
public void handleClick()
{
setContents();
}
更新 这是另一种查找我在此处找到的单词宽度的方法:http://answers.unity3d.com/questions/898770/how-to-get-the-width-of-ui-text-with-horizontal-ov.html
此方法不是使用文本网格,而是查询具有字符大小的CharacterInfo结构的字体,然后添加字符的大小以获取单词的大小。这似乎是对上述技术的改进,因为它解决了上述问题#1和#2。 #1是固定的,因为没有文本网格,我不再需要微调字符大小。 #2是固定的,因为在这种方法中,一切都是以像素为单位而不是世界单位完成的。问题#3仍然存在。虽然CharacterInfo可以给我字形高度,但它没有给我任何关于行间距的信息。我做了一些谷歌搜索,发现典型的行间距是字符高度的120-145%。所以我乘以1.45得到每条线高度的上限(包括行间距)。但也许有更好的方法?
以下是使用该方法的代码:
private string myText =
"A-tisket, a-tasket\nA green and yellow basket\n" +
"I wrote a letter to my love\nAnd on the way I dropped it\n" +
"I dropped it\nI dropped it\nYes, on the way I dropped it\n" +
"A little boy he picked it up \nand put it in his pocket.\n\n" +
"Baa, baa, black sheep\nHave you any wool?\nYes sir, yes sir\n" +
"Three bags full.\nOne for my master\nAnd one for the dame\n" +
"One for the little boy\nWho lives down the lane.";
public Text textBox;
public Button nextButton;
private int wordnum;
private List<string> words;
private Font myFont;
private int myFontSize;
void Start ()
{
words = new List<string> (
myText.Split (new string[] { " ", "\n", "\t" },
System.StringSplitOptions.RemoveEmptyEntries));
nextButton.enabled = false;
wordnum = 0;
setContents ();
}
void setContents ()
{
float heightSoFar = 0;
float linewidth = 0;
float maxheight = 0;
CharacterInfo characterInfo = new CharacterInfo();
textBox.text = "";
myFont = textBox.font;
myFontSize = textBox.fontSize;
while (heightSoFar < textBox.rectTransform.rect.height && wordnum < words.Count) {
char[] chars = (words[wordnum] + " ").ToCharArray();
float width = 0;
foreach(char c in chars) {
myFont.GetCharacterInfo(c, out characterInfo, myFontSize);
width += characterInfo.advance;
if (characterInfo.glyphHeight * 1.45f > maxheight) maxheight = characterInfo.glyphHeight * 1.45f;
}
if (linewidth + width > textBox.rectTransform.rect.width) {
// time for new line
heightSoFar += maxheight;
// are we still within vertical bounds? if so, add a new line
if (heightSoFar < textBox.rectTransform.rect.height) {
textBox.text += "\n" + words [wordnum] + " ";
wordnum++;
linewidth = width;
}
} else {
// add it to this line
textBox.text += words [wordnum] + " ";
wordnum++;
linewidth += width;
}
}
if (wordnum < words.Count) {
nextButton.enabled = true;
} else {
nextButton.enabled = false;
}
}
public void handleClick()
{
setContents();
}