上文实现了UITexture的流光效果,但是有时候我们希望使用图集的UISprite也能有流光效果,那怎么办呢。一种做法是将图片独立出来,使用UITexture,结合上文的方法,这里我们来看看直接操作UISprite该怎么做。
需要注意的是,本文有些使用到的原理在上文已经解释过了,就不再赘述。建议大家还是先看前一篇。
流光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;
}
}
...
首先起一个脚本来设置上面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();
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 ();
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;
}
}
– 目前发现UISprite使用这种方案后,在手机上,图片的清晰度会下降,目前我还没有时间来研究这块,也欢迎大家留言讨论
– 这种方案会增加DrawCall,因为使用了独立的材质,没有办法