Unity菜鸟开发纪要——UI擦除

UI做刮刮卡的效果又是一搜一堆,代码都是复制粘贴那种,只能自己捣鼓一下。使用RenderTexure的是一种,不使用的比较少,但是原理没怎么说。这次使用的原理就是:开始的时候,取得遮罩图的像素数据,在鼠标滑动的时候,将屏幕坐标转换到遮罩在父节点下的坐标,然后以该点作为中心,生成需要擦除的区域大小,然后通过与刚开始记录的遮罩数据进行比较,替换需要擦除的区域的像素值,同时计算每一点像素的alpha值,在shader中做反转,达到透明的效果。绘制函数放在了鼠标拖动时进行,没有放在update中,现在唯一的不足就是拖动太快时没有做平滑处理,导致出现断块。但是没啥时间,先记录一下,没有像其他说的生成大量预设,透明区域的形状可以自己找图设定。下面上代码:

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


public class MaskErase : MonoBehaviour, IDragHandler, IPointerDownHandler
{
    // 取图片的父节点,转成本地坐标
    public GameObject parent;
    // 相机转行坐标使用
    public Camera UICam;
    // public int rectScale = 10;
    // 控制形状
    public Texture2D shapeTex;
    Texture2D tempRt;
    // 转成本地节点下的坐标下使用
    RectTransform rectTrans;
    private int imgWidth = 0;
    private int imgHeight = 0;
    private Material material;
    // 数组保留像素值, 记录采样图的数据
    float[,] samplePixel;
    // 记录遮罩图的数据,判断是否可以替换当前像素的值
    float[,] screenPixel;
    void Start()
    {
        rectTrans = parent.GetComponent<RectTransform>();
        Image localImg = gameObject.GetComponent<Image>();
        material = localImg.material;
        imgWidth = Mathf.FloorToInt(gameObject.GetComponent<RectTransform>().rect.width);
        imgHeight = Mathf.FloorToInt(gameObject.GetComponent<RectTransform>().rect.height);

        screenPixel = new float[imgWidth, imgHeight];
        samplePixel = new float[Mathf.FloorToInt(shapeTex.width), Mathf.FloorToInt(shapeTex.height)];

        
        // 制作一张保存信息的贴图
        tempRt = new Texture2D(imgWidth, imgHeight, TextureFormat.RGBA32, false);
        tempRt.name = "信息贴图";
        ResetErase();
        // Get Smaple Pixel 
        for (int i = 0; i < Mathf.FloorToInt(shapeTex.width); i++)
        {
            for (int j = 0; j < Mathf.FloorToInt(shapeTex.height); j++)
            {
                samplePixel[i, j] = shapeTex.GetPixel(i, j).a;
                
            }
        }
        
    }

    void ResetErase()
    {
         // 初始化
        for (int i = 0; i < imgWidth; i++)
        {
            for (int j = 0; j < imgHeight; j++)
            {
                Color color = new Color(0, 0, 0, 0);
                tempRt.SetPixel(i, j, color);
                screenPixel[i, j] = 0;
            }
        }

        tempRt.Apply();
        material.SetTexture("_SampleTex", tempRt);
    }


    public void OnPointerDown(PointerEventData data){
        Vector2 localPos = ScreenPointToLocal(Input.mousePosition);
        DrawMask(localPos);
    }
    

    public void OnDrag(PointerEventData data)
    {
        Vector2 localPos = ScreenPointToLocal(Input.mousePosition);
        DrawMask(localPos);
    }

    
    Vector2 ScreenPointToLocal(Vector3 mousePos)
    {
        Vector2 outPos;
        if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTrans, mousePos, UICam, out outPos))
        {
            // 建立笛卡尔坐标系
            outPos = new Vector2((int)outPos.x + imgWidth / 2, (int)outPos.y + imgHeight / 2);
            return outPos;
        }
        return Vector2.zero;
    }

    // 绘制消除区域的像素信息
    void DrawMask(Vector2 pos)
    {
        int startX = Mathf.FloorToInt(pos.x - shapeTex.width / 2);
        int endX = Mathf.FloorToInt(pos.x + shapeTex.width / 2);
        int startY = Mathf.FloorToInt(pos.y - shapeTex.height / 2);
        int endY = Mathf.FloorToInt(pos.y + shapeTex.height / 2);

        for (int i = startX; i < endX; i++)
        {
            for (int j = startY; j < endY; j++)
            {
                if (i < 0 || i >= imgWidth || j < 0 || j >= imgHeight)
                {
                    continue;
                }

                // 采样图的坐标值
                int sampleX = i - startX;
                int sampleY = j - startY;
                float alpha = samplePixel[sampleX, sampleY];
                
                float a = alpha > screenPixel[i, j] ? alpha : screenPixel[i, j];
                Color color = new Color(0, 0, 0, a);
            
                tempRt.SetPixel(i, j, color);
                if (alpha > screenPixel[i, j])
                {
                    screenPixel[i, j] = alpha;
                }
                
            }
        }

        tempRt.Apply();
        material.SetTexture("_SampleTex", tempRt);
    }
}

使用的shader:

Shader "UI/MaskSet"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _SampleTex ("Mask Texture " , 2D) = "black" {}
        
    }
    SubShader
    {
       Tags{
            "Queue" = "Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
        }

        Cull Off ZWrite Off ZTest Always
        // 混合模式 最终颜色 = 源颜色 * 源a  + (1 - a) * 目标颜色
        Blend SrcAlpha OneMinusSrcAlpha


        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;

            // 由脚本传入的像素信息
            sampler2D _SampleTex;
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 maskCol = tex2D(_SampleTex, i.uv);
                // 取相反值
                col.a = 1 - maskCol.a;
              
        
                return col;
            }
            ENDCG
        }
    }
}

在UI上生成一张Image作为遮罩,然后将shade对应的材质球交给Image,运行即可在拖动鼠标看到效果。

测试工程在其他地方上传

C#脚本改了一下,使用线性插值的方式做平滑处理

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


public class MaskErase : MonoBehaviour, IDragHandler, IPointerDownHandler
{
    // 取图片的父节点,转成本地坐标
    public GameObject parent;
    // 相机转行坐标使用
    public Camera UICam;
    // public int rectScale = 10;
    // 控制形状
    public Texture2D shapeTex;
    Texture2D tempRt;
    // 转成本地节点下的坐标下使用
    RectTransform rectTrans;
    private int imgWidth = 0;
    private int imgHeight = 0;
    private Material material;
    // 数组保留像素值, 记录采样图的数据
    float[,] samplePixel;
    // 记录遮罩图的数据,判断是否可以替换当前像素的值
    float[,] screenPixel;

    // 上一次的鼠标位置
    Vector2 lastPos = Vector2.zero;
    void Start()
    {
        rectTrans = parent.GetComponent<RectTransform>();
        Image localImg = gameObject.GetComponent<Image>();
        material = localImg.material;
        imgWidth = Mathf.FloorToInt(gameObject.GetComponent<RectTransform>().rect.width);
        imgHeight = Mathf.FloorToInt(gameObject.GetComponent<RectTransform>().rect.height);

        screenPixel = new float[imgWidth, imgHeight];
        samplePixel = new float[Mathf.FloorToInt(shapeTex.width), Mathf.FloorToInt(shapeTex.height)];

        
        // 制作一张保存信息的贴图
        tempRt = new Texture2D(imgWidth, imgHeight, TextureFormat.RGBA32, false);
        tempRt.name = "信息贴图";
        ResetErase();
        // Get Smaple Pixel 
        for (int i = 0; i < Mathf.FloorToInt(shapeTex.width); i++)
        {
            for (int j = 0; j < Mathf.FloorToInt(shapeTex.height); j++)
            {
                samplePixel[i, j] = shapeTex.GetPixel(i, j).a;
                
            }
        }
        
    }

    void ResetErase()
    {
         // 初始化
        for (int i = 0; i < imgWidth; i++)
        {
            for (int j = 0; j < imgHeight; j++)
            {
                Color color = new Color(0, 0, 0, 0);
                tempRt.SetPixel(i, j, color);
                screenPixel[i, j] = 0;
            }
        }

        tempRt.Apply();
        material.SetTexture("_SampleTex", tempRt);
    }


    public void OnPointerDown(PointerEventData data){
        Vector2 localPos = ScreenPointToLocal(Input.mousePosition);
        DrawMask(localPos);
        lastPos = localPos;
    }
    

    public void OnDrag(PointerEventData data)
    {
        Vector2 localPos = ScreenPointToLocal(Input.mousePosition);

        // 取形状控制贴图的一半作为是否进行插值的判断
        if ((Mathf.Abs(localPos.x - lastPos.x) > (shapeTex.width / 2)))
        {
            List<Vector2> pointsList = new List<Vector2>();
            if ((Mathf.Abs(localPos.y - lastPos.y) > (shapeTex.height / 2)))
            {
                pointsList = GetInterplotion(lastPos, localPos, true);
            }
            else
            {
                pointsList = GetInterplotion(lastPos, localPos, false);
            }
          
            for (int i = 0; i < pointsList.Count; i++)
            {
                DrawMask(pointsList[i]);
            }
        }

        else
        {
            DrawMask(localPos);
        }

        lastPos = localPos;
    }


    // 由于拖动一般是直线,所以可以使用线性插值的方式
    List<Vector2> GetInterplotion(Vector2 start, Vector2 end, bool isReverse)
    {
        List<Vector2> pointsList = new List<Vector2>();
        if (isReverse)
        {
            float k = (end.x - start.x) / (end.y - start.y);
            float b = start.x - k * start.y;
            // 1 / 3的距离进行插值
            for (int i = (int)start.y; i < (int)end.y; i = i + (shapeTex.height / 3))
            {
                pointsList.Add(new Vector2(i, (int)((i * k) + b)));
            }

        }

        else
        {
            
            float k = (end.y - start.y) / (end.x - start.x);
            float b = start.y - k * start.x;
            // 1 / 3的距离进行插值
            for (int i = (int)start.x; i < (int)end.x; i = i + (shapeTex.width / 3))
            {
                pointsList.Add(new Vector2(i, (int)((i * k) + b)));
            }
        }
      

        return pointsList;
    }
    
    Vector2 ScreenPointToLocal(Vector3 mousePos)
    {
        Vector2 outPos;
        if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTrans, mousePos, UICam, out outPos))
        {
            // 建立笛卡尔坐标系
            outPos = new Vector2((int)outPos.x + imgWidth / 2, (int)outPos.y + imgHeight / 2);
            return outPos;
        }
        return Vector2.zero;
    }

    // 绘制消除区域的像素信息
    void DrawMask(Vector2 pos)
    {
        int startX = Mathf.FloorToInt(pos.x - shapeTex.width / 2);
        int endX = Mathf.FloorToInt(pos.x + shapeTex.width / 2);
        int startY = Mathf.FloorToInt(pos.y - shapeTex.height / 2);
        int endY = Mathf.FloorToInt(pos.y + shapeTex.height / 2);

        for (int i = startX; i < endX; i++)
        {
            for (int j = startY; j < endY; j++)
            {
                if (i < 0 || i >= imgWidth || j < 0 || j >= imgHeight)
                {
                    continue;
                }

                // 采样图的坐标值
                int sampleX = i - startX;
                int sampleY = j - startY;
                float alpha = samplePixel[sampleX, sampleY];
                
                float a = alpha > screenPixel[i, j] ? alpha : screenPixel[i, j];
                Color color = new Color(0, 0, 0, a);
            
                tempRt.SetPixel(i, j, color);
                if (alpha > screenPixel[i, j])
                {
                    screenPixel[i, j] = alpha;
                }
                
            }
        }

        tempRt.Apply();
        material.SetTexture("_SampleTex", tempRt);
    }

    void OnDestroy()
    {
        
        
    }
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值