UGUI源代码之Text—实现自定义的字间距属性
以下内容是根据Unity 2020.1.01f版本进行编写的
UGUI源代码之Text—实现自定义的字间距属性
- 1、目的
- 2、参考
- 3、代码阅读
- 4、准备修改UGUI源代码
- 5、实现自定义Text组件,增加字间距属性
- 6、最终效果
1、目的
很多时候,美术在设计的时候是想要使用文本的字间距属性的,但是UGUI的Text组件并不支持字间距属性,因此想要自己实现一个
但是实现Text组件的最核心代码,Unity并没有公开出来,但是通过查看NGUI的Label控件的源代码,可以看出UGUI实现Text的核心方法
2、参考
本文参考Unity官方的UGUI源代码,以及NGUI插件
Github地址:https://github.com/Unity-Technologies/uGUI
3、代码阅读
首先看下UGUI的Text类,继承Graphic类的类一般都是通过通用OnPopulateMesh方法生成对应的Mesh的,所以直接看Text的OnPopulateMesh方法:
protected override void OnPopulateMesh(VertexHelper toFill)
{
if (font == null)
return;
// We don't care if we the font Texture changes while we are doing our Update.
// The end result of cachedTextGenerator will be valid for this instance.
// Otherwise we can get issues like Case 619238.
m_DisableFontTextureRebuiltCallback = true;
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents);
cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);
// Apply the offset to the vertices
IList<UIVertex> verts = cachedTextGenerator.verts;
float unitsPerPixel = 1 / pixelsPerUnit;
int vertCount = verts.Count;
// We have no verts to process just return (case 1037923)
if (vertCount <= 0)
{
toFill.Clear();
return;
}
Vector2 roundingOffset = new Vector2(verts[0].position.x, verts[0].position.y) * unitsPerPixel;
roundingOffset = PixelAdjustPoint(roundingOffset) - roundingOffset;
toFill.Clear();
if (roundingOffset != Vector2.zero)
{
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
}
else
{
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
}
m_DisableFontTextureRebuiltCallback = false;
}
从上面的代码可以看出,生成文本Mesh的Vertex是通过cachedTextGenerator.PopulateWithErrors的函数生成的,此函数的3个参数分别是文本的内容(string类型),Text组件的设置,Text所在的gameObject。那么这个cachedTextGenerator是啥呢,F12导航到定义此属性:
public TextGenerator cachedTextGenerator
{
get { return m_TextCache ?? (m_TextCache = (m_Text.Length != 0 ? new TextGenerator(m_Text.Length) : new TextGenerator())); }
}
cachedTextGenerator这个属性是一个TextGenerator类,再次按F12导航到TextGenerator类(部分):
//
// 摘要:
// Will generate the vertices and other data for the given string with the given
// settings.
//
// 参数:
// str:
// String to generate.
//
// settings:
// Generation settings.
//
// context:
// The object used as context of the error log message, if necessary.
//
// 返回结果:
// True if the generation is a success, false otherwise.
public bool PopulateWithErrors(string str, TextGenerationSettings settings, GameObject context)
{
TextGenerationError textGenerationError = PopulateWithError(str, settings);
if (textGenerationError == TextGenerationError.None)
{
return true;
}
if ((textGenerationError & TextGenerationError.CustomSizeOnNonDynamicFont) != 0)
{
Debug.LogErrorFormat(context, "Font '{0}' is not dynamic, which is required to override its size", settings.font);
}
if ((textGenerationError & TextGenerationError.CustomStyleOnNonDynamicFont) != 0)
{
Debug.LogErrorFormat(context, "Font '{0}' is not dynamic, which is required to override its style", settings.font);
}
return false;
}
跳转后发现这个类是dll里的类,无法直接查看函数的内容,其中以上代码是Text组件所使用到的PopulateWithErrors函数的定义
关于UGUI的Text组件实现过程在这里就断了,但是NGUI是UGUI的前身,Unity开发团队将NGUI的开发团队收到自己开发团队下,并且由此开发了UGUI。
因此可以想着可以通过查看NGUI对应UGUI的Text类的代码,了解UGUI实现Text的过程,从而让Text实现更多的功能
说干就干,新建项目导入NGUI插件,粗略看了NGUI的控件,估计Label类就是对应UGUI的Text类,生成Text的代码如下:
/// <summary>
/// Process the raw text, called when something changes.
/// </summary>
public void ProcessText (bool legacyMode = false, bool full = true)
{
if (!isValid) return;
mChanged = true;
shouldBeProcessed = false;
float regionX = mDrawRegion.z - mDrawRegion.x;
float regionY = mDrawRegion.w - mDrawRegion.y;
NGUIText.rectWidth = legacyMode ? (mMaxLineWidth != 0 ? mMaxLineWidth : 1000000) : width;
NGUIText.rectHeight = legacyMode ? (mMaxLineHeight != 0 ? mMaxLineHeight : 1000000) : height;
NGUIText.regionWidth = (regionX != 1f) ? Mathf.RoundToInt(NGUIText.rectWidth * regionX) : NGUIText.rectWidth;
NGUIText.regionHeight = (regionY != 1f) ? Mathf.RoundToInt(NGUIText.rectHeight * regionY) : NGUIText.rectHeight;
mFinalFontSize = Mathf.Abs(legacyMode ? Mathf.RoundToInt(cachedTransform.localScale.x) : defaultFontSize);
mScale = 1f;
if (NGUIText.regionWidth < 1 || NGUIText.regionHeight < 0)
{
mProcessedText = "";
return;
}
bool isDynamic = (trueTypeFont != null);
if (isDynamic && keepCrisp)
{
UIRoot rt = root;
if (rt != null) mDensity = (rt != null) ? rt.pixelSizeAdjustment : 1f;
}
else mDensity = 1f;
if (full) UpdateNGUIText();
if (mOverflow == Overflow.ResizeFreely)
{
NGUIText.rectWidth = 1000000;
NGUIText.regionWidth = 1000000;
if (mOverflowWidth > 0)
{
NGUIText.rectWidth = Mathf.Min(NGUIText.rectWidth, mOverflowWidth);
NGUIText.regionWidth = Mathf.Min(NGUIText.regionWidth, mOverflowWidth);
}
}
if (mOverflow == Overflow.ResizeFreely || mOverflow == Overflow.ResizeHeight)
{
NGUIText.rectHeight = 1000000;
NGUIText.regionHeight = 1000000;
}
if (mFinalFontSize > 0)
{
bool adjustSize = keepCrisp;
for (int ps = mFinalFontSize; ps > 0; --ps)
{
// Adjust either the size, or the scale
if (adjustSize)
{
mFinalFontSize = ps;
NGUIText.fontSize = mFinalFontSize;
}
else
{
mScale = (float)ps / mFinalFontSize;
NGUIText.fontScale = isDynamic ? mScale : ((float)mFontSize / mFont.defaultSize) * mScale;
}
NGUIText.Update(false);
// Wrap the text
bool fits = NGUIText.WrapText(printedText, out mProcessedText, false, false, mOverflowEllipsis);
if (mOverflow == Overflow.ShrinkContent && !fits)
{
if (--ps > 1) continue;
else break;
}
else if (mOverflow == Overflow.ResizeFreely)
{
mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText);
int w = Mathf.Max(minWidth, Mathf.RoundToInt(mCalculatedSize.x));
if (regionX != 1f) w = Mathf.RoundToInt(w / regionX);
int h = Mathf.Max(minHeight, Mathf.RoundToInt(mCalculatedSize.y));
if (regionY != 1f) h = Mathf.RoundToInt(h / regionY);
if ((w & 1) == 1) ++w;
if ((h & 1) == 1) ++h;
if (mWidth != w || mHeight != h)
{
mWidth = w;
mHeight = h;
if (onChange != null) onChange();
}
}
else if (mOverflow == Overflow.ResizeHeight)
{
mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText);
int h = Mathf.Max(minHeight, Mathf.RoundToInt(mCalculatedSize.y));
if (regionY != 1f) h = Mathf.RoundToInt(h / regionY);
if ((h & 1) == 1) ++h;
if (mHeight != h)
{
mHeight = h;
if (onChange != null) onChange();
}
}
else
{
mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText);
}
// Upgrade to the new system
if (legacyMode)
{
width = Mathf.RoundToInt(mCalculatedSize.x);
height = Mathf.RoundToInt(mCalculatedSize.y);
cachedTransform.localScale = Vector3.one;
}
break;
}
}
else
{
cachedTransform.localScale = Vector3.one;
mProcessedText = "";
mScale = 1f;
}
if (full)
{
NGUIText.bitmapFont = null;
NGUIText.dynamicFont = null;
}
}
从代码可以看出,函数一开始基本都是对一些设置的参数进行读取,计算等,生成文本的函数可能是UpdateNGUIText,NGUIText.Update
先看UpdateNGUIText的代码:
public void UpdateNGUIText ()
{
Font ttf = trueTypeFont;
bool isDynamic = (ttf != null);
NGUIText.fontSize = mFinalFontSize;
NGUIText.fontStyle = mFontStyle;
NGUIText.rectWidth = mWidth;
NGUIText.rectHeight = mHeight;
NGUIText.regionWidth = Mathf.RoundToInt(mWidth * (mDrawRegion.z - mDrawRegion.x));
NGUIText.regionHeight = Mathf.RoundToInt(mHeight * (mDrawRegion.w - mDrawRegion.y));
NGUIText.gradient = mApplyGradient && (mFont == null || !mFont.packedFontShader);
NGUIText.gradientTop = mGradientTop;
NGUIText.gradientBottom = mGradientBottom;
NGUIText.encoding = mEncoding;
NGUIText.premultiply = mPremultiply;
NGUIText.symbolStyle = mSymbols;
NGUIText.maxLines = mMaxLineCount;
NGUIText.spacingX = effectiveSpacingX;
NGUIText.spacingY = effectiveSpacingY;
NGUIText.fontScale = isDynamic ? mScale : ((float)mFontSize / mFont.defaultSize) * mScale;
if (mFont != null)
{
NGUIText.bitmapFont = mFont;
for (; ; )
{
UIFont fnt = NGUIText.bitmapFont.replacement;
if (fnt == null) break;
NGUIText.bitmapFont = fnt;
}
if (NGUIText.bitmapFont.isDynamic)
{
NGUIText.dynamicFont = NGUIText.bitmapFont.dynamicFont;
NGUIText.bitmapFont = null;
}
else NGUIText.dynamicFont = null;
}
else
{
NGUIText.dynamicFont = ttf;
NGUIText.bitmapFont = null;
}
if (isDynamic && keepCrisp)
{
UIRoot rt = root;
if (rt != null) NGUIText.pixelDensity = (rt != null) ? rt.pixelSizeAdjustment : 1f;
}
else NGUIText.pixelDensity = 1f;
if (mDensity != NGUIText.pixelDensity)
{
ProcessText(false, false);
NGUIText.rectWidth = mWidth;
NGUIText.rectHeight = mHeight;
NGUIText.regionWidth = Mathf.RoundToInt(mWidth * (mDrawRegion.z - mDrawRegion.x));
NGUIText.regionHeight = Mathf.RoundToInt(mHeight * (mDrawRegion.w - mDrawRegion.y));
}
if (alignment == Alignment.Automatic)
{
Pivot p = pivot;
if (p == Pivot.Left || p == Pivot.TopLeft || p == Pivot.BottomLeft)
{
NGUIText.alignment = Alignment.Left;
}
else if (p == Pivot.Right || p == Pivot.TopRight || p == Pivot.BottomRight)
{
NGUIText.alignment = Alignment.Right;
}
else NGUIText.alignment = Alignment.Center;
}
else NGUIText.alignment = alignment;
NGUIText.Update();
}
从代码可以看出,此函数主要是读取当前文本控件的设置,并将这些设置赋值到NGUIText静态类中对应的属性上,最后也是会调用NGUIText.Update函数
因此,查看NGUIText.Update函数的代码:
static public void Update () { Update(true); }
/// <summary>
/// Recalculate the 'final' values.
/// </summary>
static public void Update (bool request)
{
finalSize = Mathf.RoundToInt(fontSize / pixelDensity);
finalSpacingX = spacingX * fontScale;
finalLineHeight = (fontSize + spacingY) * fontScale;
useSymbols = (dynamicFont != null || bitmapFont != null) && encoding && symbolStyle != SymbolStyle.None;
#if DYNAMIC_FONT
Font font = dynamicFont;
if (font != null && request)
{
font.RequestCharactersInTexture(")_-", finalSize, fontStyle);
#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7
if (!font.GetCharacterInfo(')', out mTempChar, finalSize, fontStyle) || mTempChar.vert.height == 0f)
{
font.RequestCharactersInTexture("A", finalSize, fontStyle);
{
if (!font.GetCharacterInfo('A', out mTempChar, finalSize, fontStyle))
{
baseline = 0f;
return;
}
}
}
float y0 = mTempChar.vert.yMax;
float y1 = mTempChar.vert.yMin;
#else
if (!font.GetCharacterInfo(')', out mTempChar, finalSize, fontStyle) || mTempChar.maxY == 0f)
{
font.RequestCharactersInTexture("A", finalSize, fontStyle);
{
if (!font.GetCharacterInfo('A', out mTempChar, finalSize, fontStyle))
{
baseline = 0f;
return;
}
}
}
float y0 = mTempChar.maxY;
float y1 = mTempChar.minY;
#endif
baseline = Mathf.Round(y0 + (finalSize - y0 + y1) * 0.5f);
}
#endif
}
从代码可以看出,有两个函数可能是获取文字信息的:font.RequestCharactersInTexture,font.GetCharacterInfo
但是,鼠标悬停RequestCharactersInTexture函数发现其没有返回值(如上图),根据函数名猜测此函数是请求函数的第一个参数中的字符串,根据字符串在当前Label控件材质球的Texture上生成对应的字符
然后再看GetCharacterInfo,看到其中有out的参数,并且有返回值,根据函数名猜测是根据参数中的字符,字号以及字体样式返回该字符的参数,并将这些参数保存到一个叫做CharacterInfo的类中
跳转到GetCharacterInfo类查看代码:
[MethodImpl(MethodImplOptions.InternalCall)]
[FreeFunction("TextRenderingPrivate::GetCharacterInfo", HasExplicitThis = true)]
public extern bool GetCharacterInfo(char ch, out CharacterInfo info, [DefaultValue("0")] int size, [DefaultValue("FontStyle.Normal")] FontStyle style);
发现这是一个dll文件里的类,无法查看里面的代码
那就看看CharacterInfo类有什么属性是可以使用的
CharacterInfo类代码(为了方便看改成截图形式):
可以看出,CharacterInfo类也是dll文件里的类,但是里面有不少属性是public的,可以使用
逐个属性查看之后,发现有一个叫advance的属性:
//
// 摘要:
// The horizontal distance, rounded to the nearest integer, from the origin of this
// character to the origin of the next character.
public int advance
{
get
{
return (int)Math.Round(width, MidpointRounding.AwayFromZero);
}
set
{
width = value;
}
}
此属性是当前字符的水平距离,四舍五入到最接近的整数,从该点的原点算起。此字符到下一个字符的原点。大致意思就是字符的宽度。
根据这个属性,就可以实现Text的字间距属性了。
Text显示出文字的根本是通过Vertex顶点生成Mesh,生成Mesh后再通过Canvas Renderer渲染出来。所以我们只需要计算位置,纹理和色值都正确的Vertex顶点,并通过顶点生成Mesh,就可以实现文本了
4、准备修改UGUI源代码
请看这篇:UGUI源代码之修改源代码的前期准备
已经准备过的同学可以跳过
5、实现自定义Text组件,增加字间距属性
新建一个C#脚本,重命名为TextSpacing,并使其继承Text类,为其增加一个调整字间距的属性,然后重写OnPopulateMesh方法就可以了,上代码:
[SerializeField] private float m_characterSpacing = 0f;
protected override void OnPopulateMesh(VertexHelper toFill)
{
if (font == null)
return;
if (text.Length <= 0)
{
toFill.Clear();
return;
}
// We don't care if we the font Texture changes while we are doing our Update.
// The end result of cachedTextGenerator will be valid for this instance.
// Otherwise we can get issues like Case 619238.
m_DisableFontTextureRebuiltCallback = true;
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents);
cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);
//--------------------------------------------------------------------------------
toFill.Clear();
float currentLineTotalWidth = 0f;
float currentTotalHeight = 0f;
int lineCount = 1;
Vector3 startPos = Vector3.zero;
List<float> totalWidthtList = new List<float>();
//定义字间距向量
Vector3 characterSpacingVector = new Vector3(m_characterSpacing, 0, 0);
font.RequestCharactersInTexture(text, fontSize, fontStyle);
CharacterInfo ch_firstChar;
font.GetCharacterInfo(text[0], out ch_firstChar,fontSize, fontStyle);
currentLineTotalWidth = ch_firstChar.advance;
currentTotalHeight = fontSize;
if(rectTransform.sizeDelta.y < currentTotalHeight)
{
return;
}
List<CharacterInfo> characterInfoList = new List<CharacterInfo>();
characterInfoList.Add(ch_firstChar);
for (int i = 0;i < text.Length;i++)
{
if (i + 1 < text.Length)
{
CharacterInfo next_ch;
font.GetCharacterInfo(text[i + 1], out next_ch, fontSize, fontStyle);
characterInfoList.Add(next_ch);
if (text[i] == '\n')
{
lineCount++;
totalWidthtList.Add(currentLineTotalWidth);
currentLineTotalWidth = next_ch.advance;
currentTotalHeight += lineSpacing + fontSize;
if (verticalOverflow == VerticalWrapMode.Truncate && currentTotalHeight > rectTransform.sizeDelta.y)
{
break;
}
continue;
}
//自动换行
if (horizontalOverflow == HorizontalWrapMode.Wrap && (currentLineTotalWidth + next_ch.advance + m_characterSpacing) > rectTransform.sizeDelta.x)
{
lineCount++;
totalWidthtList.Add(currentLineTotalWidth);
currentLineTotalWidth = next_ch.advance;
currentTotalHeight += lineSpacing + fontSize;
if (verticalOverflow == VerticalWrapMode.Truncate && currentTotalHeight > rectTransform.sizeDelta.y)
{
break;
}
}
else
{
if (!(text[i + 1] == '\n'))
{
currentLineTotalWidth += next_ch.advance + m_characterSpacing;
}
}
}
else
{
if (text[i] == '\n')
{
lineCount++;
totalWidthtList.Add(currentLineTotalWidth);
currentLineTotalWidth = 0;
continue;
}
}
}
//加上最后一行的字符宽度
totalWidthtList.Add(currentLineTotalWidth);
//重置部分属性
lineCount = 1;
currentLineTotalWidth = ch_firstChar.advance;
currentTotalHeight = fontSize;
startPos = GetStartPosition(lineCount, totalWidthtList);
for (int i = 0; i < text.Length; i++)
{
CharacterInfo ch = characterInfoList[i];
UIVertex[] vertices = new UIVertex[4];
vertices[0] = UIVertex.simpleVert;
vertices[1] = UIVertex.simpleVert;
vertices[2] = UIVertex.simpleVert;
vertices[3] = UIVertex.simpleVert;
vertices[0].position = startPos + new Vector3(ch.minX, ch.maxY, 0);
vertices[1].position = startPos + new Vector3(ch.maxX, ch.maxY, 0);
vertices[2].position = startPos + new Vector3(ch.maxX, ch.minY, 0);
vertices[3].position = startPos + new Vector3(ch.minX, ch.minY, 0);
//Vector2 adjustVector = Vector2.zero;
Vector2 adjustVector = new Vector2(0, 0.00f);
vertices[0].uv0 = ch.uvTopLeft + adjustVector;
vertices[1].uv0 = ch.uvTopRight + adjustVector;
vertices[2].uv0 = ch.uvBottomRight + adjustVector;
vertices[3].uv0 = ch.uvBottomLeft + adjustVector;
vertices[0].color = color;
vertices[1].color = color;
vertices[2].color = color;
vertices[3].color = color;
if (text[i] != '\n')
toFill.AddUIVertexQuad(vertices);
if (i + 1 < text.Length)
{
CharacterInfo next_ch = characterInfoList[i + 1];
//适应换行符,如果当前字符是换行符,则直接进行换行操作
if (text[i] == '\n')
{
lineCount++;
currentLineTotalWidth = next_ch.advance;
startPos = GetStartPosition(lineCount, totalWidthtList);
currentTotalHeight += lineSpacing + fontSize;
if (verticalOverflow == VerticalWrapMode.Truncate && currentTotalHeight > rectTransform.sizeDelta.y)
{
break;
}
continue;
}
//自动换行
if (horizontalOverflow == HorizontalWrapMode.Wrap && (currentLineTotalWidth + next_ch.advance + m_characterSpacing) > rectTransform.sizeDelta.x)
{
lineCount++;
currentLineTotalWidth = next_ch.advance;
startPos = GetStartPosition(lineCount, totalWidthtList);
currentTotalHeight += lineSpacing + fontSize;
if (verticalOverflow == VerticalWrapMode.Truncate && currentTotalHeight > rectTransform.sizeDelta.y)
{
break;
}
}
else
{
startPos += new Vector3(ch.advance, 0, 0) + characterSpacingVector;
//适应换行符,如果下一个字符是换行符,则不需要增加当前行的字符总宽度
if (!(text[i + 1] == '\n'))
{
currentLineTotalWidth += next_ch.advance + m_characterSpacing;
}
}
}
else
{
if (text[i] == '\n')
{
lineCount++;
currentLineTotalWidth = 0;
continue;
}
}
}
m_DisableFontTextureRebuiltCallback = false;
}
private Vector3 GetStartPosition(int lineCount, List<float> totalWidthtList)
{
int totalLineCount = totalWidthtList.Count;
float leftPosInRect = rectTransform.rect.xMin;
float upPosInRect = rectTransform.rect.yMax;
float halfLineWidth = totalWidthtList[lineCount - 1] / 2;
float rectTransformWidth = rectTransform.sizeDelta.x;
float rectTransformHeight = rectTransform.sizeDelta.y;
switch (alignment)
{
case TextAnchor.UpperLeft:
return new Vector3(leftPosInRect, upPosInRect - fontSize * lineCount - lineSpacing * (lineCount - 1), 0);
case TextAnchor.UpperCenter:
return new Vector3(-halfLineWidth, upPosInRect - fontSize * lineCount - lineSpacing * (lineCount - 1), 0);
case TextAnchor.UpperRight:
return new Vector3(leftPosInRect + rectTransformWidth - totalWidthtList[lineCount - 1], upPosInRect - fontSize * lineCount - lineSpacing * (lineCount - 1), 0);
case TextAnchor.MiddleLeft:
return new Vector3(leftPosInRect, (fontSize * totalLineCount + lineSpacing * (totalLineCount - 1)) / 2 - fontSize * lineCount, 0);
case TextAnchor.MiddleCenter:
return new Vector3(-halfLineWidth, (fontSize * totalLineCount + lineSpacing * (totalLineCount - 1)) / 2 - fontSize * lineCount, 0);
case TextAnchor.MiddleRight:
return new Vector3(leftPosInRect + rectTransformWidth - totalWidthtList[lineCount - 1], (fontSize * totalLineCount + lineSpacing * (totalLineCount - 1)) / 2 - fontSize * lineCount, 0);
case TextAnchor.LowerLeft:
return new Vector3(leftPosInRect, upPosInRect - fontSize * lineCount + (fontSize * totalLineCount - rectTransformHeight) + lineSpacing * (totalLineCount - lineCount + 1), 0);
case TextAnchor.LowerCenter:
return new Vector3(-halfLineWidth, upPosInRect - fontSize * lineCount + (fontSize * totalLineCount - rectTransformHeight) + lineSpacing * (totalLineCount - lineCount + 1), 0);
case TextAnchor.LowerRight:
return new Vector3(leftPosInRect + rectTransformWidth - totalWidthtList[lineCount - 1], upPosInRect - fontSize * lineCount + (fontSize * totalLineCount - rectTransformHeight) + lineSpacing * (totalLineCount - lineCount + 1), 0);
default:
return Vector3.zero;
}
}
这里增加了一个调整字间距的属性m_characterSpacing,为了让这个属性显示在Inspector面板,需要为这个脚本增加一个对应的Editor脚本,这里不贴代码了。
实现的逻辑其实很简单,就是对于每个需要显示的字符,根据其所需的宽度,重新算一遍其所在的位置,如果当前是自动换行并且宽度不够,那么就把行数加一,重新在起始的X轴位置生成下一行的字符,适配好换行符,并正确获取到不同对齐方式的起点位置。
看着很复杂,其实就是算对齐比较麻烦。
6、最终效果
大佬们找到问题欢迎拍砖~