1. 程式人生 > >[Unity]UGUI ScrollView ui特效遮擋的問題

[Unity]UGUI ScrollView ui特效遮擋的問題

當使用滾動列表的時候,例如商城,好友列表。我們要在每個列表Item上新增UI特效的時候(Particle System或Mesh Renender等)會發現特效並不能根據父控制元件的大小而裁減掉,如圖:


注:UGUI和NGUI都存在這個問題,NGUI則是在UIPanel Clipping 為Soft Clip時。

我們要實現的效果是超框的部分也應該和其他UI一樣被裁減掉。現在的思路是修改特效的shader,給shader傳一個值,記錄scrollview四個邊在世界座標的位置,然後在顯示的時候判斷是否在框內,若不在則隱藏。

首先先處理shader這塊,為了不影響其他的特效。可以把需要修改的shader都複製一份重新命名。(我這邊的處理是 a.shader 複製成 a 1.shader )下面的程式碼是在原本shader的基礎上進行的修改,修改處註釋已標出。

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 
			}
		} 
	}
}

shader處理好後,接下來要做的就是將UI特效裡面所有的shader都替換成新的。可以寫一個指令碼,掛在UI特效上,然後再Start裡面替換好shader然後計算出裁剪框(UGUI的ScrollView或NGUI的UIPanel,等)的四個邊界的世界座標。最後將這個值傳給新的shader即可。程式碼如下,用的UGUI的ScrollView。當然NGUI的思路也是一樣樣的:

Unity:

using System.Collections.Generic;
using UnityEngine;

namespace UI {

	public class EffectClip : MonoBehaviour {

        [SerializeField] RectTransform m_rectTrans;//遮擋容器,即ScrollView

        List<Material> m_materialList = new List<Material>();//存放需要修改Shader的Material
        Transform m_canvas;//UI的根,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
            };
        }
    }
}

最後將指令碼掛上所有的UI特效上後:執行看到如下的效果,多餘的已被裁剪: