I would also love some input from UT on this, if it’s not already possible with UI components.
To summarise, I’m trying to implement a message box that will automatically resize based on its contents.
We previously did this manually in NGUI, but I’ve been trying to use the new UI components to automate it more. (as I feel it should be possible in a robust UI system)
Let me show you what we have and what we want, and if this needs to be something Unity include in their API.
Desired Behaviour
So this is our message box, pretty basic, a title Text, a content Text, and a Button (which sits within a Horizontal Layout Group, but that’s unrelated).
When more text is added, the box should resize to fit it. This is possible using a Vertical Layout Group and a Content Size Fitter set to Vertical Fit: Preferred Size. This then scales the height up to fit the new text, this is great, but looks a bit odd when it gets too tall.
Ideally we would want it to scale in an even manner, or at a specifiable aspect ratio (for want of a better word).
I have been unable to get this working with the standard UI components. If there is a way, I’d love to know how.
The problem with using the Horizontal Fit, is that the text will make the width of the box too large, as the text you see above is one line. See:
This is what we want:
As you can see, the width has scaled up just enough to keep the general aspect of the box, while fitting all contents. This is done through a (pretty messy right now) small script sitting on the RectTransform of the message box.
Solution (sort of)
So, I won’t go into the details of how the whole box is set up right now, but I can do if it becomes relevant/people want to know. I can upload an example project potentially at some point too, if needed.
The script:
using UnityEngine;
using System.Collections;
/// <summary>
/// This component resizes a panel (or any object with a RectTransform), to try to fit the specified aspect ratio (within a threshold)
/// </summary>
[ExecuteInEditMode]
public class PanelRatioFitter : MonoBehaviour
{
#region Public Variables
/// <summary>
/// The targetted aspect ratio of the RectTransform
/// </summary>
public Vector2 targetAspect = new Vector2(16, 9);
#endregion
#region Private Variables
/// <summary>
/// A ref of the local RectTransform for performance
/// </summary>
RectTransform cachedRect;
/// <summary>
/// The percentage 'boundary' within which the aspect ratio is seen to be acceptable ie 1.78 +/- x%
/// </summary>
float aspectDelta = 5f;
/// <summary>
/// A store of the previous frames rect height, used to check if the height has changed
/// </summary>
float previousHeight;
/// <summary>
/// A check to see if the resize is attempting to flick between 2 close-to-target ratios
/// </summary>
bool loopingCheck1;
/// <summary>
/// A check to see if the resize is attempting to flick between 2 close-to-target ratios
/// </summary>
bool loopingCheck2;
bool resizing;
#endregion
#region Unity Functions
// Use this for initialization
void Start ()
{
cachedRect = GetComponent<RectTransform>();
}
void Update()
{
if (cachedRect)
{
// If the height of the rect has changed, check and perform the resize
if (Mathf.Abs(cachedRect.sizeDelta.y - previousHeight) > 0.01f)
{
StartCoroutine(Resize());
previousHeight = cachedRect.sizeDelta.y;
}
else
{
loopingCheck1 = loopingCheck2 = false;
}
}
else
{
cachedRect = GetComponent<RectTransform>();
}
}
#endregion
#region Custom Functions
IEnumerator Resize()
{
// if the function is found to have 'flicked' between 2 acceptable aspect ratios (and therefor can't decide
// which to keep), this makes sure it does not continue attempting to resize.
if ((loopingCheck1 && loopingCheck2) || resizing)
yield break;
resizing = true;
float increment = 10f;
// First loop to check the current aspect ratio being lower than the targetted ratio, slightly increasing the width until it is acceptable
while (cachedRect.sizeDelta.x / cachedRect.sizeDelta.y < ((targetAspect.x / targetAspect.y) - (((targetAspect.x / targetAspect.y) / 100f) * aspectDelta)))
{
cachedRect.sizeDelta = new Vector2((cachedRect.sizeDelta.x) + increment, cachedRect.sizeDelta.y);
increment += 10f;
if (increment > 100)
{
break;
}
yield return new WaitForEndOfFrame();
loopingCheck1 = true;
}
increment = 10f;
// Now loop to check the current aspect ratio being higher than the targetted ratio, slightly decreasing the width until it is acceptable
while (cachedRect.sizeDelta.x / cachedRect.sizeDelta.y > ((targetAspect.x / targetAspect.y) + (((targetAspect.x / targetAspect.y) / 100f) * aspectDelta)))
{
cachedRect.sizeDelta = new Vector2((cachedRect.sizeDelta.x) - increment, cachedRect.sizeDelta.y);
increment += 10f;
if (increment > 100)
{
break;
}
yield return new WaitForEndOfFrame();
loopingCheck2 = true;
}
resizing = false;
}
#endregion
}
It’s a pretty bad way of doing it, but it works for the most part.
There are a few bugs, but this is a really quick test implementation.
The main problem (other than the fact it’s not built in AFAIK), which admittedly is minor, is that it doesn’t update correctly in the editor. I’ve tried a few things to get it working, a custom editor script, using ExecuteInEditMode, manually calling SceneView redraws. But my thinking eventually was, should this just be something the UI system can do natively?
And that’s my real question, though input on the rest of the implementation also welcome, of course.
And if this was to be in Unity (Assuming it isn’t already and I just don’t know how to do it), should it be a separate component (like my script), or a ‘restrict ratio’ or ‘restrict x/y’ input to the Content Size Fitter?
What are peoples thoughts? And UT’s, if anyone’s about?
Sorry for the super long post.
TL DR - Is there a way/Should there be a way, to restrict the Content Size Fitter by a ratio or specific size?