[Unity]UGUI ScrollView特效裁剪

Unity特效在ScrollView中的裁剪

1.序列帧

按照特效的效果,做序列帧动画


问题和缺点

效果会差点

2.添加相机

添加一个Camera,这个Canera只显示ScrollView的实际显示区域
我们先添加一个新的Layer层RewardEffect,并将新添加的Camera的显示层设置为RewardEffect.
Camera中的Viewport Rect是其可见的区域,这里我们要调整为这个区域与ScrollView的显示区域吻合.
特效的Layer要设置为RewardEffect,特效不再会出现在UICamera中,而是由新的Camera负责显示.由于相机区域的设置,特效在区域内的部分会正常显示,拖动超出相机范围后,就看不到了,实现了裁剪的效果。

如何比较方便准确的设置摄像范围,有一个脚本UIViewport,将这个脚本挂在新加的摄像机上.在ScrollView的左上角和右下角位置各放一个Transform,设置到UIViewport
image


问题和缺点
1.特效所属的Layer层级要高于UI的的Layer层,当这个界面弹出另一个界面(比如奖励领取界面),特效就显示在新界面之上了(可选择在新界面出现的时候监听暂时隐藏特效,但解决方案不够完美).

3.使用RenderTexture

1.创建相机,相机提前做好prefab,然后在使用的时候动态创建.

GameObject camera = (GameObject)Resources.Load(@"UI/Prefabs/Camera/RoleCameraVariateInfo", typeof(GameObject));

if (camera != null)  
{  
    GameObject go = GameObject.Instantiate(camera) as GameObject;

    if (go != null &&
        go.camera != null)
    {
       m_Camera = go.camera;
    }  
}  

2.加载特效文件,并设置特效及相机位置

Object model = Resources.Load("Effect/UI/Business/Busi_Effect001Test");

if (null == model)
{
    return;
}

m_Model = GameObject.Instantiate(model) as GameObject;

if (null == m_Model)
{
    return;
}

var t = m_Model.transform;
t.localPosition = new Vector3(0f, m_nIndex * MODEL_OFFSET, 0f);
t.localScale = modelScale > 0 ? new Vector3(modelScale, modelScale, modelScale) : new Vector3(1f, 1f, 1f);

m_Camera.transform.localPosition = new Vector3(cameraX, cameraY + m_nIndex * MODEL_OFFSET, cameraZ);

3.给相机设置targetTexture

m_Camera.clearFlags = CameraClearFlags.Color;
m_Camera.renderingPath = RenderingPath.DeferredLighting;
RenderTexture rt = RenderTexture.GetTemporary(uiTexture.width, uiTexture.height, 0);
if (rt != null)
{
   m_Camera.targetTexture = rt;
   m_Camera.mainTexture = rt;
   m_Camera.Render();
}

问题和缺点
1.当特效种类较多,不同应用场景多的时候,开发成本偏高.

4.通过Shader实现裁剪

将ScrollView的显示区域传给特效的Shader,超出显示区域部分将透明度设置为0,实现裁剪.这个方案实施分为两个部分:修改特效的Shader和显示区域传给Shader.
1.修改特效的Shader,下面是我们修改后的Shader,其中// add clip的部分是为了实现裁剪添加的部分:

// 裁剪shader
Shader "Effect_Mid/ScrollClipAdditive"
{
    Properties
    {
        ......
        // add clip
        _MinX ("Min X", Float) = -10
        _MaxX ("Max X", Float) = 10
        _MinY ("Min Y", Float) = -10
        _MaxY ("Max Y", Float) = 10
        // add clip
    }
    SubShader
    {
        Tags
        {
            ......
        }
        Pass
        {
            ......
            // ZTest Off
            Fog { Color (0,0,0,0) }
            ......
            // add clip
            float _MinX;
            float _MaxX;
            float _MinY;
            float _MaxY;
            // add clip
            ......

            struct VS_OUTPUT
            {
                ......
                // add clip
                float3 vpos : TEXCOORD2;
                // add clip
            };

            VS_OUTPUT vert(VS_INPUT In)
            {
                ......
                // add clip
                Out.vpos = mul(UNITY_MATRIX_MVP, In.position);
                // add clip
                ......

                return Out;
            }

            float4 frag(VS_OUTPUT In) : COLOR
            {
                ......
                // add clip
                color.a *= (In.vpos.x >= _MinX );
                color.a *= (In.vpos.x <= _MaxX);
                color.a *= (In.vpos.y >= _MinY );
                color.a *= (In.vpos.y <= _MaxY);
                // add clip
                return color;
            }

            ENDCG
        }
    }

    Fallback off
    CustomEditor "EffectMaterialEd"
}

另外一个shader

Shader "PJ Particles/PJ Additive 1"
{
    Properties
    {
        _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
        _MainTex ("Particle Texture (A = Transparency)", 2D) = "white" {}
        _InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0

        //新增 记录裁剪框的四个边界的值
        _Area ("Area", Vector) = (0,0,1,1)
        //----end----
    }

    Category
    {
        Tags { "Queue"="Transparent+300" "IgnoreProjector"="True" "RenderType"="Transparent" }
        Blend SrcAlpha One
        AlphaTest Greater .01
        ColorMask RGB
        Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,0) }
        BindChannels
        {
            Bind "Color", color
            Bind "Vertex", vertex
            Bind "TexCoord", texcoord
        }

        SubShader
        {
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                #pragma multi_compile_particles

                #include "UnityCG.cginc"

                sampler2D _MainTex;
                fixed4 _TintColor;

                //新增,对应上面的_Area
                float4 _Area;
                //----end----

                struct appdata_t
                {
                    float4 vertex : POSITION;
                    fixed4 color : COLOR;
                    float2 texcoord : TEXCOORD0;
                };

                struct v2f
                {
                    float4 vertex : POSITION;
                    fixed4 color : COLOR;
                    float2 texcoord : TEXCOORD0;
                    //新增,记录顶点的世界坐标
                    float2 worldPos : TEXCOORD1;
                    //----end----
                };

                float4 _MainTex_ST;

                v2f vert (appdata_t v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.color = v.color;
                    o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
                    //新增,计算顶点的世界坐标
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xy;
                    //----end----
                    return o;
                }

                sampler2D _CameraDepthTexture;
                float _InvFade;

                fixed4 frag (v2f i) : COLOR
                {
                    //新增,判断顶点坐标是否在裁剪框内
                    bool inArea = i.worldPos.x >= _Area.x && i.worldPos.x <= _Area.z && i.worldPos.y >= _Area.y && i.worldPos.y <= _Area.w;
                    //----end----

                    //如果在裁剪框内return原本的效果,否则即隐藏
                    return inArea? 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord) : fixed4(0,0,0,0);
                }
                ENDCG
            }
        }
    }
}

2.将显示区域传给Shader,为了方便使用,写了一个脚本,使用时将该脚本挂在特效上或多个特效的父节点上,然后设置UIPanel给脚本.
image

脚本代码如下:

using UnityEngine;
using System.Collections;

public class EffectClipImpl : MonoBehaviour
{
    // 要实现特效参见的UIPanel(UIPanel和ScrollView是结合使用的,真正管理裁剪区域的是UIPanel)
    public UIPanel m_RootPanel = null;

    // 默认值,写了一个很大的数值,保证不设置Panel时可以正常显示
    float m_MinX = -10;
    float m_MinY = -10;
    float m_MaxX = 10;
    float m_MaxY = 10;

    void Start()
    {
        Reset();
    }

    public void Reset()
    {
        if (m_RootPanel != null)
        {
            // 获取UI摄像机,为了获取panel相对于屏幕中心点的位置
            GameObject UIRootCamera = GameObject.Find("UI/UIRoot(Clone)/UICamera(Clone)");
            if (UIRootCamera != null)
            {
                // 获取panel相对于屏幕中心点的位置,准确说是获取ScrollView的实际显示区域相对于屏幕中心点的位置
                Vector3 toCameraPos = UIRootCamera.transform.InverseTransformPoint(m_RootPanel.transform.position);

                // 处理UIPanel的Center偏移
                toCameraPos += new Vector3(m_RootPanel.baseClipRegion.x, m_RootPanel.baseClipRegion.y, 0);

                // 去除滑动的偏移量
                // 添加特效的时候ScrollView可能已经滑动过了,这时候ScrollView的坐标位置发生了变化
                // 我们根据ScrollView的坐标位置计算显示区域的位置,要计算这部分滑动的量
                toCameraPos += new Vector3(m_RootPanel.clipOffset.x, m_RootPanel.clipOffset.y, 0);

                // 获取Panel实际显示区域的大小
                Vector2 viewSize = m_RootPanel.GetViewSize();

                // 过渡部分处理,panel的过渡部分透明度会渐变消失
                // 我们项目使用的Softness基本都是4,比较小,这里处理为Softness的一半区域后消失
                // 虽然方案不完美,看上去还可以(有需要的话可以把Softness的值也传给Shader,做过渡消失的处理)
                Vector2 clipSoftness = m_RootPanel.clipSoftness;
                Vector2 clipSize = viewSize - clipSoftness * 0.5f;

                // 获取窗口大小
                Vector2 screenViewSize = m_RootPanel.GetWindowSize();
                float width = screenViewSize.x;
                float height = screenViewSize.y;

                // 计算要传送给Shder的四个值,最小X,最大X,最小Y,最大Y
                // 这个值的大小是相对于屏幕常和宽的比例值
                m_MinX = -((float)(clipSize.x * 0.5f - toCameraPos.x)) / (width * 0.5f);
                m_MinY = -((float)(clipSize.y * 0.5f - toCameraPos.y)) / (height * 0.5f);
                m_MaxX = ((float)(clipSize.x * 0.5f + toCameraPos.x)) / (width * 0.5f);
                m_MaxY = ((float)(clipSize.y * 0.5f + toCameraPos.y)) / (height * 0.5f);

                // 传输范围数据
                ExcuteChild(gameObject);
            }
        }
    }

    // 传输范围数据
    void ExcuteChild(GameObject go)
    {
        if (go.renderer != null)
        {
            // material与sharedMaterial用法相同,但是效率有差别
            // sharedMaterial是共享材质,修改的话内存只占用一份,如果使用material的话,每次属性修改都会new一份新的出来
            // 但在编辑器模式下,修改sharedMaterial会导致本地文件发生变化,为防止不必要的文件变动和提交,这里区分平台处理
#if UNITY_EDITOR
            Material[] list = go.renderer.materials;
#else  
            Material[] list = go.renderer.sharedMaterials;
#endif
            for (int i = 0; i < list.Length; i++)
            {
                list[i].SetFloat("_MinX", m_MinX);
                list[i].SetFloat("_MinY", m_MinY);
                list[i].SetFloat("_MaxX", m_MaxX);
                list[i].SetFloat("_MaxY", m_MaxY);
            }
        }

        // 遍历处理子节点
        int count = go.transform.childCount;
        for (int i = 0; i < count; i++)
        {
            ExcuteChild(go.transform.GetChild(i).gameObject);
        }
    }
}

另外一个脚本配合第二个shader

using System.Collections.Generic;
using UnityEngine;

namespace UI
{
    public class EffectClip : MonoBehaviour
    {
        // 遮挡容器,即ScrollView
        [SerializeField] RectTransform m_rectTrans;

        // 存放需要修改Shader的Material
        List<Material> m_materialList = new List<Material>();

        // UI的根,Canvas
        Transform m_canvas;
        float m_halfWidth, m_halfHeight, m_canvasScale;

        void Start ()
        {
            m_canvas = GameObject.Find("Canvas").transform;

            // 获取所有需要修改shader的material,并替换shader
            var particleSystems = GetComponentsInChildren<ParticleSystem>();
            for(int i = 0, j = particleSystems.Length; i < j ; i++)
            {
                var ps = particleSystems[i];
                var mat = ps.GetComponent<Renderer>().material;
                m_materialList.Add(mat);
                mat.shader = Shader.Find(mat.shader.name + " 1");
            }

            var renders = GetComponentsInChildren<MeshRenderer>();
            for(int i = 0, j = renders.Length; i < j; i++)
            {
                var ps = renders[i];
                var mat = ps.material;
                m_materialList.Add(mat);
                mat.shader = Shader.Find(mat.shader.name + " 1");
            }

            // 获取UI的scale,容器的宽高的一半的值
            m_canvasScale = m_canvas.localScale.x;
            m_halfWidth = m_rectTrans.sizeDelta.x * 0.5f * m_canvasScale;
            m_halfHeight = m_rectTrans.sizeDelta.y * 0.5f * m_canvasScale;

            // 给shader的容器坐标变量_Area赋值
            Vector4 area = CalculateArea(m_rectTrans.position);
            for(int i = 0, len = m_materialList.Count; i < len; i++)
            {
                m_materialList[i].SetVector("_Area", area);
            }
        }

        // 计算容器在世界坐标的Vector4,xz为左右边界的值,yw为下上边界值
        Vector4 CalculateArea(Vector3 position)
        {
            return new Vector4()
            {
                x = position.x - m_halfWidth,
                y = position.y - m_halfHeight,
                z = position.x + m_halfWidth,
                w = position.y + m_halfHeight
            };
        }
    }
}

总结

第一个方案,效果打了折扣

第二个方案,开发维护成本高,有一些不好避免的问题

第三个方案,显示粒子特效异常,可以有选择的进行使用;

第四个方案,是个比较好的实现方案.如果有裁剪需求的特效使用的是通用的几个Shader,那么这个方案就更划算了.即使有几个特殊的Shader需要修改,成本也不算太高.

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现Unity UGUIScrollView滑动居放大,其他的缩小,可以按照以下步骤进行操作: 1. 创建一个ScrollView,用于显示内容,并设置合适的大小和位置。 2. 在ScrollView创建一个Content对象,用于放置所有需要显示的子对象,并设置Layout Group组件,以确保内容按照一定的布局排列。 3. 在每个子对象上添加一个自定义的脚本,用于控制子对象的缩放和位置。脚本需要包含以下几个要点: a. 监听ScrollView的滑动事件,获取当前的滑动位置。 b. 根据当前滑动位置,计算每个子对象在滑动过程应该设置的缩放比例。例如,距离居的子对象应该更大,而距离边缘的子对象应该更小。 c. 根据计算得到的缩放比例,分别对每个子对象进行缩放设置。可以使用RectTransform的scale属性来实现缩放功能。 d. 根据子对象的缩放比例和位置信息,将子对象移动到ScrollView的合适位置。可以使用RectTransform的anchoredPosition属性来实现位置调整。 e. 可以根据需要,在脚本添加其他的功能,例如点击子对象时的反应等。 4. 将自定义的脚本添加到所有的子对象上,确保每个子对象都能根据滑动进行缩放和位置调整。 通过以上步骤,我们可以实现在Unity UGUIScrollView滑动过程,距离居的子对象放大,而距离边缘的子对象缩小的效果。具体的缩放比例和位置调整可以根据实际需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值