在Flash上​​跳帧

时间:2011-10-28 21:29:13

标签: flash actionscript-3

有没有办法在Flash上​​原生启用帧跳过?

当您开发游戏时,您可以开发动画以匹配游戏的速度,并以目标帧速率(通常为Flash的24-40fps)进行。但是如果用户的电脑太慢,并且无法保持目标帧率,Flash会自动降低fps,使应用程序播放缓慢。

如果你使用基于时间的逻辑,基于帧的渲染将与基于时间的逻辑不匹配,事情会很奇怪,并且会有很多极端情况需要解决。

事实上我知道有些游戏使用这种跳帧逻辑,比如Popcap的Zuma Blitz。他们是自己实施跳帧吗?

我不能在项目的这么晚才实现这个,除非我能以某种方式重新实现MovieClip类并使其易于框架跳过。另外,自己控制动画(覆盖Flash原生MovieClip控件)的开销是不是太大了?

6 个答案:

答案 0 :(得分:9)

使用方法是考虑每秒像素的变化,而不是每帧像素。 然后,您可以在EnterFrame上运行主循环。在enterframe中,调用getTimer()以毫秒为单位获取系统时间。并将其与上次运行脚本时的值进行比较。这将让您确切知道自上一帧以来经过的时间量。使用该金额来确定如何移动东西。

以下是一个示例,无论帧速率如何,都会以相同的速率移动圆圈:

{
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.utils.getTimer;

    /**
     * ...
     * @author Zachary Foley
     */
    public class Main extends Sprite 
    {
        private var lastFrame:int = 0;
        private var thisFrame:int;
        private var pixelsPerSecond:Number = 200;
        private var circle:Shape
        private var percentToMove:Number; 
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
            circle = new Shape();
            circle.graphics.beginFill(0xFF0000);
            circle.graphics.drawCircle(0, 0, 25);
            circle.x = 50;
            addChild(circle);
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }

        private function onEnterFrame(e:Event):void 
        {
            // Get the miliseconds since the last frame
            thisFrame = getTimer();
            percentToMove = (thisFrame - lastFrame) / 1000;
            // Save the value for next frame.
            lastFrame = thisFrame;
            // Update your system based on time, not frames.
            circle.x += pixelsPerSecond * percentToMove;

        }

        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
        }

    }

}

如果您发现低帧速率会影响碰撞计算的准确性,请使用计时器单独运行它们。这只是绘图循环,它不可避免地与帧速率相关联。

而且你知道,你不能在flash中跳帧。它由玩家处理。最好的方法是调整帧渲染技术以适应可变帧速率。

答案 1 :(得分:3)

好的,我意识到你不是在寻找以下'解决方案',这将无效,因为所有的帧仍将播放:

stage.frameRate = new_frame_rate;

唯一的解决方案是跳过enterFrame回调中的帧:

// Defined elsewhere.
var current_frame:Number = 0;
var frame_skip_factor:uint; // Skip one frame every 'x' frames.
var is_frame_skip_active:Boolean;

function on_enter_frame(event:Event):void
{
   if ( is_frame_skip_active && current_frame++ % frame_skip_factor == 0) { return; }

   // Game logic/rendering code...
}

(假设,根据最佳实践,您在单个回调中集中了游戏逻辑/动画),但是当运行任何异步回调时,这可能会出现竞争条件/复杂情况。

在运行时级别无法实际跳过帧;这是AVM合同的一部分 - 不跳过任何帧,所有代码都运行,无论如何。如果性能是一个问题,您可以尝试异步代码执行。有几种方法可以做到:

1)将一些计算卸载到Pixel Bender,以便可以异步和/或并行处理(http://www.adobe.com/devnet/flex/articles/flashbuilder4_pixelbender.html)

2)在多个帧上展开冗长操作的执行(需要状态保存和状态恢复,la memento模式)。详情(以及精彩的阅读):http://www.senocular.com/flash/tutorials/asyncoperations/

无论如何,我(首先)建议使用Flash Builder Profiler或Stats类(Doob先生)这样的工具明确地识别性能瓶颈。

Blitting可能是解决方案(也为动画的每个帧创建一个带有tile的spritesheet)。但无论如何,我认为你需要做的是继承MovieClip并覆盖play(),stop()和gotoAndPlay()方法。你的课应该是这样的:

public class MyMovieClip extends MovieClip
{
   override public function play():void
   {
      addFrameSkipListener();

      super.play();
   }

   override public function gotoAndPlay(frame:Object, scene:String = null):void
   {
      addFrameSkipListener();

      super.gotoAndPlay(frame, scene);
   }

   // ....
}

如果需要,帧跳过侦听器将根据当前帧速率或帧时间跳过帧。当然,您还需要在动画结束时删除frameSkipListener。

虽然MovieClip覆盖解决方案可能会在纸上执行您想要的操作,但如果您有很多对象,这实际上可能会降低性能,因为额外的ENTER_FRAME侦听器会增加一些开销。

答案 2 :(得分:2)

你最大的问题是你不是在吹嘘。艺术家仍然可以在时间轴上动画并导出精灵表。我会花一些时间认真地了解基于flash播放器时间轴的动画的性能限制。以下是一些可以帮助您入门的链接:

如您所见,使用gotoAndStop()从时间线控制动画;关于你正在使用的精灵的数量,动画的方法是更糟糕的。

您还可以考虑使用新的molehill 2d图形API,Starling Framework

要直接解决您的问题,我会先讨论已经讨论过的解决方案,在运行时执行一个缓冲渲染来执行精灵表。根据任何时间点所需的精灵数量,您可以将低动画性能换成内存限制。

祝你好运。

答案 3 :(得分:1)

我最近遇到了类似的事情;也许以下方面有所帮助:

  • gotoAndStop(frame)将立即执行第
  • 页的actionscript
  • 您可以在单个ENTER_FRAME事件(或任何其他事件)中执行多个gotoAndStop调用。

作为测试,我在Flash Pro CS5.5中创建了一个新的AS3.0项目。我创建了一个新的MovieClip并将时间轴扩展到23帧。

在第一帧我添加了以下代码:

import flash.events.Event;

// do something every frame
this.addEventListener(Event.ENTER_FRAME, handleEnterFrame);
// let event handler change the playHead
this.stop();

// advance playhead 2 frames every frame
function handleEnterFrame(anEvent: Event): void
{
  trace('* ENTER_FRAME');
  this.gotoNextFrame();
  this.gotoNextFrame();
}

// advance to next frame, show playheads position
function gotoNextFrame(): void
{
  // at last frame?
  if (this.currentFrame < this.totalFrames)
  {
    // no, advance to next frame
    var before: int = this.currentFrame;
    this.gotoAndStop(this.currentFrame + 1);
    trace('before: ' + before + ', current: ' + this.currentFrame);
  }
  else
  {
    // last frame, stop updating the playhead
    this.removeEventListener(Event.ENTER_FRAME, handleEnterFrame);
  }
}

在第5帧,我创建了一个关键帧并添加了代码:

this.gotoAndPlay(10);

在第14帧,我创建了一个关键帧并添加了代码:

this.gotoAndPlay(16);

运行后,我得到了以下跟踪输出:

* ENTER_FRAME
before: 1, current: 2
before: 2, current: 3
* ENTER_FRAME
before: 3, current: 4
before: 4, current: 10
* ENTER_FRAME
before: 11, current: 12
before: 12, current: 13
* ENTER_FRAME
before: 13, current: 16
before: 16, current: 17
* ENTER_FRAME
before: 17, current: 18
before: 18, current: 19
* ENTER_FRAME
before: 19, current: 20
before: 20, current: 21
* ENTER_FRAME
before: 21, current: 22
before: 22, current: 23
* ENTER_FRAME

我不知道上述情况有多么高效或耗时。我只能在一个MovieClip中使用它(在开发的后期阶段,如果主角也可以运行它会很方便,但是没有运行动画的预算;角色在时间线中包含脚步生成脚本以便正确定时)。

在您的情况下,您可以检查自上次ENTER_FRAME以来经过了多少时间,然后提前正确的帧数。

您可以创建MovieClip的子类并将其用作基类(要快速执行此操作,请选择库中需要此项的所有MovieClip,右键单击,属性,调整基类)。对于那些已经为actionscript链接的MovieClip,可以通过从新的MovieClip类扩展来更新.as文件。

答案 4 :(得分:1)

据我所知,AS3中没有任何东西能够以给定的间隔跳过movieClip帧,或者通过时间轴动画比正常情况更快地前进。像Zuma这样的游戏可能要么手动完成,要么在不使用时间轴的情况下开始制作动画。

要手动完成,您有两个主要选项:

时间轴选项: (可能不值得,原因如下所述)如果您真的与时间轴动画完全相关,改变帧速率是一个大问题,你没有时间或技术自由来用代码重做你的动画,你可以使用Event.ENTER_FRAME监听器从每个调用一个函数要检查stage.frameRate的movieClip帧,如果frameRate已经下降,则跳过下一帧或两帧。

例如:

var preferredFrameRate = 24; //use whatever your timeline animations are set to
function skipFramesIfNeeded() {
    var currentFrameRate = stage.frameRate; 
    var speedDifference = 1 - (currentFrameRate / preferredFrameRate);//value between 0-1
    if(.5 <= speedDifference < .66) {
        gotoAndPlay(this.currentFrame+1);
    } else if(.66 <= speedDifference) {
        gotoAndPlay(this.currentFrame+2);
    }
}   

这样做有几个大问题,但最糟糕的是你只能跳过整个帧 - 跳过每隔一帧会使你的速度加倍,跳过每两帧会使它增加三倍,依此类推。所以这不会像使用代码而不是时间轴来驱动动画一样平滑或高效。从每个动画帧执行代码也可能进一步降低速度。

仅限代码:如果您能找到时间,那么接受其他人的建议会更好。将您的动画从时间轴上移开,并使用代码驱动它们。保持动画和转换速度的设置或乘数,或每帧移动的像素数。

然后,您可以存储首选的frameRate,将其与stage.frameRate进行比较,并相应地调整乘数。这样可以使速度更平滑。

例如:

var preferredFrameRate = 24; //or whatever you want this to run at
var defaultSpeedMultiplier = 1; 
var speedMultiplier = 1; //this is the multiplier we'll update

function updateAnimationSpeed() { 
    var currentFrameRate = stage.frameRate;  // 18, hypothetically
    var speedDifference = 1 - (currentFrameRate / preferredFrameRate); //.25, or a difference of 25%
    speedMultiplier = defaultSpeedMultiplier  + extraSpeed; 
    //speed multiplier increases from 1 to 1.25 to make up for less frequent frames 
}

对于艺术家创建的视觉状态,您可以加载一组编号的位图,并按照从一帧移动到另一帧的顺序在它们之间切换。

编辑:理论上,您可能只是检测到更改,然后更改frameRate,但如果用户的计算机无法跟上,Flash可能会忽略它。

答案 5 :(得分:1)

你可以在舞台根部的某个地方创建一个或多个帧长的空影片剪辑,在其中放置一个十秒左右的完全无声的声音片段,然后将声音设置为流和循环。

这实际上会在Flash中为您提供一个无障碍的可变帧速率,其中文件中任何位置的每个时间轴和基于代码的动画将始终运行准确的预期时间,必要时跳过帧,但仍然执行来自的所有代码时间线甚至来自视觉上跳过的帧。这是一个非常简单且无忧无虑的解决方案,我很惊讶更多的人似乎并不了解它。