using System; using System.Collections.Generic; namespace UnityEngine.UI { /// /// Labels are graphics that display text. /// [AddComponentMenu("UI/Text", 10)] public class Text : MaskableGraphic, ILayoutElement { [SerializeField] private FontData m_FontData = FontData.defaultFontData; #if UNITY_EDITOR // needed to track font changes from the inspector private Font m_LastTrackedFont; #endif [TextArea(3, 10)][SerializeField] protected string m_Text = String.Empty; private TextGenerator m_TextCache; private TextGenerator m_TextCacheForLayout; static protected Material s_DefaultText = null; // We use this flag instead of Unregistering/Registering the callback to avoid allocation. [NonSerialized] protected bool m_DisableFontTextureRebuiltCallback = false; protected Text() { useLegacyMeshGeneration = false; } // text generator,得到字符串在glyph atlas中的UV,构建mesh /// /// Get or set the material used by this Text. /// public TextGenerator cachedTextGenerator { get { return m_TextCache ?? (m_TextCache = (m_Text.Length != 0 ? new TextGenerator(m_Text.Length) : new TextGenerator())); } } public TextGenerator cachedTextGeneratorForLayout { get { return m_TextCacheForLayout ?? (m_TextCacheForLayout = new TextGenerator()); } } /// /// Text's texture comes from the font. /// public override Texture mainTexture { get { if (font != null && font.material != null && font.material.mainTexture != null) return font.material.mainTexture; if (m_Material != null) return m_Material.mainTexture; return base.mainTexture; } } public void FontTextureChanged() { // Only invoke if we are not destroyed. if (!this) return; if (m_DisableFontTextureRebuiltCallback) return; cachedTextGenerator.Invalidate(); if (!IsActive()) return; // this is a bit hacky, but it is currently the // cleanest solution.... // if we detect the font texture has changed and are in a rebuild loop // we just regenerate the verts for the new UV's if (CanvasUpdateRegistry.IsRebuildingGraphics() || CanvasUpdateRegistry.IsRebuildingLayout()) UpdateGeometry(); else SetAllDirty(); } public Font font { get { return m_FontData.font; } set { if (m_FontData.font == value) return; FontUpdateTracker.UntrackText(this); m_FontData.font = value; FontUpdateTracker.TrackText(this); #if UNITY_EDITOR // needed to track font changes from the inspector m_LastTrackedFont = value; #endif SetAllDirty(); } } /// /// Text that's being displayed by the Text. /// public virtual string text { get { return m_Text; } set { if (String.IsNullOrEmpty(value)) { if (String.IsNullOrEmpty(m_Text)) return; m_Text = ""; SetVerticesDirty(); } else if (m_Text != value) { m_Text = value; SetVerticesDirty(); SetLayoutDirty(); } } } /// /// Whether this Text will support rich text. /// public bool supportRichText { get { return m_FontData.richText; } set { if (m_FontData.richText == value) return; m_FontData.richText = value; SetVerticesDirty(); SetLayoutDirty(); } } /// /// Wrap mode used by the text. /// public bool resizeTextForBestFit { get { return m_FontData.bestFit; } set { if (m_FontData.bestFit == value) return; m_FontData.bestFit = value; SetVerticesDirty(); SetLayoutDirty(); } } public int resizeTextMinSize { get { return m_FontData.minSize; } set { if (m_FontData.minSize == value) return; m_FontData.minSize = value; SetVerticesDirty(); SetLayoutDirty(); } } public int resizeTextMaxSize { get { return m_FontData.maxSize; } set { if (m_FontData.maxSize == value) return; m_FontData.maxSize = value; SetVerticesDirty(); SetLayoutDirty(); } } /// /// Alignment anchor used by the text. /// public TextAnchor alignment { get { return m_FontData.alignment; } set { if (m_FontData.alignment == value) return; m_FontData.alignment = value; SetVerticesDirty(); SetLayoutDirty(); } } public bool alignByGeometry { get { return m_FontData.alignByGeometry; } set { if (m_FontData.alignByGeometry == value) return; m_FontData.alignByGeometry = value; SetVerticesDirty(); } } public int fontSize { get { return m_FontData.fontSize; } set { // font size改变的时候重建mesh if (m_FontData.fontSize == value) return; m_FontData.fontSize = value; SetVerticesDirty(); SetLayoutDirty(); } } public HorizontalWrapMode horizontalOverflow { get { return m_FontData.horizontalOverflow; } set { if (m_FontData.horizontalOverflow == value) return; m_FontData.horizontalOverflow = value; SetVerticesDirty(); SetLayoutDirty(); } } public VerticalWrapMode verticalOverflow { get { return m_FontData.verticalOverflow; } set { if (m_FontData.verticalOverflow == value) return; m_FontData.verticalOverflow = value; SetVerticesDirty(); SetLayoutDirty(); } } public float lineSpacing { get { return m_FontData.lineSpacing; } set { if (m_FontData.lineSpacing == value) return; m_FontData.lineSpacing = value; SetVerticesDirty(); SetLayoutDirty(); } } /// /// Font style used by the Text's text. /// public FontStyle fontStyle { get { return m_FontData.fontStyle; } set { if (m_FontData.fontStyle == value) return; m_FontData.fontStyle = value; SetVerticesDirty(); SetLayoutDirty(); } } public float pixelsPerUnit { get { var localCanvas = canvas; if (!localCanvas) return 1; // For dynamic fonts, ensure we use one pixel per pixel on the screen. if (!font || font.dynamic) return localCanvas.scaleFactor; // For non-dynamic fonts, calculate pixels per unit based on specified font size relative to font object's own font size. if (m_FontData.fontSize <= 0 || font.fontSize <= 0) return 1; return font.fontSize / (float)m_FontData.fontSize; } } protected override void OnEnable() { base.OnEnable(); cachedTextGenerator.Invalidate(); FontUpdateTracker.TrackText(this); } protected override void OnDisable() { FontUpdateTracker.UntrackText(this); base.OnDisable(); } protected override void UpdateGeometry() { if (font != null) { base.UpdateGeometry(); } } #if UNITY_EDITOR protected override void Reset() { AssignDefaultFont(); } #endif internal void AssignDefaultFont() { font = Resources.GetBuiltinResource("Arial.ttf"); } // 根据FontData设置TextGenerationSettings public TextGenerationSettings GetGenerationSettings(Vector2 extents) { var settings = new TextGenerationSettings(); settings.generationExtents = extents; if (font != null && font.dynamic) { settings.fontSize = m_FontData.fontSize; settings.resizeTextMinSize = m_FontData.minSize; settings.resizeTextMaxSize = m_FontData.maxSize; } // Other settings settings.textAnchor = m_FontData.alignment; settings.alignByGeometry = m_FontData.alignByGeometry; settings.scaleFactor = pixelsPerUnit; settings.color = color; // 这里是顶点颜色 settings.font = font; settings.pivot = rectTransform.pivot; settings.richText = m_FontData.richText; settings.lineSpacing = m_FontData.lineSpacing; settings.fontStyle = m_FontData.fontStyle; settings.resizeTextForBestFit = m_FontData.bestFit; settings.updateBounds = false; settings.horizontalOverflow = m_FontData.horizontalOverflow; settings.verticalOverflow = m_FontData.verticalOverflow; return settings; } static public Vector2 GetTextAnchorPivot(TextAnchor anchor) { switch (anchor) { case TextAnchor.LowerLeft: return new Vector2(0, 0); case TextAnchor.LowerCenter: return new Vector2(0.5f, 0); case TextAnchor.LowerRight: return new Vector2(1, 0); case TextAnchor.MiddleLeft: return new Vector2(0, 0.5f); case TextAnchor.MiddleCenter: return new Vector2(0.5f, 0.5f); case TextAnchor.MiddleRight: return new Vector2(1, 0.5f); case TextAnchor.UpperLeft: return new Vector2(0, 1); case TextAnchor.UpperCenter: return new Vector2(0.5f, 1); case TextAnchor.UpperRight: return new Vector2(1, 1); default: return Vector2.zero; } } readonly UIVertex[] m_TempVerts = new UIVertex[4]; 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; // 生成对应的网格数据(顶点、UV) var settings = GetGenerationSettings(extents); cachedTextGenerator.PopulateWithErrors(text, settings, gameObject); // Apply the offset to the vertices IList verts = cachedTextGenerator.verts; float unitsPerPixel = 1 / pixelsPerUnit; //Last 4 verts are always a new line... (\n) int vertCount = verts.Count - 4; 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; } public virtual void CalculateLayoutInputHorizontal() {} public virtual void CalculateLayoutInputVertical() {} public virtual float minWidth { get { return 0; } } public virtual float preferredWidth { get { var settings = GetGenerationSettings(Vector2.zero); return cachedTextGeneratorForLayout.GetPreferredWidth(m_Text, settings) / pixelsPerUnit; } } public virtual float flexibleWidth { get { return -1; } } public virtual float minHeight { get { return 0; } } public virtual float preferredHeight { get { var settings = GetGenerationSettings(new Vector2(GetPixelAdjustedRect().size.x, 0.0f)); return cachedTextGeneratorForLayout.GetPreferredHeight(m_Text, settings) / pixelsPerUnit; } } public virtual float flexibleHeight { get { return -1; } } public virtual int layoutPriority { get { return 0; } } #if UNITY_EDITOR public override void OnRebuildRequested() { // After a Font asset gets re-imported the managed side gets deleted and recreated, // that means the delegates are not persisted. // so we need to properly enforce a consistent state here. FontUpdateTracker.UntrackText(this); FontUpdateTracker.TrackText(this); // Also the textgenerator is no longer valid. cachedTextGenerator.Invalidate(); base.OnRebuildRequested(); } // 当编辑器里修改了参数值的时候调这个回调,setDirty,后续重建mesh // The Text inspector editor can change the font, and we need a way to track changes so that we get the appropriate rebuild callbacks // We can intercept changes in OnValidate, and keep track of the previous font reference protected override void OnValidate() { if (!IsActive()) { base.OnValidate(); return; } if (m_FontData.font != m_LastTrackedFont) { Font newFont = m_FontData.font; m_FontData.font = m_LastTrackedFont; FontUpdateTracker.UntrackText(this); m_FontData.font = newFont; FontUpdateTracker.TrackText(this); m_LastTrackedFont = newFont; } base.OnValidate(); } #endif // if UNITY_EDITOR } }