2017-08-31 70 views
1

計時器和表格存在一些奇怪的錯誤。計時器不會在表格關閉之前打勾

我正在編輯遊戲。編輯器有兩種形式 - MainFormPreviewFormPreviewForm只包含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.InvokeRequiredglSurface.InvokeRequired都是false

請幫我弄清楚到底發生了什麼。

+0

'PreviewForm.Show(本); //表單是「模態」形式是「不」模態。 ShowDialog會使它成爲模態。 – LarsTech

+3

它看起來像你在創建它們之前啓動計時器。然後當你創建它們時,你不會啓動它們。 – LarsTech

+0

'我已經測試過可能的跨線程交互':你在使用多線程嗎? Windows.Forms.Timers是爲單線程應用程序設計的。 –

回答

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

如果您需要更多的答案 - 您需要提供[mcve] –

0

編輯:有一個更深層的問題。

你真的不應該使用系統計時器來驅動你的遊戲更新和渲染。

大多數平臺上的系統定時器的準確度低,不適用於高性能多媒體,如音頻和大多數遊戲。在Windows上,System.Windows.Forms.Timer使用精度非常低的Win32定時器,通常導致間隔至少15ms(見this answer)。有關更多信息,請參閱this technical breakdownthis overview。基本上,即使你的代碼工作正常,你的框架會結結巴巴。

大多數遊戲「嘀」通過在主線程中運行一個無限循環,這樣每次(順序不一定)以下:

  • 回調到框架來處理未決的OS事件。
  • 跟蹤自上次「打勾」以來的時差,因此與幀無關的定時起作用。
  • 更新遊戲狀態(例如物理和遊戲邏輯,可能在另一個線程中)。
  • 根據以前的更新渲染場景(可能在另一個線程中)。

正如評論者所指出的,在你的計時器代碼的主要問題是初始化RunOnVisibleChanged之間的分裂。我無法重現在子窗體關閉後計時器觸發的情況。我懷疑你沒有發佈的其他代碼是原因。如果您使用OpenTK.GameWindow,您將爲自己節省很多麻煩。它爲您處理豐滿的循環,類似於XNA。 This是將其與WinForms集成的一種方法的示例。有關更多信息,請參閱the manual

Run中,您設置了Interval並啓動每個計時器。否設置Tick回調。在OnVisibleChanged中,重新創建計時器並分配Tick回調。沒有設置間隔,並且計時器尚未啓動。

Run中的定時器初始化代碼基本上被忽略,因爲沒有設置刻度回調,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. 
    // ... 
} 
+0

請注意我在OP下的評論。 –