利用AS3块传输技术呈现游戏元素

许多类型的游戏 ,用户体验都依赖于终端可拥有的屏幕像素和移动物体有多快。当让大量的DisplayObject对象动起来时,如MovieClip或Sprite对象,Adobe Flash Player可能在表现上会大大折扣。Flash Player必须遍历显示对象树并为每个基于向量的DisplayObject计算渲染输出,这样会消耗CPU周期成为真正的瓶颈,尤其是低端机。
许多屏幕动画 游 戏都是预先载入一张位图,这种技术被称为blitting(块传输)。块传输虽不能解决所有性能问题,但它能使动画运行平滑,统一大多数机器上动画帧频。 Blitting术语来自于BitBLT为Xerox Alto计算机日常中创建。BitBLT发音为“bit blit”,主张bit-block (image) transfer图像基于位块传输,是一种采用数张位图并合并成一张位图的技术。在Flash Player中复制位图像素到一张渲染图中比分别地渲染每个DisplayObject更为快速。
在本文中,我描述了软件位块传输技术并提供示例代码 ,因此你可以在ActionScript中应用 它。
要求
为了满足本文条件,你需要下列软件和文件


预备知识
本文假定你已熟悉Flash Builder 4 和 ActionScript 3项目工作的知识。
介绍sprite sheet
一个游戏由许多图形元件组成,例如赛道上的车或森林中的一棵树。在本文中,这些元件都是位图。一组位图放在一个单独的图像文件中称为sprite sheet。例如,一个sprite sheet可能包含一个角色行走动画的所有帧。这个词衍生于sprite,在计算机图形世界中,指的是一张图像或一个大场景中集成的动画。虽然位块传输技 术可以使用不同来源的位图数据 ,但在本文中重点放在sprite sheet。
一个sprite sheet由什么构成?
一个sprite sheet可以对不同大小的位图进行组合。将所有图形元素组装到一个大的图像文件中会减少加载 时 间(打开和读取一个包含100帧的较大文件比打开读取100个小文件更为快速)并提供压缩的好处。特别是sprite sheet持有同样大小形成一个序列的位图或围绕一个特定游戏元素动画。例如,本文中使用的sprite sheet有五列四行,每格40*40像素,每个都包含着一个brown collector褐色元素(见图1)。
fig01.jpg

下载 (14.89 KB)
2010-3-1 22:03


图1 sprite sheet示例显示了20个单元格,每格为40*40像素

设置ActionScript项目
在你运行示例代码前,你将需要在Flash Builder 4按照以下步骤设置项目。
1. 下载并解压示例文件
2. 选择File > New > ActionScript Project来创建项目
3. 输入ActionScriptBlitting作为项目名称并点击Finish
4. 从示例文件中复制下列文件和文件到项目默认包中:ActionScriptBlittingPart1.as, ActionScriptBlittingPart2.as, ActionScriptBlittingPart3.as, ActionScriptBlittingPart4.as和spritesheets。Spritesheets文件夹中包含了用于 ActionScript示例的PNG文件。
5. 在Package Explorer中,右键点击ActionScriptBlitting项目在弹出菜单上选择Properties。
6. 在Properties属性对话框中点击ActionScript Applications。
7. 点击Add,选择ActionScriptBlittingPart1.as,并点击OK。
8. 将ActionScriptBlittingPart2.as,ActionScriptBlittingPart3.as, 和 ActionScriptBlittingPart4.as重复第7步。
9. 点击OK
现在Flash Builder 4中示例代码就设置好了,你就可以运行该示例。

用ActionScript嵌入一个sprite sheet
在ActionScript中你可以通过使用Embed 元数据标签嵌入图像。(更多信息请查看Embedding metadata with Flash )一旦它们被嵌入,你可创建类的实例并附加到显示列表,如ActionScriptBlittingPart1.as:

ActionScriptBlittingPart1.as

package
{
import flash.display.Sprite;
[SWF(width=480, height=320, frameRate=24, backgroundColor=0xE2E2E2)]
public class ActionScriptBlittingPart1 extends Sprite
{
public function ActionScriptBlittingPart1()
{
addChild(new BrownCollector());
}
[Embed(source="spritesheets/browncollector.png")]
public var BrownCollector:Class;
}
}

要运行第一个示例,请参照下列步骤:
 在Package Explorer中,在ActionScriptBlittingPart1.as文件上点击右键选择Run Application。
 当浏览器打开时,你会看到所有单元格都有PNG图像(browncollector.png),效果 见图1.
 关闭浏览器窗口。
Blitting a sprite sheet
第二步,你将要用到Flash Player APIBitmapBitmapData ,从sprite sheet复制一个单元格(或帧)到屏幕上,而这个操作可以通过BitmapData.copyPixels()方法来完成,复制输入位图数据的像素到正在制作 的位图实例上。在ActionScript中纳入blitting,copyPixels()方法还提供参数来定义要复制输入位图的区域以及如何定义和合并alpha像素。

ActionScriptBlittingPart2.as

package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.geom.Point;
import flash.geom.Rectangle;
[SWF(width=480, height=320, frameRate=24, backgroundColor=0xE2E2E2)]
public class ActionScriptBlittingPart2 extends Sprite
{
public function ActionScriptBlittingPart2()
{
// Create input bitmap instance
spritesheet = (new BrownCollector() as Bitmap).bitmapData;

// Add a Bitmap to the display list that will copyPixels() to.
canvas = new BitmapData(480, 320, true, 0xFFFFFF);
addChild(new Bitmap(canvas));
rect = new Rectangle(0, 0, 40,40); // 1st Tile
//** Section 1 ** //
// rect = new Rectangle(40, 0, 40, 40); // 2nd Tile
// rect = new Rectangle(80, 0, 40, 40); // 3rd Tile
// ...
// rect = new Rectangle(160, 120, 40, 40); // 20th Tile
canvas.copyPixels(spritesheet, rect, new Point(0, 0));
//** END Section 1 **/

/** Section 2 ** //
for (var i:int = 0; i < 20; i++)
{
rect.x = (i % 5) * 40;
rect.y = int(i / 5) * 40;
canvas.copyPixels(spritesheet, rect, new Point(i*10, 0));
// Section 3:
// canvas.copyPixels(spritesheet, rect,
// new Point(i*10, 0), null, null, true);
}
//** END Section 2 **/
}

[Embed(source="spritesheets/browncollector.png")]
public var BrownCollector:Class;
public var canvas:BitmapData;
public var spritesheet:BitmapData;
public var rect:Rectangle;
}
}

在Package Explorer中的ActionScriptBlittingPart2.as文件上点击右键并选择Run Application。你将会看到来自sprite sheet的第一个单无格显示在浏览器中(见图2)。
fig02.jpg

下载 (1.56 KB)
2010-3-1 22:11


图2 ActionScriptBlittingPart2输出(利用Section 1代码)

显示所有单元格
现在你绘制了1个单元格了,为什么不是绘制所有单元格呢?若要绘制所有,参照以下步骤:
1. 在Flash Builder 4中打开ActionScriptBlittingPart2.as
2. 找到标为“Section 1”的代码处并注释掉它。
3. 找到标为“Section 2”的代码处取消注释。
4. 保存文件
5. 再次运行ActionScriptBlittingPart2.as
Section 2的代码使用了循环来绘制每格较之前的单元格效果水平偏移10像素。
fig03.jpg

下载 (2.71 KB)
2010-3-1 22:15


图3 ActionScriptBlittingPart2输出(利用Section 2代码)
不过,看起来结果并不完全正确,而这正是因为BitmapData.copyPixels()的alpha参数导致的,最后三个参数 (alphaBitmapData, alphaPoint, 和 mergeAlpha)提供了不同地处理透明区的方式。由于sprite sheet中的PNG图片已处理过alpha数据,就不再需要alphaBitMapData 或 alphaPoint了。你只需要设置最后一个参数来打开alpha合成,mergeAlpha为true。
确保改变:
1. 注释掉Section 2中调用copyPixels()处:

canvas.copyPixels(spritesheet, rect, new Point(i*10, 0));

2. 取消注释Section 3中调用copyPixels()处:

canvas.copyPixels(spritesheet, rect, new Point(i*10, 0), null, null, true);

3. 保存该文件。
4. 再次运行ActionScriptBlittingPart2.as。你会看到图像有序地重叠(见图4)
fig04.jpg

下载 (4.26 KB)
2010-3-1 22:18


图4 ActionScriptBlittingPart2输出(利用Section 3代码)
让sprite sheet动起来
现在你已知道如何显示来自sprite sheet的位图数据,接下来就是让它动起来。ActionScriptBlittingPart3.as中的代码赋予brown collector图像活力,可将它移动到舞台上鼠标点击处。
在Flash Player中利用计时器创建一个平滑的动画
基本思想是使用一个可以基于Flash Player帧的timer计时器,或两者结合的计时器。一个典型的方法是结合使用ENTER_FRAME事件和调用getTimer()来控制各种电脑 环境中动画的速度。下面的代码摘录自ActionScriptBlittingPart3.as(行62):

/**
* Handles the timer
*/
private function enterFrameHandler(event:Event):void
{
tickPosition = int((getTimer() % 1000) / framePeriod);

if (tickLastPosition != tickPosition)
{
tickLastPosition = tickPosition;
canvas.lock();
canvas.fillRect(canvasClearRect, 0x000000);
render();
canvas.unlock();
}
}

enterFrameHandler()方法处理ENTER_FRAME事件。该代码确定了SWF启动之后经过的秒数,这一数字除以framePeriod,期间游戏设计者想要呈现动画,如果这个值不同于上一次渲染帧的事件,清除画布上的内容,动画被重新渲染。
在Flash Player中定时(有时描述为elastic race track)可以改变,触发ENTER_FRAME事件可以在不同的机器上有显著地变化。结合这个事件和定时检测可以使动画在一致频率时平滑,即使是一台 机器运行速度远比SWF帧频快。同样如果该SWF帧频增加,动画帧频可以维持在一个一致较低的频率,使CPU能够处理其它逻辑而无需渲染每一帧。无论哪种 方式,你都需要找出渲染一致性和CPU利用率之间的平衡。如果你把帧频设置过高,低端机器可能无法处理。
当你运行ActionScriptBlittingPart3.as程序时,你会看到一个brown collector围绕着圆圈旋转。
Moving the animation
Flash Player MouseEvent.MOUSE_UP 事件提供了鼠标在舞台上点击的x和y的坐标值。将鼠标点击处坐标一直作为collector的当前x和y位置,你可以在每次画布渲染时移动图像到不同的位 置处。这给予了用户移动它的能力。下面的代码是从ActionScript类ActionScriptBlittingPart3.as(79行)摘录了 如何用blitting来移动动画:

/**
* Render any bitmap data.
*/
private function render():void
{
rect.x = (currentTile % 5) * 40;
rect.y = int(currentTile / 5) * 40;
collectorX += (destX-collectorX-20)/5;
collectorY += (destY-collectorY-30)/5;
canvas.copyPixels(spritesheet, rect,
new Point(collectorX, collectorY), null, null, true);
currentTile = ++currentTile % 20;
}

/**
* Used to move the animation around.
*/
private function mouseUpHandler(event:MouseEvent):void
{
destX = event.stageX;
destY = event.stageY;
}
}

mouseUpHandler()方法存储了鼠标点击处的x和y坐标值作为目标地址。之后的render()方法确定了一个在collector与目标位 置之间非线性运动,记录点击的全局坐标作为新位置,然后将它提供给copyPixels()方法作为blitting的位置。
如果动画运行时在舞台中点击,该collector会朝着点击位置处移动。
合并多张sprite sheet动画
最后示例在一个单独的Stage上整合了多个sprite sheet。在上一个示例中,为了移动动画你需要存储位图数据放置的信息。当合并动画,你将需要保持更多位置的跟踪。除了位置,你可能需要维护并处理深度 (它决定了当两个位图重叠时会显示哪一个),变化动画状态,不同的动画帧频,碰撞检测等等。
在ActionScriptBlittingPart4.as中,collector深度最低。随机创建地彩色凝胶从屏幕顶部下降。如果一个凝胶碰撞到collector,将会运行第三种动画——凝胶在collector上熔化。
深度
在Flash的中你可能曾创建过动画,如在舞台上放置多个对象。你可能还使用过mx.effects包来移动或旋转对象。如果对象重叠,其z- index(在显示列表中的深度)决定了对象叠放顺序。虽然位块传输,只有一个对象要显示:目标位置。由于正在处理渲染,那么你也将需要保持对象深度跟踪 并确保一切都按正确的顺序进行复制。
为了保持彩色凝胶从collector的顶部开始动画,你必须管理blitting的秩序。在ActionScriptBlittingPart4.as 中的render()方法,collector已经被混合(行110)后完成所有凝胶块传输(行138和144)
创建凝胶与其元数据
每个彩色凝胶创建于enterFrameHandler()方法中。当一个彩色凝胶被创建,在createGel()方法中会设置其初始化属性包括一个随 机x,y为0的位置,默认状态,meltFrame为零,一个唯一名称。该凝胶随后被保存到gels中,render()方法中会遍历这个 Dictionary实例。

/**
* Create a gel
*/
private function createGel():void
{
var gel:Object = new Object();
gel.posX = ((Math.random() * 0xffffff) % 280) + 20
gel.posY = 0;
gel.state = "animate";
gel.meltFrame = 0;
gel.name = "gel" + gelCount++;
gels[gel.name] = gel;
}

凝胶的逻辑与渲染状态
render()方法移动彩色凝胶的逻辑并更改熔解状态。

/**
* Render any bitmap data.
*/
private function render():void
{
rect.x = (currentTile % 5) * 40;
rect.y = int(currentTile / 5) * 40;
collectorX += (destX-collectorX-20)/5;
collectorY += (destY-collectorY-30)/5;
canvas.copyPixels(spritesheetCollector, rect,
new Point(collectorX, collectorY), null, null, true);
// Render Gel at half the frame rate, to slow it down
if (currentTile % 2 == 1)
{
rect.x = ((currentTile-1) % 5) * 40;
rect.y = int((currentTile-1) / 5) * 40;
}
for each (var gel:Object in gels)
{
// Hit Check 5 px within Y and X
if (Math.abs(gel.posY - collectorY + 6) < 14
&& Math.abs(gel.posX - collectorX) < 10)
{
gel.state = "melt";
}
if (gel.state == "melt")
{
// Clear out if done melting
if (gel.meltFrame < 20)
gel.meltFrame++;
else
{
delete gels[gel.name];
continue;
}
rect.x = (gel.meltFrame % 5) * 40;
rect.y = int(gel.meltFrame / 5) * 40;
canvas.copyPixels(spritesheetGelMelt, rect, new Point(collectorX-1, collectorY-12), null, null, true);
continue;
}
else
{
canvas.copyPixels(spritesheetGel, rect, new Point(gel.posX, gel.posY), null, null, true);
}

gel.posY += 3;
if (gel.posY > 320)
{
delete gels[gel.name];
continue;
}

}

currentTile = ++currentTile % 20;
}

凝胶动画是collector速度的一半,通过为奇数帧重置rect值为currentTile-1。接下来,在屏幕上代码循环所有彩色凝胶并检测 collector碰撞。如果发生碰撞它会更改状态为“melt”。在这种状态下,render()用彩色凝胶熔解sprite sheet并且其meltFrame总数为20帧时移除。当凝胶与collector的x和y位置发生碰撞时,会出现溶解动画,只要凝胶在移动就会紧随 collector。如果没有发生碰撞,凝胶向下移动3像素,当它的y坐标超出320时将会从舞台上移除。正如你所看到的,利用blitting你必须处 理所有动画的逻辑。

延伸阅读
在本文中你已学会如何创建一个软blitting引擎。你可以使用这些技术在其它Flash Player中渲染情景。你可能想要探索位块传输过程中创建源位图数据的不同方法,例如,转换DisplayObject动画帧到一个位图中并缓存它们到一个数组中。
dieselblittingengine: Blitting engine for Flash Player 9 and later (Google Code)
Blitting and duble-buffering for tile-based games in Flash and Flex: Part 1 of 3 (Jesse Warden)
AS3 Basic Blitting #2 : Rotation - Part 1 (Jeff Fulton)
Basics of tile sheet animation (or blitting) (Jeff Fulton)

关于作者
Renaun Erickson在Adobe Systems里工作,负责ActionScript, Flex和不同地服务器技术开发。他活跃于组织中积极地发言并用博客(renaun.com/blog)记录各个Flash Platform下的子项目,包括视频,音频,logging,和Flash游戏。当他不编程时,喜欢玩游戏,户外运动,吉普车,和陪着家人。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值