1

我目前正在开发一个使用图形类来创建游戏中所有图片的游戏,我还试图从一个单独的线程中绘制图片以阻止我的主线程冻结。但是每次我尝试运行程序时,表格上什么都没有出现,我得到这个错误......

System.NullReferenceException:对象引用未设置为对象的实例。

在 c:\users\samuel\documents\visual studio 2012\Projects\Single_Player\Single_Player\Form1.vb:line 12 中的 Single_Player.Form1.mainPaint(PaintEventArgs e)

如果我的代码确实有效,我希望它能在屏幕上移动一个椭圆,无论如何这是我的代码......

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
4

2 回答 2

2

您不能在工作线程中绘制,窗口基本上是线程不安全的。当计时器滴答时,您的计时器 Tick 事件处理程序将崩溃并烧毁。Tick 事件没有 PaintEventArgs 参数。

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

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
    Me.Invalidate()
End Sub
于 2013-04-10T16:12:55.560 回答
0

上面的答案是不正确的。它可以做到,然后它可以做得更好

计时器以预设的时间间隔运行,这对可预测性有好处,但对性能不利,因为它总是以相同的速度或更慢地运行。计时器在游戏中很昂贵,因此如果有的话,请相应地使用它们。

下面是我写的一个快速示例,展示了在线程中绘图,将其传递给委托并使用扫描层将图像写入主 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
于 2016-11-12T21:01:10.107 回答