2012-12-29 71 views
2

我目前正在用GDI +製作遊戲,我知道它不是開發遊戲的最佳解決方案,但由於它是一個學校項目,我別無選擇。圖形在GDI +外形下呈現+

大約每運行一次我的遊戲,圖形都會在屏幕左上角的窗體外呈現。

我正在使用雙緩衝,如果這有助於縮小問題。

渲染代碼如下所示:

while (true) 
{  
    // Create buffer if it don't exist already 
    if (context == null) 
    { 
     context = BufferedGraphicsManager.Current; 
     this.buffer = context.Allocate(CreateGraphics(), this.DisplayRectangle); 
    } 

    // Clear the screen with the forms back color 
    this.buffer.Graphics.Clear(this.BackColor); 

    // Stuff is written to the buffer here, example of drawing a game object: 
    this.buffer.Graphics.DrawImage(
     image: SpriteSheet, 
     destRect: new Rectangle(
      this.Position.X 
      this.Position.Y 
      this.SpriteSheetSource.Width, 
      this.SpriteSheetSource.Height), 
     srcX: this.SpriteSheetSource.X, 
     srcY: this.SpriteSheetSource.Y, 
     srcWidth: this.SpriteSheetSource.Width, 
     srcHeight: this.SpriteSheetSource.Height, 
     srcUnit: GraphicsUnit.Pixel); 

    // Transfer buffer to display - aka back/front buffer swapping 
    this.buffer.Render(); 
} 

它很容易用截圖來解釋:

Game gfx outside win form problem

+0

是直接渲染到窗體還是控件? –

+0

什麼是'BufferedGraphicsManager'的樣子? – Ryan

+0

它正在被渲染到窗體中,代碼被放置在從Form基類派生的類中。 – Lange

回答

2

大約每十一次我跑我的遊戲,圖形獲取呈現 超出屏幕左上角的表格。

從屏幕快照和描述中,您偶爾會繪製到Window的桌面設備上下文(DC);獲取DC時使用零窗口句柄(IntPtr.Zero)的效果是什麼。 這使我相信你可以在窗體創建之前開始遊戲循環,從而使圖形上下文指向一個零窗口句柄。

正如評論所證實的那樣,您正在爲遊戲循環使用一個單獨的線程,導致這種情況的隨機行爲。一旦處理線程,在線程啓動和完成的時間(尤其是線程可以通過多核/ cpu計算機並行運行)時,並不總是獲得相同的結果兩次。每次運行遊戲應用程序時,遊戲循環線程都可以在UI線程上的窗體窗口創建並顯示之前啓動並執行。

4

這是Winforms中的一個設計錯誤,使BufferedGraphicsXxx類公開。它們是Winforms中雙緩衝支持的實現細節,並且它們不能很好地適應錯誤。

你肯定會使用從Allocate()返回的BufferedGraphics錯誤。您可以在遊戲循環內以高速率創建緩衝區。但是你忘記在循環結尾處理你使用的緩衝區。這將以高速率消耗設備上下文(HDC)。如果你的程序沒有運行垃圾收集程序,那麼Windows就會拔出插件並且不會讓你創建新的設備上下文。內部CreateCompatibleDC()調用將失敗並返回NULL。 BufferedGraphicsContext類否則會遺漏代碼以檢查此錯誤並使用NULL句柄進行處理。並開始繪畫到桌面窗口而不是窗體。

解決方法是將Allocate()調用移動到循環外部,以便僅執行一次。但是現在當用戶改變窗口大小時,你會遇到一個新問題,緩衝區不再是正確的大小。

越好的捕鼠器就是不使用BufferedGraphics類,而是讓Winforms保持正確。有幾種方法可以在Winforms中獲得gameloop,但最簡單的方法就是使用OnPaint()方法渲染場景,並立即請求另一個繪畫,以便一遍又一遍地調用它。與此類似:

public partial class Form1 : Form { 
    public Form1() { 
     InitializeComponent(); 
     this.DoubleBuffered = true; 
     this.ResizeRedraw = true; 
    } 
    protected override void OnPaint(PaintEventArgs e) { 
     RenderScene(e.Graphics); 
     this.Invalidate(); 
    } 
} 

其中RenderScene()應該使用傳遞的Graphics實例繪製遊戲對象。請注意,您不再需要使用已完成的Clear()。

+0

雖然在沒有任何動作時'OnPaint'的末尾會調用'Invalidate()'來使用大量的電源嗎? – Ryan

+0

當然,它會燃燒100%的核心,儘可能快地繪製幀。就像他原來的遊戲循環一樣。形式保持完全響應輸入,繪畫是一個低優先級的任務。如果這是一個問題,那麼最簡單的解決方案是一個Timer事件處理程序調用Invalidate()。一個15毫秒的間隔可以達到64 fps,30毫秒可以達到32 fps,假設它能跟上。 –