Search code examples
c#unity-game-engine

I need help creating mouse-centered zooming


I've been trying to make a system in my 2D unity game where you can zoom in and out by scrolling, and it should center that zoom around the cursor, like zooming in on a web browser. With the way the game is set up, I can't alter the camera in any way so I'm attempting to do this mouse-centered zoom by positioning and scaling the transform that this script is attatched to (the camera is directly facing this GameObject). I have the scaling part down, but I've been trying to get the positioning to work for days and it just won't work properly. Here is my code.

Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 mousePercentPoint = new Vector2((mousePos.x - transform.position.x)/(fieldMask.transform.localScale.x * currentZoom - transform.position.x), (mousePos.y - transform.position.y)/(fieldMask.transform.localScale.y * currentZoom - transform.position.y));
float zoomDelta = Input.GetAxis("Mouse ScrollWheel");
currentZoom += zoomDelta;
currentZoom = Mathf.Clamp(currentZoom, minZoom, maxZoom);
transform.localScale = new Vector3(currentZoom, currentZoom, 1.0f);
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 mousePostPercentPoint = new Vector2((mousePos.x - transform.position.x)/(fieldMask.transform.localScale.x * currentZoom - transform.position.x), (mousePos.y - transform.position.y)/(fieldMask.transform.localScale.y * currentZoom - transform.position.y));
transform.position = new Vector3((mousePercentPoint.x - mousePostPercentPoint.x), mousePercentPoint.y - mousePostPercentPoint.y, transform.position.z);

FieldMask.transform.localScale is supposed to be a reference size for the default scale of the transform

I've tried various methods, I think this attempt involved calculating the position of the mouse relative to the transform's bounds as a percent (using fieldMask.transform.localScale as a base size), and then scaling that up/down and repositioning the attatched transform according, but it has not worked correctly.


Solution

  • As I understand what you could try is

    // [as before] get the mouse position in world space
    Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    
    // [as before] get zoom input
    float zoomDelta = Input.GetAxis("Mouse ScrollWheel");
    
    // cache currentZoom
    float originalZoom = currentZoom;
    
    // get original position offset in world space
    Vector3 mousePositionOffset = transform.position - mousePosition;
    
    // [as before] update zoom and scale
    currentZoom = Mathf.Clamp(currentZoom + zoomDelta, minZoom, maxZoom);
    transform.localScale = new Vector3(currentZoom, currentZoom, 1.0f);
    
    // calculate factor originalZoom -> new zoom
    float zoomFactor = currentZoom / originalZoom;
    
    // update position based on and zoom change
    transform.position = mousePosition + mousePositionOffset * zoomFactor;
    

    so the trick is basically

    • you base the positioning on the mouse position rather than the original object position
    • you use the factor new Zoom Level / previous Zoom Level to update the position offset