News:Starling PageFlip
作者:郭少瑞
更新(2012-12-31):
https://github.com/NeoGuo/StarlingPageFlip
在今年的最后一天,这个效果终于更新为软纸翻页的版本(封面和封底仍然是硬纸),演示地址: http://www.todoair.com/demo/book/StarlingBook2.html
硬纸到软纸的最大困难,在于拖拽中的几何算法和纹理拼接(因为Starling还未提供遮罩,所以不能按照遮罩的思路做,只能根据顶点拼接纹理)。几何算法是从天地会的这篇帖子中获取的,感谢这位作者。拼接则相对简单,显示中如果遇到五边形,则用一个三角形和一个四边形拼接。通过合理控制每个多边形的顶点坐标和纹理UV坐标,可以模拟实现原来遮罩才能做的效果。
在iPad一代上进行性能测试,稳定在60FPS左右,视频演示: http://v.youku.com/v_show/id_XNDk1NzI2MTM2.html
源码下载:
您可以从这里下载完整项目和源码:点击这里下载
Flash的翻书效果想必大家都看到过很多,不过基于Stage3D的版本似乎还很难找到(也可能是我孤陋寡闻了,如果您知道的话欢迎补充)。现在很多项目已经开始使用Stage3D(或基于Stage3D的衍生框架比如Starling)来制作了,有时候也需要将原有传统Flash的效果移植到Stage3D层面来实现,这样融合更方便,性能上也可能更强大一些。
下面是一个基于Starling的翻书效果实现,当然还很简陋,只实现了硬皮翻页,软纸翻页还没有实现。不过我觉得在这个例子上进行一下扩展,用图片纹理坐标和顶点控制的方式,是可以实现出原来遮罩才能实现的效果的(传统Flash翻书效果大都使用遮罩),所以这个例子权作抛砖引玉,期待能有更为完善的例子出来。
效果演示
您可以点击下面的地址,查看这个Demo的实现效果:http://www.todoair.com/demo/book/StarlingBook.html
Sorry,因为没写Loading,文件尺寸较大,需要您耐心等待一会儿。
实现过程
为了尽可能利用Starling的批处理优势来提升性能,所以素材采用了TextureAtlas的方式,将所有页的图片集中到一张大图上(当然这个地方也有缺点,如果一张图放不下的话,就要分几张图存放,并修改获取素材的方式,以便从不同的源中获得纹理)。在渲染处理上,使用QuadBatch来代替普通的显示对象叠加,来尽量提升性能。在笔者的电脑上,这个例子可以稳定在60FPS运行。
核心是PageFlipContainer这个类,这个类使用一个QuadBatch实例来更新显示,并侦听Touch事件来启动翻页过程。具体代码如下:
package test.pf { import flash.display.Bitmap; import flash.geom.Point; import starling.display.Image; import starling.display.QuadBatch; import starling.display.Sprite; import starling.events.Event; import starling.events.Touch; import starling.events.TouchEvent; import starling.events.TouchPhase; import starling.textures.Texture; import starling.textures.TextureAtlas; import test.TrangleImage; /** * 基于Starling的翻页组件 * @author shaorui */ public class PageFlipContainer extends Sprite { /**包含内页的图集*/ private var altas:TextureAtlas; /**书的宽度*/ private var bookWidth:Number; /**书的高度*/ private var bookHeight:Number; /**书的总页数*/ private var bookCount:Number; /**批处理显示*/ private var quadBatch:QuadBatch; /**左侧显示页面页码*/ private var leftPageNum:int = -1; /**右侧显示页面页码*/ private var rightPageNum:int = 0; /**翻动中的页面编码(正面,反面为+1)*/ private var flipingPageNum:int = -1; /**正在翻页的位置(-1到1),由程序控制,外部无须调用*/ public var flipingPageLocation:Number = -1; /**是否需要更新*/ private var needUpdate:Boolean = true; /**@private*/ public function PageFlipContainer(altas:TextureAtlas,bookWidth:Number,bookHeight:Number,bookCount:Number) { super(); this.altas = altas; this.bookWidth = bookWidth; this.bookHeight = bookHeight; this.bookCount = bookCount; initPage(); } /**初始化页*/ private function initPage():void { quadBatch = new QuadBatch(); addChild(quadBatch); textures = altas.getTextures(); cacheImage = new Image(textures[0]); flipImage = new ImagePage(textures[0]); addEventListener(Event.ENTER_FRAME,enterFrameHandler); addEventListener(Event.ADDED_TO_STAGE,firstFrameInit); addEventListener(TouchEvent.TOUCH,onTouchHandler); } /**显示的时候初始化第一个画面*/ private function firstFrameInit():void { removeEventListener(Event.ADDED_TO_STAGE,firstFrameInit); enterFrameHandler(); needUpdate = false; } /**用于缓存纹理的图片*/ private var cacheImage:Image; /**翻动的图片*/ private var flipImage:ImagePage; /**缓存的纹理数组*/ private var textures:Vector.<Texture>; /**每帧调用*/ private function enterFrameHandler(event:Event=null):void { if(stage == null || !needUpdate) return; quadBatch.reset(); if(flipingPageNum >= 0) { leftPageNum = flipingPageNum - 1; rightPageNum = flipingPageNum + 2; } //选择左侧的页面 if(validatePageNumber(leftPageNum)) { cacheImage.x = 0; cacheImage.texture = textures[leftPageNum]; quadBatch.addImage(cacheImage); } //渲染右侧的页面 if(validatePageNumber(rightPageNum)) { cacheImage.x = bookWidth/2; cacheImage.texture = textures[rightPageNum]; quadBatch.addImage(cacheImage); } //渲染正在翻转的页面 if(validatePageNumber(flipingPageNum)) { if(flipingPageLocation>=0) flipImage = new ImagePage(textures[flipingPageNum]); else flipImage = new ImagePage(textures[flipingPageNum+1]); flipImage.setLocation(flipingPageLocation); quadBatch.addImage(flipImage); flipImage.dispose(); } } /**是否处于拖动状态*/ private var isDraging:Boolean = false; /**触碰处理*/ private function onTouchHandler(event:TouchEvent):void { var touch:Touch = event.getTouch(this); if(touch != null && (touch.phase == TouchPhase.BEGAN || touch.phase == TouchPhase.MOVED || touch.phase == TouchPhase.ENDED)) { var point:Point = touch.getLocation(this); var imgWidth:Number = bookWidth/2; if(touch.phase == TouchPhase.BEGAN) { isDraging = true; if(point.x >= imgWidth) { if(validatePageNumber(rightPageNum)) { flipingPageNum = rightPageNum; } } else { if(validatePageNumber(leftPageNum)) { flipingPageNum = leftPageNum-1; } } } else if(touch.phase == TouchPhase.MOVED) { if(isDraging) { flipingPageLocation = (point.x-imgWidth)/imgWidth; if(flipingPageLocation > 1) flipingPageLocation = 1; if(flipingPageLocation < -1) flipingPageLocation = -1; validateNow(); } } else { isDraging = false; finishTouchByMotion(point.x); } } else { needUpdate = false; } } /**触控结束后,完成翻页过程*/ private function finishTouchByMotion(endX:Number):void { var imgWidth:Number = bookWidth/2; needUpdate = true; touchable = false; addEventListener(Event.ENTER_FRAME,executeMotion); function executeMotion(event:Event):void { if(endX >= imgWidth) { flipingPageLocation += 0.04; if(flipingPageLocation >= 1) { flipingPageLocation = 1; removeEventListener(Event.ENTER_FRAME,executeMotion); tweenCompleteHandler(); } } else { flipingPageLocation -= 0.04; if(flipingPageLocation <= -1) { flipingPageLocation = -1; removeEventListener(Event.ENTER_FRAME,executeMotion); tweenCompleteHandler(); } } } } /**动画执行完毕后的重置*/ private function tweenCompleteHandler():void { if(flipingPageLocation == 1) { leftPageNum = flipingPageNum-1; rightPageNum = flipingPageNum; } else { leftPageNum = flipingPageNum+1; rightPageNum = flipingPageNum+2; } flipingPageNum = -1; validateNow(); touchable = true; } /**验证某个页面是否合法*/ private function validatePageNumber(pageNum:int):Boolean { if(pageNum >= 0 && pageNum < bookCount) return true; else return false; } /**当前页码*/ public function get pageNumber():int { if(leftPageNum >= 0) return leftPageNum; else return rightPageNum; } /**强制更新一次显示*/ public function validateNow():void { needUpdate = true; enterFrameHandler(); needUpdate = false; } /**跳页*/ public function gotoPage(pn:int):void { if(pn < 0) pn = 0; if(pn >= bookCount) pn = bookCount-1; if(pn == 0) { leftPageNum = -1; rightPageNum = 0; } else if(pn == bookCount-1) { leftPageNum = pn; rightPageNum = -1; } else { if(pn%2==0) pn = pn - 1; leftPageNum = pn; rightPageNum = pn+1; } flipingPageNum = -1; validateNow(); } } }
使用方式:
/**初始化*/ private function initGame(event:Event):void { /*----------------------翻页组件-----------------------*/ //把图片合集到一起,减少DRW值 var bookImgs:Bitmap = new bookImgClass(); var xml:XML = XML(new bookXml()); //这个工具可以给图片加上阴影,提升显示效果 ShadowUtil.addShadow(bookImgs,xml); var texture:Texture = Texture.fromBitmap(bookImgs); var atlas:TextureAtlas = new TextureAtlas(texture,xml); //创建一个翻页容器,设置纹理,书的尺寸和总页数 pageFlipContainer = new PageFlipContainer(atlas,800,480,8); pageFlipContainer.x = 100; pageFlipContainer.y = 100; addChild(pageFlipContainer); //创建一个按钮控制翻页 var btn:Button = new Button(Texture.fromBitmap(new btnImgClass() as Bitmap),"下一页"); btn.x = 100; btn.y = 600; btn.addEventListener(TouchEvent.TOUCH,btnTouchHandler); addChild(btn); } /**翻页*/ private function btnTouchHandler(event:TouchEvent):void { var touch:Touch = event.getTouch(event.target as DisplayObject); if(touch != null && touch.phase == TouchPhase.ENDED) { var pn:int = pageFlipContainer.pageNumber+1; if( pn%2==0 ) pn+=1; if( pn >= 8 ) pn = 0; pageFlipContainer.gotoPage(pn); } }
下载
您可以从这里下载完整项目和源码:点击这里下载