(二)Cocos2d-JS制作一个微信报名宣传页——框架实现
接下来,我们要开始思考,如何填充我们的主要功能——5个页面的框架。
在这里我们使用cc.Layer.extend定义出5个Layer,并在每个layer中,实现appear方法和disappear方法,分别用于控制页面的入场动画和出场动画,后面,我们会详细介绍每个layer的动画,现在我们设计出layer的框架后呢,我们就可以动手编写MainScene里面的initUI的剩余部分和nextPage函数的编写了,一下是完整的initUI()代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | initUI: function () { var bg = new cc.Sprite(res.background_png); bg.anchorX = 0; bg.anchorY = 0; bg.scaleX = cc.winSize.width / bg.width; bg.scaleY = cc.winSize.height / bg.height; this .addChild(bg, 0); this .arrow = new cc.Sprite( "#arrow.png" ); this .arrow.setPosition(cc.pAdd(cc.visibleRect.bottom, cc.p(0, 50))); var posY = this .arrow.y; var arrowAction = cc.repeatForever(cc.sequence(cc.spawn(cc.moveTo(0.8, cc.p( this .arrow.x, posY + 30)).easing(cc.easeIn(0.5)), cc.fadeOut(1)), cc.delayTime(0.8), cc.callFunc(function () { this .arrow.y = this .arrow.y - 30; this .arrow.opacity = 255; }, this ))); this .arrow.runAction(arrowAction); this .addChild( this .arrow, 1); this .menuItemToggle = new cc.MenuItemToggle( new cc.MenuItemImage( "#music.png" ), new cc.MenuItemImage( "#music_sel.png" ), this .toggleMusicCallback, this ); this .menuItemToggle.setPosition(cc.pAdd(cc.visibleRect.right, cc.p(- this .menuItemToggle.width / 2 - 30, 140))); var togglemenu = new cc.Menu( this .menuItemToggle); togglemenu.anchorX = 0; togglemenu.anchorY = 0; togglemenu.x = 0; togglemenu.y = 0; this .addChild(togglemenu, 1); this .animLayer = new cc.Layer(); this .addChild( this .animLayer); this .sceneList.push( new Layer1()); this .sceneList.push( new Layer2()); this .sceneList.push( new Layer3()); this .sceneList.push( new Layer4()); this .sceneList.push( new Layer5()); for (var i = 0; i < this .sceneList.length; i++) { var scene = this .sceneList; scene.anchorX = 0; scene.anchorY = 0; scene.x = 0; scene.y = 0; if ( this .currentIndex != i) { scene.setVisible( false ); } this .animLayer.addChild(scene, this .sceneList.length - i); } }, |
可以看到,首先,我们在MainScene中定义了一个animLayer,用来控制各个页面的动画层,然后我们定义了一个数组用来存储我们的需要的5个Layer,最后他们添加到animLayer里面,并将非当前显示页的Layer隐藏,这样引擎就不会去绘制他们了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | addTouch: function () { var self = this ; self.listener = cc.EventListener.create({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true , startPosY: 0, onTouchBegan: function (touch, event) { this .startPosY = touch.getLocation().y; return true ; }, onTouchMoved: function (touch, event) { }, onTouchEnded: function (touch, event) { if (musicPlayStatus) { playMusic( true ); } if (canChangePage) { var delta = touch.getLocation().y - this .startPosY; if (delta > 15 && self.currentIndex < self.sceneList.length - 1) { self.changePage(++self.currentIndex, true ); } else if (delta < -15 && self.currentIndex > 0) { self.changePage(--self.currentIndex, false ); } } }, onTouchCancelled: function (touch, event) { } }); cc.eventManager.addListener(self.listener, self); }, changePage: function (index, next) { canChangePage = false ; var scene = next ? this .sceneList[index - 1] : this .sceneList[index + 1]; if (index == 4) { this .togleArrow( false ); } else { this .togleArrow( true ); } var nextPage = function () { scene.visible = false ; this .sceneList[index].visible = true ; this .sceneList[index].appear(); }; if (scene) { scene.disappear(nextPage, this ); } } |
我们想好如何去编写5个页面后,就可以开始编写addTouch()和changePage()了,在这里,我们设置了一个全局变量canChangePage来判断当前换页的操作是否可以执行,如果动画正在播放中,我们不能进行页面的切换操作。由于iOS和某些Android特定版本的限制,需要有实际的点击才能触发音频的播放,所以我们在onTouchEnded的时候,去触发音频播放。
说到音频播放,我们有2个选择,第一可以使用引擎自带的cc.audioEngine,其中集成了多种浏览器的兼容播放,可以很省时省力的进行音乐播放,不过这里,由于我们比较在意code的体积,所以直接使用简单粗暴的html标签<audio></audio>以下是我们简单实现的音乐播放,使用一个全局变量musicPlayStatus来控制音频的播放状态,其实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | var initMusic = function () { var audio = getById( "myAudio" ); audio.src = "res/bg.mp3" ; } var playMusic = function (status) { var audio = getById( "myAudio" ); if (status) { if (audio.paused) { audio.play(); musicPlayStatus = true ; } } else { if (!audio.paused) { audio.pause(); musicPlayStatus = false ; } } } |
由于我们不希望音乐在开始加载页面的时候就进行下载,所以我们使用initMusic()这个方法来初始化音乐,并使用playMusic()的方法来控制音乐的播放。
同时我们需要在MainScene加入一个方法,用来监听浏览器进入后台,和从后台进入前台的事件,用来控制音乐的播放状态,引擎已经封装好了对应的事件,我们可以非常方便的去监听这些事件,可以大大减少开发的周期。
1 2 3 4 5 6 7 8 | initHideEvent: function () { cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function () { playMusic( true ); }); cc.eventManager.addCustomListener(cc.game.EVENT_HIDE, function () { playMusic( false ); }); } |
第一个页面的实现
由于我们已经制定好了,页面的规则,所以每个页面我们要做的就以下几点:
实现UI的布局;
实现appear入场动画;
实现disappear出场动画;
控制能否滑动到下一个屏幕的状态;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | initUI: function () { canChangePage = false ; this .accLayer = new cc.Layer(); this .accLayer.anchorX = 0; this .accLayer.anchorY = 0; this .accLayer.x = 0; this .accLayer.y = 0; this .addChild( this .accLayer); this .logo = new cc.Sprite( "#logo.png" ); this .logo.scale = 0; this .logo.setPosition(cc.pAdd(cc.visibleRect.center, cc.p(0, this .logo.height / 3 * 2))); this .addChild( this .logo); this .leftFont = new cc.Sprite( "#font_left.png" ); this .leftFont.setPosition(cc.pAdd(cc.visibleRect.left, cc.p(- this .leftFont.width / 2, - this .leftFont.height / 2))); this .addChild( this .leftFont); this .rightFont = new cc.Sprite( "#font_right.png" ); this .rightFont.setPosition(cc.pAdd(cc.visibleRect.right, cc.p( this .rightFont.width / 2, - this .rightFont.height / 2))); this .addChild( this .rightFont); this .leftUpPic = new cc.Sprite( "#block_left up.png" ); this .leftUpPic.anchorX = 0, this .leftUpPic.anchorY = 1; this .leftUpPic.setPosition(cc.pAdd(cc.visibleRect.topLeft, cc.p(- this .leftUpPic.width, this .leftUpPic.height))); this .accLayer.addChild( this .leftUpPic); this .leftDownPic = new cc.Sprite( "#block_left down.png" ); this .leftDownPic.anchorX = 0, this .leftDownPic.anchorY = 0; this .leftDownPic.setPosition(cc.pAdd(cc.visibleRect.bottomLeft, cc.p(- this .leftDownPic.width + 20, - this .leftDownPic.height))); this .accLayer.addChild( this .leftDownPic); this .rightUpPic = new cc.Sprite( "#block_right up.png" ); this .rightUpPic.anchorX = 1, this .rightUpPic.anchorY = 1; this .rightUpPic.setPosition(cc.pAdd(cc.visibleRect.topRight, cc.p( this .rightUpPic.width, this .rightUpPic.height))); this .accLayer.addChild( this .rightUpPic); this .rightDownPic = new cc.Sprite( "#block_right down.png" ); this .rightDownPic.anchorX = 1, this .rightDownPic.anchorY = 0; this .rightDownPic.setPosition(cc.pAdd(cc.visibleRect.bottomRight, cc.p( this .rightDownPic.width, - this .rightDownPic.height))); this .accLayer.addChild( this .rightDownPic); } |
以上是对第一个页面的布局,从图上我们其实就可以看到,页面由logo、左上、左下、右上、右下、和左右两列文字组成,至于布局我们就不多说了,主要有以下几点。
可以看到我们依然使用相对布局,让每个ui可以适应不同屏幕分辨率
由于有入场动画,所以我们将这些UI都放到屏幕外面了
我们对三角形的物体,放置在了一个独立的Layer -- accLayer,为的是以后如果需要添加陀螺仪的监听,
我们可以很方便的进行修改。
接下来我们要实现的时入场动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | appear: function () { var logoAction = cc.sequence(cc.scaleTo(0.5, 1), cc.callFunc(function () { this .leftUpPic.leftUpAction = cc.moveTo(0.5, cc.p( this .leftUpPic.x + this .leftUpPic.width, this .leftUpPic.y - this .leftUpPic.height + 80)); this .leftUpPic.runAction( this .leftUpPic.leftUpAction); this .leftDownPic.leftDownAction = cc.moveTo(0.5, cc.p( this .leftDownPic.x + this .leftDownPic.width, this .leftDownPic.y + this .leftDownPic.height)); this .leftDownPic.runAction( this .leftDownPic.leftDownAction); this .rightUpPic.rightUpAction = cc.moveTo(0.5, cc.p( this .rightUpPic.x - this .rightUpPic.width - 30, this .rightUpPic.y - this .rightUpPic.height - 30)); this .rightUpPic.runAction( this .rightUpPic.rightUpAction); this .rightDownPic.rightDownAction = cc.moveTo(0.5, cc.p( this .rightDownPic.x - this .rightDownPic.width, this .rightDownPic.y + this .rightDownPic.height)); this .rightDownPic.runAction( this .rightDownPic.rightDownAction); this .leftFont.fontLeftAction = cc.sequence(cc.delayTime(0.3), cc.moveTo(0.5, cc.p( this .leftFont.x + this .leftFont.width, this .leftFont.y))); this .leftFont.runAction( this .leftFont.fontLeftAction); this .rightFont.fontRightAction = cc.sequence(cc.delayTime(0.4), cc.moveTo(0.4, cc.p( this .rightFont.x - this .rightFont.width, this .rightFont.y))); this .rightFont.runAction( this .rightFont.fontRightAction); }, this ), cc.delayTime(1.3), cc.callFunc(function () { canChangePage = true ; }, this )); this .logo.runAction(logoAction); } |
appear的实现方式非常简单,其实就是把几个我们上面写好的UI,移动到我们希望的位置。
logoAction,使用前面介绍过的函数cc.sequence()和cc.scaleTo(),最后在结束的时候调用cc.callFunc()。
在cc.callFunc()中呢,我们对其余的元素一一进行了入场动画,主要使用了cc.moveTo()这个方法,可以看到,我们从屏幕外面,移动到了屏幕里面。
值得注意的是,我们在logoAction的最后,使用cc.delayTime()延时了一段时间,然后再执行canChangePage = true主要是为了在动画播放过程中,不然用户滑动改变页面。
接下来就是disappear方法的实现了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | disappear: function (callback, target) { var action = cc.sequence(cc.scaleTo(0.5, 0), cc.callFunc(function () { this .leftUpPic.runAction( this .leftUpPic.leftUpAction.reverse()); this .leftDownPic.runAction( this .leftDownPic.leftDownAction.reverse()); this .rightUpPic.runAction( this .rightUpPic.rightUpAction.reverse()); this .rightDownPic.runAction( this .rightDownPic.rightDownAction.reverse()); this .leftFont.runAction( this .leftFont.fontLeftAction.reverse()); this .rightFont.runAction( this .rightFont.fontRightAction.reverse()); }, this ), cc.delayTime(0.9), cc.callFunc(function () { if (target && callback) { callback.call(target); } }, this )); this .logo.runAction(action); } |
很简单,我们也是让logo执行了一个action,然后再执行disappear传进来的回调方法就ok了。
这里,我们使用了action.reverse()这个方法,可以很简便的实现大多数action的reverse。
动画播放完毕后,调用回调方法执行我们需要的回调操作。
ok,此时我们第一个页面已经编写完了,赶紧运行下试试吧。接下来,由于第二第四第五个页面跟第一个页面都很类似,我们就不重复讲了,大家看看代码吧,也当做练练手。我们接着重点说说第三个页面的实现。
第三个页面的实现
第三个页面,是区别于其他页面的一个页面,主要原因是其余的页面,我们基本使用的是cc.moveTo()这个方法,配合上cc.delayTime()等,实现一系列的移动,只要多试验几次,我们就可以很清楚这些action的使用方法,各种混搭之后,可以实现出很复杂也很绚丽的效果。
不过接下来我们将介绍一个之前没有用过的接口cc.progressTimer()来快速实现我们的一条往下滑动的效果。
cc.progressTimer() 多数情况用于使用某个sprite进行计时,倒计时操作,如下图,我们可以很清楚的看到他的效果,我们使用这个效果,可以很容易的实现出那个功能。
我们需要准备一张图片,如下图,然后使用progressTimer,就可以很容易实现这个功能了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | initUI: function () { ... this .line = new cc.ProgressTimer( new cc.Sprite(res.line_png)); this .line.type = cc.ProgressTimer.TYPE_BAR; this .line.midPoint = cc.p(0, 1); this .line.barChangeRate = cc.p(0, 1); this .line.percentage = 0; this .line.setPosition(cc.p(cc.winSize.width / 2 - 30, cc.winSize.height - this .line.height / 2)); this .addChild( this .line); ... }, appear: function () { //20,50,80,100 this .leftUpPic.inAciton = cc.moveTo(0.2, cc.p( this .leftUpPic.x + this .leftUpPic.width + 20, this .leftUpPic.y - this .leftUpPic.height + 20)); this .leftUpPic.runAction( this .leftUpPic.inAciton); this .leftDownPic.inAciton = cc.moveTo(0.2, cc.p( this .leftDownPic.x + this .leftDownPic.width + 20, this .leftDownPic.y + this .leftDownPic.height + 20)); this .leftDownPic.runAction( this .leftDownPic.inAciton); this .rightUpPic.inAciton = cc.moveTo(0.2, cc.p( this .rightUpPic.x - this .rightUpPic.width - 30, this .rightUpPic.y - this .rightUpPic.height - 30)); this .rightUpPic.runAction( this .rightUpPic.inAciton); this .rightDownPic.inAciton = cc.moveTo(0.2, cc.p( this .rightDownPic.x - this .rightDownPic.width - 20, this .rightDownPic.y + this .rightDownPic.height + 80)); this .rightDownPic.runAction( this .rightDownPic.inAciton); var action = cc.sequence(cc.delayTime(0.2), cc.progressTo(0.3, 20), cc.callFunc(function () { this .firstPoint.inAction = cc.scaleTo(0.2, 1); this .firstPoint.runAction( this .firstPoint.inAction); this .firstText.inAction = cc.sequence(cc.delayTime(0.2), cc.spawn(cc.moveTo(0.2, cc.p( this .firstText.x - this .firstText.width / 2, this .firstText.y)), cc.fadeIn(0.5))); this .firstText.runAction( this .firstText.inAction); }, this ), cc.delayTime(0.7), cc.progressTo(0.3, 50), cc.callFunc(function () { this .secondPoint.inAction = cc.scaleTo(0.2, 1); this .secondPoint.runAction( this .secondPoint.inAction); this .secondText.inAction = cc.sequence(cc.delayTime(0.2), cc.spawn(cc.moveTo(0.2, cc.p( this .secondText.x + this .secondText.width / 2, this .secondText.y)), cc.fadeIn(0.5))); this .secondText.runAction( this .secondText.inAction); }, this ), cc.delayTime(0.7), cc.progressTo(0.3, 80), cc.callFunc(function () { this .thirdPoint.inAction = cc.scaleTo(0.2, 1); this .thirdPoint.runAction( this .thirdPoint.inAction); this .thirdText.inAction = cc.sequence(cc.delayTime(0.2), cc.spawn(cc.moveTo(0.2, cc.p( this .thirdText.x - this .thirdText.width / 2, this .thirdText.y)), cc.fadeIn(0.5))); this .thirdText.runAction( this .thirdText.inAction); }, this ), cc.delayTime(0.7), cc.progressTo(0.3, 100), cc.callFunc(function () { this .forthPoint.inAction = cc.scaleTo(0.2, 1); this .forthPoint.runAction( this .forthPoint.inAction); canChangePage = true ; }, this )); this .line.runAction(action); } |
上面的代码看似很多,不过,只要分清楚我们的目的,那就很清晰了(ps:initUI只贴出了一部分跟progressTimer相关的代码,具体代码请直接看源码吧)。
我们重点介绍一下cc.progressTimer()
他跟所有Cocos2d-JS的API一样,使用new来创建对象
progressTimer的type参数用于设置类型,这里我们使用cc.ProgressTimer.TYPE_BAR也就是条状的progress
midPoint参数,用于设置开始progress的起始位置,有点类似于锚点传入一个cc.p()x y范围都是0到1
barchangeRate就是每次dt更新的百分比
顺便介绍一下cc.progressTo(duration,percent):主要用于cc.progressTimer当中,从在duration的时间内,
从当前的百分比增加到percent。
介绍了基础的用法之后呢,我们对cc.progressTimer()有了一定的了解,接下来我们主要使用cc.progressTimer里面的cc.progressTo()这个方法去实现第三个场景的动画,经过对图片的测量,我们选定了20%,50%,80%,100%进行分段的价值,所以我们通过cc.sequence(cc.progressTo(),cc.callFunc(),cc.delayTime(),.....)这样一组循环,去分段加载那个图片。
到这里为止,我们比较详细的介绍了Cocos2d-JS一些常用的action,并且用他,实现了一个比较美观的动画,大家有兴趣可以动手亲自去试试。