用到的场景:聊天系统。
NGUI版本:3.5.6。
已支持的功能,静态(位图)字体的图文混排。
需要实现的:动态字体的图文混排。
实现思路:用两个Label,一个只负责渲染文字,一个负责渲染图片。用isSymbol区分是渲染图片还是渲染文字。渲染文字的跳过符号的顶点和uv的添加,渲染图片的跳过正常字符的顶点和uv添加。
修改NGUIText.cs的代码如下:
static public void Print (string text, BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols) { if (string.IsNullOrEmpty(text)) return; int indexOffset = verts.size; Prepare(text); // Start with the white tint mColors.Add(Color.white); int ch = 0, prev = 0; float x = 0f, y = 0f, maxX = 0f; float sizeF = finalSize; Color gb = tint * gradientBottom; Color gt = tint * gradientTop; Color32 uc = tint; int textLength = text.Length; Rect uvRect = new Rect(); float invX = 0f, invY = 0f; float sizePD = sizeF * pixelDensity; // Advanced symbol support contributed by Rudy Pangestu. bool subscript = false; int subscriptMode = 0; // 0 = normal, 1 = subscript, 2 = superscript bool bold = false; bool italic = false; bool underline = false; bool strikethrough = false; const float sizeShrinkage = 0.75f; float v0x; float v1x; float v1y; float v0y; float prevX = 0; if (bitmapFont != null) { uvRect = bitmapFont.uvRect; invX = uvRect.width / bitmapFont.texWidth; invY = uvRect.height / bitmapFont.texHeight; } else { if (symbolFont != null && useWgSymbol) { uvRect = symbolFont.uvRect; invX = uvRect.width / symbolFont.texWidth; invY = uvRect.height / symbolFont.texHeight; } } for (int i = 0; i < textLength; ++i) { ch = text[i]; prevX = x; // New line character -- skip to the next line if (ch == ' ') { if (x > maxX) maxX = x; if (alignment != Alignment.Left) { Align(verts, indexOffset, x - finalSpacingX); indexOffset = verts.size; } x = 0; y += finalLineHeight; prev = 0; continue; } // Invalid character -- skip it if (ch < ' ') { prev = ch; continue; } // Color changing symbol if (encoding && ParseSymbol(text, ref i, mColors, premultiply, ref subscriptMode, ref bold, ref italic, ref underline, ref strikethrough)) { Color fc = tint * mColors[mColors.size - 1]; uc = fc; if (gradient) { gb = gradientBottom * fc; gt = gradientTop * fc; } --i; continue; } // See if there is a symbol matching this text BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null; if (symbol != null) { v0x = x + symbol.offsetX * fontScale; v1x = v0x + symbol.width * fontScale; v1y = -(y + symbol.offsetY * fontScale); v0y = v1y - symbol.height * fontScale; // Doesn't fit? Move down to the next line if (Mathf.RoundToInt(x + symbol.advance * fontScale) > rectWidth) { if (x == 0f) return; if (alignment != Alignment.Left && indexOffset < verts.size) { Align(verts, indexOffset, x - finalSpacingX); indexOffset = verts.size; } v0x -= x; v1x -= x; v0y -= finalLineHeight; v1y -= finalLineHeight; x = 0; y += finalLineHeight; prevX = 0; } if (isSymbol || !useWgSymbol) { verts.Add(new Vector3(v0x, v0y)); verts.Add(new Vector3(v0x, v1y)); verts.Add(new Vector3(v1x, v1y)); verts.Add(new Vector3(v1x, v0y)); } x += finalSpacingX + symbol.advance * fontScale; i += symbol.length - 1; prev = 0; if (uvs != null) { Rect uv = symbol.uvRect; float u0x = uv.xMin; float u0y = uv.yMin; float u1x = uv.xMax; float u1y = uv.yMax; if (isSymbol || !useWgSymbol) { uvs.Add(new Vector2(u0x, u0y)); uvs.Add(new Vector2(u0x, u1y)); uvs.Add(new Vector2(u1x, u1y)); uvs.Add(new Vector2(u1x, u0y)); } } if (cols != null) { if (symbolStyle == SymbolStyle.Colored) { if (isSymbol || !useWgSymbol) { for (int b = 0; b < 4; ++b) cols.Add(uc); } } else { Color32 col = Color.white; col.a = uc.a; if (isSymbol || !useWgSymbol) { for (int b = 0; b < 4; ++b) cols.Add(col); } } } } else // No symbol present { GlyphInfo glyph = GetGlyph(ch, prev); if (glyph == null) continue; prev = ch; if (subscriptMode != 0) { glyph.v0.x *= sizeShrinkage; glyph.v0.y *= sizeShrinkage; glyph.v1.x *= sizeShrinkage; glyph.v1.y *= sizeShrinkage; if (subscriptMode == 1) { glyph.v0.y -= fontScale * fontSize * 0.4f; glyph.v1.y -= fontScale * fontSize * 0.4f; } else { glyph.v0.y += fontScale * fontSize * 0.05f; glyph.v1.y += fontScale * fontSize * 0.05f; } } float y0 = glyph.v0.y; float y1 = glyph.v1.y; v0x = glyph.v0.x + x; v0y = glyph.v0.y - y; v1x = glyph.v1.x + x; v1y = glyph.v1.y - y; float w = glyph.advance; if (finalSpacingX < 0f) w += finalSpacingX; // Doesn't fit? Move down to the next line if (Mathf.RoundToInt(x + w) > rectWidth) { if (x == 0f) return; if (alignment != Alignment.Left && indexOffset < verts.size) { Align(verts, indexOffset, x - finalSpacingX); indexOffset = verts.size; } v0x -= x; v1x -= x; v0y -= finalLineHeight; v1y -= finalLineHeight; x = 0; y += finalLineHeight; prevX = 0; } if (ch == ' ') { if (underline) { ch = '_'; } else if (strikethrough) { ch = '-'; } } // Advance the position x += (subscriptMode == 0) ? finalSpacingX + glyph.advance : (finalSpacingX + glyph.advance) * sizeShrinkage; // No need to continue if this is a space character if (ch == ' ') continue; // Texture coordinates if (uvs != null) { if (bitmapFont != null) { glyph.u0.x = uvRect.xMin + invX * glyph.u0.x; glyph.u1.x = uvRect.xMin + invX * glyph.u1.x; glyph.u0.y = uvRect.yMax - invY * glyph.u0.y; glyph.u1.y = uvRect.yMax - invY * glyph.u1.y; } if (!isSymbol || !useWgSymbol) { for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j) { if (glyph.rotatedUVs) { uvs.Add(glyph.u0); uvs.Add(new Vector2(glyph.u1.x, glyph.u0.y)); uvs.Add(glyph.u1); uvs.Add(new Vector2(glyph.u0.x, glyph.u1.y)); } else { uvs.Add(glyph.u0); uvs.Add(new Vector2(glyph.u0.x, glyph.u1.y)); uvs.Add(glyph.u1); uvs.Add(new Vector2(glyph.u1.x, glyph.u0.y)); } } } } // Vertex colors if (cols != null) { if (glyph.channel == 0 || glyph.channel == 15) { if (gradient) { float min = sizePD + y0 / fontScale; float max = sizePD + y1 / fontScale; min /= sizePD; max /= sizePD; s_c0 = Color.Lerp(gb, gt, min); s_c1 = Color.Lerp(gb, gt, max); if (!isSymbol || !useWgSymbol) { for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j) { cols.Add(s_c0); cols.Add(s_c1); cols.Add(s_c1); cols.Add(s_c0); } } } else { if (!isSymbol || !useWgSymbol) { for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j) cols.Add(uc); } } } else { // Packed fonts come as alpha masks in each of the RGBA channels. // In order to use it we need to use a special shader. // // Limitations: // - Effects (drop shadow, outline) will not work. // - Should not be a part of the atlas (eastern fonts rarely are anyway). // - Lower color precision Color col = uc; col *= 0.49f; switch (glyph.channel) { case 1: col.b += 0.51f; break; case 2: col.g += 0.51f; break; case 4: col.r += 0.51f; break; case 8: col.a += 0.51f; break; } if (!isSymbol || !useWgSymbol) { Color32 c = col; for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j) cols.Add(c); } } } if (!isSymbol || !useWgSymbol) { // Bold and italic contributed by Rudy Pangestu. if (!bold) { if (!italic) { verts.Add(new Vector3(v0x, v0y)); verts.Add(new Vector3(v0x, v1y)); verts.Add(new Vector3(v1x, v1y)); verts.Add(new Vector3(v1x, v0y)); } else // Italic { float slant = fontSize * 0.1f * ((v1y - v0y) / fontSize); verts.Add(new Vector3(v0x - slant, v0y)); verts.Add(new Vector3(v0x + slant, v1y)); verts.Add(new Vector3(v1x + slant, v1y)); verts.Add(new Vector3(v1x - slant, v0y)); } } else // Bold { for (int j = 0; j < 4; ++j) { float a = mBoldOffset[j * 2]; float b = mBoldOffset[j * 2 + 1]; float slant = a + (italic ? fontSize * 0.1f * ((v1y - v0y) / fontSize) : 0f); verts.Add(new Vector3(v0x - slant, v0y + b)); verts.Add(new Vector3(v0x + slant, v1y + b)); verts.Add(new Vector3(v1x + slant, v1y + b)); verts.Add(new Vector3(v1x - slant, v0y + b)); } } } // Underline and strike-through contributed by Rudy Pangestu. if (underline || strikethrough) { GlyphInfo dash = GetGlyph(strikethrough ? '-' : '_', prev); if (dash == null) continue; if (!isSymbol || !useWgSymbol) { if (uvs != null) { if (bitmapFont != null) { dash.u0.x = uvRect.xMin + invX * dash.u0.x; dash.u1.x = uvRect.xMin + invX * dash.u1.x; dash.u0.y = uvRect.yMax - invY * dash.u0.y; dash.u1.y = uvRect.yMax - invY * dash.u1.y; } float cx = (dash.u0.x + dash.u1.x) * 0.5f; float cy = (dash.u0.y + dash.u1.y) * 0.5f; uvs.Add(new Vector2(cx, cy)); uvs.Add(new Vector2(cx, cy)); uvs.Add(new Vector2(cx, cy)); uvs.Add(new Vector2(cx, cy)); } } if (subscript && strikethrough) { v0y = (-y + dash.v0.y) * sizeShrinkage; v1y = (-y + dash.v1.y) * sizeShrinkage; } else { v0y = (-y + dash.v0.y); v1y = (-y + dash.v1.y); } if (!isSymbol || !useWgSymbol) { verts.Add(new Vector3(prevX, v0y)); verts.Add(new Vector3(prevX, v1y)); verts.Add(new Vector3(x, v1y)); verts.Add(new Vector3(x, v0y)); } Color tint2 = uc; if (strikethrough) { tint2.r *= 0.5f; tint2.g *= 0.5f; tint2.b *= 0.5f; } tint2.a *= 0.75f; Color32 uc2 = tint2; if (!isSymbol || !useWgSymbol) { cols.Add(uc2); cols.Add(uc); cols.Add(uc); cols.Add(uc2); } } } } if (alignment != Alignment.Left && indexOffset < verts.size) { Align(verts, indexOffset, x - finalSpacingX); indexOffset = verts.size; } mColors.Clear(); }