// ------------------------------------------------------------------ // Desc: // ------------------------------------------------------------------ private static float BuildLine(string _text, exISpriteFont _sprite, ref int _parsedVbIndex, float _top) { // cache property var letterSpacing = _sprite.letterSpacing; var wordSpacing = _sprite.wordSpacing; bool useKerning = _sprite.useKerning; exFont font = _sprite.font; // Vector2 pos = new Vector2(0.0f, _top); float lastAdvance = 0.0f; float lastWidth = 0.0f; for (int _charIndex = 0; _charIndex < _text.Length; ++_charIndex, _parsedVbIndex += 4, pos.x += letterSpacing) { char c = _text[_charIndex]; // if new line if (c == '\n' || c == '\r') { vertices.buffer[_parsedVbIndex + 0] = new Vector3(); vertices.buffer[_parsedVbIndex + 1] = new Vector3(); vertices.buffer[_parsedVbIndex + 2] = new Vector3(); vertices.buffer[_parsedVbIndex + 3] = new Vector3(); ++_charIndex; _parsedVbIndex += 4; break; } bool hasPreviousChar = _charIndex > 0; if (hasPreviousChar) { pos.x += lastAdvance; // kerning if (useKerning) { pos.x += font.GetKerning(_text[_charIndex - 1], c); } // wordSpacing if (c == ' ') { pos.x += wordSpacing; } } float width, advance; if (BuildChar(font, c, pos, _parsedVbIndex, out width, out advance)) { // advance x lastWidth = width; lastAdvance = advance; } } return(pos.x + lastWidth); }
public static void DoInspectorGUI(exSpriteBaseInspector _inspector, SerializedProperty _textProp, SerializedProperty _textAlignProp, SerializedProperty _useKerningProp, SerializedProperty _wrapWordProp, SerializedProperty _lineHeightProp, SerializedProperty _customLineHeightProp, SerializedProperty _letterSpacingProp, SerializedProperty _wordSpacingProp, SerializedProperty _topColorProp, SerializedProperty _botColorProp) { _inspector.customSizeProp.boolValue = true; { // font exISpriteFont sp = _inspector.serializedObject.targetObject as exISpriteFont; if (sp != null) { EditorGUI.BeginChangeCheck(); exFont.TypeForEditor fontType = (exFont.TypeForEditor)EditorGUILayout.EnumPopup("Font Type", sp.fontType); int oldFontSize = sp.fontSize; if (EditorGUI.EndChangeCheck()) { sp.fontType = fontType; if (fontType == exFont.TypeForEditor.Dynamic) { if (sp.dynamicFont == null) { sp.SetFont(Resources.GetBuiltinResource(typeof(Font), "Arial.ttf") as Font); } sp.fontSize = oldFontSize; } EditorUtility.SetDirty(sp as Object); sp.lineHeight = sp.fontSize; // 自动重设行高 } EditorGUI.indentLevel++; if (fontType == exFont.TypeForEditor.Bitmap) { EditorGUI.BeginChangeCheck(); exBitmapFont font = EditorGUILayout.ObjectField("Font", sp.bitmapFont, typeof(exBitmapFont), false) as exBitmapFont; if (EditorGUI.EndChangeCheck()) { sp.SetFont(font); EditorUtility.SetDirty(sp as Object); sp.lineHeight = sp.fontSize; // 自动重设行高 } } else { EditorGUI.BeginChangeCheck(); Font font = EditorGUILayout.ObjectField("Font", sp.dynamicFont, typeof(Font), false) as Font; if (EditorGUI.EndChangeCheck()) { sp.SetFont(font); EditorUtility.SetDirty(sp as Object); sp.lineHeight = sp.fontSize; // 自动重设行高 } EditorGUI.BeginChangeCheck(); var fontStyle = (FontStyle)EditorGUILayout.EnumPopup("Font Style", sp.fontStyle); var fontSize = EditorGUILayout.IntField("Font Size", sp.fontSize); //sp.lineHeight = EditorGUILayout.IntField("Line Height", sp.lineHeight); if (EditorGUI.EndChangeCheck()) { sp.fontStyle = fontStyle; sp.fontSize = fontSize; EditorUtility.SetDirty(sp as Object); } } EditorGUI.indentLevel--; } } // text EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_textProp, new GUIContent("Text")); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.text = _textProp.stringValue; EditorUtility.SetDirty(sp as Object); } } } // textAlign EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_textAlignProp, new GUIContent("Text Align")); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.textAlign = (TextAlignment)_textAlignProp.enumValueIndex; EditorUtility.SetDirty(sp as Object); } } } // useKerning EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_useKerningProp, new GUIContent("Use Kerning")); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.useKerning = _useKerningProp.boolValue; EditorUtility.SetDirty(obj); } } } // wrap word EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_wrapWordProp, new GUIContent("Wrap Word"), true); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.wrapWord = _wrapWordProp.boolValue; EditorUtility.SetDirty(obj); } } } // custom line height EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_customLineHeightProp, new GUIContent("Custom Line Height"), true); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.customLineHeight = _customLineHeightProp.boolValue; EditorUtility.SetDirty(obj); } } } if (_customLineHeightProp.boolValue) { EditorGUI.indentLevel++; // line height EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_lineHeightProp, new GUIContent("Line Height"), true); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.lineHeight = _lineHeightProp.intValue; EditorUtility.SetDirty(obj); } } } EditorGUI.indentLevel--; } // letter spacing EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_letterSpacingProp, new GUIContent("Letter Spacing"), true); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.letterSpacing = _letterSpacingProp.intValue; EditorUtility.SetDirty(obj); } } } // word spacing EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_wordSpacingProp, new GUIContent("Word Spacing"), true); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.wordSpacing = _wordSpacingProp.intValue; EditorUtility.SetDirty(obj); } } } // topColor EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_topColorProp, new GUIContent("Top Color"), true); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.topColor = _topColorProp.colorValue; EditorUtility.SetDirty(obj); } } } // botColor EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(_botColorProp, new GUIContent("Bot Color"), true); if (EditorGUI.EndChangeCheck()) { foreach (Object obj in _inspector.serializedObject.targetObjects) { exISpriteFont sp = obj as exISpriteFont; if (sp != null) { sp.botColor = _botColorProp.colorValue; EditorUtility.SetDirty(obj); } } } }
// ------------------------------------------------------------------ /// Return used vertex count // ------------------------------------------------------------------ private static int BuildTextInLocalSpace(exISpriteFont _sprite, out float maxWidth) { if ((_sprite.updateFlags & exUpdateFlags.Color) == 0) { colors32 = null; // no need to update color } //texelSize = new Vector2(); if ((_sprite.updateFlags & exUpdateFlags.UV) == 0) { uvs = null; // no need to update uv } //else if (uvs != null && _sprite.font.texture != null) { // texelSize = _sprite.font.texture.texelSize; //} float halfLineHeightMargin = (_sprite.lineHeight - _sprite.fontSize) * 0.5f; _sprite.height = halfLineHeightMargin; maxWidth = 0; // NOTE: 这里不用考虑预分配的空间用不完的情况,因为大部分情况下只需输出单行文本。 // 而且一个mesh其实也显示不了太多字。 StringBuilder strBuilder = new StringBuilder(_sprite.text.Length); int cur_index = 0; int parsedVbIndex = vbIndex; bool finished = false; while (finished == false) { int line_width = 0; strBuilder.Length = 0; // Clear exTextUtility.CalcTextLine(out line_width, out cur_index, strBuilder, _sprite.text, cur_index, (int)_sprite.width, _sprite.font, _sprite.wordSpacing, _sprite.letterSpacing, _sprite.wrapWord, false, false); int lineStart = parsedVbIndex; float lineWidth = BuildLine(strBuilder.ToString(), _sprite, ref parsedVbIndex, -_sprite.height); if (lineWidth > maxWidth) { maxWidth = lineWidth; } // text alignment switch (_sprite.textAlign) { case TextAlignment.Left: // top left break; case TextAlignment.Center: // convert to top center float halfLineWidth = lineWidth * 0.5f; for (int i = lineStart; i < parsedVbIndex; ++i) { vertices.buffer[i].x -= halfLineWidth; } break; case TextAlignment.Right: // convert to top right for (int i = lineStart; i < parsedVbIndex; ++i) { vertices.buffer[i].x -= lineWidth; } break; } _sprite.height += _sprite.lineHeight; finished = (cur_index >= _sprite.text.Length); } _sprite.height += halfLineHeightMargin; return(parsedVbIndex - vbIndex); }
// ------------------------------------------------------------------ // Desc: // ------------------------------------------------------------------ public static void BuildText(exISpriteFont _sprite, Space _space, exList <Vector3> _vertices, int _vbIndex, exList <Vector2> _uvs = null, exList <Color32> _colors32 = null) { // 保存变量到全局,减少内部传参 vertices = _vertices; uvs = _uvs; colors32 = _colors32; vbIndex = _vbIndex; // request font texture, this may called OnFontTextureRebuilt to set _sprite.updateFlags _sprite.font.RequestCharactersInTexture(_sprite.text); _sprite.height = 0.0f; float displayWidth = 0; // 实际渲染的宽度不等于sprite.width(换行宽度) int visibleVertexCount = 0; if (_sprite.font.isValid) { visibleVertexCount = BuildTextInLocalSpace(_sprite, out displayWidth); } if (visibleVertexCount == 0 && _sprite.vertexCount >= 4) { visibleVertexCount = 4; // 放四个假的点,用于计算后续变换,才能得出正确的boundingbox _vertices.buffer[_vbIndex + 0] = new Vector3(); _vertices.buffer[_vbIndex + 1] = new Vector3(); _vertices.buffer[_vbIndex + 2] = new Vector3(); _vertices.buffer[_vbIndex + 3] = new Vector3(); } int invisibleVertexStart = _vbIndex + visibleVertexCount; // calculate anchor and offset Vector2 anchorOffset = new Vector2(); // convert to top left switch (_sprite.textAlign) { case TextAlignment.Left: break; case TextAlignment.Center: anchorOffset.x = displayWidth * 0.5f; break; case TextAlignment.Right: anchorOffset.x = displayWidth; break; } // convert anchor from top center to user defined switch (_sprite.anchor) { case Anchor.TopLeft: anchorOffset.y = 0.0f; break; case Anchor.TopCenter: anchorOffset.x -= displayWidth * 0.5f; anchorOffset.y = 0.0f; break; case Anchor.TopRight: anchorOffset.x -= displayWidth; anchorOffset.y = 0.0f; break; case Anchor.MidLeft: anchorOffset.y = _sprite.height * 0.5f; break; case Anchor.MidCenter: anchorOffset.x -= displayWidth * 0.5f; anchorOffset.y = _sprite.height * 0.5f; break; case Anchor.MidRight: anchorOffset.x -= displayWidth; anchorOffset.y = _sprite.height * 0.5f; break; case Anchor.BotLeft: anchorOffset.y = _sprite.height; break; case Anchor.BotCenter: anchorOffset.x -= displayWidth * 0.5f; anchorOffset.y = _sprite.height; break; case Anchor.BotRight: anchorOffset.x -= displayWidth; anchorOffset.y = _sprite.height; break; default: anchorOffset.x -= displayWidth * 0.5f; anchorOffset.y = _sprite.height * 0.5f; break; } // offset Vector3 offset = anchorOffset + _sprite.offset; Vector2 shearOffset = _sprite.shear; if (shearOffset.x != 0) { shearOffset.x = _sprite.GetScaleY(_space) * shearOffset.x; } if (shearOffset.y != 0) { shearOffset.y = _sprite.GetScaleX(_space) * shearOffset.y; } for (int i = _vbIndex; i < invisibleVertexStart; i += 4) { Vector3 v0 = _vertices.buffer[i + 0]; Vector3 v1 = _vertices.buffer[i + 1]; Vector3 v2 = _vertices.buffer[i + 2]; Vector3 v3 = _vertices.buffer[i + 3]; // apply anchor and offset v0 += offset; v1 += offset; v2 += offset; v3 += offset; // shear if (shearOffset.x != 0) { float halfCharHeight = (v1.y - v0.y) * 0.5f; float topOffset = shearOffset.x * (halfCharHeight + anchorOffset.y); float botOffset = shearOffset.x * (-halfCharHeight + anchorOffset.y); v0.x += botOffset; v1.x += topOffset; v2.x += topOffset; v3.x += botOffset; } if (shearOffset.y != 0) { float halfCharWidth = (v2.y - v0.y) * 0.5f; float leftOffset = shearOffset.y * (-halfCharWidth + anchorOffset.x); float rightOffset = shearOffset.y * (halfCharWidth + anchorOffset.x); v0.y += leftOffset; v1.y += leftOffset; v2.y += rightOffset; v3.y += rightOffset; } // transform if (_space == Space.World) { v0 = _sprite.cachedWorldMatrix.MultiplyPoint3x4(v0); v1 = _sprite.cachedWorldMatrix.MultiplyPoint3x4(v1); v2 = _sprite.cachedWorldMatrix.MultiplyPoint3x4(v2); v3 = _sprite.cachedWorldMatrix.MultiplyPoint3x4(v3); v0.z = 0; v1.z = 0; v2.z = 0; v3.z = 0; } _vertices.buffer[i + 0] = v0; _vertices.buffer[i + 1] = v1; _vertices.buffer[i + 2] = v2; _vertices.buffer[i + 3] = v3; } bool hasPivot = invisibleVertexStart > _vbIndex; if (hasPivot) { // hide invisible vertex 防止撑大boundingbox int vbEnd = _vbIndex + _sprite.vertexCount; for (int i = invisibleVertexStart; i < vbEnd; i += 4) { #if UNITY_EDITOR if (i >= _vertices.buffer.Length) { Debug.Log(string.Format("[BuildText|exSpriteFont] i: {0} vbEnd: " + vbEnd + " _vertices: " + _vertices.buffer.Length, i)); } #endif _vertices.buffer[i + 0] = _vertices.buffer[_vbIndex]; _vertices.buffer[i + 1] = _vertices.buffer[_vbIndex]; _vertices.buffer[i + 2] = _vertices.buffer[_vbIndex]; _vertices.buffer[i + 3] = _vertices.buffer[_vbIndex]; } } vertices = null; uvs = null; colors32 = null; // TODO: pixel-perfect }
// ------------------------------------------------------------------ // Desc: // ------------------------------------------------------------------ public static exUpdateFlags UpdateBuffers(exISpriteFont _sprite, Space _space, float _alpha, exList <Vector3> _vertices, exList <Vector2> _uvs, exList <Color32> _colors32, exList <int> _indices, int _vbIndex, int _ibIndex) { #if UNITY_EDITOR if (_sprite.text == null || _sprite.vertexCount < _sprite.text.Length * exMesh.QUAD_VERTEX_COUNT) { Debug.LogError("顶点缓冲长度不够,是否绕开属性直接修改了text_?: " + _sprite.vertexCount, _sprite as Object); return(_sprite.updateFlags); } #endif bool colorUpdated = false; // 有渐变色时,每个字符单独计算顶点色,否则全局填充顶点色 // Debug.Log(string.Format("[UpdateBuffers|SpriteFontBuilder] _vbIndex: {0} _ibIndex: {1}", _vbIndex, _ibIndex)); if ((_sprite.updateFlags & exUpdateFlags.Text) != 0) { if (_alpha > 0.0f) { // 初始化渐变色要用到的全局参数 Color tmp = _sprite.color; tmp.a *= _alpha; topFinalColor = _sprite.topColor * tmp; botFinalColor = _sprite.botColor * tmp; if (_sprite.fontSize != 0) { botFinalColor = exMath.Lerp(topFinalColor, botFinalColor, 1.0f / _sprite.fontSize); // 预除 } else { botFinalColor = topFinalColor; Debug.LogWarning("Failed to use gradient font color due to invalid font size", _sprite as Object); } } else { topFinalColor = new Color(); botFinalColor = new Color(); } colorUpdated = topFinalColor != botFinalColor; if ((_sprite.updateFlags & exUpdateFlags.Text & ~exUpdateFlags.Color) != 0 || colorUpdated) { BuildText(_sprite, _space, _vertices, _vbIndex, _uvs, colorUpdated ? _colors32 : null); } } if ((_sprite.updateFlags & exUpdateFlags.Index) != 0 && _indices != null) { // update index buffer int indexBufferEnd = _ibIndex + _sprite.indexCount - 5; for (int i = _ibIndex, v = _vbIndex; i < indexBufferEnd; i += 6, v += 4) { _indices.buffer[i] = v; _indices.buffer[i + 1] = v + 1; _indices.buffer[i + 2] = v + 2; _indices.buffer[i + 3] = v + 2; _indices.buffer[i + 4] = v + 3; _indices.buffer[i + 5] = v; } } if ((_sprite.updateFlags & exUpdateFlags.Color) != 0 && _colors32 != null && colorUpdated == false) { // if not gradient we just need to fill same color here Color tmp = _sprite.topColor * _sprite.color; Color32 color32 = new Color(tmp.r, tmp.g, tmp.b, tmp.a * _alpha); int vertexBufferEnd = _vbIndex + _sprite.text.Length * 4; for (int i = _vbIndex; i < vertexBufferEnd; ++i) { _colors32.buffer[i] = color32; } } exUpdateFlags updatedFlags = _sprite.updateFlags; _sprite.updateFlags = exUpdateFlags.None; return(updatedFlags); }