Честный таймер

На днях у меня возникла резко возникла необходимость в «правильном» таймере для генерации событий. Когда затестировал стандартный Timer на маленьких временных интервалах я пришел в тихий ужас, например, на величине тика 125мс я получил колоссальное расхождение между реальными шагами порядка 30 процентов или около того. И вот я стал думать, а что же мне вообще делать?

В первую очередь нагуглилось вот такое решение но от этого мало что изменилось в лучшую сторону, лаги уменьшились примерно до 10 процентов(проверял через getTimer()). И тут я совершенно случайно наткнулся на один интересный метод Смысл его в следующем: как ни странно но событие Event.SOUND_COMPLETE работает на редкость четко, и если создать пустой звуковой файл нужной длинны (в моем случае 125мс (120bpm)), то можно на его SOUND_COMPLETE в рекурсивном вызове например генерить событие «tick». Вот кусочек моего кода:


private function startTimer(e:MouseEvent):void 
{
     start_bt.removeEventListener(MouseEvent.CLICK, startTimer);

     silentSound = new silence120(); //грузим пустой звук длительностью 125мс из библиотеки
     this.timerTick(new Event("timer_start")); // Запускаем таймер
}
private function timerTick(event:Event):void {
      this.dispatchEvent(new Event("tick")); // диспатчим тик
                        
      channel = silentSound.play();
                        
      channel.addEventListener(Event.SOUND_COMPLETE, timerTick); // переподписываемся на событие SOUND_COMPLETE
                        
      myFunction();//тут можем также что-нибудь выполнять на каждый тик нашего импровизированного таймера
                        
}

Может быть данный способ не совсем верен с этической точки зрения, но главное что он работает.

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

p.s. пустой звуковой файл я создал в Adobe Audition Generate->Silence 0.125
  • +5

Комментарии (28)

+3
Вообще не пользуюсь таймерами. Вместо них такое:
if (getTimer() - prevTime > delay) 
{
  prevTime = getTimer();
  doStuff();
}
0
Плюсую.

Я в основном спрайте сцены делаю
public class Main extends Sprite 
{
    private var _oldTime: int = getTimer();
    private var _world: World;
                
    public function Main(): void 
    {
        if (stage)
            init();
        else
            addEventListener(Event.ADDED_TO_STAGE, init);
    }
                
    private function init(e: Event = null): void 
    {
        removeEventListener(Event.ADDED_TO_STAGE, init);
        addChild(__world = new World());
                        
        addEventListener(Event.ENTER_FRAME, function(e: Event): void
        {
            var currTime: int = getTimer();
            _oldTime = currTime;
            _world.update(currTime - _oldTime);
        });
    }
}

, где World — класс мира, в котором разворачиваются все действия игры и который, как и все спрайты игры, наследуются от SpriteEx, который в свою очередь имеет метод update:
public class SpriteEx extends Sprite
{
    public function update(timeElapsed: uint): void
    {
        var child: DisplayObject;
        for (var i: int = 0; i < numChildren; ++i)
        {
            child = getChildAt(i);
                                
            if (child is SpriteEx)
                ((child as SpriteEx).update(timeElapsed));
        }
    }

    // ...
}

В любом месте я перегружаю метод update и получаю значение, сколько времени прошло.
0
При таком методе погрешность будет постоянно накапливаться. Лучше завести счетчик тиков и производить сравнение со временем старта таймера, а не со временем предыдущего срабатывания.
0
какая погрешность? что вы подразумеваете под?
0
Каждый раз проверка времени происходит на основании предыдущего срабатывания таймера. Это срабатывание не зря повесили не на getTimer() — prevTime == delay, а на getTimer() — prevTime > delay, потому что точного срабатывания не будет. Вот и выходит что эта неточность постоянно накапливается.

Я бы уж делал как-то так:


private var count:int = 0;
private var start:int = getTimer();
private var delay:int = 125; // к примеру

...

// в каком-то onEnterFrame (хотя нафига тогда все это)
if (int((getTimer() - start) / delay) >= count)
{
    count++;
    doSomethingEveryTick();
}
+1
я вас понял, проще вычитать это отклонение из нового значения времени

t = new time — prevtime;
if (t >= delay)
{
prevtime = new time — (t — delay);
событие();
}
0
не только проще, но и производительность у вычитания больше чем у деления.
0
Оптимизировать код за счет деления «up to 60» раз в секунду в угоду кривому подсчету интервала — курам на смех.
+2
Слушай, не умеешь считать — учи математику за младшие классы школы, если на больше ты не способен.
То не в состоянии въехать в оптимизацию с секторным делением большой карты.
То не в состоянии въехать в код с заменой деления более простыми операциями. Второй вариант кода без накопления погрешности, потому что она не сбрасывается, а учитывается за счет вычитания от эталонной величины.
Ты написал говнокод. Если ты даже основу игрового цикла пишешь говнокодом, мне страшно представить, что там у тебя в коде вообще. И это не предобразования Фурье блин, это элементарная математика. Программисты блин. Паттерны идите учите лучше.
И самый ужас — ты не учишся и не в состоянии въехать в простейщий чужой код.
0
Да ну все уже ) Остапа понесло.
+2
не люблю кичащуюся неграмотность. терпеть просто не могу. уж если указываешь на ошибку делай это нормально. А то вроде и правильно — первый пример с погрешностью. действительно. Однако твой код, хоть и без нее, но он же «индусский»! И при этом ты чужой правильный и «прямой» понять не можешь — это взрыв мозга, вынос здравого смысла, разрыв шаблона навсегда!!! аааа я так не могуууууу!!! пойду напьюсь.
0
:)

Ну что значит проще? Твой код просто избегает «страшного и ужасного» деления пару десятков раз в секунду. При этом в жертву ты приносишь вот это самое ">". Сам смотри. Вот наступил момент, когда условие выполняется. И наступил этот момент не ровно через delay, а немного позже. И ты это «немного позже» берешь за новую отправную точку. Другими словами с каждым тиком ты ставишь контрольную точку на отметке delay + погрешность. Вот так она и накапливается.

Мой код такой погрешностью не страдает в принципе.
0
что в твоем что в моем случае есть погрешность в 1 кадр которая не накапливается.
ты видимо код не понял, объясняю на примере
шаг кадра 33 мс, допустим мы выполнили шаг на 30 мс позже. тогда для нового времени берем время на 30мс раньше. т.о. через пару кадров мы выполним его раньше на кадр, благодаря вычитанию. а без вычитания будет отклонения на 0-1 кадров каждый тик

что то, что это не страшное и ужасное, а бесполезная оптимизация, не понятно какое прикладное применение у такого точного таймера, если точность все равно вычисляется с погрешностью <= 30мс
0
Я говорю про этот код, в котором накапливается погрешность:
if (getTimer() - prevTime > delay) 
{
  prevTime = getTimer();
  doStuff();
}

Вот мой код, в котором ее нет:
if (int((getTimer() - start) / delay) >= count)
{
    count++;
    doSomethingEveryTick();
}

Из-за чего сыр-бор — трудно поменять единственную строку, чтобы все стало ровнее? Да пишите как знаете, только не доказывайте мне полезность накопительных погрешностей.
+3
забей — это человек мягко говоря с нетрадиционным мышлением и упертостью достойной одного вьючного животного.
У него это либо трололо, дибо воинструющее свойство указанного выше вьючного животного.
Вот тут flashgamedev.ru/viewtopic.php?f=10&t=4916 я пытался обьяснить как сделать отпимизацию за счет посекторного разбиения карты — не понятно было только ему. Выводы сделать можно. Сии комментарии еще одна иллюстрация.
зы: сделал короче комментарий по поводу более оптимального кода.
+3
prevTime = getTimer();

меняем на
prevTime += delay;

профит!
:)
0
Отлично )
+1
Тоже всегда пользуюсь таким методом.
0

private var time_:Number = getTimer();
...
var elapsed:Number = getTimer() - realTime_;
time_ += elapsed;
elapsed = Math.min(1/30.0, elapsed);//чтобы не при тормозах не дергалось и оставалось играбельно
updateGame(elapsed);

Если нужен именно таймер, а он часто нужен, то вот что у меня есть:

        public class Timer 
        {
                public function Timer() 
                {
                        queue_ = new Vector.<TimerCallback>;
                        if (pool_ == null)
                                pool_ = new Vector.<TimerCallback>;
                        time_ = 0;
                }
                
                public function add(delay:Number, callback:Function, param:* = undefined):void
                {
                        var timerCallback:TimerCallback;
                        
                        if (pool_.length == 0)
                                timerCallback = new TimerCallback;
                        else
                                timerCallback = pool_.pop();
                                
                        timerCallback.time = time_ + delay;
                        timerCallback.callback = callback;
                        timerCallback.param = param;
                        
                        queue_.push(timerCallback);
                        queue_.sort(TimerCallback.compare);
                }
                
                public function update(elapsed:Number):void
                {
                        var nextTime:Number = time_ + elapsed;
                        while (queue_.length > 0 && queue_[queue_.length - 1].time <= nextTime)
                        {
                                var timerCallback:TimerCallback = queue_.pop();
                                time_ = timerCallback.time;
                                if(timerCallback.param == undefined)
                                        timerCallback.callback();
                                else
                                        timerCallback.callback(timerCallback.param);
                                timerCallback.callback = null;
                                timerCallback.param = undefined;
                                pool_.push(timerCallback);
                        }
                        time_ = nextTime;
                }
                
                public function get time():Number
                {
                        return time_;
                }
                
                private var queue_:Vector.<TimerCallback>;
                private var time_:Number;
                private static var pool_:Vector.<TimerCallback> = null;
        }
}

class TimerCallback
{
        public var time:Number;
        public var callback:Function;
        public var param:*;
        
        public static function compare(a:TimerCallback, b:TimerCallback):Boolean
        {
                return a.time < b.time;
        }
+2
if (getTimer() — prevTime > delay)
{
prevTime = getTimer();
doStuff();
}
Однако, какой вы тут холивар развели. Я вам просто идею метода подкинул, а тут начали про погрешности. Это же и так ясно, но не писать же тут целую статью, смысл моего поста был немного в другом — просто показать другой метод вызова событий через определенный интервал времени. И то, что, ваши частицы будут умирать не через 1 сек, а через 1.01 сек не имеет для игрока никакого значения.
У всех был такой период, когда мы пытались сделать что-то идеальное, качественное. Когда я учился в школе, тоже писал всякие движки и никому не нужную фигню. Просто задумайтесь над тем, что вы делаете. Можно делать хорошие игры, а можно делать якобы идеальный код, но последнее не будет означать, что ваша игра тоже получится классной. Всем успехов и не ссорьтесь :)
0
Философия годная. Она хорошо применима к человеку, для которого большой труд — учесть какие-то мелочи. Мой код пускай и не так элегантен, как предложил (молодец кстати) 4mlr, но без особого труда делает отсчет просто ровнее, всего-то. И пофиг на истерики оппонентов ;)
+9
Если у пользователя не окажется звуковой карты — ваш таймер сделает «Пу» :D
0
Я забыл сказать что игра музыкальная)) а на счет проверки на наличии карты, спасибо за идею, кто-то про это писал кстати в блогах, надо полистать.
+9
редкостный костыль :)
+4
Я снова полюбил flash, когда начал все время считать в ENTER_FRAME.
0
В первой своей игре я сделал обычный таймер. Это был физ.пазл, и если игра тормозила, игрок просто не успевал процти уровень (time attack). Что, ясное дело, очень злило игроков… Так что, с тех пор только ENTER_FRAME.
+1
Точнее, я вообще перестал делать time attack. Не казуаль это:)
+2
Вообще в мануале четко написано, что все «таймерные» события имеют расхождения и погрешность зависит от производительности компа + частота кадров.

Если на долю секунды происходит подвисание — все таймеры соответственно подвисают тоже.

Самый адекватный вариант это через getTimer(); + делать один на всю игру ENTER_FRAME и в него через Vector.аттачить фунции на обновления.

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