原文: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实现的硬皮翻书效果相关推荐

  1. winform实现翻书效果_如何用PPT实现翻书效果?

    在PPT中,我们需要配合内容使用不同的动画效果,加深观众对内容的理解.当内容是讲述故事.过场片段,或是老师的课件讲解教材时,有一个实用好看的动画效果,就是"翻书效果"动画. 例如上 ...

  2. 自定义控件android特效,Android自定义控件eBook实现翻书效果实例详解

    本文实例讲述了Android自定义控件eBook实现翻书效果的方法.分享给大家供大家参考,具体如下: 效果图: Book.java文件: package com.book; import androi ...

  3. html5实现3d翻页效果,利用css3 3d transform制作超逼真翻书效果

    本教程给大家带来一个非常有创意的翻书效果,使用的是css 3D transforms属性和css transitions属性.这里将给你展示两种不同的图书设计:精装书和平装书.这两种设计只需要简单的改 ...

  4. settimeout怎么用_怎么实现一个3d翻书效果

    本篇主要讨论以下两种翻书动画的实现: 第一种是整页翻转的效果: 这种整页翻转的效果主要是做rotateY的动画,并结合一些CSS的3d属性实现. 第二种折线翻转的效果,如下图所示: 主要是通过计算页面 ...

  5. 前端学习(284):纯css实现翻书效果

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" ...

  6. css3书页翻转,CSS3实现3D翻书效果

    先上效果图:(样式有点丑,可以忽略一下下,效果出来了就好,后期加到其他项目中方便更改0.0) 类似翻书效果,原本的意思是使用JS来控制的,点击一次之后使用setInterval去控制书页翻过去的动画, ...

  7. 3d饼图 vue_怎么实现一个3d翻书效果

    本篇主要讨论以下两种翻书动画的实现: 第一种是整页翻转的效果: 这种整页翻转的效果主要是做rotateY的动画,并结合一些CSS的3d属性实现. 第二种折线翻转的效果,如下图所示: 主要是通过计算页面 ...

  8. cocos2dx实现翻书效果。

    因为项目需求,需要使用cocos实现3d翻书的效果,刚开始确实没有什么思路,cocos2d做3d的效果这不是开玩笑吗.但是,再难也得做啊,没办法. 开始查资料,在百度,google上搜索了好几天,基本 ...

  9. JS实现图片翻书效果

    <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...

最新文章

  1. perl 如何更新_Perl 进度条模块
  2. 任正非最新讲话:华为专家队伍怎么建?
  3. oracle 表导入到powerDesigner 中
  4. Windows性能分析器概述(三)
  5. 【CC精品教程】任务三:CC刺像控点,提交空三,新建重建项目(三维格网、三维点云、DOM和DSM)
  6. Spark 常见问题小结
  7. 保险报业携手万丈金数 探索大数据应用升级
  8. [解决] HiveServer2中使用jdbc访问hbase时导致ZooKeeper连接持续增加的解决
  9. Linux命令常用的快捷键
  10. 拿到参考资料的预训练模型,太可怕了!
  11. 如何定义适配器adapter类_【设计模式】第六篇:来康康适配器模式
  12. 项目经验:某大厂大数据项目总结
  13. 音频传输中的I2S协议
  14. 如何利用卡诺云系统管理早教机构?昆明收银系统还有此妙用!
  15. php阴阳万年历转换的接口,PHP编程实现阳历转换为阴历的方法实例
  16. MachineLearning in Action (机器学习实战)源码和数据集下载地址
  17. Java 给Word指定字符串添加批注
  18. 工程力学(17)—应力状态和强度理论
  19. 从小白开始自学数据结构——第十二天【图及其基本概念和邻接表的定义】
  20. QT报错:error dependent 'xxx' does not exist.

热门文章

  1. JavaScript中如何自定义属性操作
  2. if/else if多分支语句(JS)
  3. 用过的人都知道,AWT_Swing_多选框功能可是很好用啊
  4. mysql 键缓冲区_mysql:键缓存
  5. java语句电脑定时关机_月光软件站 - 编程文档 - Java - windows定时关机程序
  6. Vue.js实现可配置的登录表单
  7. FineUIPro控件库深度解析
  8. 在Lotus Notes设置邮件转发
  9. Step by Step 创建一个WCF Service
  10. angular路由模块(二)