News:Starling PageFlip

来自Starling中文站
跳转到: 导航, 搜索

作者:郭少瑞


更新(2012-12-31):

https://github.com/NeoGuo/StarlingPageFlip

在今年的最后一天,这个效果终于更新为软纸翻页的版本(封面和封底仍然是硬纸),演示地址: http://www.todoair.com/demo/book/StarlingBook2.html

硬纸到软纸的最大困难,在于拖拽中的几何算法和纹理拼接(因为Starling还未提供遮罩,所以不能按照遮罩的思路做,只能根据顶点拼接纹理)。几何算法是从天地会的这篇帖子中获取的,感谢这位作者。拼接则相对简单,显示中如果遇到五边形,则用一个三角形和一个四边形拼接。通过合理控制每个多边形的顶点坐标和纹理UV坐标,可以模拟实现原来遮罩才能做的效果。

Starling-book-soft-demo.png

在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);
	}
}

下载

您可以从这里下载完整项目和源码:点击这里下载

个人工具
名字空间

变换
操作
导航
Starling中文资料
Starling原创教程
论坛
友链
工具箱