天天看點

NGUI的流光shader解決方案(二)(支援UITexture和使用圖集的UISprite)

上文實作了UITexture的流光效果,但是有時候我們希望使用圖集的UISprite也能有流光效果,那怎麼辦呢。一種做法是将圖檔獨立出來,使用UITexture,結合上文的方法,這裡我們來看看直接操作UISprite該怎麼做。

需要注意的是,本文有些使用到的原理在上文已經解釋過了,就不再贅述。建議大家還是先看前一篇。

先寫Shader

流光Shader的原理都是差不多的,這裡的關鍵問題在于:UV坐标,因為紋理是圖集這張大圖,uv坐标怎麼對應到流光紋理上呢,這個其實是個簡單的數學問題,看下圖就明白了:

NGUI的流光shader解決方案(二)(支援UITexture和使用圖集的UISprite)

這是一個Atlas,A點代表Sprite的左下角,B點是右上角,整個坐标系表示的是uv坐标,問題就轉化為:已知C點相對于大圖的uv坐标,求C點相對于Sprite小圖的uv坐标。

A點坐标 = ( spriteOffsetX/atlasWidth, spriteOffsetY/atlasHeight )
B點坐标 = ( (spriteOffsetX+spriteWidth)/atlasWidth, (spriteOffsetY+spriteHeight)/atlasHeight )
C點相對于大圖的uv坐标 = ( cUvX, cUvY )
那麼C點相對于小圖的uv坐标 = ( x, y )

x = (cUvX - A.x)/(B.x - A.x)
x = (cUvx - spriteOffsetX/atlasWidth) / (spriteWidth/atlasWidth)
y同理
           

那麼我們的Shader代碼就出來了,同樣在預設的Transparent Colored的代碼基礎上進行修改:

Properties
    {
        ...
        //流光相關
        _WidthRate ("Sprite.width/Atlas.width", float) = 1
        _HeightRate ("Sprite.height/Atlas.height", float) = 1
        _XOffset("offsetX/Atlas.width", float) = 0
        _YOffset("offsetY/Atlas.height", float) = 0
        //流光紋理
        _FlowLightTex("FlowLight Texture",2D) = "white"{}
        //流光強度
        _FlowLightPower("FlowLight Power",float) = 1
        //流光開關,0關閉,1開啟
        _IsOpenFlowLight ("IsOpenFlowLight", float) = 0
        //流光的偏移,通過設定此值來達到uv動畫效果
        _FlowLightOffset("FlowLight Offset", float) = 0
    }
    ...
    fixed4 frag (v2f IN) : COLOR
    {
        fixed4 colorMain = tex2D(_MainTex, IN.texcoord) * IN.color;

        //如果開啟流光
        if(_IsOpenFlowLight > 0.5) {
            //計算部分
            float2 flow_uv = float2( (IN.texcoord.x-_XOffset)/_WidthRate, (IN.texcoord.y-_YOffset)/_HeightRate );
            flow_uv.x /= 2;
            flow_uv.x -= _FlowLightOffset;
            fixed4 colorFlowLight = tex2D(_FlowLightTex, flow_uv) * _FlowLightPower;
            colorFlowLight.rgb *= colorMain.rgb;
            colorMain.rgb += colorFlowLight.rgb;
            colorMain.rgb *= colorMain.a;
        }

        return colorMain;
    }
           

好了,接下來讓UISprite用上自己的Shader

自定義材質

我們知道,UISprite預設使用的是Atlas對應的材質,如果我們把整個Atlas的材質換了,那麼同一圖集的都會受到影響,跟我們期望的不一樣。

是以這裡修改UISprite的源碼,加個Material的變量,讓我們可以随意的給某一個UISprite指定材質:

//UISprite.cs
...
//我們加的變量
public Material _testMaterial;

//修改自帶方法的傳回
public override Material material { 
    get { 
        if (_testMaterial != null)
            return _testMaterial;
        else if (mAtlas != null)
            return mAtlas.spriteMaterial;
        else
            return null;
    } 
}
...
           
NGUI的流光shader解決方案(二)(支援UITexture和使用圖集的UISprite)

Ok,萬事俱備,就差控制腳本了。

控制腳本

首先起一個腳本來設定上面Shader中的幾個變量:

using UnityEngine;
using System.Collections;

public class FlowLightHelpTool : MonoBehaviour
{
    private float widthRate;
    private float heightRate;
    private float xOffsetRate;
    private float yOffsetRate;
    private UISprite sprite;

    void Awake()
    {
        sprite = GetComponent<UISprite>();
        widthRate = sprite.GetAtlasSprite().width * 1.0f / sprite.atlas.spriteMaterial.mainTexture.width;
        heightRate = sprite.GetAtlasSprite().height * 1.0f / sprite.atlas.spriteMaterial.mainTexture.height;
        xOffsetRate = sprite.GetAtlasSprite().x * 1.0f / sprite.atlas.spriteMaterial.mainTexture.width;
        yOffsetRate = (sprite.atlas.spriteMaterial.mainTexture.height-(sprite.GetAtlasSprite().y + sprite.GetAtlasSprite().height)) * 1.0f / sprite.atlas.spriteMaterial.mainTexture.height;
    }

    private void Start()
    {
        sprite.material.SetFloat("_WidthRate", widthRate);
        sprite.material.SetFloat("_HeightRate", heightRate);
        sprite.material.SetFloat("_XOffset", xOffsetRate);
        sprite.material.SetFloat("_YOffset", yOffsetRate);
    }
}
           

再加上跟上一篇差不多的控制腳本:

using UnityEngine;
using System.Collections;

public class EffectFlowLight : MonoBehaviour {
    //起始的uv坐标
    public float mUvStart = 0f;
    //uv移動的速度
    public float mUvSpeed = 0.02f;
    //一次動畫uv移動的最大長度
    public float mUvXMax = 0.9f;
    //流光的間隔時間
    public float mTimeInteval = 3f;

    private float mUvAdd;
    private bool mIsPlaying;

    void Awake () {
        UISprite sp = gameObject.GetComponent<UISprite> ();
        sp.onRender += UpdateMaterial;

        mUvAdd = 0;
        mIsPlaying = true;
    }

    //NGUI更新Material的回調
    private void UpdateMaterial(Material mat) {
        if (mIsPlaying) {
            //逐幀移動uv
            mUvAdd += mUvSpeed;
            mat.SetFloat ("_FlowLightOffset", mUvStart + mUvAdd);
            mat.SetFloat ("_IsOpenFlowLight", 1f);

            //如果移動的uv已經超過最大值,重置,準備下一次流光
            if (mUvAdd >= mUvXMax) {
                mIsPlaying = false;
                mat.SetFloat ("_IsOpenFlowLight", 0f);
                Invoke ("PlayOnceAgain", mTimeInteval);
            }
        } else {
            mat.SetFloat ("_IsOpenFlowLight", 0f);
        }
    }

    //再次觸發流光
    private void PlayOnceAgain() {
        mUvAdd = 0;
        mIsPlaying = true;
    }

}
           

加上腳本,運作,搞定:

NGUI的流光shader解決方案(二)(支援UITexture和使用圖集的UISprite)

TODO

– 目前發現UISprite使用這種方案後,在手機上,圖檔的清晰度會下降,目前我還沒有時間來研究這塊,也歡迎大家留言讨論

– 這種方案會增加DrawCall,因為使用了獨立的材質,沒有辦法