环境:
win7 64位
Cocos2d-JS v3.0 (final)
Cocos Code IDE v1.0.0.RC2
注意本文是在非web上实现,使用shader本人的浏览器不能正常显示效果
本文基于上一篇文章:Cocos2d-JS下往Sprite SkeletonAnimation Armature(骨骼动画)添加shader 进行拓展。
正文:
使用shader还原方法有很多种,这里先介绍一下本人的理解:图片添加shader并不会影响图片原来的各种属性,只是在屏幕上显示之前,把图片的信息和所用的shader进行各种处理,把处理后的效果显示出来。
用一个公式解释的话,那就是 A (图片) + B(shader) = C(最终显示效果)。
而不是A+B=A。
(实际上opengl变换并不是相加,这里只是打个比方,示意一下)
既然理解了,那么要在已经使用shader的图片/骨骼动画/等等上取消特效就很简单了,直接使用shader,只是这个shader不添加任何特效,单纯的显示图片原来的信息,那就好了。
这里介绍两种方法:
第一种:在app.js最下面添加这样一个方法(具体可以参照上文app.js里面garySprte(sprite)方法):
function sourceSprite(sprite) { if (sprite) { var shader = new cc.GLProgram(); shader.initWithString(cc.SHADER_POSITION_COLOR_VERT,cc.SHADER_POSITION_COLOR_FRAG );//没有特效的shader shader.link(); shader.updateUniforms();//我的理解是经过一系列的矩阵变换渲染到屏幕上 sprite.setShaderProgram(shader);//应用到精灵上 } }
这里不需要添加新的.vsh和.fsh文件了,因为cc.SHADER_POSITION_COLOR_VERT 和cc.SHADER_POSITION_COLOR_FRAG 已经是了,我们可以到cocos2d-js 官方文档查看API :http://www.cocos2d-x.org/reference/html5-js/V3.0/index.html
接着在中间位置找到CCShaders.js
接着在73到100行可以看到具体的定义
然后在app.js里面的
- graySprite(this.sprite);
后面添加
this.scheduleOnce(function(){ sourceSprite(this.sprite);
},6);//表示6秒后执行还原方法:sourceSprite(this.sprite)
不过这里还有另外一种更加简单的方法,那就是在使用(带特效)shader之前,先保存目标的shader,具体操作为:
graySprite(this.sprite);之前,添加这么一句获取目标的shader:
var spriteBefore = this.sprite.getShaderProgram();接着在这句的后面,添加定时器操作:
this.scheduleOnce(function(){ this.sprite.setShaderProgram(spriteBefore);
},5);//表示6秒后执行还原:this.sprite.setShaderProgram(spriteBefore);
同理,SkeletonAnimation也是一样,在ctor方法return true前添加:
var spineBoyBefore = spineBoy.getShaderProgram(); this.scheduleOnce(function(){ spineBoy.setShaderProgram(spineBoyBefore) ;},6);//6秒后执行还原
注意在创建spineBoy后添加。
然后特别说明一下,添加特效shader时要注意SkeletonAnimation的gl_Position为:
gl_Position = (CC_PMatrix * CC_MVMatrix) * a_position;不然还原后位置会发生变化 ,感觉就像在位移
最后就是Armature动画
这个要块骨骼皮肤渲染的比较麻烦,因为在遍历的时候使用for in了,而不是for(;;){},在for in中,就算在使用特效shader前保存了原本的shader,之前再使用定时器设定X秒后运行还原,尽管执行的次数跟骨骼数量一样,但只对一个骨骼有效,在遍历方法里:
grayArmature: function (armature) { var locBoneDic = armature.getBoneDic(); for (var key in locBoneDic) { var bone = locBoneDic[key]; if (bone && bone.getDisplayRenderNode()) { var node = bone.getDisplayRenderNode(); graySprite(node); } } }
因为是在X秒后再对node进行还原shader,而此时node保存的只是遍历骨骼时保存的最后一个骨骼,所以说,要用for(;;){},把骨骼添加进node[i]数组(或者列表里面),X秒后操作的时候每个骨骼都保留了下来,但是,由于本人刚接触JS,而且基础不咋地,就继续探索别的方法了。
那,先保存原来的shader,再写一个遍历方法,再设置为原来的shader不就可以了?
cocos2d-js的定时器延迟函数:
scheduleCallbackForTarget
scheduleOnce
等等,我在这篇文章总结了一下使用延时函数要注意的地方http://www.jb51.cc/article/p-zxbnxooz-qm.html
本文使用的实现方式,那就是在每帧更新方法里面设置一个标志,X秒后把标志设置为true,然后执行遍历方法,接着把标志设置为false
bc:图片原本的shader
fa:标志位
armature:骨骼动画
接着在onEnter里面添加:(其实添加的位置最好是在ctor,也就是初始化的时候,不过本人注重实现效果,优化什么的完全没有考虑)
this.fa = false;//初始化标志位为false
this.scheduleUpdate();//开启update,每秒刷新时的操作 this.scheduleOnce(function() { this.fa = true; },7); //7秒后把标志位设置为true
重写update方法:
update : function() { if (this.fa) {//上面用延迟方法把this.fa设置为7秒后为true this.grayArmature(this.armature,false);//下面会介绍修改的grayArmature方法 this.fa = false; } },
接着把修改onEnter方法:
onEnter : function() {//主要是更新armature的作用域,改变了X坐标 this._super(); var size = cc.winSize; var skins = new Array(); ccs.armatureDataManager.addArmatureFileInfo(res.COwboy_png0,res.CowBoy_plist0,res.CowBoy_exportjson); this.armature = ccs.Armature.create("Cowboy"); this.armature.getAnimation().play("Walk"); this.armature.x = size.width * 0.8; this.armature.y = size.height / 2; this.armature.setScale(0.5); this.grayArmature(this.armature,true); this.addChild(this.armature,4,0); this.fa = false; this.scheduleUpdate(); this.scheduleOnce(function() { this.fa = true; },7); },最后,改动grayArmature
grayArmature:function(armature,flag){ var locBoneDic = armature.getBoneDic(); for (var key in locBoneDic) { var bone = locBoneDic[key]; if (bone && bone.getDisplayRenderNode()) { var node = bone.getDisplayRenderNode(); if (flag) { this.bc = node.getShaderProgram();//保存原本的shader graySprite(node); } else { node.setShaderProgram(this.bc); } } } },
方法最后有没有逗号,根据自己实际情况而定
最后看看效果,运行程序后第4秒时:
第5秒:
第6秒(注意要做gl_Position的处理):
第7秒:
最后整个app.js:有些测试函数本人就懒得去掉了,大家可以自己拿去改改试一下效果,中间的图片使用的延迟方法跟上面介绍的有出入,不过大家可以到官方文档查查API,效果跟上面介绍是一样的。
var HelloWorldLayer = cc.Layer.extend({ sprite:null,bc: null,fa:null,armature:null,ctor:function () { // //////////////////////////// // 1. super init first this._super(); // /////////////////////////// // 2. add a menu item with "X" image,which is clicked to quit the // program // you may modify it. // ask the window size var size = cc.winSize; // add a "close" icon to exit the progress. it's an autorelease object var closeItem = new cc.MenuItemImage( res.CloseNormal_png,res.CloseSelected_png,function () { cc.log("Menu is clicked!"); },this); closeItem.attr({ x: size.width - 20,y: 20,anchorX: 0.5,anchorY: 0.5 }); var menu = new cc.Menu(closeItem); menu.x = 0; menu.y = 0; this.addChild(menu,1); // /////////////////////////// // 3. add your codes below... // add a label shows "Hello World" // create and initialize a label //var helloLabel = new cc.LabelTTF("Hello World"+SimpleNativeClass.getAnotherMoreComplexField(),"Arial",38); var helloLabel = new cc.LabelTTF("Hello World",38); // position the label on the center of the screen helloLabel.x = size.width / 2; helloLabel.y = 0; // add the label as a child to this layer this.addChild(helloLabel,5); // add "HelloWorld" splash screen" this.sprite = new cc.Sprite(res.HelloWorld_png); this.sprite.attr({ x: size.width / 2,y: size.height / 2,scale: 0.5,rotation: 180 }); var spriteBefore = this.sprite.getShaderProgram(); this.addChild(this.sprite,0); graySprite(this.sprite); cc.director.getScheduler().scheduleCallbackForTarget(this,function(){ this.sprite.setShaderProgram(spriteBefore); },5,false,!this._isRunning ); this.sprite.runAction( cc.sequence( cc.rotateTo(2,0),cc.scaleTo(2,1,1) ) ); helloLabel.runAction( cc.spawn( cc.moveBy(2.5,cc.p(0,size.height - 40)),cc.tintTo(2.5,255,125,0) ) ); var spineBoy = new sp.SkeletonAnimation(res.SpineBoy_json,res.SpineBoy_atalas); spineBoy.setPosition(cc.p(size.width * 0.1,size.height / 2 - 150)); spineBoy.setAnimation(0,'walk',true); spineBoy.setMix('walk','jump',0.2); spineBoy.setMix('jump',0.4); var spineBoyBefore = spineBoy.getShaderProgram(); graySprite(spineBoy); this.addChild(spineBoy,2); this.scheduleOnce(function(){ spineBoy.setShaderProgram(spineBoyBefore) ;},6); return true; },onEnter : function() { this._super(); var size = cc.winSize; var skins = new Array(); ccs.armatureDataManager.addArmatureFileInfo(res.COwboy_png0,update : function() { if (this.fa) { cc.log("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); this.grayArmature(this.armature,false); this.fa = false; } },grayArmature:function(armature,flag){ var locBoneDic = armature.getBoneDic(); cc.log(locBoneDic); for (var key in locBoneDic) { var bone = locBoneDic[key]; if (bone && bone.getDisplayRenderNode()) { var node = bone.getDisplayRenderNode(); if (flag) { this.bc = node.getShaderProgram(); graySprite(node); } else { node.setShaderProgram(this.bc); } } } },grayArmature2:function(armature,bc){ var locBoneDic = armature.getBoneDic(); cc.log("131231313"); for (var key in locBoneDic) { var bone = locBoneDic[key]; if (bone && bone.getDisplayRenderNode()) { var node = bone.getDisplayRenderNode(); node.setShaderProgram(bc); } } },}); function graySprite(sprite) { if(sprite) { var shader = new cc.GLProgram(); shader.init(res.Grat_vsh,res.Gray_fsh); shader.link(); shader.updateUniforms(); sprite.setShaderProgram(shader); } } function backToSprite(sprite) { if(sprite) { var shader = new cc.GLProgram(); shader.init(cc.SHADER_POSITION_COLOR_VERT,cc.SHADER_POSITION_COLOR_FRAG ); shader.link(); shader.updateUniforms(); sprite.setShaderProgram(shader); } } var HelloWorldScene = cc.Scene.extend({ onEnter:function () { this._super(); var layer = new HelloWorldLayer(); this.addChild(layer); } });