对于判断不规则形状的按钮的点击,一般使用的方式是:
- 打开按钮图片的“Read/Write Enabled”选项,并设置图片的Image.alphaHitTestMinimumThreshold来判断alpha低于阈值的区域响应点击;
- 使用 PolygonCollider2D 并重写 Image 的 IsRaycastLocationValid 方法来判断;
但是上述方法都有各自的缺点,第一种方法开启 Read/Write 之后会使图片的内存占用翻倍,并且不支持Crunched压缩图片格式,第二种方法需要编辑多边形形状,对于有大量不规则图片按钮的项目来说有点耗时耗力。
为了解决上述问题,研究了另外一种方法来进行点击判断,原理很简单,就是用一个单独的shader将按钮的id(或者序号)通过同一个摄像机渲染到一张RenderTexture上,然后监听点击事件,点击之后根据点击的屏幕坐标从之前渲染的图上读取颜色信息,并转化回按钮的id(或者序号)来判断点中了哪个按钮。
此方法对于按钮位置变化不大,按钮数量又非常多的情况是比较合适的。只有在按钮有变化的情况下需要重新渲染一张位置贴图,其余时间只要监听点击事件并取样判断即可。如果精度要求不高,取样贴图的分辨率可以进一步降低以提高采样效率。对于原按钮图片几乎没有要求,可以关闭 Read/Write Enable,也可以设置任意压缩格式。
示例代码和shader如下:
public class TouchDetectHelp : MonoBehaviour {
public Texture2D renderTex;
public float sizeScale = 0.5f;
public void CaptureButton()
{
var uiCam = ZGameRuntime.Instance.cameras.ui;
var shaderToRender = Shader.Find("Custom/TouchDetect");
RenderTexture rt = RenderTexture.GetTemporary(Mathf.RoundToInt(Screen.width * sizeScale), Mathf.RoundToInt(Screen.height * sizeScale));
uiCam.targetTexture = rt;
uiCam.RenderWithShader(shaderToRender, "RenderType");
uiCam.targetTexture = null;
Texture2D texture = new Texture2D(rt.width, rt.height, TextureFormat.R8, false);
RenderTexture.active = rt;
texture.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
RenderTexture.active = null;
texture.Apply();
RenderTexture.ReleaseTemporary(rt);
if(renderTex)
Destroy(renderTex);
renderTex = texture;
}
public int GetTouchButtonId()
{
if (!renderTex)
{
CaptureButton();
}
#if UNITY_EDITOR || UNITY_STANDALONE
var screenPosX = Input.mousePosition.x;
var screenPosY = Input.mousePosition.y;
#else
var touch = Input.GetTouch(0);
var screenPosX = touch.position.x;
var screenPosY = touch.position.y;
#endif
var touchColor = renderTex.GetPixel(Mathf.RoundToInt(screenPosX * sizeScale), Mathf.RoundToInt(screenPosY * sizeScale));
if (!GameUtils.FloatEqual(touchColor.r, 0))
{
return Mathf.RoundToInt(touchColor.r * 255 / 5f - 5);
}
return -1;
}
private int _buttonIndexId;
private int ButtonIndexIndexId
{
get
{
if(_buttonIndexId == 0)
{
_buttonIndexId = Shader.PropertyToID("_ButtonId");
}
return _buttonIndexId;
}
}
private void SetButtonId(Image buttonImg, int buttonId)
{
var buttonMat = new Material(Shader.Find("Legacy Shaders/Particles/Alpha Blended"));
var btnIndex = buttonId + 5;
buttonMat.SetFloat(ButtonIndexIndexId, btnIndex);
buttonImg.material = buttonMat;
}
}
对应的渲染用shader,在build in shader 基础上略做修改:
Shader "Custom/TouchDetect" {
Properties {
_MainTex ("Particle Texture", 2D) = "white" {}
_ButtonId("Furnish Id", Range(0, 1)) = 0
}
Category {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
Cull Off Lighting Off ZWrite Off
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
sampler2D _MainTex;
float _ButtonId;
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_OUTPUT_STEREO
};
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
fixed4 frag (v2f i) : SV_Target
{
clip(_ButtonId - 0.01);
fixed4 col = tex2D(_MainTex, i.texcoord);
clip(col.a - 0.1);
fixed idToColor = _ButtonId * 5 / 255;
return fixed4(idToColor, 0, 0, 1);
}
ENDCG
}
}
}
}
|