2013-04-10 109 views
1

我目前正在使用图形类创建游戏中所有图片的游戏,我也试图从单独的线程中绘制图片来阻止我的主线程冻结。但每次我尝试运行程序时,窗体出现什么也没有它,我得到这个错误...从一个单独的线程绘画?

System.NullReferenceException:对象不设置到对象的实例。

在Single_Player.Form1.mainPaint(PaintEventArgs的E)在C:\用户\塞缪尔\文件\视觉工作室2012 \项目\ Single_Player \ Single_Player \ Form1.vb的:行12

如果我的代码做的工作,我期待它在屏幕上移动一个椭圆,反正这是我的代码...

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load 
    Dim paintT As New Threading.Thread(AddressOf mainPaint) 
    paintT.Start() 
End Sub 

Private Sub mainPaint(e As PaintEventArgs) 
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias 
    e.Graphics.CompositingQuality = Drawing2D.CompositingQuality.HighQuality 
    Dim playerX As Integer = 0 
    Dim playerY As Integer = 206 
    Do While 0/0 
     e.Graphics.DrawRectangle(New Pen(Color.FromArgb(128, Color.Black)), 0, 0, 884, 24) 
     e.Graphics.FillRectangle(New SolidBrush(Color.FromArgb(128, Color.Black)), 0, 0, 884, 24) 
     e.Graphics.DrawString("Life: 5", New Font("DigifaceWide", 20, GraphicsUnit.Pixel), New SolidBrush(Color.FromArgb(191, Color.Green)), 2, 0) 
     e.Graphics.DrawString("Score: 0", New Font("DigifaceWide", 20, GraphicsUnit.Pixel), New SolidBrush(Color.FromArgb(191, Color.White)), 100, 0) 
     e.Graphics.FillEllipse(New SolidBrush(Color.FromArgb(128, Color.Blue)), playerX, playerY, 24, 24) 
     playerX = playerX + 1 
     playerY = playerY + 1 
     e.Graphics.Clear(Color.Transparent) 
     Threading.Thread.Sleep(50) 
    Loop 
End Sub 

更新(再次) 我到目前为止的代码(这时候我已经通过设计窗口中增加了一个定时器)...

Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown 
    Timer1.Interval = 50 
    Timer1.Start() 
End Sub 

Private Sub mainBackgroundPB_Paint(sender As Object, e As PaintEventArgs) Handles Timer1.Tick 
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias 
    e.Graphics.CompositingQuality = Drawing2D.CompositingQuality.HighQuality 
    e.Graphics.DrawRectangle(New Pen(Color.FromArgb(128, Color.Black)), 0, 0, 884, 24) 
    e.Graphics.FillRectangle(New SolidBrush(Color.FromArgb(128, Color.Black)), 0, 0, 884, 24) 
    e.Graphics.DrawString("Life: 5", New Font("DigifaceWide", 20, GraphicsUnit.Pixel), New SolidBrush(Color.FromArgb(191, Color.Green)), 2, 0) 
    e.Graphics.DrawString("Score: 0", New Font("DigifaceWide", 20, GraphicsUnit.Pixel), New SolidBrush(Color.FromArgb(191, Color.White)), 100, 0) 
    e.Graphics.FillEllipse(New SolidBrush(Color.FromArgb(128, Color.Blue)), playerX, playerY, 24, 24) 
    playerX = playerX + 1 
    playerY = playerY + 1 
    e.Graphics.Clear(Color.Transparent) 
End Sub 
+2

这是因为'PaintEventArgs'为空,你有没有从程序线程 – vmeln 2013-04-10 13:50:53

+3

也是为什么你要画上一个differeent线程传递给'mainPaint',如果你想要的东西发生只使用一个计时器因为你不能从另一个线程绘制。 – user1937198 2013-04-10 13:52:19

+0

@VolodymyrMelnychuk:那么我将如何将参数传递给mainPaint? – Sam 2013-04-10 13:58:00

回答

2

你不能在工作线程漆,窗户根本线程安全的。计时器滴答事件处理程序会在计时器滴答时崩溃并烧毁。 Tick事件没有PaintEventArgs参数。

只有Paint事件有这些参数,添加事件和移动你的代码。您可以使用计时器触发绘画,使事件处理程序如下所示:

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick 
    Me.Invalidate() 
End Sub 
0

上面的答案不正确。它CAN来完成,然后将其CAN可以做得更为出色。

定时器,以预设间隔,它可以很好的对predictabilitys起见运行,但不利于性能,因为它总是会以相同的速度或更慢运行。定时器在游戏中是昂贵的,所以要相应地使用它们,如果有的话。

下面是一个简单的例子我写了表示在一个线程绘制,其传递到一个委托和使用扫描层写入图像到主UI。

改进建议被注释为好。

Public Class Form1 

Enum enumGameObjectTypes 
    RedBox = 0 
    BlueBox = 1 
    YellowCircle = 2 
End Enum 

Structure structGameObjects 
    Dim Type As enumGameObjectTypes 
    Dim Location As Point 
End Structure 

' When you have global variables, they are accessible outside the thread. 
' You cannot access them on the drawing thread however, without using SyncLock to lock 
' the object. 
Public GameObjects As New List(Of structGameObjects) 
Public tDrawingThread As New System.Threading.Thread(AddressOf DrawingThread) 

' We have to use delegates to pass the image 
Delegate Sub DrawingDelegateSub(TheDrawnImage As Image) 
Public DrawingDelegate As DrawingDelegateSub 

' Set this to true when it's time to exit cleanly 
Public _ExitThreadSafelyNow As Boolean = False 

Public Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load 
    Dim rand As New Random 

    For cnt = 1 To rand.Next(5, 20) 
     ' Create some game objects 
     Dim newObj As New structGameObjects 
     With newObj 
      .Type = rand.Next(0, 2) 
      .Location = New Point(rand.Next(0, cnt * 100), rand.Next(0, cnt * 50)) 
     End With 
    Next 

    ' Set up a delegate (basically a worker that can "move data between threads" safely) 
    DrawingDelegate = AddressOf DrawImage 

    ' Start the thread 
    tDrawingThread.Start() 
End Sub 

Public Sub DrawImage(ByVal DrawnImage As Bitmap) 

    Dim tmpImage As New Bitmap(1024, 768) 

    ' Draw the image to the picture box using an intermediary layer... 
    ' The reason we dont just say pb.Image = DrawnImage is because the bitmaps are passed by pointer references NOT just by the image 
    ' So, passing it direct would be non-thread safe. 
    ' 
    ' This also isn't the *right* way to do this, but it is just an example. 
    ' 
    ' What we're doing is all the heavy lifting in the thread and then we're scanning pixel by pixel through the image drawn by the thread. 
    ' 
    ' The performance here will be awful, so the fix; if you can understand how it works, is to use Bitmap.LockBits; 
    ' Performance will ramp up 1000x right away. https://msdn.microsoft.com/en-us/library/5ey6h79d(v=vs.110).aspx 
    For x = 1 To DrawnImage.Width - 1 
     For y = 1 To DrawnImage.Height - 1 
      tmpImage.SetPixel(x, y, DrawnImage.GetPixel(x, y)) 
     Next 
    Next 

    pb.Image = tmpImage 
    Debug.Print("Wrote frame !") 
End Sub 

' Main drawing loop 
Sub DrawingThread() 
    Dim rectBounds As New Rectangle(0, 0, 1024, 768) 

    ' 
    ' 
    Do Until _ExitThreadSafelyNow = True 

     ' Set up the next frame 
     Dim ImageBuffer As New Bitmap(rectBounds.Width, rectBounds.Height) 
     Dim g As Graphics = Graphics.FromImage(ImageBuffer) ' Create the graphics object 
     g.FillRectangle(Brushes.Black, rectBounds) 

     ' Lock the gameobjects object until we're done using it. 
     SyncLock GameObjects 
      For Each obj In GameObjects 
       Select Case obj.Type 
        Case enumGameObjectTypes.RedBox 
         g.FillRectangle(Brushes.Red, New Rectangle(obj.Location.X, obj.Location.Y, 15, 15)) 
         ' 
        Case enumGameObjectTypes.BlueBox 
         g.FillRectangle(Brushes.Blue, New Rectangle(obj.Location.X, obj.Location.Y, 20, 20)) 
         ' 
        Case enumGameObjectTypes.YellowCircle 
         g.FillEllipse(Brushes.Yellow, New Rectangle(obj.Location.X, obj.Location.Y, 15, 15)) 
         ' 
        Case Else 
         g.FillEllipse(Brushes.Pink, New Rectangle(obj.Location.X, obj.Location.Y, 15, 15)) 
       End Select 
      Next 
     End SyncLock 


     '/// All done drawing by this point... 

     'TODO: Eventually implement Bitmap.LockBits here! 


     SyncLock ImageBuffer 
      ' Send the image onto the main thread. 
      Call DrawingDelegate(ImageBuffer) 
     End SyncLock 


    Loop 
End Sub 

Private Sub Form1_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing 

    _ExitThreadSafelyNow = True 
End Sub 
End Class