Игровой цикл в Firemonkey для Android

В настоящее время я делаю 2D-игру в Firemonkey для своего телефона Android, используя некоторые элементы управления TImage и контролируя их положение и углы. Просто как тот. Я попытался использовать свой обычный способ зацикливания, который работает хорошо/безупречно в Windows, но не работает на Android.

Способ 1. Бесконечный цикл (плохо)

Моя основная проблема заключается в том, что я хочу избежать нежелательного/неожиданного поведения, используя «бесконечный цикл», подобный этому, где я должен вызывать Application.ProcessMessages():

while (Game.IsRunning()) do
begin
  { Update game objects and stuff }
  World.Update();


  { Process window messages, or the window will not respond anymore obviously }
  Application.ProcessMessages(); { And thats what i want to avoid }
end;

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

Способ №2: TTimer (даже не думайте об этом ;-))

Поскольку TTimer ужасен во многих отношениях и не предназначен для таких целей, подход, когда я просто ставлю TTimer с минимальным интервалом, отпадает. Кроме того, я никогда не пробовал другие таймеры, но если есть действительно для игр, я, конечно, попробую :)

Способ №3: OnIdle — как я обычно это делаю в Windows

В Windows я могу использовать Application.OnIdle-Event.

procedure GameLoop(Sender: TObject; var Done: Boolean);
begin
  { Update game objects and stuff }
  World.Update();

  { Set done to false }
  Done := False;
end;

...

Application.OnIdle := GameLoop;

Он вызывается каждый раз, когда приложение простаивает над сообщениями Windows. Производительность кажется такой же или немного хуже по сравнению с № 1, а общая архитектура намного надежнее, поэтому я обычно использую этот метод. Однако на Android кажется, что он вызывается по-другому в сочетании с TForm.MouseMove. Там, где в Windows OnIdle продолжает работать отлично, на Android он будет зависать/останавливаться, пока TForm.MouseMove вызывается из сенсорного ввода (RAD Studio автоматически компилирует его в сенсорные события)

Однако я могу запустить OnIdle самостоятельно в событии MouseMove, вызвав Application.DoIdle и "помощь" "Циклу", где, я думаю, он пропускает вызов beimg, но это также работает очень плохо и снова вызывает нежелательное поведение и хуже производительность при работе с сенсорным вводом.

И это возвращает меня к методу № 1, который на данный момент лучше всего работает на Android. Есть ли какой-либо другой способ создания надежного способа (постоянно и быстро вызываемого события, такого как OnIdle или около того) создания такого цикла или любого способа избежать проблем, с которыми я столкнулся в методе № 3 в сочетании с MouseMove-Events? Кажется, что Android-телефон недостаточно мощный, чтобы иметь достаточно «Время простоя» рядом с формами-событиями и мировыми обновлениями.

Кроме того, могу ли я рассмотреть возможность использования потока для мировой логики и рядом с ним метода № 1 в моей основной форме/потоке для обновления рендеринга? Безопасно ли обновлять элементы управления формы бесконечным циклом (с помощью Application.ProcessMessages()) и получать отображаемые данные из другого потока, работающего с миром, объектами и т.д.?


person KoalaGangsta    schedule 17.10.2016    source источник
comment
Вы можете использовать анонимную поточность (с синхронизацией) для достижения этой цели.   -  person nolaspeaker    schedule 18.10.2016
comment
Если вы хотите избежать задержек игрового цикла, вызванных пользовательским интерфейсом FireMonkey, лучшим выбором будет перенести всю игровую логику во вторичный поток. Но даже это может полностью решить вашу проблему, особенно если целевое устройство имеет только одно вычислительное ядро. Вы видите, что FireMonkey довольно плохо/медленно обрабатывает сообщения пользовательского интерфейса. В качестве теста просто создайте новое приложение FireMonkey и разместите на форме около 100 панелей. Затем скомпилируйте его и посмотрите, как быстрое движение мыши по вашей форме вызовет значительный скачок загрузки ЦП. ...   -  person SilverWarior    schedule 19.10.2016
comment
... Такой тест способен максимально использовать процессор на моем 7-летнем ноутбуке, и это не просто какой-то слабый ноутбук, поскольку я могу без проблем играть в Far Cry 3 на средних настройках. Теперь я могу только представить, какую загрузку процессора вызовет FireMonkey на мобильном телефоне, используя этот сценарий. Вот почему я не рассматриваю возможность использования FireMonkey для разработки игр.   -  person SilverWarior    schedule 19.10.2016
comment
Спасибо вам обоим за ваши комментарии! Как вы думаете, я мог бы улучшить обмен сообщениями пользовательского интерфейса, рисуя все игровые объекты, используя холст в OnPaint-Event, вместо того, чтобы рисовать, как 50+ TImages? Я также начал небольшую награду, если кто-то из вас захочет попробовать :)   -  person KoalaGangsta    schedule 19.10.2016
comment
@KoalaGangsta: Просто любопытно, что ты в итоге сделал? Я также хочу реализовать цикл анимации в FM, и я наткнулся на ваш пост. Спасибо   -  person costa    schedule 01.09.2017


Ответы (1)


Я думаю, что правильным вариантом является реализация TAnimation и переопределение ProcessAnimation. TAnimation автоматически планируется при запуске через TAnimator.

  TGameLoop = class(TAnimation)
  protected
    procedure ProcessAnimation; override;
  end;
  [...]
  procedure TGameLoop.ProcessAnimation;
  begin
    //call updates
  end;

и начал так:

procedure TForm1.FormCreate(Sender: TObject);
begin
  FLoop := TGameLoop.Create(Self);
  FLoop.Loop := True;
  FLoop.Name := 'GameLoop';
  FLoop.Parent := Self;
  TAnimator.StartAnimation(Self, 'GameLoop');
end;

По умолчанию для анимации установлено значение FPS 60. Одна вещь, которую я не мог понять прямо сейчас, - это как рассчитать Deltatime с момента последнего вызова, используя только свойства TAnimation. Это важно для работы с переменным временем кадра. Но вы можете сделать это сами, используя System.Diagnostics.TStopwatch. Остановите часы в начале ProcessAnimation и получите Ticks, начните новый после завершения ProcessAnimation.

РЕДАКТИРОВАТЬ: базовый пример для Deltatime (прошедшее время с момента последнего звонка в секундах). Учитывая, что вы поместили ViewPort3D в Form1, добавили TCube с именем Cube1, TGameLoop объявлен в том же блоке, что и Form1 (я знаю, уродливо, но с демонстрационным назначением), а TGameLoop имеет поле FWatch типа TStopWatch.

procedure TGameLoop.ProcessAnimation;
var
  LDelta: Single;
begin
  FWatch.Stop;
  LDelta := FWatch.ElapsedTicks / FWatch.Frequency;
  Form1.Cube1.RotationAngle.Y := Form1.Cube1.RotationAngle.Y + 50*LDelta;
  FWatch := TStopwatch.StartNew();
end;

EDIT2: лучший пример, который немного лучше распределяет ошибки округления/измерения в течение всей жизни игры. Вместо измерения только между вызовами мы измеряем с первого кадра. TGameLoop имеет новое поле FLastElapsedTicks(Double)

procedure TGameLoop.FirstFrame;
begin
  FWatch := TStopWatch.StartNew();
end;

procedure TGameLoop.ProcessAnimation;
var
  LDelta: Single;
  LTickDelta, LElapsed: Double;
begin
  LElapsed := FWatch.ElapsedTicks;
  LTickDelta := LElapsed - FLastElapsedTicks;
  FLastElapsedTicks := LElapsed;
  LDelta := LTickDelta / FWatch.Frequency;
  Form1.Cube1.RotationAngle.Y := Form1.Cube1.RotationAngle.Y + 50*LDelta;
end;
person Alexander B.    schedule 21.10.2016
comment
Большое спасибо за ваш ответ! После некоторого тестирования кажется, что это не решает проблему, из-за которой мое событие OnMouseMove запаздывает, но я думаю, что мне нужно углубиться в тестирование, чтобы это могло сработать. Не знаете, есть ли возможность поиграться с FPS? На мобильных устройствах он работает очень медленно :-/ Спасибо! - person KoalaGangsta; 24.10.2016
comment
Вы проверили, не сжигает ли ваш Gamelogic процессор? Вы не указали телефон, с которым работаете. В противном случае можно было бы поместить его в дополнительную ветку, как обсуждалось в других комментариях к вашему основному сообщению. Может быть, вы могли бы подробнее рассказать о том, над чем вы работаете? На это может повлиять многое. Чтобы поиграть с FPS, используйте AniFrameRate-Property. - person Alexander B.; 24.10.2016
comment
Что-то еще, что вы могли бы попробовать, это опрос клавиш/мыши в вашем игровом цикле перед выполнением вашей логики, чтобы избежать вмешательства в события. Затем вашей игровой логике потребуется доступ к какому-то интерфейсу, в котором вы сохранили эти значения. - person Alexander B.; 24.10.2016