上文實作了UITexture的流光效果,但是有時候我們希望使用圖集的UISprite也能有流光效果,那怎麼辦呢。一種做法是将圖檔獨立出來,使用UITexture,結合上文的方法,這裡我們來看看直接操作UISprite該怎麼做。
需要注意的是,本文有些使用到的原理在上文已經解釋過了,就不再贅述。建議大家還是先看前一篇。
先寫Shader
流光Shader的原理都是差不多的,這裡的關鍵問題在于:UV坐标,因為紋理是圖集這張大圖,uv坐标怎麼對應到流光紋理上呢,這個其實是個簡單的數學問題,看下圖就明白了:
這是一個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;
}
}
...
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;
}
}
加上腳本,運作,搞定:
TODO
– 目前發現UISprite使用這種方案後,在手機上,圖檔的清晰度會下降,目前我還沒有時間來研究這塊,也歡迎大家留言讨論
– 這種方案會增加DrawCall,因為使用了獨立的材質,沒有辦法