计时器和表格有一些奇怪的错误。
我正在制作游戏编辑器。编辑器有两种形式 - MainForm
和PreviewForm
。 PreviewForm
仅包含OpenGL输出控件(基于OpenTK的GLControl的自定义控件),名为glSurface
。
glSurface有两个内联计时器(Windows.Forms.Timer
) - 一个用于渲染,另一个用于更新游戏状态。计时器以glSurface
方法Run(double updateRate, double frameRate)
触发。
所以,我想展示PreviewForm
并从MainForm
运行更新和渲染。
我的代码是:
PreviewForm = new PreviewForm();
PreviewForm.glSurface.Run(60d, 60d);
PreviewForm.Show(this); //Form is "modal"
Run
方法的正文:
if (Running)
throw new Exception("Already run");
_updateRate = updateRate;
_renderRate = frameRate;
var renderFrames = Convert.ToInt32(1000/frameRate);
var updateFrames = Convert.ToInt32(1000/updateRate);
RenderTimer.Interval = renderFrames;
UpdateTimer.Interval = updateFrames;
RenderTimer.Start();
UpdateTimer.Start();
Running = true;
正在OnVisibleChanged事件中初始化计时器:
protected override void OnVisibleChanged(EventArgs e)
{
...
RenderTimer = new Timer();
UpdateTimer = new Timer();
RenderTimer.Tick += RenderTick;
UpdateTimer.Tick += UpdateTick;
...
}
奇怪的事情从这里开始。
显示PreviewForm
时,没有任何反应。但当我关闭那个表格时,两个计时器都会发射它们的事件!我已经测试了可能的跨线程交互,但PreviewForm.InvokeRequired
和glSurface.InvokeRequired
都是false
。
请帮我弄清楚到底发生了什么。
答案 0 :(得分:0)
在这种情况下,在一个代码块中声明并初始化并启动计时器:
{
.../...
RenderTimer = new Timer();
UpdateTimer = new Timer();
RenderTimer.Tick += RenderTick;
UpdateTimer.Tick += UpdateTick;
var renderFrames = Convert.ToInt32(1000/frameRate);
var updateFrames = Convert.ToInt32(1000/updateRate);
RenderTimer.Interval = renderFrames;
UpdateTimer.Interval = updateFrames;
RenderTimer.Start();
UpdateTimer.Start();
.../...
}
如果没有看到程序流程,这是最安全的选择。看来这些变量是OnVisibleChanged
事件的本地变量,所以当您从if (Running)
调用它们时,我不确定您是如何获得空参考例外的
您可以做的另一件事是让它们成为类变量,并确保在使用它们之前对它们进行初始化。然后在if语句中调用start。
至于表单关闭时开始的问题,无法根据您所显示的代码确定。
答案 1 :(得分:0)
修改:这是一个更深层次的问题。
你真的不应该使用系统计时器来推动游戏更新和渲染。
大多数平台上的系统定时器都具有低精度,不适合高性能多媒体,如音频和大多数游戏。在Windows System.Windows.Forms.Timer
上使用精度特别低的Win32计时器,通常会导致间隔时间至少减少15毫秒(请参阅this answer)。有关详细信息,请参阅this technical breakdown和this overview。基本上,即使您的代码正常工作,您的框架也会断断续续。
大多数游戏" tick"通过在主线程中运行无限循环,每次执行以下操作(不一定按此顺序):
如评论者所述,您的计时器代码中的主要问题是初始化分为Run
和OnVisibleChanged
。我无法重现在子窗体关闭后计时器触发的情况。我怀疑你发布的其他代码是原因。如果您使用OpenTK.GameWindow
,您将为自己省去很多麻烦。它为你处理循环饱满,类似于XNA。 This是将其与WinForms集成的一种方法的示例。有关详细信息,请参阅the manual。
在Run
中,您设置Interval
并启动每个计时器。未设置Tick
个回调。在OnVisibleChanged
中,您可以重新创建计时器并指定Tick
个回调。没有设置间隔,并且计时器尚未启动。
Run
中的计时器初始化代码基本上被忽略,因为没有设置tick回调并且OnVisibleChanged
重新创建了计时器。在您致电OnVisibleChanged
后不久,Run
几乎会立即触发PreviewForm.Show(this)
。
如果您已经开始使用系统计时器,那么这应该可行:
// somewhere before Run(ideally in the initialization of the main form).
RenderTimer.Interval = Convert.ToInt32(1000 / frameRate);
RenderTimer.Tick += RenderTick;
UpdateTimer.Interval = Convert.ToInt32(1000 / updateRate);
UpdateTimer.Tick += UpdateTick;
void Run(double frameRate, double updateRate)
{
// ...
RenderTimer.Start();
UpdateTimer.Start();
// ...
Running = true;
}
// ...
protected override void OnVisibleChanged(EventArgs e)
{
// ...
// Don't initialize timers here.
// ...
}