[SOLVED] EventSystem input, scale with screen size, and different resolutions

I’m working on a unit selection system for an RTS based on Kiwasi’s tutorial on the subject and I’ve run into an annoying problem. I’m using “Scale with screen size” and a screen space camera to keep my UI properly scaled on different resolutions with a default setting of 1920x1080. When the game is set in 1080p mode, everything works perfectly fine. If I change the resolution to, say, 720p however, the cursor position is completely offset in the wrong place.

Working as intended:

Super busted:

Here’s the modified code I’m using. I tried setting up a manual offset based on information from the canvas scaler and the camera resolution, but I couldn’t get that to work at all.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class DragHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerClickHandler
{
    [SerializeField]
    CanvasScaler canvasScaler;

    [SerializeField]
    Image selectionBoxImage;
    [SerializeField]
    Image selectionManager;

    Vector2 startPosition;
    Rect selectionRect;

    PointerEventData mainEventData;

    public void OnBeginDrag(PointerEventData eventData)
    {
        selectionBoxImage.gameObject.SetActive(true);
        startPosition = eventData.position;
        selectionRect = new Rect();
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (eventData.position.x < startPosition.x)
        {
            selectionRect.xMin = eventData.position.x;
            selectionRect.xMax = startPosition.x;
        }
        else
        {
            selectionRect.xMin = startPosition.x;
            selectionRect.xMax = eventData.position.x;
        }

        if (eventData.position.y < startPosition.y)
        {
            selectionRect.yMin = eventData.position.y;
            selectionRect.yMax = startPosition.y;
        }
        else
        {
            selectionRect.yMin = startPosition.y;
            selectionRect.yMax = eventData.position.y;
        }

        if (eventData.position.x < 0)
        {
            selectionRect.x = 0;
            selectionRect.width = startPosition.x;
        }

        if (eventData.position.x > selectionManager.rectTransform.rect.width)
        {
            selectionRect.width = selectionManager.rectTransform.rect.width - selectionRect.x;
        }

        if (eventData.position.y < 0)
        {
            selectionRect.y = 0;
            selectionRect.height = startPosition.y;
        }

        if (eventData.position.y > selectionManager.rectTransform.rect.height)
        {
            selectionRect.height = selectionManager.rectTransform.rect.height - selectionRect.y;
        }

        selectionBoxImage.rectTransform.offsetMin = selectionRect.min;
        selectionBoxImage.rectTransform.offsetMax = selectionRect.max;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        selectionBoxImage.gameObject.SetActive(false);
        foreach(Selectable selectable in Selectable.allSelectables)
        {
            if (selectionRect.Contains(Camera.main.WorldToScreenPoint(selectable.transform.position)))
            {
                selectable.OnSelect(eventData);
            }
        }
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        Selectable.DeselectAll(eventData);

        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventData, results);

        float distance = 0f;

        foreach (RaycastResult result in results)
        {
            if (result.gameObject == gameObject)
            {
                distance = result.distance;
                break;
            }
        }

        GameObject nextObject = null;
        float maxDistance = Mathf.Infinity;

        foreach (RaycastResult result in results)
        {
            if (result.distance > distance && result.distance < maxDistance)
            {
                nextObject = result.gameObject;
                maxDistance = result.distance;
            }
        }

        if (nextObject)
        {
            ExecuteEvents.Execute<IPointerClickHandler>(nextObject, eventData, (x, y) => { x.OnPointerClick((PointerEventData)y); });
        }
    }
}

After some amount of digging, I came across Canvas.scaleFactor, which seems to do exactly what I was trying to do, only correctly.

2 Likes