2
\$\begingroup\$

I'd like to uniformly scale a Sprite in Unity, so that its height on the screen matches the height of a button on a canvas. The scene setup looks like:

  • Canvas with a CanvasScaler and a reference resolution of 800x1280 pixels.
  • A button on the canvas with a height of 90 pixels.
  • A sprite with a texture size of 250x250 pixels.
  • The sprite's pixels per unit setting is set 100.
  • An orthographic camera with a size of 5.
  • The transform.scale values of all involved components are (1,1,1).

Now the question is, how do you calculate the sprite's localScale value so that it appears with the same height (90 pixels) as the button? What's the math behind it?

\$\endgroup\$
  • \$\begingroup\$ Do you need to put the image on top of the button? \$\endgroup\$ – user100681 Nov 27 '17 at 15:57
  • \$\begingroup\$ The canvas (button) should remain above the image/scene. \$\endgroup\$ – SePröbläm Nov 27 '17 at 16:42
  • \$\begingroup\$ If the sprite is a UI image you do not need to calculate (unless I have not understood your question) You only use the properties that unity offers for the UI and do the calculations (Anchor presets / stretch). \$\endgroup\$ – NorbyAriel Nov 27 '17 at 17:23
  • \$\begingroup\$ Well, the sprite isn't a UI image... It's positioned in world space outside the canvas. \$\endgroup\$ – SePröbläm Nov 27 '17 at 17:47
1
\$\begingroup\$

Here's a script that performs the matching, with each step of the math called out:

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(SpriteRenderer))]
public class SpriteScaler : MonoBehaviour {

    public CanvasScaler matchCanvas;
    public float pixelHeightOnCanvas = 90f;
    SpriteRenderer _sprite;

    private void Start() {
        _sprite = GetComponent<SpriteRenderer>();
    }

    void Update () {
        // You might not need to call this every Update,
        // just when something changes size/configuration.
        UpdateScale();
    }    

    void UpdateScale() {
        // The canvas will try to scale its reference resolution
        // to match the screen's dimensions in either x or y.
        // (Assuming it's in Overlay mode or using a fullscreen camera
        // - if rendering to a smaller rect, use that pixel rect instead)
        Vector2 scaleFactorRange = new Vector2(
            Screen.width / matchCanvas.referenceResolution.x,
            Screen.height / matchCanvas.referenceResolution.y);

        // When the screen's aspect ratio isn't the same as the reference,
        // the canvas picks between two scale factors with matchWidthOrHeight
        float scaleFactor = Mathf.Lerp(
            scaleFactorRange.x,
            scaleFactorRange.y,
            matchCanvas.matchWidthOrHeight);

        // We can now compute how much it will scale our in-canvas
        // dimensions to produce on-screen pixel dimensions.
        float heightInScreenPixels = pixelHeightOnCanvas * scaleFactor;

        // For the next part, we need to know what camera we're
        // being rendered by - consider caching this if it's constant.
        Camera cam = Camera.main;

        // We'll convert the screen height into a fraction of the camera's
        // vertical span (which might be less than the screen's if rendering
        // to a smaller viewport rect).
        float heightAsViewFraction = heightInScreenPixels / cam.pixelRect.height;

        // Now we can convert that to a desired world height by multiplying
        // by the camera's vertical size - note that orthographicSize is
        // only half the height of the camera's view, hence the 2x.    
        float heightInWorldUnits = 2f * cam.orthographicSize * heightAsViewFraction;

        // Lastly, we need to know how big "this" sprite is at scale = 1.
        float nativeWorldHeight = _sprite.sprite.rect.height / _sprite.sprite.pixelsPerUnit;

        // And our scale factor is the multiplier that gets us from our
        // native world size to the desired world size.
        transform.localScale = Vector3.one * heightInWorldUnits / nativeWorldHeight;
    }
}

Example of the two shapes matched-up

Because a "90px" button might actually appear smaller if its texture includes a border, in this example I dropped my pixelHeightOnCanvas value to 88.

\$\endgroup\$
  • \$\begingroup\$ Can i ask why you multiply by Vector3.one? \$\endgroup\$ – user100681 Nov 28 '17 at 14:00
  • 2
    \$\begingroup\$ To convert a single-component float into a 3-component vector. LocalScale expects a vector of scale factors for the x, y, and z directions respectively. Multiplying by Vector3.one gives a concise way to produce a vector with the desired value in all three components. \$\endgroup\$ – DMGregory Nov 28 '17 at 14:02
  • \$\begingroup\$ So you could also write: float f = heightInWorldUnits / nativeWorldHeight; transform.localScale = new Vector3(f,f,f); right? Thanks ;) \$\endgroup\$ – user100681 Nov 28 '17 at 14:04
  • 1
    \$\begingroup\$ Yes indeed, that will give the same result. \$\endgroup\$ – DMGregory Nov 28 '17 at 14:06
  • \$\begingroup\$ @GabrieleVierti If I'm not mistaken, you can also just abbreviate one value for all of them as in float f = heightInWorldUnits / nativeWorldHeight; transform.localScale = new Vector3(f); \$\endgroup\$ – John Hamilton Nov 29 '17 at 11:14
0
\$\begingroup\$

I do not think you need any calculation.

Select the canvas and in the inspector you should match the pixels per unity unit.

enter image description here

The same in the texture inspector

enter image description here

2 test examples

enter image description here

enter image description here

\$\endgroup\$
  • \$\begingroup\$ Worldspace and canvas use two different coordinate systems, so I'm afraid, the solution isn't that simple. But thank you anyway! \$\endgroup\$ – SePröbläm Nov 27 '17 at 19:52
  • \$\begingroup\$ Friend, I am not going to discuss this, because maybe my understanding of English is playing against me, but what has to do with the coordinates of canvas and space with units per pixel? or with the scale? \$\endgroup\$ – NorbyAriel Nov 27 '17 at 20:15
  • \$\begingroup\$ Thank you for your helpfulness. See, the question is: How to calculate the sprite's localScale to ensure it appears with a certain size on the screen. I'm looking for the formula - the math behind it, to be able to do it in a script. Just putting some magic numbers in inspector doesn't do it. \$\endgroup\$ – SePröbläm Nov 28 '17 at 8:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.