Starling实现的硬皮翻书效果

作者:郭少瑞

———————————————————————————————————————–

更新(2012-12-31):

在今年的最后一天,这个效果终于更新为软纸翻页的版本(封面和封底仍然是硬纸),演示地址:
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事件来启动翻页过程。具体代码如下:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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.;
         /**每帧调用*/
         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 = 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();
         }
     }
}

使用方式:

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
/**初始化*/
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);
     }
}

下载

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

参考:

http://pageflip.hu/

你可能感兴趣的:(starling)