我正在开发一款使用图形类来创建游戏中所有图片的游戏,我也试图从一个单独的线程中绘制图片以阻止我的主线程冻结。但每次我尝试运行程序时,表单上都没有显示任何内容,我收到此错误...
System.NullReferenceException:未将对象引用设置为对象的实例。
在c:\ users \ samuel \ documents \ visual studio 2012 \ Projects \ Single_Player \ Single_Player \ Form1.vb:第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
答案 0 :(得分: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
答案 1 :(得分:0)
上述答案不正确。 CAN 完成,然后 CAN 做得更好。
定时器以预设的间隔运行,这对于可预测性而言是好的,但对性能有害,因为它总是以相同的速度或更慢的速度运行。计时器在游戏中很昂贵,所以如果有的话,请相应地使用它们。
下面是我写的一个快速示例,它在线程中显示绘图,将其传递给委托并使用扫描层将图像写入主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