Location Boston, MA
Interests Programming, Photography, Cooking, Game Development, Reading, Guitar, Running, Travel, Never having enough time...
This Site Made with Astro, Cloudinary , and many many hours of writing, styling, editing, breaking things , fixing things, and hoping it all works out.
Education B.S. Computer Science, New Mexico Tech
Contact site at dillonshook dot com
Random Read another random indie blog on the interweb
Subscribe Get the inside scoop and $10 off any print!
Minimizing string GC in Unity
An easy trap to fall into when you’re starting out in Unity is creating strings each frame in your update loop. Say you have score that can change from a bunch of things: eliminating foes, jumping on a block, collecting a ring, or just existing. Whatever floats your boat. Then in your score updater component you just do:
void Update(){
textComponent.text = "Score: " + score.ToString();
}
Easy and done. Problem is, you’re now creating a string (which are immutable in C# and allocate memory) each frame. This will really add up and create lots of garbage which will need collecting.
The Unity docs have a little bit on this subject if you search for “strings”. However it leaves much to the reader to figure out in their own sweet time.
There are a couple approaches I’ve thought of that each have some pros and cons.
Solution 1
Use images or some other render method instead of text. For example if you have a countdown timer that only uses a single digit you can make images for 0-9 and then swap them out based on the timer value.
Pros: Doesn’t need to allocate memory at all.
Cons: Tedious to implement, only works well for fixed length and known strings.
Solution 2
Use events to trigger a function to update the text when the value changes. For example if you have an OnScoreChange event that you wire up to an UpdateScoreText function.
Pros: Only update the text (and allocate memory) when the value changes
Cons: Cumbersome to wire up all these events and handlers for every piece of text that has this pattern.
Solution 3
Manually keep track of the previous value and only change the text when it changes.
int previousScore = -1;
void Update(){
if(score != previousScore){
previousScore = score;
tmp.text = "Score: " + score.ToString();
}
}
Pros: Only update the text when the value changes
Cons: Annoying amount of boilerplate for each string to update, even though it’s less than the event binding solution.
Solution 4 - Preferred
Create a class to keep track of all the previous values and only update the strings when the value changes. In the example below there is a ScoreChanger component that arbitrarily increases a score and then uses this method to only update the string when the score changes.
/* StringChanger.cs */
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class ScoreChanger : MonoBehaviour {
TMP_Text tmp;
StringChanger stringChanger = new StringChanger();
float timer;
int score = 0;
void Awake(){
tmp = GetComponent<TMP_Text>();
}
void Update(){
timer += Time.deltaTime;
if((int)timer % 2 == 0){
score++;
}
stringChanger.UpdateString(tmp, nameof(score), score);
}
}
public class StringChanger {
Dictionary<string, int> prevIntValues = new Dictionary<string, int>();
Dictionary<string, float> prevFloatValues = new Dictionary<string, float>();
public void UpdateString(TMP_Text tmp, string name, int value, string format = null){
if(!prevIntValues.ContainsKey(name) || prevIntValues[name] != value){
prevIntValues[name] = value;
if(!string.IsNullOrEmpty(format)){
tmp.text = string.Format(format, value);
}else{
tmp.text = value.ToString();
}
}
}
public void UpdateString(TMP_Text tmp, string name, float value, string format = null){
if(!prevFloatValues.ContainsKey(name) || prevFloatValues[name] != value){
prevFloatValues[name] = value;
if(!string.IsNullOrEmpty(format)){
tmp.text = string.Format(format, value);
}else{
tmp.text = value.ToString();
}
}
}
}
Pros: Only update the text when the value changes, and a single point to store previous values
Cons: Overloads for each type of data you want to track have to be created to avoid allocations from boxing that would happen if you stored the values as objects.
This way is great for most of my use cases and since I primarily have int and float values it’s not too clunky to create the overloads. Let me know in the comments if you think of any other ways to tackle this problem or have suggestions for improvement!
Want the inside scoop?
Sign up and be the first to see new posts
No spam, just the inside scoop and $10 off any photo print!