- Home /
How to scale a sprite to the size of a Rect on screen?
Hi,
I'd like to do some zooming in NGUI and as it's not supposed to have it's camera orthographic size changed (according to the creator), I'm instead scaling and re-positioning a sprite (a map). The map has a number of buttons scattered over it and I'd like to zoom in (scale it up) as much as possible so that they are all on screen (the buttons are also child object of the map).
To give a brief overview of how it works;
Scale
To calculate the correct scale to aim for, I first found all the colliders of the buttons and extracted their X and Y coordinates. I then found the smallest and largest X and Y values and created a Rect box (which I'll refer to as the 'bounding box') from them. The bounding box now surrounds all of the buttons and represents what the scaled map should look like (i.e the full screen view). To find the relative proportions of the Map to the bounding box, I divided the Map's width and height by the bounding box's width and height. I then used this value to determine the best scale value (a single value for both X and Y) to use based on these proportions. I'm using the Map's Transform to scale it (as well as the buttons, as they are child objects). The scaling works well and Lerps to the correct value, (and therefore the size) that the map should be. I know this because if I manually move the camera in Scene view so that all buttons are on screen (once the map is fully scaled) the full screen view is identical to that of the Gizmo showing the bounding box.
Position
However, I'm having some trouble with the positioning of the Map/Camera. I'm thinking that moving the camera is the way to go here as there are fewer things connected to it. To calculate the correct position that it should move to, I'm using the bounding box's centre value (that is, Rect.centre). The problem is, that it doesn't seem to be moving to the correct position. I'm not sure what the problem is but have a feeling is might have to do with the coordinate systems used. I've tried converting the centre position to world space, but this makes no difference.
Object Structure
NGUIRoot
- 2D Camera
- Map Panel (This holds the script)
- - Map Sprite (this is what is scaled)
Any help would be very much appreciated as I'm not really sure what to try next.
The script is below, I've tried to comment is as much as possible but it should hopefully be quite straight forward.
public static ScaleMapZoom instance;
public List<Transform> targets = new List<Transform>();
public List<float> BBoxXs = new List<float>();
public List<float> BBoxYs = new List<float>();
public float smallestX;
public float largestX;
public float smallestY;
public float largestY;
public float boundingBoxPadding = 2f;
public bool doZoom = false;
public float ScaleDuration = 0.5f;
public Rect BoundingBoxSize;
public float ZoomedMapScale = 0;
public Camera NGUICam;
public GameObject Map;
public Transform LeftAnchor;
public Transform RightAnchor;
public Transform TopAnchor;
public Transform BottomAnchor;
private Vector2 rectBottomleftW;
private Vector2 rectTopRightW;
private Vector3 pos;
private float origZpos;
public Rect boundingBox;
private Vector2 MidPoint;
private bool hasZoomed = false;
void Awake()
{
instance = this;
// Cache original Z coordinate of camera.
origZpos = NGUICam.transform.position.z;
}
void Update()
{
if (Input.GetKey(KeyCode.Z) && !hasZoomed)
{
hasZoomed = true;
// Add all buttons to list.
targets.Clear();
foreach (GameObject go in HeadTracker.instance.HeadsList)
{
targets.Add(go.transform);
}
// Find out the size the bounding box needs to be to contain all the heads in the scene.
boundingBox = CalculateTargetsBoundingBox();
// Find the centre position of the box. This is where the Map will have to moved to.
pos = CalculateMapPosition(boundingBox);
// Calculate the scale the map has to be to reflect the bounding box and fill the screen.
ZoomedMapScale = CalculateMapScale(boundingBox);
// Position and scale the map.
StartCoroutine(TransposeMap());
}
}
Rect CalculateTargetsBoundingBox()
{
// Initialise the variables required to hold the coordinates and set them to infinity (plus or minus).
float minX = Mathf.Infinity;
float maxX = Mathf.NegativeInfinity;
float minY = Mathf.Infinity;
float maxY = Mathf.NegativeInfinity;
BBoxXs.Clear();
BBoxYs.Clear();
// Loop through the targets..
foreach (Transform t in targets)
{
minX = t.GetComponent<Collider>().bounds.min.x;
maxX = t.GetComponent<Collider>().bounds.max.x;
minY = t.GetComponent<Collider>().bounds.min.y;
maxY = t.GetComponent<Collider>().bounds.max.y;
BBoxXs.Add(minX);
BBoxXs.Add(maxX);
BBoxYs.Add(minY);
BBoxYs.Add(maxY);
// Find min and max values
float[] minMaxX = FindMinMaxValues(BBoxXs.ToArray());
float[] minMaxY = FindMinMaxValues(BBoxYs.ToArray());
smallestX = minMaxX[0];
largestX = minMaxX[1];
smallestY = minMaxY[0];
largestY = minMaxY[1];
}
// Create the Rect Bounding box
BoundingBoxSize = Rect.MinMaxRect(
smallestX,
largestY,
largestX,
smallestY);
rectBottomleftW = new Vector2(smallestX, smallestY);
rectTopRightW = new Vector2(largestX, largestY);
// Used for the GIzmo
MidPoint = (rectBottomleftW - rectTopRightW) * 0.5f + rectTopRightW;
// Return the Rect
return BoundingBoxSize;
}
float[] FindMinMaxValues(float[] floatArray)
{
float max = floatArray[0];
float min = floatArray[0];
for (int i = 1; i < floatArray.Length; i++)
{
if (floatArray[i] > max)
{
max = floatArray[i];
}
if (floatArray[i] < min)
{
min = floatArray[i];
}
}
float[] minMaxArray = { min, max };
return minMaxArray;
}
// Not sure about this function
Vector3 CalculateMapPosition(Rect boundingBox)
{
// Cache the center position of the bounding box (i.e. where the camera should be).
Vector2 boundingBoxCenter = boundingBox.center;
// Turn into a Vector3
Vector3 centre = new Vector3(boundingBoxCenter.x, boundingBoxCenter.y, Map.transform.position.z);
// Return the position, keeping the original camera Z-coordinate.
return centre;
}
float CalculateMapScale(Rect boundingBox)
{
// Find the Map width and height - anchors placed on map in scene
float mapWidth = RightAnchor.position.x - LeftAnchor.position.x;
float mapHeight = TopAnchor.position.y - BottomAnchor.position.y;
// Calculate the proportion difference between the Map and Bounding Box (width and height)
float newMapWScale = (mapWidth / boundingBox.width);
float newMapHScale = (mapHeight / Mathf.Abs(boundingBox.height));
// Determine what to use as the scale.
// Use smallest scale possible.
float newMapScale = 0;
if (newMapWScale >= newMapHScale)
newMapScale = newMapHScale;
else
newMapScale = newMapWScale;
return newMapScale;
}
// Make the position and scale changes
IEnumerator TransposeMap()
{
doZoom = false;
// Cache the initial values of the map scale and and camera position.
Vector3 initialPos = NGUICam.transform.localPosition;
Vector3 initialScale = Map.transform.localScale;
Vector3 newMapPos = Vector3.zero;
while (elapsedTime < ScaleDuration)
{
// POSITION - move camera
Vector3 newCamPos = Vector3.Lerp(initialPos, pos, (elapsedTime / ScaleDuration));
NGUICam.transform.position = new Vector3(pos.x, pos.y, origZpos);
//Vector3 newMapPos = Vector3.Lerp(initialPos, pos, (elapsedTime / ScaleDuration));
//Map.transform.localPosition = new Vector3(newMapPos.x, newMapPos.y, origZpos);
// SCALE - scale map
Map.transform.localScale = Vector3.Lerp(initialScale, new Vector3(ZoomedMapScale, ZoomedMapScale, 1), (elapsedTime / ScaleDuration));
elapsedTime += Time.deltaTime;
yield return null;
}
}
void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireCube(MidPoint, new Vector3((largestX - smallestX) - boundingBoxPadding, (largestY - smallestY) + boundingBoxPadding, 0));
}
}
Your answer
