2016-10-17 36 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然后需要访问某些存储这些值的界面。 –