Auto-Scaling Sprites to always use the same amount of space.

So I like using sprites as GUI elements, but they don’t have some of the nifty features that normal GUI elements do- Positioning something in Viewport space is easy, but I want to auto-scale the sprite to always take up the same amount of space on the screen. (Much like GUI textures can scale to a percentage of the screen size.) What is the best way to do this?

I confess I have read similar questions to this, but I’ve attempted implementing a number of their solutions and none of them quite get what I want done. (usually they end up stretching the sprite and not scaling the whole sprite uniformly.) Since I was having trouble wrapping my head around this problem, I was hoping I might have better luck with posting my own question. Thanks much!!

Solved it!

The problem: auto-scale a sprite to always take up the same amount of space on the screen.

The solution:

using UnityEngine;
using System.Collections;

public class spriteScale : MonoBehaviour {

	SpriteRenderer sr;

	Vector3 designScaleW = Vector3.zero; //scale in world space
	float designSizeW = 0f; // size of the sprite in x in world space.
	float designScaleV = 0f; // viewport size of the sprite- percent of x that the sprite takes.
	public Vector2 designScreen = Vector2.zero; // the pixel size of the screen
	float designScreenWidth = 0f; // the size of the width of the screen in units.	

	float wScreenWidth = 0f; //how many units wide the screen is- "w" means world space.
	float wScreenHeight = 0f; //how many units tall the screen is

	void Start()
	{
		sr = GetComponent<SpriteRenderer>();
		if(sr==null)
		{
			Debug.LogError("Gameobject doesn't have a SpriteRenderer!");
		}

		designScaleW = transform.localScale; //save the original scale.
		designSizeW = sr.sprite.bounds.size.x * designScaleW.x; //save the original size, at the original scale.
		designScreenWidth = (Camera.main.orthographicSize*2f)/designScreen.y * designScreen.x; //get the width of the design screen
		designScaleV = designSizeW/designScreenWidth; //get the design viewport size. this is the target for the new resolution.

	}

	void Update()
	{
		wScreenHeight = Camera.main.orthographicSize * 2f; // calculation for how many units tall the screen is.
		wScreenWidth = wScreenHeight/Screen.height*Screen.width; // calculation for how many units wide the screen is.

		//We use the amount of space it took up in design to calculate what the scale needs to be now.
		float desiredSize = designScaleV * wScreenWidth; //The desired size is the design viewport size times the width of the screen in world units
		float xScale = desiredSize/sr.sprite.bounds.size.x; //calculate the scale in the x axis.
		float yScale = (xScale - designScaleW.x) + designScaleW.y; //calculate the scale in the y axis.
		Vector3 spriteScale = new Vector3(xScale, yScale, 1);
		transform.localScale = spriteScale; //Set the scale!
	}

}

Simply input the screen size you are designing in, in the editor, and it works! The screen size can be found in the stats tab in the game window.

I have this set up to test with in the editor, in the game it should all happen just once at start!

I’m not sure how this works with parents, but it wouldn’t be a big deal to alter it to compensate for the scale of the parent.

It probably wouldn’t be too hard to use the bounds of a quad as a world space canvas to visually organize sprites into a really simple version of the upcoming GUI system.

The benefits:
Put all of your GUI stuff into an atlas and get cut back on those draw calls.
since your GUI is now defined in world space, you can add particle effects to your GUI, use the render layers, really anything you can do with normal gameobjects.

Hope this is a benefit to somebody! :slight_smile:

Thanks for the help. I took your code and simplified it some for my uses. Here’s what I used:
(In my example, I want a Sprite to appear when a collider is hit by a raycast)

float distToSnap = Vector3.Distance(hit.collider.transform.position, transform.position);

float screenHeight = Screen.height/2; //This will need to be fine-tuned for aspect ratio
float screenWidth = Screen.width;

float scaleX = distToSnap/screenWidth * 100; //100 is the ~ pixel size
float scaleY = distToSnap/screenHeight * 100;

tempSprite.transform.localScale = new Vector3(scaleX, scaleY, 1);