Стандартный таймер во flash. Стоит ли его использовать в играх?

Март 20th, 2010 автор: Oleg Antipov

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

Ведь многие считают – миллисекунды они и в Африке миллисекунды. Например частота вызова события ENTER_FRAME может упасть с увеличением нагрузки на CPU, но наш таймер то работает с временными отрезками и не зависит от какой то там производительности :) поэтому время он отмеряет весьма точно.

Что ж вносите пациента в операционную, будем его резать. Код нашего примерчика довольно прост:

 
//Временной квант, по прошествии которого 
//срабатывает событие таймера
var timeQuant:uint=2000;
 
//Инициализация таймера
var myTimer:Timer = new Timer(timeQuant);
myTimer.addEventListener(TimerEvent.TIMER, TimerElapsed);
myTimer.start();
 
//Будем проверять наш таймер с помощью системного таймера. 
//Запоминаем начальное время
var startTime:Number = getTimer();
 
//Функция вызывается каждый квант времени
function TimerElapsed(event:TimerEvent):void
{
	//Cчитаем сколько времени прошло по системному таймеру
	var diffTime:Number=getTimer() - startTime;
	//Cчитаем ошибку в процентах
	var errPercent:Number=(timeQuant-diffTime)*100/timeQuant;
 
	//Выводим в консоль данные
	trace("Квант времени:" + timeQuant + "  Прошло времени:" + 
		  diffTime + "  Ошибка:" + errPercent + "%");
 
	//Запоминаем время начала нового кванта времени
	startTime = getTimer();
}

Всё как видите просто. С помошью функции getTimer() получаем системное время, и с помощью него проверяем точность таймера флеша. В данном примере квант времени, с которым работает таймер равен 2 секундам. Давайте запустим и узнаем результаты:


Квант времени:2000 Прошло времени:2071 Ошибка:-3.55%
Квант времени:2000 Прошло времени:2014 Ошибка:-0.7%
Квант времени:2000 Прошло времени:2013 Ошибка:-0.65%
Квант времени:2000 Прошло времени:2020 Ошибка:-1%
Квант времени:2000 Прошло времени:2016 Ошибка:-0.8%
Квант времени:2000 Прошло времени:2013 Ошибка:-0.65%
Квант времени:2000 Прошло времени:2014 Ошибка:-0.7%
Квант времени:2000 Прошло времени:2011 Ошибка:-0.55%
Квант времени:2000 Прошло времени:2014 Ошибка:-0.7%


Ошибка менее 1%. Впринципе это вполне хорошо и даже отлично. Теперь давайте уменьшим квант времени до 1 секунды var timeQuant:uint=1000;
Результаты получаются следующие:


Квант времени:1000 Прошло времени:1024 Ошибка:-2.4%
Квант времени:1000 Прошло времени:1069 Ошибка:-6.9%
Квант времени:1000 Прошло времени:1044 Ошибка:-4.4%
Квант времени:1000 Прошло времени:1047 Ошибка:-4.7%
Квант времени:1000 Прошло времени:1044 Ошибка:-4.4%
Квант времени:1000 Прошло времени:1044 Ошибка:-4.4%
Квант времени:1000 Прошло времени:1047 Ошибка:-4.7%
Квант времени:1000 Прошло времени:1044 Ошибка:-4.4%
Квант времени:1000 Прошло времени:1047 Ошибка:-4.7%


Хехе, теперь ошибка уже составляет 4.5%. Продолжим наши эксперименты. Для кванта времени в 500 миллисекунд:


Квант времени:400 Прошло времени:409 Ошибка:-2.25%
Квант времени:400 Прошло времени:429 Ошибка:-7.25%
Квант времени:400 Прошло времени:475 Ошибка:-18.75%
Квант времени:400 Прошло времени:423 Ошибка:-5.75%
Квант времени:400 Прошло времени:461 Ошибка:-15.25%
Квант времени:400 Прошло времени:429 Ошибка:-7.25%
Квант времени:400 Прошло времени:429 Ошибка:-7.25%
Квант времени:400 Прошло времени:432 Ошибка:-8%
Квант времени:400 Прошло времени:428 Ошибка:-7%
Квант времени:400 Прошло времени:429 Ошибка:-7.25%
Квант времени:400 Прошло времени:428 Ошибка:-7%
Квант времени:400 Прошло времени:429 Ошибка:-7.25%



Еее, ошибка около 8%! Продолжаем 8) квант времени в 10 миллисекунд:


Квант времени:10 Прошло времени:61 Ошибка:-510%
Квант времени:10 Прошло времени:24 Ошибка:-140%
Квант времени:10 Прошло времени:10 Ошибка:0%
Квант времени:10 Прошло времени:16 Ошибка:-60%
Квант времени:10 Прошло времени:19 Ошибка:-90%
Квант времени:10 Прошло времени:22 Ошибка:-120%
Квант времени:10 Прошло времени:13 Ошибка:-30%
Квант времени:10 Прошло времени:14 Ошибка:-40%
Квант времени:10 Прошло времени:15 Ошибка:-50%
Квант времени:10 Прошло времени:19 Ошибка:-90%
Квант времени:10 Прошло времени:14 Ошибка:-40%



Таймер становится весьма не стабильным то ошибка 30% то и все 120%. На этом этапе уже невозможно говорить о какой либо точности измерения времени. Ну и конечно мы должны довести наш эксперемент до конца и узнать всю правду о нашем поциенте. ИТААААКК…. 1 миллисекунда! Поехали:


Квант времени:1 Прошло времени:13 Ошибка:-1200%
Квант времени:1 Прошло времени:5 Ошибка:-400%
Квант времени:1 Прошло времени:5 Ошибка:-400%
Квант времени:1 Прошло времени:6 Ошибка:-500%
Квант времени:1 Прошло времени:6 Ошибка:-500%
Квант времени:1 Прошло времени:13 Ошибка:-1200%
Квант времени:1 Прошло времени:4 Ошибка:-300%
Квант времени:1 Прошло времени:6 Ошибка:-500%
Квант времени:1 Прошло времени:5 Ошибка:-400%
Квант времени:1 Прошло времени:6 Ошибка:-500%
Квант времени:1 Прошло времени:36 Ошибка:-3500%


Таймер просто колбасит, видимо предел его точности – чтото около 35-45 миллисекунд. Ну и да ладно, скажете вы. Точность вполне сгодится…. Чтож давайте поставим снова квант времени 1 секунда и добавим слегка вычислений в код нашего таймера:

//Функция вызывается каждый квант времени
function TimerElapsed(event:TimerEvent):void
{
	//Cчитаем сколько времени прошло по системному таймеру
	var diffTime:Number=getTimer() - startTime;
	//Cчитаем ошибку в процентах
	var errPercent:Number=(timeQuant-diffTime)*100/timeQuant;
 
	//Выводим в консоль данные
	trace("Квант времени:" + timeQuant + "  Прошло времени:" + 
		  diffTime + "  Ошибка:" + errPercent + "%");
 
	//Запоминаем время начала нового кванта времени
	startTime = getTimer();
 
	var i:uint;
	var j:uint;
	var k:Number=0;
 
	for(i=0;i<1000;i++)
		for(j=0;j<1000;j++)
		{
			k=k+Math.sin(k*Math.random());
		}
}

В предыдущий раз ошибка была около 4.5%, посмотрим сколько сейчас выйдет:


Квант времени:1000 Прошло времени:1350 Ошибка:-35%
Квант времени:1000 Прошло времени:1349 Ошибка:-34.9%
Квант времени:1000 Прошло времени:1357 Ошибка:-35.7%
Квант времени:1000 Прошло времени:1357 Ошибка:-35.7%
Квант времени:1000 Прошло времени:1360 Ошибка:-36%
Квант времени:1000 Прошло времени:1355 Ошибка:-35.5%


Ну не фига, почти 36%! Тихий ужас. Оказывается таймер не только неточен на целые 45 миллисекунд, его точность ещё и зависит от объёма вычислений находящихся в обработчике события.

Как это не печально но факт. Мораль сей басни очень проста –необходимо замерять интервалы времени только с помошью getTimer(). Только так мы можем быть уверенны, что вне зависимости от производительности компьютера игрока, наши объекты будут двигаться с одинаковой скоростью на всех компьютерах.

На этом всё, Адью!
Если кто хочет сам погонять на своей машине пример, то его можно качнуть здесь: http://www.anegmetex.com/devblog/files/TimerTest.rar

Категория: Хитрости

10 Комментариев

  1. Max Firsoff

    ну хорошо, все ясно, таймер – shit.
    как будет реализована замена таймера через getTimer()?

  2. Oleg Antipov

    Полноценной замены мы сделать не можем. Для точного вызова нужного куска кода через заданный промежуток времени необходима многопоточность (чтобы не зависить от других вычислений), которой во флеше к сожалению нет. Однако получить информацию насколько миллисекунд произошла ошибка – можем. И исходя из этого можем скорректировать допустим движение объекта.

    Например стоит организовывать движения объекта вот таким образом:

    var diffTime:Number=getTimer() – startTime;
    myMovieClip.x+=vx*diffTime; //где vx – скорость движения объекта в пиксель\мс
    startTime = getTimer();

    И не стоит писать вот так:
    myMovieClip.x+=5;

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

  3. Игорь

    вызов таймера синхронизирован с framerate. Соответственно:
    - таймер не может быть быстрее чем 1/framerate msec

    - если он попал в “междуфрейменье” то ждет начала следующего фрейма.

    В AS3 эксперименты не проводил, но в старой виртуальной машине флеша было так. Да и в концепцию вписывается.

  4. Vovka

    ПОЦиент – goood! )))))

  5. Andrew

    Игорь прав: таймер не может быть быстрее чем 1/framerate, у вас скорее всего стандартные 30fps, следовательно для отрезков меньше 33мс его нет смысла использовать

  6. Oleg Antipov

    Да, похоже на то )

  7. a.b

    таймер не может быть быстрее чем 1/framerate

    Так и в чем тогда мнимое преимущество перед ентерфреймом?

    Получается что таймер тоже по ентерфрейму запускается)))

  8. Oleg Antipov

    Впринципе в играх лучше вообще таймер не использовать, а всё держать в одном ENTER_FRAME. Паузу делать удобнее опять же ;)

  9. vitalik14

    для этого дела есть

    stage.frameRate

    устоновите его хотяб 1000

    и посмотрите что будет=)

  10. Старук

    из Адоб хелпа :
    Примечание. Не рекомендуется задавать свойству delay значение меньше 20 миллисекунд. Частота объекта Timer ограничена 60 кадрами в секунду, то есть задержка меньше 16,6 миллисекунд вызывает проблемы во время выполнения.
    тесты после < 500 не объективны.

Оставить комментарий