2016-10-17 13 views
6

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

Способ № 1: «End-менее» петля (Bad)

Моя главная проблема заключается в том, что я хочу, чтобы избежать нежелательного/неожиданное поведение при использовании «конец бескоммутаторная петлю», как это, где у меня есть позвонить 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 (Dont даже думать об этом ;-))

С 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 компилирует его в события касания automaticlly)

Однако я могу запустить OnIdle самостоятельно в MouseMove Событие, позвонив по телефону Application.DoIdle и «помогите» в «Loop», где я думаю он пропускает beimg, но это работает и очень плохо и приносит снова нежелательное поведение и худшую производительность при работе с сенсорным вводом.

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

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

+0

Вы можете использовать анонимную многопоточность (при синхронизации) для достижения этой – nolaspeaker

+0

Если вы хотите, чтобы избежать цикла игры лагов вызваны FireMonkey UI, то ваш лучший выбором будет двигаться все ваши игровой логики к вторичной нити. Но даже это может полностью решить вашу проблему, особенно если целевое устройство имеет только одно процессорное ядро. Вы видите, что FireMonkey довольно плохо/медленно обрабатывает сообщения пользовательского интерфейса. В качестве теста просто создайте новое приложение FireMonkey и разместите около 100 панелей на форме. Затем скомпилируйте его и посмотрите, как быстрые движения мыши над вашей формой могут вызвать значительный всплеск использования ЦП. ... – SilverWarior

+0

... Такой тест способен максимизировать использование ЦП на моем 7-летнем ноутбуке, и это не просто слабый ноутбук, так как я могу играть в Far Cry 3 в настройках среды без каких-либо проблем. Теперь я могу себе представить, какое использование ЦП будет использовать FireMonkey на мобильном телефоне, используя этот сценарий. Вот почему я не планирую использовать FireMonkey для любой разработки игры. – SilverWarior

ответ

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.

EDIT: Основной пример для Deltatime (Истекшее время с момента последнего вызова в секундах). Учитывая, что вы разместили ViewPort3D в Form1, добавлен TCube с именем Cube1, TGameLoop объявлен в том же подразделении, что и Form1 (я знаю, уродливый, но Demopurpose) и 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 (Двухместный)

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; 
+0

Большое спасибо за ваш ответ! После некоторого тестирования кажется, что это не решает проблему, которая заставляет мое событие OnMouseMove «отставать», но я думаю, что мне нужно идти глубже в тестировании, так что это может сработать. Знаете ли вы, есть ли шанс поиграть с FPS? На мобильном телефоне, похоже, работает ** очень ** медленно: -/Спасибо! – KoalaGangsta

+0

Вы проверили, является ли ваша гамелига «горящим» процессором? Вы не указали Телефон, с которым работаете. В противном случае включение его в дополнительный поток может быть вариантом, как описано в других комментариях вашего mainpost. Возможно, вы можете подробнее рассказать о том, над чем работаете? На это можно влиять. Чтобы играть в FPS, используйте AniFrameRate-Property. –

+0

Что-то еще, что вы могли бы попробовать, это опрос Keys/Mouse в вашем Gameloop, прежде чем выполнять свою логику, чтобы избежать помех от событий. Затем вашей гамелогии необходимо получить доступ к некоторому интерфейсу, в котором вы сохранили эти значения. –