作者:郭少瑞
———————————————————————————————————————–
更新(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/