Unity-UI-Rounded-Corners icon indicating copy to clipboard operation
Unity-UI-Rounded-Corners copied to clipboard

Current approach requires multiple materials for different sized images

Open chrisdeeming opened this issue 3 years ago • 1 comments

For the project I am working on, there are many UI elements which will have different dimensions and different radius values. There are buttons of various sizes, there are UI panels with various sizes which are sometimes nested within each other.

I didn't initially realise that each of these UI elements would need different materials, so all of my rounded corner elements were all using the same material.

Of course I'd be setting a large panel with a width/height of 1280 x 768 to have a radius value of 50 then I'd be setting a very small button to have a radius of 10 and those values would be affecting the large panel.

Because there will be many different elements with many different dimensions, I don't really want to be making a material each time.

I also found the setup somewhat strenuous.

  1. Create material
  2. Add Image game object
  3. Add Rounded corners component
  4. Assign material to both image and rounded corners component

Is there a an alternative approach that is worth exploring?

I'm really quite new to this but I made some adjustments which seem to work for me, but I can't quite find a consensus as to whether it may have wider issues.

using UnityEngine;
using UnityEngine.UI;

[ExecuteInEditMode, RequireComponent(typeof(Image))]
public class RoundedImage : MonoBehaviour
{
	private static readonly int Props = Shader.PropertyToID("_WidthHeightRadius");

	public float radius;

	private Image _image;
	private bool _isSetup;

	void OnRectTransformDimensionsChange()
	{
		Refresh();
	}
	
	private void OnValidate()
	{
		Refresh();
	}
		
	private void SetupMaterial()
	{
		if (_isSetup)
		{
			return;
		}

		_image = GetComponent<Image>();
		if (_image.material != null)
		{
			_image.material = Instantiate(_image.material);
		}

		_isSetup = true;	
	}

	private void Refresh()
	{
		SetupMaterial();
			
		var rect = ((RectTransform) transform).rect;
		_image.material.SetVector(Props, new Vector4(rect.width, rect.height, radius, 0));
	}
}

After creating a single material which can be re-used across any image of any size, the steps to set it up are as follows:

  1. Add Image game object
  2. Add Rounded corners component
  3. Assign material to image only

The changes to the script are as follows:

  • We use RequireComponent to ensure we always have an Image component.
  • There is no longer a public property to provide a reference to the material.
  • Because we know the image already has a reference to the material we get that component and get a reference to the material from that.
  • If we made changes to the material on the image directly we would still experience the same issue I've had with the different values on different objects fighting with each other.
  • So we instead replace the reference to the material on the image with a clone that we instantiate.

I imagine there is some overhead to instantiating a new instance of the material but I think these would represent what would have to be multiple separate materials for my use case anyway so it seems ok to me.

This is how it looks. You can see the outer white panel has a much smaller radius than the inner red panel. This is using the same material on different objects.

image

If I flip back to the original approach you can see that both the outer and inner element have now ended up with the same radius:

image

I haven't yet looked at converting the independent rounded corners script but I suspect it would work in a similar way.

Does this seem beneficial or might there be considerable issues with this approach?

chrisdeeming avatar Mar 11 '21 01:03 chrisdeeming

Really, we want 5px for one object and another 20px, can they not be independent?

Thank you @kirevdokimov This solutions works for me👍.

BirukTes avatar Mar 19 '21 16:03 BirukTes