1

I'm working on a traditional roguelike project. I have a player log that displays what happens, played moved here, robot attacked player, player casts ability etc. This is fairly useful information and it updates frequently, every time the player takes an action at least one line will be generated and if there are any enemies around they too will generate lines.

I'm using a TMP wrapped inside a Viewport that is inside a scroll rect to display the last 10 or so lines of the log, and the scroll rect gives us the ability to scroll back up to look at previous lines.

Originally, I would naively add lines to the TMP.text and this would just grow, but, I quickly noticed performance issues, whenever text was appended the TMP.GenerateTextMesh/GenerateText calls would quickly grow in magnitude to the point where it was visibly slowing the game down after one hundred lines or so.

So to partially fix the problem I now store the log's text in a rotating circular buffer and keep only 20 or so lines in the TMP.text that makes the performance fine-ish. But that .text value still needs to change when new lines are added or the user scrolls to look at old lines. Even with a relatively small amount of text, 20 or so lines, the calls for TMP.GenerateText are taking around ~10ms (with deep profiler on) which is an insane cost for a relatively simple piece of UI. The cost is per update, so once made the text is cheap to render, but if you have a text field changing frequently the cost is exorbitant.

Is there a better solution to display a dynamic text field with frequently changing content? I'd imagine anything with a chat client is facing similar issues so there must be a solution.

FrontBadger
  • 41
  • 1
  • 4
  • Usually a chat client uses one text element for one dialog. You can do something similar like one text for one line then put them in a layout group. – shingo Jun 29 '22 at 05:00
  • 1
    `anything with a chat client` is usually not using a 3D game rendering engine to display the chat client. hundreds of lines is indeed a big issue since TMP basically has to convert all this into a 3D mesh with vertices and then render it. A Unity app is not really made for processing and displaying huge amounts of text. One way though might be to simply not update your text display each and every frame but rather allow spikes every 3 seconds or so and update your display in intervals – derHugo Jun 29 '22 at 06:54
  • Losing 10ms every once in a while really doesn't sound that bad. That said, concatenating strings is a very slow operation - is that what the 'circular buffer' is doing? If so try using a `Stringbuilder`. – Absinthe Jun 29 '22 at 08:17
  • 3D text in a combat log seems bit little like overkill to me, why not just used a standard raster font? It's waaay quicker than rendering 3D text and I'll think you'll find that's what MMORPGs do anyway. –  Jun 29 '22 at 11:04
  • Thanks for the comments. A couple of points to clarify. Concatenating the strings is already done with a stringbuilder and is not the source of the slowdown. The source of the slowdown is the TMP rebuilding the vertices to render the new text data. The text is not 3D and rendering time is not an issue, it renders quickly. The issue is the rebuilding time. TMP does not rebuild every frame, it only rebuilds when new input comes in but, I like the idea of batching the new text coming in for 1000ms or so in case there are a number of keypresses, +1 derHugo. – FrontBadger Jun 29 '22 at 13:44
  • Also if anyone knows how to do a simpler text display in Unity that doesn't involve having to rebuild vertices and render them as 3d objects I am all ears, some simple googling did not yield any results. – FrontBadger Jun 29 '22 at 13:45

3 Answers3

0

As per derHugo's idea, batching the incoming text every X ms made a huge performance difference when the user is pressing keys frequently and generating large text changes.

Now when new text comes in, if the batch time has not started, it starts the batch timer, collects all the text for the next 250ms and then updates the TMP with the new data.

FrontBadger
  • 41
  • 1
  • 4
0
  1. Split each line to separate TextMeshPro-Component
  2. Disable additional features if you can, especially Auto-Sizing
  3. Use smaller font atlas resolution
  4. use less characters font
  5. Not use ContentSizeFitter, Resize RectTransform by yourself
  6. disable RectTransform.anchors from stretch-mode-values
  7. disable RectMask.Culling (You can make this by reflection)
  8. disable RectMask.Softness (You can make this by reflection)
  9. hide labels by CanvasGroup.alpha = 0, not by TMPro.alpha or GameObject.SetActive or TMPro.enabled or TMPro.text = Empty
  10. Disable ScrollRect if it's not need
  11. not resize RectTransform, if you can (single-line console)
  12. not use LayoutGroup, position TMPros by yourself
0

Make sure you didn't create so many font assets, duplicate the original material of one which has the same font atlas instead and customize it with what you want to like bloom or light. Use mobile mode when you don't need the desktop version.

Hadestrb
  • 1
  • 1