Пропуск кадра на Flash - PullRequest
       1

Пропуск кадра на Flash

8 голосов
/ 29 октября 2011

Есть ли способ встроенного пропуска кадров во Flash?

Когда вы разрабатываете игру, вы разрабатываете анимацию, соответствующую темпу игрового процесса, и делаете это с целевой частотой кадров (обычно 24-40fps для Flash).Но если компьютер пользователя работает слишком медленно и не может поддерживать целевую частоту кадров, Flash автоматически понижает частоту кадров, заставляя приложение проигрываться медленно.

Если вы используете логику, основанную на времени, рендеринг на основе кадров будетне соответствуют логике, основанной на времени, и все будет странно, и будет много угловых случаев, чтобы обойти.

Я знаю, что некоторые игры используют такую ​​логику пропуска кадров, как, например, PopcapЗума Блиц.Они сами реализуют пропуск кадров?

Я не могу позволить себе реализовать это так поздно в проекте, если не смогу каким-то образом переопределить класс MovieClip и сделать его легко пропускаемым.Кроме того, не будут ли чрезмерные накладные расходы на управление анимацией самостоятельно (перезапись встроенного в Flash элемента управления MovieClip) слишком большими издержками?

Ответы [ 6 ]

9 голосов
/ 29 октября 2011

Подход, который нужно использовать, заключается в том, чтобы думать об изменениях пикселей в секунду, а не пикселей на кадр. Затем вы можете запустить свой основной цикл на 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
        }

    }

}

Если вы обнаружите, что низкие частоты кадров влияют на точность вычислений столкновений, запустите их отдельно с таймером. Это просто цикл рисования, который неизбежно связан с частотой кадров.

И чтобы вы знали, что вы не можете пропустить кадр во флэш-памяти. Это обрабатывается игроком. Наилучший подход - настроить технику рендеринга кадров, чтобы она соответствовала переменной частоте кадров.

3 голосов
/ 02 ноября 2011

Хорошо, я понимаю, что вы не ищете следующее «решение», которое не будет работать, поскольку все кадры будут воспроизводиться:

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). Распределить выполнениедлительных операций над несколькими кадрами (требуется сохранение состояния и восстановление состояния, шаблон lamento).Подробности (и отличное чтение) здесь: http://www.senocular.com/flash/tutorials/asyncoperations/

В любом случае, я бы (в первую очередь) рекомендовал бы окончательно определить узкое место в производительности с помощью такого инструмента, как Flash Builder Profiler или класс Stats (Mr.Уб)

Блиттинг может быть решением (создайте таблицу спрайтов с плиткой для каждого кадра анимации).Но в любом случае, я думаю, что вам нужно будет создать подкласс 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 голосов
/ 02 ноября 2011

Твоя самая большая проблема в том, что ты не блешь. Художники все еще могут анимировать на временной шкале и экспортировать спрайт-листы. Я бы потратил некоторое время, чтобы серьезно понять ограничения производительности, связанные с анимацией на временной шкале флэш-плеера. Вот пара ссылок, с которых можно начать:

Как видите, управление анимацией с временной шкалы осуществляется с помощью gotoAndStop (); это худший из возможных методов анимации в отношении количества спрайтов, с которыми вы работаете.

Вы также можете изучить работу с новым графическим API-интерфейсом molehill, Starling Framework

Чтобы обратиться к вашему вопросу напрямую, я бы поддержал решение, которое уже обсуждалось, выполняя буферизованный рендер для спрайтов во время выполнения. В зависимости от количества спрайтов, требуемого в любой момент времени, вы можете поменять низкую производительность анимации на ограничения памяти.

Удачи.

1 голос
/ 08 мая 2012

Вы можете просто создать пустой или около сотни кадров длинный видеоклип где-нибудь в корне вашей сцены, вставить в него около десяти секунд или около того совершенно бесшумный аудиоклип и установить звук для потоковой передачи и петли.

Это фактически даст вам беспроблемную переменную частоту кадров во Flash, где каждая временная шкала и основанная на коде анимация в любом месте вашего файла всегда будут работать в течение точного намеченного времени, пропуская кадры там, где это необходимо, но все же выполняя весь код изграфик даже из визуально пропущенных кадров.Это действительно простое и беспроблемное решение, я удивлен, что все больше людей об этом не знают.

1 голос
/ 03 ноября 2011

Насколько я знаю, в AS3 нет ничего, что позволяло бы пропускать кадры мувиклипа с заданным интервалом или продвигаться быстрее, чем обычно, посредством анимации на временной шкале. Такие игры, как Zuma, вероятно, либо делают это вручную, либо запускают анимацию без использования временной шкалы для начала.

Чтобы сделать это вручную, у вас есть два основных варианта:

Параметр временной шкалы: (может не стоить этого по причинам, объясненным ниже) Если вы действительно, абсолютно привязаны к анимации временной шкалы, и изменение кадра Тарифы вызывают большие проблемы, и у вас нет времени или технической свободы, чтобы переделывать анимацию с помощью кода, вы, вероятно, можете использовать прослушиватель Event.ENTER_FRAME для вызова функции из каждого из кадров movieClip. проверить stage.frameRate и пропустить следующий кадр или два, если 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, вероятно, проигнорирует ее.

1 голос
/ 02 ноября 2011

Я недавно столкнулся с чем-то похожим;может быть, в какой-то мере поможет следующее:

  • gotoAndStop (frame) немедленно выполнит скрипт действия в frame
  • , вы можете выполнить несколько вызовов gotoAndStop в пределах одного события ENTER_FRAME (или любого другого события).

В качестве теста я создал новый проект AS3.0 в Flash Pro CS5.5.Я создал новый 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 и использовать его какбазовый класс (чтобы быстро это сделать, выберите все мувиклипы, которые требуют этого в библиотеке, щелкните правой кнопкой мыши, выберите свойства, настройте базовый класс).Для тех MovieClips, которые уже связаны с actioncript, файл .as можно просто обновить, расширив его из нового класса MovieClip.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...