Как
я и обещал в своем блоге, напишу
небольшую статью про синхронизацию главного игрового цикла с учетом особенностей Blitzmax. Это,
в общем, не должно составлять больших проблем, но моей целью был не простой
цикл, а динамический, с подстройкой частоты обновления под конфигурацию. Так,
чтобы на более медленных машинах частота обновления (прохода главного цикла)
уменьшалась до приемлимого минимума и не грузила процессор, а на быстрых –
поднималась до оптимального значения. Код должен быть максимально совместим с
учетом мультиплатформенности, в дебри не лезть и своих модулей на С/С++ не
писать (не царское это дело, да еще под три платформы – трижды не царское).
Итак,
цели: использовать только стандартные возможности языка для совместимости;
максимальное снижение ненужной нагрузки на систему; независимость скорости анимации; управление частотой обновления для снижения нагрузок на слабые
системы. Скажу сразу – эта задача в
идеале решена не была. Зато было достигнуты почти все цели и еще одна:
минимизация используемого кода и ресурсов. Теперь подробнее. Я
не буду приводить всех листингов (чтобы просто не набирать их заново), приведу
только последний выбранный вариант. Использованные тупиковые варианты, думаю,
можно для интереса будет написать и самому при желании.
Итак:
как синхронизировать главный цикл? Типичное решение – это ввести цикл ожидания
на основе счетчика времени одного прохода и использовать его для паузы в
главном цикле. При достижении значения, определяемого частотой обновления,
продолжать главный цикл. Все работает вполне отлично. Даже можно дописать код
регуляции частоты обновления на основе разницы времени на отрисовку и
обновление сцены и номинальной частоты обновления, если эта разница есть,
конечно. Возникает одно но – все процессорное время жрет наша игра. Ну почти
все, т.к. все никто ей не отдаст. Какой здесь выход? Выход в том, что в цикле
ожидания надо отдавать время системе, причем желательно на определенны период
времени, который можно рассчитать. Но такой возможности в Blitzmax я не нашел.
Есть команда WaitSystem, которая и
передает время системе, но в ней нельзя указать на сколько, - и возврат в наше
приложение происходит только при посылке ему какого-либо сообщения (мышкой там
провели над ним и т.п.). Т.е. – никакой анимации и плавного движения мы не
получаем.
Второй
способ состоял в использовании таймера. Делается таймер с нужной частотой
обновления и в главный цикл добавляется команда WaitTimer, заменяющая цикл ожидания. Плюс способа один: WaitTimer передает управление системе,
отъедая минимум системного времени, что нам и надо. Минус: в стандартном
таймере мы не можем никак регулировать частоту – она задается только один раз
при его создании. Небольшое копание в WinApi, привело к выводам, что все это
решаемо, но в Blitzmax не сделано. Возможно из-за совместимости с другими ОС,
хотя мне кажется странной невозможность изменения интервала ожидания по ходу
работы таймера. Есть конечно промежуточное решение – пересоздавать каждый раз в
главном цикле таймер с нужным нам интервалом для синхронизации. Но у него
недостаток в избыточном потреблении ресурсов. Было бы прекрасно, если бы была
просто команда, отдающая управление системе на какой-то отрезок времени. Ее
можно реализовать на том же АПИ – timeSetEvent
(для Windows, по крайней мере) и не потреблять лишних ресурсов для класса
таймера. Для достижения одной из целей – равномерности движения вне зависимости
от скорости надо просто добавить коэффициент, который будет рассчитываться в
главном цикле, и корректировать скорость анимации объектов.
Третий
способ, на котором я остановился, основан на внимательном рассмотрении функций
Blitzmax Graphics и Flip. У Graphics есть аргумент hertz,
который определяет частоту обновления созданного окна. А у Flip – флаг со значением -1, который привязывает отрисовку к
частоте обновления окна, созданного Graphics.
Используя это в связке, нам не нужно создавать лишний таймер для синхронизации.
Но как и в случае с таймером, мы не можем регулировать скорость обновления.
Вот
полученный код:
Strict
Local bExit% = False
Const fBaseFPS# = 50 Const cTimeDelta% = 1000/fBaseFPS '1000 - milisec in second / fps Local fps%= -1
Global fSpeedMul# = 1;
Graphics 800 , 600 , 0 , fBaseFPS
While Not bExit
bExit = KeyDown(KEY_ESCAPE) | AppTerminate()
Local startTime = MilliSecs();
DrawScene()
'draw info
DrawText "FPS: " + fps , 8 , 8 DrawText "K: "+Round(fSpeedMul), 100,8 DrawText "Mem: "+GCMemAlloced(), 300,8
'animate scene
UpdateScene()
'Delay(30)
Flip(-1)'by Graphics refresh rate
fps = 1 / Float(MilliSecs() - startTime) * 1000 fSpeedMul = fBaseFPS / fps
Wend
Global x#=0, y#=300
Function UpdateScene() x :+ 0.5 * fSpeedMul If x > GraphicsWidth() Then x = 0 End Function
Function DrawScene() Cls DrawOval(x,y, 50, 50) End Function
Function Round$(value!, nDecimals%=2)
Local str$ = value Local pos% = Instr(str , ".") str = str[..pos + nDecimals] Return str
End Function End
|
Итоги:
получен главный цикл, отъедающий время пропорционально своим усилиям,
учитывающий задержки при отрисовке и обновлении, максимально совместимый и
наименее ресурсоемкий как со стороны написания (меньше кода), так и со стороны
системных ресурсов (лишний таймер не создается). Недостаток – на слабых
платформах, не укладывающихся в номинальное время прохода цикла, будет
забираться все время системы (отведенное на процесс естественно), тогда как
понизив частоту отрисовки, можно было бы его уменьшить, снизив общую нагрузку.
Еще
в процессе выявлено несколько недостатков языка:
- не обновляется содержимое
окна при таскании его за заголовок (с тем-же фреймвоком от Поп-Кап таких
проблем нет). - нет нужных команд
для временной передачи лишнего времени системе. В Win и PalmOS это
вполне возможно, в других ОС, думаю, тоже. - нет возможности
изменять частоту таймера в процессе его работы.
|