博主逛csdn有几年的时间了,但每每都是遇到问题时,来寻找解决方案的情况居多,并未开通过博客,也未发过任何文章,随着自己从网上得到的帮助越来越多,本着回馈与共同进步的想法,遂开通博客,并将自己在日常开发中遇到的问题记录下来,供广大网友们参考与讨论。
本人14年底因为项目需要随开始接触Unity3D引擎,博主一开始是用cocos2dx引擎开发手游,接触U3D后,发现确实U3D才是一套完整的手游开发解决方案,比起2dx上手也容易很多,加上NGUI,做游戏的UI确实比cocos2dx快了很多,因为博主身在一个创业公司,团队里面人手有限,因此现在项目的整个UI开发完全由博主一人承担,在此过程中确实遇到了很多问题,还好通过各种途径都解决了,我会在后期的博客中陆续将遇到的问题以及解决方案公布出来,供大家参考。
下面进入正题,因策划需求将无法点击的按钮以及一些可点击图标设置为禁用状态,即灰化,其实实现这个不算什么难事,最简单的就是让美术将所有需要灰化的图标出一份资源,然后程序动态换图就ok了。
然而这种解决方案最大的弊端就是,如果游戏内有大量需要灰化的图标,那么势必会导致最后包体变大,我们游戏正好就是此种情况,于是便选择了另一种解决方式,即通过程序写shader来解决此种问题。
在网上也查了一些文章,也有一些现成的shader例如这个shader
fixed4 frag (v2f i) : COLOR
{
fixed4 col;
if(i.color.r < 0.01 && i.color.g < 0.01 && i.color.b < 0.01 )
{
col = tex2D(_MainTex, i.texcoord);
float grey = dot(col.rgb, float3(0.299, 0.587, 0.114));
col.rgb = float3(grey, grey, grey);
}
else
{
col = tex2D(_MainTex, i.texcoord)* i.color;
}
return col;
}
这个shader就是在片段阶段时通过float grey = dot(col.rgb, float3(0.299, 0.587, 0.114)); 这句语句来实现灰化的,就是将顶点的像素与一个值进行点成,来影响每个顶点的像素来呈现出整体灰化的效果,这个shader最大的问题就在于外面的if判断,NGUI对每个Sprite的顶点进行渲染时,每次都要进行一次if else判断的话,会非常影响效率,尤其是如果你需要置灰的Sprite非常多的话,那么渲染就会非常耗时与影响效率,参考将Scrollview下放置超过100个Sprite再滑动,就可以大致了解了(这个问题在后期会给出解决方案)。
下面给出我的解决方案,首先先贴出我的shader:
Shader “UI/Sprite/Gray” {
Properties
{
_MainTex ("Base (RGB), Alpha (A)", 2D) = "black" {}
}
SubShader
{
LOD 200
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
}
Pass
{
Cull Off
Lighting Off
ZWrite Off
Fog { Mode Off }
Offset -1, -1
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct appdata_t
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
fixed4 color : COLOR;
};
struct v2f
{
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
fixed4 color : COLOR;
};
v2f o;
v2f vert (appdata_t v)
{
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.texcoord = v.texcoord;
o.color = v.color;
return o;
}
fixed4 frag (v2f i) : COLOR
{
fixed4 col;
col = tex2D(_MainTex, i.texcoord);
col.rgb = dot(col.rgb, fixed3(.222,.707,.071));
return col;
}
ENDCG
}
}
shader就不详细介绍了,以后会专门开个专题来讲讲shader,这个shader里面核心代码只有一句 col.rgb = dot(col.rgb, fixed3(.222,.707,.071)); 其实跟网上的shader没什么区别,只是去掉了if else而已,这时有人可能会问了,你这样那我如何将sprite恢复成normal状态,别急,下面才是肉戏。我们知道NGUI的Sprite是通过图集来存储资源的,那么我如何来给单个Sprite换shader而又不影响图集里面其他的资源呢?就是通过动态给UISprite换材质的方法来实现,首先我们需要创建一个类,这里我创建了一个类叫XLSprite,它集成自UISprite,下面我将整个类贴上来:
using UnityEngine;
using System.Collections;
using System;
public class XLSprite : UISprite
{
protected GameObject panelObj = null;
protected Material GrayMaterial;
/// <summary>
/// ngui对Sprite进行渲染时候调用
/// </summary>
/// <value>The material.</value>
public override Material material
{
get
{
Material mat = base.material;
if (mat == null)
{
mat = (mAtlas != null) ? mAtlas.spriteMaterial : null;
}
if (GrayMaterial !=null)
{
return GrayMaterial;
}
else
{
return mat;
}
}
}
/// <summary>
/// 调用此方法可将Sprite变灰
/// </summary>
/// <value>The material.</value>
public void SetGray()
{
Material mat = new Material (Shader.Find("UI/Sprite/Gray"));
mat.mainTexture = material.mainTexture;
GrayMaterial = mat;
RefreshPanel(gameObject);
}
/// <summary>
/// 隐藏按钮,setActive能不用尽量少用,效率问题。
/// </summary>
/// <value>The material.</value>
public void SetVisible(bool isVisible)
{
if (isVisible)
{
transform.localScale = new Vector3(1,1,1);
}
else
{
transform.localScale = new Vector3(0,0,0);
}
}
/// <summary>
/// 将按钮置为禁止点击状态,false为禁用状态
/// </summary>
/// <value>The material.</value>
public void SetEnabled(bool isEnabled)
{
if (isEnabled)
{
BoxCollider lisener = gameObject.GetComponent<BoxCollider> ();
if (lisener)
{
lisener.enabled = true;
}
SetNormal();
}
else
{
BoxCollider lisener = gameObject.GetComponent<BoxCollider> ();
if (lisener)
{
lisener.enabled = false;
}
SetGray();
}
}
/// <summary>
/// 将GrayMaterial置为null,此时会调用默认材质,刷新panel才会重绘Sprite
/// </summary>
/// <value>The material.</value>
public void SetNormal()
{
GrayMaterial = null;
RefreshPanel(gameObject);
}
///刷新panel,重绘Sprite
void RefreshPanel(GameObject go)
{
if (panelObj == null)
{
panelObj = NGUITools.FindInParents(go);
}
if (panelObj != null)
{
panelObj.enabled = false;
panelObj.enabled = true;
}
}
}
上面代码都有注释,就不多介绍了,注意material中的mat可能是私有变量,需要将其改为public。这样每次建立Sprite时候,直接添加XLSprite就ok,里面还有一些其他封装的方法,供大家参考,这里主要介绍下,如何实现的动态换材质,通过上面代码,大家可以大致看到是在SetGray()中,动态的创建了一个material并且赋值给Sprite,然后刷新panel时,在重绘这个Sprite时候,会调用material,这个时候,会返回我们创建好的GrayMaterial,在我们需要将Sprite恢复正常时候,仅需将我们的GrayMaterial置为null,就OK了。
下面是效果图
效果看来还是蛮ok的,今天就先介绍到这里,过段时间会介绍一下ScrollView下将按钮变灰的方法,上述方案只适用在非ScrollView下使用,在ScrollView下会出现变灰的Sprite无法正常遮罩的情况,那这种情况下具体的解决方案,会在下期的博客里讲解。