2013-04-10 104 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