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

来源:互联网 发布:贵金属操盘软件 编辑:程序博客网 时间:2024/04/20 23:29

许多类型的游戏,用户体验都依赖于终端可拥有的屏幕像素和移动物体有多快。当让大量的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 beta
  • Flash Player 9 以上版本
  • 示例文件:actionscript_blitting.zip (ZIP, 185 KB)


预备知识
     本文假定你已熟悉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


图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


图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 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游戏。当他不编程时,喜欢玩游戏,户外运动,吉普车,和陪着家人。