2016-10-17 68 views
6

我正在使用一些TImage控件並控制它們的位置和角度爲我的Android手機制作Firemonkey中的2D遊戲。就那麼簡單。我試圖用我正常的循環方式,它在Windows中運行良好/無瑕,但在Android上失敗。Android版Firemonkey中的遊戲循環

方法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事件。

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結合起來似乎被稱爲不同的。 OnIdle在Windows OnIdle上一直保持正常工作,在Android上,它會在觸摸輸入中調用TForm.MouseMove時延遲/停止(RAD Studio自動將它編譯爲觸摸事件)

但是我可以在MouseMove中自己觸發OnIdle通過調用Application.DoIdle和「協助」「循環」,我認爲它錯過了被稱爲beimg的事件,但這種方法也非常糟糕,並且在使用觸摸輸入時會帶來不必要的行爲和更糟的性能。

這讓我回到方法#1,它似乎現在在Android上效果最好。有沒有其他的方式來創建一個可靠的方法(像OnIdle這樣一個持續而快速的被稱爲事件)創建這樣一個循環或以任何方式來避免方法#3與MouseMove事件結合所面臨的問題?看起來Android手機沒有足夠強大的功能,足以在格式事件和世界更新旁邊擁有足夠的「空閒時間」

此外,我可以考慮使用線程作爲世界邏輯,並且在它旁邊使用方法# 1在我的主窗體/線程上更新渲染?通過無限循環(使用Application.ProcessMessages())更新表單控件並從另一個處理世界上的線程,對象等獲取所顯示的數據是否安全?

+0

您可以使用匿名線程(與之同步)來實現這一 – nolaspeaker

+0

如果你想避免遊戲循環滯後FireMonkey UI引起那麼你最好的選擇是將所有的遊戲邏輯到輔助線程。但即便如此,如果目標設備只有一個處理核心,這可能會完全解決您的問題。您看到FireMonkey在處理UI消息時非常糟糕/緩慢。作爲一項測試,只需創建一個新的FireMonkey應用程序,並在窗體上放置大約100個面板。然後對其進行編譯並觀察鼠標在您的表單上的移動速度如何會導致CPU使用率顯着上升。 ... – SilverWarior

+0

...這樣的測試是能夠最大限度地利用我的7歲筆記本電腦的CPU使用率,它不僅僅是一些弱電筆記本,因爲我可以在媒體設置上播放Far Cry 3而不會有任何問題。現在我可以想象使用這種情況下FireMonkey在手機上會導致哪種CPU使用率。這就是爲什麼我不考慮使用FireMonkey進行任何遊戲開發。 – SilverWarior

回答

1

我認爲一個有效的選擇是實現TAnimation並覆蓋ProcessAnimation。通過TAnimator運行時,會自動計劃TAnimation。

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的動畫。我現在無法弄清的一件事是如何通過僅使用TAnimation的屬性來計算自上次調用以來的Deltatime。這是處理可變幀時間所必需的。但是你冷靜地使用System.Diagnostics.TStopwatch來做這件事。在ProcessAnimation開始時停下手錶並獲取Ticks,在完成ProcessAnimation後開始新手錶。

編輯: Deltatime的一個基本示例(上次調用以來的時間(以秒爲單位))。假設你在Form1上放置了一個ViewPort3D,添加了名爲Cube1的TCube,TGameLoop被聲明在與Form1相同的單元中(我知道,醜陋但是​​Demopurpose)並且TGameLoop有一個類型爲TStopWatch的Field FWatch。

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; 

編輯2:一個更好的例子,在整個遊戲生命週期中分佈舍入/測量錯誤好一點。我們不是隻在兩次通話之間進行測量,而是從第一幀起測量。 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

您是否檢查過您的gamelogic是否正在「燒」CPU?您尚未指定您正在使用的手機。否則,將其放入一個額外的線程可能是一個選項,正如在您的主貼的其他評論中討論的。 也許你可以詳細說明你正在做什麼更詳細?有很多可以影響它。 要玩FPS,請使用AniFrameRate-Property。 –

+0

你可以嘗試的其他東西是在執行邏輯之前在你的Gameloop中輪詢鍵/鼠標,以避免與事件相沖突。您的gamelogic然後需要訪問某些存儲這些值的界面。 –