Unity - Updating Font Size tags in TMPro Stylesheet on runtime? - unity3d

I have a style sheet set up in which I set up tags that set font size such as <size=23px>. Is it possible to somehow modify the stylesheet on runtime to change these sizes (even if I have to do it one by one)? It's for implementing a font size slider in the UI settings of the game (ideally I'd like to multiply each fontsize by a modifier).
Thank you
Edit: To clarify, I set the sizes via opening tags in the style sheets and I am looking to update these settings on runtime, NOT update the text size on the TextMeshProUGUI components!
I'm able to reference my default styleSheet directly. According to https://docs.unity3d.com/Packages/com.unity.textmeshpro#1.3/api/TMPro.TMP_StyleSheet.html "GetStyle" retrieves "the Style matching the HashCode" (or name if I give it a string parameter). Once I get the style, however, I have very limited choice: https://docs.unity3d.com/Packages/com.unity.textmeshpro#1.3/api/TMPro.TMP_Style.html?q=TMP_Style based on this, my best bet would be to query "styleOpeningDefinition".
I am able to get the opening tags via styleOpeningDefinition, and then find and replace the relevant number that denotes the size in the "<size="50px">" tag but it's a readonly property so I cant set it afterwards.
Here's the code:
TMP_Text text = GetComponent<TMP_Text>();
TMP_Style style = styleSheet.GetStyle("H1");
openingTags = style.styleOpeningDefinition;
string[] size = openingTags.Split("px><cspace=");
string _splitString = size[0].Replace("<size=", "");
float _currentSize = float.Parse(_splitString);
_currentSize *= multiplier;
string _newTags = "<size=" + _currentSize + "px>" + size[1];
style.styleOpeningDefinition = _newTags; //Property or indexer 'TMP_Style.styleOpeningDefinition' cannot be assigned to -- it is read only
style.RefreshStyle();

In Unity, you can use the UnityEngine.UI.Text component to change the font size of text elements on runtime. You can use the fontSize property of the Text component to change the font size.
using UnityEngine;
using UnityEngine.UI;
public class FontSizeModifier : MonoBehaviour
{
public Text text;
public float fontSizeModifier = 1f;
void Update()
{
text.fontSize = (int)(text.fontSize * fontSizeModifier);
}
}
This script will update the font size of the text element on every frame, multiplying the current font size by the fontSizeModifier variable.
You can use this script and attach it to the text element you want to modify, or you can create a list of texts and modify them all via a loop.

Related

Unity how to Create Texts via script?

I'm trying to create Texts via script. I want to create texts with the name of objects of a certain tag.
"For example, if I have two objects named Cube and Sphere and both have a tag of "TargetObj" then their names should be displayed as texts on the screen(in the case Cube, Sphere)"
I want to achieve this regardless of the number of objects. so a loop is needed.
Here is what I've tried so far.
[SerializeField] GameObject LevelCanvas;
targetObjects = GameObject.FindGameObjectsWithTag("TargetObj");
foreach (var obj in targetObjects)
{
Text mytext = LevelCanvas.AddComponent<Text>();
mytext.text = "Find " + obj.name;
Font ArialFont = (Font)Resources.GetBuiltinResource(typeof(Font), "Arial.ttf");
mytext.font = ArialFont;
mytext.material = ArialFont.material;
}
it only shows one object name although I have 2 more objects that were supposed to be shown as well.
You need to create a text object on a new gameobject.
static Text CreateText(Transform parent)
{
var go = new GameObject();
go.transform.parent = parent;
var text = go.AddComponent<Text>();
return text;
}
var mytext = CreateText(LevelCanvas.transform);
myext.text = ...
Many components in unity can only be added once per game object. That means you need a separate game object for every text you want to show.
A typical way to approach this is to create a "prefab" of a game object which has a text element already. This way you can set a default font and other settings.
Then in code you use the Instantiate method on that prefab and via GetComponent() you can access the text component instance to set the desired string to display.

Custom value slider unity

I want to return displayed value (a,b,c,d) not numbers (0.01-0.019 ect)
A help will be nice and many people will be happy.
This code look like percentage display but I dont want percentage I want a text
Mathf.RoundToInt(value * 100) + "%"
Instead of making a whole new custom slider, utilize what Unity already provides then map the output to whatever format you desire. Sliders can be set to have a fixed number of outputs as well as be fixed to only whole numbers.
If you look at the slider component in the editor, the fields Min Value, Max Value, and Whole Numbers should be set. The most important is to set Whole Numbers to true and Max Value to the highest possible value - 1 of your new mapped set.
Next, when the value changes for your slider, you will want to set a callback delegate to your script to retrieve the value.
You can either add the callback in the editor using the UnityAction UI in the editor or you can programmatically add the callback by accessing the onValueChange listeners and adding a new listener. For the example, I will do everything in code to remove any sort of confusion.
All that is left is to map our retrieved values our slider outputs to some desired output. As your desired output seem to just be alphanumeric values, you can actually make the solution even easier by knowing that taking an integer value and adding the character 'a' to it will result in the corresponding alphanumeric value (where 0 is mapped to a and 25 to z).
[SerializeField] private Slider slider = null;
private void Start()
{
slider.onValueChanged.AddListener(delegate { SliderValueChangedCallback(); });
}
/// <summary>
/// Called when our slider value changes
/// </summary>
private void SliderValueChangedCallback()
{
// grab out numeric value of the slider - cast to int as the value should be a whole number
int numericSliderValue = (int)slider.value;
// now, plot our value to an alphanumeric one
char alphaNumericValue = (char)(numericSliderValue + 'a');
Debug.Log(alphaNumericValue);
}
Now this solution will only work if you want your outputted values to be alphanumeric values. If you want a more modular solution, here is one:
[SerializeField] private Slider slider = null;
[SerializeField] private Text currentValue = null;
[SerializeField] private List<string> yourValueList = new List<string> { "First Message", "Second Message", "Third Value", "4 for some reason", "the letter 6" };
private void Start()
{
slider.onValueChanged.AddListener(delegate { SliderValueChangedCallback(); });
// assuring that our slider is setup properly to map values
slider.minValue = 0;
slider.maxValue = yourValueList.Count - 1;
slider.wholeNumbers = true;
}
/// <summary>
/// Called when our slider value changes
/// </summary>
private void SliderValueChangedCallback()
{
// grab out numeric value of the slider - cast to int as the value should be a whole number
int numericSliderValue = (int)slider.value;
// debugging - do whatever you want with this value
currentValue.text = yourValueList[numericSliderValue];
}
Here is a gif of it in action:
Let me know if you have questions. I was unsure if you visually want to show these markers on the slider as well, but that can also be done using the normalized positions and a horizontal layout group.

Range Slider with ETO form

A range slider is a slider with two "knobs" and the second "knob" must always have a value > that of the first "knob".
What would be the best way to achieve a range slider in ETO forms?
looks like the Slider : Control class does not expose enough information to create a slider with double Knobs.
Perhaps one way to "fake" it is to put two slider objects side by side, where the value of the first slider becomes the min value of the second slider (this will require some resizing too)?
If we were to create a custom double slider object,
http://pages.picoe.ca/docs/api/html/T_Eto_Forms_Slider.htm
To create custom controls in Eto.Forms, you can use the Drawable class, which gives you a Paint event that you can draw the UI you need, handle mouse events, etc. Setting CanFocus to true allows it to be focused so you can handle key events. For example:
public class RangeSlider : Drawable
{
public RangeSlider()
{
CanFocus = true;
Size = new Size(200, 20);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// draw the range slider using e.Graphics
}
}

Unity - Set GUI.Box background color

I'm trying to set the background color of a GUI.Box:
void OnGUI()
{
string LatLong;
LatLong = map.calc.prettyCurrentLatLon;
var mousePosition = Input.mousePosition;
float x = mousePosition.x + 10;
float y = Screen.height - mousePosition.y + 10;
GUI.backgroundColor = Color.red;
GUI.Box(new Rect(x, y, 200, 200), LatLong);
}
However, the box is showing in a semi-transparent black, and the white text is subdued, not opaque white.
You have to use s gui style:
private GUIStyle currentStyle = null;
void OnGUI()
{
InitStyles();
GUI.Box( new Rect( 0, 0, 100, 100 ), "Hello", currentStyle );
}
private void InitStyles()
{
if( currentStyle == null )
{
currentStyle = new GUIStyle( GUI.skin.box );
currentStyle.normal.background = MakeTex( 2, 2, new Color( 0f, 1f, 0f, 0.5f ) );
}
}
private Texture2D MakeTex( int width, int height, Color col )
{
Color[] pix = new Color[width * height];
for( int i = 0; i < pix.Length; ++i )
{
pix[ i ] = col;
}
Texture2D result = new Texture2D( width, height );
result.SetPixels( pix );
result.Apply();
return result;
}
Taken from unity forum.
I'm gonna slide in with a more elegant solution here before this question gets old. I saw Thomas's answer and started to wonder if there is a way to do that without having to do the "InitStyles" in the OnGUI loop. Since ideally you only want to init the GuiSkin once in Awake or Start or wherever, but only once, and then never check to see if it's null ever again.
Anyway, after some trial and error, I came up with this.
private void Awake() {
// this variable is stored in the class
// 1 pixel image, only 1 color to set
consoleBackground = new Texture2D(1, 1, TextureFormat.RGBAFloat, false);
consoleBackground.SetPixel(0, 0, new Color(1, 1, 1, 0.25f));
consoleBackground.Apply(); // not sure if this is necessary
// basically just create a copy of the "none style"
// and then change the properties as desired
debugStyle = new GUIStyle(GUIStyle.none);
debugStyle.fontSize = 24;
debugStyle.normal.textColor = Color.white;
debugStyle.normal.background = consoleBackground;
}
REVISION - 17 July 2022 - GUI Style Creation and Storage
Prelude
Style creation through the methods provided by others are certainly functional methods of providing your custom editors with a unique look. They have some fundamental issues I should point out, which my method doesn't outright correct, just alleviate. This method still needs to be expanded upon and is still a partly experimental progression from a personal plugin.
Creating styles every OnGUI Call creates unnecessary, extra instructions for your editor window. This doesn't scale well past a handful (4~) styles.
By creating styles every time OnGUI is called, you're creating textures repeatedly for the background colour (not good). Over prolonged use of this method, memory leaks can occur although unlikely.
What does my method do differently?
Creates GUIStyle and Texture2D files. GUIStyles are saved as .JSON files, which is best compatible for [JSON <-> GUIStyle] conversion and storage.
Texture2Ds are encoded from raw data to PNG format through UnityEngine.
Checks if a style file is null before fetching or creating any missing styles again.
Contains a list of all styles through the use of a Style Manifest (struct) to store the names of all textures and styles to iteratively load on fetch.
Only creates styles if they are missing. Does not spend resources on creating pre-existing styles and pre-existing styles.
GUIStyles (as JSONs) and Texture2D files are stored in a Resources folder within the Plugin folder.
It should be noted that my style methods are done with the express understanding and consideration of GUISkins existing. They are not suitable for my UI/UX needs.
How is this done?
Plugin Style Handing Diagram
I separate Style Loading into a unique namespace for style handling and contain functions, as well as public variables for global plugin access, within. This namespace creates, loads and can send back styles on the requests sent by other scripts.
A call to a function is made when the plugin is opened to load the style manifest and subsequently all styles and textures are loaded, to be relinked for use.
If the styles manifest is missing then it is recreated along with all GUIStyle files. If the styles manifest is not missing but a style is then that style is recreated and the style manifest is modified to reference the new style.
Textures are handled separately from GUIStyle loading and are collected in a separate array. They are independently checked to see if they still exist and missing textures are recreated and encoded from raw data to PNG format, with the style manifest being modified when necessary.
Instead of repeatedly creating all styles or repeatedly loading them each frame, the plugin sends a request to fetch the first style from memory and checks if the result is null. If the first style returns as null then the plugin assumes all styles have been dereferenced and calls a reload or recreation of the relevant GUIStyle files (this can happen because of the engine entering/exiting play mode and is necessary to preserve UI/UX legibility).
If the style returns as a valid reference, plugins of mine do use it but this is risky. It's a good idea to also check at least one texture because textures are at risk of being dereferenced from the Texture2D array.
Once each check is done, the plugin renders the layout as normal and the next cycle begins. Using this method overall requires extra processing time and extra storage space for the plugin but in turn:
Quicker over a longer period of time due to styles being created or loaded only when necessary
Easier to modify themes for plugins, allowing individuals to customize the tools to their preferred theme. This can also be expanded on custom "theme packs" for a plugin.
Scalable for large amounts of styles to an extent.
This method still requires experience in GUI Styles, Data Saving, JSON Utilisation and C#.

RectTransform coordinates according to TextMesh Pro text size

I'm trying to create an InputField (TextMesh Pro) with a dynamic (its text content may vary) prefix.
This spectacular image should explain the goal.
https://imgur.com/a/qx1eXOa
So I set a TextMeshPro text to use as Prefix, and by script I was trying to "move" the TextArea accordingly.
The fact is, TextArea is a RectTransform, and I'm operating in a ScreenSpace render mode.
I was trying like this:
private TextMeshProGUI prefix;
private RecTransform textArea;
public void ChangePrefixTo(string newPrefix)
{
float oldWidth = prefix.preferredWidth;
prefix.text = newPrefix;
float newWidth = prefix.preferredWidth;
Vector2 newPos = new Vector2();
newPos.x = textArea.position.x + (newWidth - oldWidth);
newPos.y = textArea.position.y;
textArea.position = newPos;
}
, but the textArea gets shot into the stars.
How can I map the RectTransform position according to the size of a TextMeshPro text?
Thanks for the help and long live the whales
Instead of using a script to resize your prefix, you can group both of your elements in a Horizontal Layout Group, check only Width for Child Controls Size.
Add a Layout Element and set your Preferred Width to define the size of your TextArea.
The Prefix will scale according to his content, and it will push your text area as it grows.