Koa*_*sta 6 delphi android game-engine game-loop firemonkey
我目前正在Firemonkey中为我的Android手机制作2D游戏,使用一些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;
Run Code Online (Sandbox Code Playgroud)
问题是,当我陷入循环并调用过程来处理消息时,我可能遇到很多问题,而且我也不认为这是一个好方法.
方法#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;
Run Code Online (Sandbox Code Playgroud)
每次应用程序在Windows消息上空闲时都会调用它.与#1相比,性能似乎相同或稍差,整体架构更可靠,这也是我通常使用此方法的原因.然而,在Android上,当与TForm.MouseMove结合使用时,它似乎被称为不同.在Windows OnIdle保持完美工作的地方,在Android上,它将滞后/停止,而TForm.MouseMove是从触摸输入调用的(RAD Studio自动将其编译为触摸事件)
然而,我可以在MouseMove事件中通过调用Application.DoIdle
和"辅助""循环" 来激活OnIdle ,我认为它错过了beimg被调用,但这也很糟糕,并且在使用触摸输入时再次带来不必要的行为和更差的性能.
这让我回到方法#1,它现在似乎在Android上运行最好.是否有任何其他方法可以创建一个可靠的方法(像OnIdle这样的持续快速调用的事件)创建这样的循环或任何方法来避免我在方法#3中与MouseMove-Events结合使用的问题?看起来Android手机不够强大,足以在表格事件和世界更新旁边留下足够的"空闲时间"
另外,我可以考虑使用线程作为世界逻辑,并在其旁边使用我的Main-Form/Thread上的方法#1来更新渲染吗?通过无限循环(使用Application.ProcessMessages())更新表单控件并从另一个线程处理世界,对象等所显示的数据是否安全?
我认为一个有效的选择是实现 TAnimation 并覆盖 ProcessAnimation。当通过 TAnimator 运行时,TAnimation 会自动调度。
TGameLoop = class(TAnimation)
protected
procedure ProcessAnimation; override;
end;
[...]
procedure TGameLoop.ProcessAnimation;
begin
//call updates
end;
Run Code Online (Sandbox Code Playgroud)
并像这样开始:
procedure TForm1.FormCreate(Sender: TObject);
begin
FLoop := TGameLoop.Create(Self);
FLoop.Loop := True;
FLoop.Name := 'GameLoop';
FLoop.Parent := Self;
TAnimator.StartAnimation(Self, 'GameLoop');
end;
Run Code Online (Sandbox Code Playgroud)
默认情况下,动画的 FPS 设置为 60。我现在无法弄清楚的一件事是如何仅使用 TAnimation 的属性来计算自上次调用以来的 Deltatime。这对于处理可变帧时间至关重要。但您可以使用 System.Diagnostics.TTopwatch 自行执行此操作。在 ProcessAnimation 开始时停止手表并获取 Ticks,在完成 ProcessAnimation 后开始一个新的。
编辑:Deltatime 的基本示例(自上次调用以来经过的时间,以秒为单位)。假设您在 Form1 上放置了一个 ViewPort3D,添加了名为 Cube1 的 TCube,TGameLoop 在与 Form1 相同的单元中声明(我知道,丑陋,但演示目的),并且 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;
Run Code Online (Sandbox Code Playgroud)
EDIT2:一个更好的例子,可以更好地在整个游戏生命周期内分配舍入/测量误差。我们不是仅在调用之间进行测量,而是从第一帧开始进行测量。TGameLoop 有一个新的 Field 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;
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
881 次 |
最近记录: |