NGUI中的Button几乎是最常用到的控件之一,并且可以组合各种组件(比如UIButtonColor,UIButtonOffset,UITweenxx),方便设置Button的各种状态下的属性,几乎可以满足我们的所有需求。
但是对于当Button的isEnabled属性设置为false时,根据设置的disableColor属性设置不可点击时的颜色时,虽然我们设置的灰色,但并不是我们想象中的样子!
设置的是灰色,实际运行结果却还是彩色的,只是暗了一点,并不能够很好地表现出其“禁用”的状态!
Unity3d中所有的渲染都是基于Shader的,而Shader绑定在Material上,打开一个NGUI例子中自带的Material,得到其使用Shader的文件
NGUI中大部分材质都使用的Unlit/Transparent Colored(PS:虽然在Unlit下,但并不是Unity3d内置的,而是NGUI扩展的)
找到其片段着色器,代码如下:
fixed4 frag (v2f i) : COLOR { fixed4 col = tex2D(_MainTex, i.texcoord) * i.color; return col; }这个片段着色器很简单,只在“最简单的着色器”上多加了一步,即将从定点着色器中传出的顶点颜色属性乘到了纹理采样得到的像素上。
看到这个代码,就很容易理解为什么是变暗,而不是变成灰色了
顶点的颜色数据是从UISprite之类的面板中传递进来的,其最大值是白色(255,255,255,255),而这里是正交化的,最大值白色对应(1.0,1.0,1.0,1.0),这也是默认值,当采样得到的像素值x1.0,相当于采样得到的纹理值;如果设置一个其他的颜色,正交化后肯定会小于1.0,当采样得到的像素值乘以这个值后,像素值会比之前小,而最小值是(0,0, 0,0)即黑色,也就是说如果设置一个不是白色的颜色,就会使像素值更接近于黑色,这就是变暗的原因!
NGUI只提供了这样一种变暗的功能,用来表现其“禁用”的状态,但是这并不是最好的结果,如果需要介于黑白之间的灰色纹理,难道非要美术对每一个可能会被置灰的纹理重新制作一张纹理吗?
这就更糟了!游戏中纹理是很占空间的,这样做相当于将UI资源翻了一倍!
还是从Shader方面入手吧!
想象一下,如果在着色器处理之前,传递一个bool值,当这个bool值为true时,正常绘制纹理;当这个bool值为false时,绘制灰色纹理。
(Unity3d的Shader中并不支持传递bool值,这里只是举个栗子)
这样看似很合理,也确实可以实现,但是会有一个问题,这个bool值肯定要在顶点着色器阶段传过去,而NGUI提供的“纹理打包”功能(即很多纹理合并成一个Atlas,即节省空间,还可以有一些其他信息,比如九宫格拉伸的参数。。。),当这个bool值为false时,这个Atlas中所有的绘制即全部变为灰色,这是不符合逻辑的,当然可以每张小图单独处理,即相当于损失掉NGUI的“纹理打包”功能
损失一个颜色值吧,作为“约定”!
选取一个颜色值,作为约定为置灰的标记,当片段着色器检测到这个颜色值之后,执行渲染灰色的shader!
这个颜色值可以任意选择,我这里选取纯黑色作为“约定颜色”,片段着色器代码如下:
fixed4 frag (v2f i) : COLOR { fixed4 col; if (i.color.r < 0.001) { 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; }其中(0.299,0.587,0.114)为灰度公式的参数
我复制了一份NGUI例子的纹理和材质,将此Shader设置到材质中,渲染效果如图
(最上面两个是原始状态下的效果,中间两个是NGUI提供的禁用状态效果,最下面两个分别是修改后Shader渲染同一个Atlas得到的结果)
这才是我想要的灰色!
(PS:感谢GYB提供的思路!)
第五、对象的自定义拷贝
对象拥有复制特性,必须实现NSCopying,NSMutableCopying协议,实现该协议的copyWithZone方法和mutableCopyWithZone方法
深拷贝和浅拷贝的区别就在于copyWithZone方法的实现,
浅拷贝代码如下:
#import <Foundation/Foundation.h> @interface Person : NSObject<NSCopying> @property(nonatomic,retain)NSString *name; @property(nonatomic,retain)NSString *age; @end
#import "Person.h" @implementation Person - (id)copyWithZone:(NSZone *)zone { //实现自定义浅拷贝 Person *person=[[self class] allocWithZone:zone]; person.age=_age; person.name=_name; return person; } @end
main函数为:
@autoreleasepool { Person *person=[[Person alloc] init]; person.name=@"andy"; person.age=@"20"; Person *person2=[person copy]; NSLog(@"person 地址为%p,person2地址为%p",person.name,person2.name); }输出结果为:
2013-09-30 17:48:41.007 FDAS[732:303] person 地址为0x1000022c8,person2地址为0x1000022c8
深拷贝代码如下:
- (id)copyWithZone:(NSZone *)zone { //实现自定义浅拷贝 Person *person=[[self class] allocWithZone:zone]; person.age=[_age copy]; person.name=[_age copy]; return person; }
结果:
2013-09-30 17:55:13.603 FDAS[742:303] person 地址为0x1000022c8,person2地址为0x1000022e8
NSArray *arr=[NSArray arrayWithObjects:@"one",@"two",nil]; NSArray *arr2=[arr copy]; NSLog(@"the dress of arr is %p the dress of arr2 is %p",arr,arr2); NSLog(@"the retainCount is %ld",arr.retainCount);
执行结果为:
2013-09-30 18:01:01.394 FDAS[787:303] the dress of arr is 0x100108320 the dress of arr2 is 0x100108320
2013-09-30 18:01:01.396 FDAS[787:303] the retainCount is 2
结果是一样的,是因为Foundation对于不可变复制对象而言,copy方法做了优化,相当于retain,故retaincount变成2.
相当于 在copyWithZone方法中:return [self retain];
第六、copy、mutableCopy和retain之间的关系
在Foundation对象中,copy是一个不可变的对象时,作用相当于retain
当使用mutableCopy时,不管源对象是否可变,副本是可变的,并且实现真正意义上的copy
当我们使用copy一个可变对象时,副本对象是不可变的。
package cc.copy; import java.io.FileInputStream; import java.io.FileOutputStream; /** * Demo描述: 利用IO流操作复制照片 * * 注意事项: * int java.io.FileInputStream.read(byte[] b) throws IOException * 方法的官方文档描述: * Reads up to b.length bytes of data from this input stream into an array of bytes. * This method blocks until some input is available. * 即可以这么理解: * FileInputStream会不断地读取字节数据到字节数组b中. * 只要读了一次,那么我们就相应地执行一次写的操作: * fileOutputStream.write(temp,0,len); * 什么情况下这个读的过程会终止呢? * 当已经读取到数据末尾的时候,有个标识符-1;表示已经到了末尾. * * 有种不太恰当的理解: * FileInputStream会一次次地读取字节数据到字节数组b中. * 每读一次后,就会稍微暂停一下,然后执行 * fileOutputStream.write(temp,0,len); * 进行数据写的操作. * * 其实如下理解更合适一些: * FileInputStream是在不断地读数据(而不要想象成带有暂停性质的一次次地读取). * 只是它每读一次的数据必然不超过b.length的大小,但有可能是不一样的长度, * 比如:在极多数情况下,最后一次的时候读取的字节数会小于b.length大小. * 每采用read()方法读一次呢,读到的数据就会存到字节数组b中,并且该方法 * 还返回了这次读取的字节的多少(len). * 于是我们对字节数组b中从o到len的数据进行写操作: * fileOutputStream.write(temp,0,len); * * 现在就明白了为什么使用: * fileOutputStream.write(temp); * 是不准确的,很可能导致复制后的照片比原始照片大. * 因为fileOutputStream.write(temp);方法每次都是 * 写了b字节数组大小的数据,而不是已经b中实际有多少 * 数据. * 这样的操作在前几次是没有什么问题的,因为每次装的 * 都是b字节数组大小的数据,但是最后一次往往是装不满 * b的.所以每次应该根据b中的实际数据到底有多少来进行 * 操作即采用fileOutputStream.write(temp,0,len); * 可以兼顾到每一次. */ public class CopyPhoto { public static void main(String[] args) { CopyPhoto copyPhoto = new CopyPhoto(); copyPhoto.copy(); } private void copy() { try { FileInputStream fileInputStream=new FileInputStream("D:\\c.jpg"); FileOutputStream fileOutputStream=new FileOutputStream("D:\\new.jpg"); int len=0; byte temp []=new byte[1024*8];; while((len=fileInputStream.read(temp))!=-1){ System.out.println("len="+len); //It is right fileOutputStream.write(temp,0,len); //It is wrong //fileOutputStream.write(temp); } fileOutputStream.close(); fileInputStream.close(); } catch (Exception e) { } } }