2012-12-29 66 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,假设它能跟上。 –