Unity ugui与不同屏幕进行像素适配的问题
发表于2018-08-15
Unity的ugui其实严格意义上是不需要我们去进行适配的,它确实帮助我们做了很多很多的工作,我们只要简单的拖拉几下就可以搞定适配的问题(这点在比ngui强了不是一点半点,不愧是unity提供的从底层开始的解决方案)。
但是保不准项目会出现一些特殊的需求,在这种情况下还是有必要去考虑适配问题的。
假设我们要做一个类似iphone的AssistiveTouch悬浮窗时,这个东西有个特点,那就是它不能出屏,出屏之后需要再弹回到屏幕范围内,这个时候,你想不处理适配的问题都不行了……
如图那个黑边白球:
那么ugui里我们要怎么办呢?
首先我们要考虑2点。
1.我们的ui资源比例是什么?
2.我们要适配的屏幕比例又是什么?
假设我们资源的图是按照960x640(3:2)来绘制的,而我们的屏幕是1920x1080(16:9),我们是不能单纯的用1920x1080屏幕的尺寸来限定运动范围,因为这2种屏幕的比例是不同的,我们在960x640屏幕上 (50,50) 像素的点,跟在1920x1080屏幕上的位置那是2个地方,所以你会蛋痛的发现,你的位置永远的都对不上……
但是我们来看看我们知道哪些有用的信息,我们知道2个屏幕的尺寸呀,所以我们可以根据它们之间的差距来转换呀!
思路很简单,就是如果我用 960 / 1920 = ? 宽的比乘以一个1920元素的尺寸或位置,那不就是等于960的尺寸和位置了。
道理很简单,但是谁除谁,也不是能随随便便那么来的,因为我们的屏幕适配是由ugui来处理的,所以我们算比的方式,一定要跟ugui的适配是相一致的,才能保证最后的结果是没有偏差的。
如果ugui是以高为适配的,你算得是宽的比,你觉得这2屏幕能对的上吗?
以上这些重要的信息都是来自于 Canvas Scaler 这个组件上,它负责了ugui中对各种尺寸屏幕的像素适配(这里我用的是Scale with Screen Size模式,自建单独的ui摄像机)
说了这么多,这里就上个我写的浮动球的代码:
using UnityEngine; using System.Collections; using UnityEngine.EventSystems; using UnityEngine.UI; using UnityEngine.Events; public class UIBtnCloud : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler, IBeginDragHandler, IDragHandler, IEndDragHandler { /// <summary> /// 按下后超过这个时间则认定为"长按" /// </summary> public float interval = 0.1f; /// <summary> /// 是否只调用一次 /// </summary> public bool invokeOnce = false; // 按下标志 private bool isPointerDown = false; // 记录时间 private float recordTime; //是否已经调用过 private bool hadInvoke = false; // 点击事件 public UnityEvent onClick = new UnityEvent(); // 按住事件 public UnityEvent onPress = new UnityEvent(); private RectTransform m_rectTransform; private float m_scale; private float m_minX, m_minY, m_maxX, m_maxY; void Start() { m_rectTransform = gameObject.GetComponent<RectTransform>(); // 计算分辨率的比例 CanvasScaler cs = XXX.GetInstance().CanvasScaler; m_scale = cs.matchWidthOrHeight == 1 ? (float)Screen.height / cs.referenceResolution.y : (float)Screen.width / cs.referenceResolution.x; // 范围 m_minX = m_rectTransform.sizeDelta.x / 2 * m_scale; m_minY = m_rectTransform.sizeDelta.y / 2 * m_scale; m_maxX = Screen.width - m_minX; m_maxY = Screen.height - m_minY; } void Update() { // 一次机会已用完 if (invokeOnce && hadInvoke) return; // 按下 if (isPointerDown) { // 算按住 if ((Time.time - recordTime) > interval) { hadInvoke = true; onPress.Invoke(); } } } #region 处理点击 // 按下 void IPointerDownHandler.OnPointerDown(PointerEventData eventData) { isPointerDown = true; recordTime = Time.time; } // 抬起 void IPointerUpHandler.OnPointerUp(PointerEventData eventData) { isPointerDown = false; hadInvoke = false; // 算点击 if ((Time.time - recordTime) < interval) { onClick.Invoke(); } } // 离开 void IPointerExitHandler.OnPointerExit(PointerEventData eventData) { isPointerDown = false; hadInvoke = false; } #endregion 处理点击 ------------------------------------- #region 处理拖拽 private void SetRectTransformPos(Vector2 position, Camera camera) { Vector3 globalMousePos; if (RectTransformUtility.ScreenPointToWorldPointInRectangle(m_rectTransform, position, camera, out globalMousePos)) { m_rectTransform.position = globalMousePos; } } // 开始拖拽 void IBeginDragHandler.OnBeginDrag(PointerEventData eventData) { } // 拖拽中 void IDragHandler.OnDrag(PointerEventData eventData) { //transform.position = eventData.position; this.SetRectTransformPos(eventData.position, eventData.pressEventCamera); } // 结束拖拽 void IEndDragHandler.OnEndDrag(PointerEventData eventData) { // 位置 float x = m_rectTransform.position.x; float y = m_rectTransform.position.y; // 超出范围处理 if (x < m_minX) { x = m_minX; } else if (x > m_maxX) { x = m_maxX; } if (y < m_minY) { y = m_minY; } else if (y > m_maxY) { y = m_maxY; } Vector2 currentPosition = new Vector2(x, y); // 设置坐标 this.SetRectTransformPos(currentPosition, eventData.pressEventCamera); } #endregion 处理拖拽 ------------------------------------- }
以上脚本挂在普通窗的button上就好了,很简单的。
外加没有做差值效果,只是生把出屏的球给拉回来,需要的同学可以自己给加上。
CanvasScaler cs = XXX.GetInstance().CanvasScaler;
获取 CanvasScaler 组件的方法,根据自己的项目去修改就好了,你的位置肯定不会跟我的一样~
这里我的资源都是按着960x640的标准来制作的,你要根据你自己的资源去调整参数,剩下的,就全是程序的事了。来自:https://blog.csdn.net/WPAPA/article/details/52209834