Hi,
I am struggling to find a way to make a text curve around a custom path. The image shows what I would like to achieve.
I’ve found a lot of old threads that suggest overriding OnFillVBO, but this function is obsolete nowadays. Are there any solutions or approaches you recommend to solve this problem?
Some more details about the circumstances:
- The path that the text should follow can have pretty much any shape, but is always two dimensional
- The path is just a List of Vector3’s
- I would prefer the text to be an object in world space and not the UI, but I think both could work if necessary
- The context for how I use this is to label custom polygons with something similar to centerline labelling.
Any help would be greatly appreciated!
Alright
Since noone replied so far, I figured there’s probably no easy built-in solution to this and started fiddling around myself. The screenshot shows the result of the solution I came up with. Please note that the approach might not work perfectly for everyone since I built this for a specific project. Also the code is probably not very clean but I’ll post it anyway since it might help someone in the future who finds this thread with a similar problem.
Here’s the function:
It basically creates single letters as TextMeshes and places them on the given path in world space, which is defined a List of Vector2’s (the VPoint structure that I use can be understood as a Vector2).
I’m sure there’s still room for improvement, but for now I am happy with the result.
/// <summary>
/// Draws a text along a given path.
/// Position defines where (relatively) on the path the text is drawn (0.5 = center).
/// PreferredSize defines the font size in world space. If the path is too short for the whole text, the size is scaled down.
/// LetterPaddingFactor defines the space between letters relatively to the font size. (1 = space is equal to font size)
/// </summary>
private static GameObject DrawTextAlongPath(string text, List<VPoint> path, float position = 0.5f, float preferredSize = 0.15f, float letterPaddingFactor = 1f)
{
GameObject textObject = new GameObject(text);
float letterPadding = preferredSize * letterPaddingFactor;
float totalTextWidth = text.Length * letterPadding; // approx
float curPointDistance = 0f;
int curPointIndex = 0;
float pointDistance = Vector2.Distance(VPointToVector2(path[curPointIndex]), VPointToVector2(path[curPointIndex + 1]));
// Calculate where on the path to start with the text
float totalPathDistance = GetPathDistance(path, 0f);
float desiredStartDistance = (totalPathDistance * 0.5f) - (totalTextWidth * 0.5f);
while(desiredStartDistance < 0)
{
preferredSize -= 0.01f;
letterPadding = preferredSize * letterPaddingFactor;
totalTextWidth = text.Length * letterPadding;
desiredStartDistance = (totalPathDistance * 0.5f) - (totalTextWidth * 0.5f);
}
float curDistance = desiredStartDistance;
while(desiredStartDistance > pointDistance)
{
desiredStartDistance -= pointDistance;
curPointDistance += pointDistance;
curPointIndex++;
pointDistance = Vector2.Distance(VPointToVector2(path[curPointIndex]), VPointToVector2(path[curPointIndex + 1]));
}
Debug.Log("curDistance: " + curDistance + ", curIndex: " + curPointIndex + ", curPointDistance: " + curPointDistance + ", totalTextWidth: " + totalTextWidth + ", pathDistance: " + totalPathDistance);
// Go through each char individually and place them along the path
foreach (char c in text)
{
// Instantiate textmesh object with chat
GameObject letterObject = new GameObject(c.ToString());
letterObject.transform.SetParent(textObject.transform);
TextMesh textMesh = letterObject.AddComponent<TextMesh>();
textMesh.anchor = TextAnchor.MiddleCenter;
textMesh.text = c.ToString();
letterObject.transform.localScale = new Vector3(preferredSize, preferredSize, preferredSize);
// Make it smooth and crisp
textMesh.fontSize = 120;
letterObject.transform.localScale /= 10;
// Calculate and set position/rotation of textmesh
float angle = Vector2.SignedAngle(VPointToVector2(path[curPointIndex + 1]) - VPointToVector2(path[curPointIndex]), Vector2.up);
Vector2 position2D = Vector2.Lerp(VPointToVector2(path[curPointIndex]), VPointToVector2(path[curPointIndex + 1]), (curDistance - curPointDistance) / pointDistance);
letterObject.transform.position = new Vector3(position2D.x, 0f, position2D.y);
letterObject.transform.rotation = Quaternion.Euler(90f, angle - 90f, 0f);
// Update current position
curDistance += letterPadding;
float tmpWidth = (curDistance - curPointDistance);
while(tmpWidth > pointDistance)
{
tmpWidth -= pointDistance;
curPointDistance += pointDistance;
curPointIndex++;
pointDistance = Vector2.Distance(VPointToVector2(path[curPointIndex]), VPointToVector2(path[curPointIndex + 1]));
}
}
return textObject;
}
Sorry about the delayed response.
This presumes you will be using TextMeshPro either the normal component or with the canvas system.
Please take a look at example “25 - Sunny Days Example” included in the TMP Examples & Extras. More specifically, look at WarpTextExample.cs script script used which bends the text. You should be able to adapt this implementation to have the text follow a curve.
There are other examples like Animating Vertex Attributes which are similar where each character jitters and changes color.
The benefit of these implementation is that you only have a single text object for each label.
I know that several TMP users have implemented their own method to have text follow some curve. I believe there might be some of those still buried somewhere on the forum here. Here is one such thread .
3 Likes
Hi Stephan
Thanks a lot for the resources, definitely seems more fleshed out than what I did. I’m gonna have a look at it.
1 Like