拖了很久的剪切终于要在这一章中被终结了,本章的主要内容是窗口模式下的绘图,相信看过上个教程的朋友一定对全屏下翻转的一些限制有所不满,这一章中也将介绍仿翻转方式,仿翻转模式是本人经过试验摸索出来的(但我不排除事先已经有这个技术),仿翻转不仅在上效果和翻转相差无几,而且甩开了诸多束缚,馋人的话就不说了,下面切入正题。
===============华丽的分割线===============
学习内容:DirectX与VB.NET编程(十四)窗口动画与剪切
·了解仿翻转方式的基本原理
·了解剪切的使用原理
·利用仿翻转方式绘制窗口模式的动画
·利用剪切限制动画的显示范围
===============华丽的分割线===============
所谓仿翻转,就是按照原来翻转的原理,但是却不使用翻转,而是用其它方式实现了,其实所谓的翻转,只是DirectDraw提供的一种进阶功能了,从本质上就是将一个表面上的图像绘制到另一个表面上,我们也可以使用Draw和Drawfast的重载函数来实现。此外,这次也将使用图片框作为承载控件,如果使用窗口作为承载控件,那么可能会标题栏上也会留下作画的痕迹,而使用图片框则不会出现这种问题。
===============华丽的分割线===============
窗口作图,还需要使用另一个概念:剪切(Clipper),剪切在初始化时需要使用Window属性绑定到一个控件上去,然后表面再使用Clipper属性指定与其关联的剪切,以后该表面上所有的作图都会被指定的控件所束缚,凡是超出该控件显示范围的内容都不会被显示出来了。
===============华丽的分割线===============
本章的内容延续上一章,也是绘制很多在不断弹动的小球,因此素材也是上一张的那个小球,这里再帖出来一次,需要下载请右键点击图片来下载。但这次不同的是动画不是显示在全屏下的了,而是显示在一个640x480的窗口中。
打开VB.NET,新建一个工程,在窗口中加入一个图片框(PictureBox),并将图片框的Size属性设置为640,480,并将窗口拖动,使其尺寸能够正好显示图片框。窗口设计如下图所示:
===============华丽的分割线===============
窗口建好后,引入DirectDraw和Threading的名称空间:
Imports Microsoft.DirectX
Imports Microsoft.DirectX.DirectDraw
Imports System.Threading
声明对象,这次需要使用剪切,所以一并声明,其它的和上一章内容一样:
Dim vDev As Device
Dim vClipper As Clipper '剪切对象
Dim vPrimarySurface As Surface
Dim vBackSurface As Surface
Dim vOffSurface As Surface
Public Const BALL_COUNT As Integer = 50
Dim X(BALL_COUNT) As Integer
Dim Y(BALL_COUNT) As Integer
Dim SpeedX(BALL_COUNT) As Integer
Dim SpeedY(BALL_COUNT) As Integer
Dim Running As Boolean
Dim DrawThread As Thread
下面是对象的初始化,需要注意的是,因为我们现在是采用仿翻转模式,也就是普通的Draw方法,那么,我们不必再按照翻转模式那样去声明这些对象了:
'获取屏幕尺寸
Dim ScreenWidth As Integer = Screen.PrimaryScreen.WorkingArea.Width
Dim ScreenHeight As Integer = Screen.PrimaryScreen.WorkingArea.Height
'初始化设备
vDev = New Device(CreateFlags.Default)
vDev.SetCooperativeLevel(PictureBox1, CooperativeLevelFlags.Normal)
'初始化剪切
vClipper = New Clipper(vDev)
vClipper.Window = PictureBox1 '指定控件为PictureBox1
'初始化主表面
Dim vPrimarySurfaceCaps As New SurfaceCaps
vPrimarySurfaceCaps.PrimarySurface = True
vPrimarySurfaceCaps.VideoMemory = True
Dim vPrimarySurfaceDesc As New SurfaceDescription(vPrimarySurfaceCaps)
vPrimarySurface = New Surface(vPrimarySurfaceDesc, vDev)
vPrimarySurface.Clipper = vClipper '指定剪切为vClipper
'初始化后备表面
Dim vBackSurfaceCaps As New SurfaceCaps
vBackSurfaceCaps.OffScreenPlain = True
vBackSurfaceCaps.VideoMemory = True
Dim vBackSurfaceDesc As New SurfaceDescription(vBackSurfaceCaps)
vBackSurface = New Surface(New Bitmap(ScreenWidth, ScreenHeight), vBackSurfaceDesc, vDev)
'初始化离屏表面
Dim vOffSurfaceCaps As New SurfaceCaps
vOffSurfaceCaps.OffScreenPlain = True
vOffSurfaceCaps.VideoMemory = True
Dim vOffSurfaceDesc As New SurfaceDescription(vOffSurfaceCaps)
vOffSurface = New Surface("C:\ball.bmp", vOffSurfaceDesc, vDev)
'设置关键色以去除背景
Dim vColorKey As New ColorKey
vColorKey.ColorSpaceHighValue = RGB(255, 0, 255)
vColorKey.ColorSpaceLowValue = RGB(255, 0, 255)
vOffSurface.SetColorKey(ColorKeyFlags.SourceDraw, vColorKey)
'随机初始化各小球位置
Randomize()
For i As Integer = 0 To BALL_COUNT
X(i) = Int(Rnd() * 620)
Y(i) = Int(Rnd() * 460)
SpeedX(i) = -5 + Int(Rnd() * 10)
SpeedY(i) = -5 + Int(Rnd() * 10)
Next
'初始化设置线程
Running = True
DrawThread = New Thread(AddressOf Draw)
DrawThread.Start()
从上面代码可以看出,主表面的Flip和Complex属性不需要改变而让其保留默认值,后备表面直接用New获得实例而不是再由主表面获取,此外后备表面的类型也发生了改变,由原来的BackBuffer变为了OffScreenPlain,因为BackBuffer仅仅用于翻转模式,在这一章中并不适合,最后需要声明一点的就是后备表面在这里必须使用一个图像来初始化,因此我们使用一个空的Bitmap来进行初始化,你也可以使用其它图片,只是这样做毫无意义,而且这里做还有一个重要的过程就是尺寸的设置,尺寸必须为你屏幕分辨率的尺寸,如果你改变这个尺寸,你会发现显示出来的画面也会随之发生拉伸或压缩,如果尺寸为你需要绘制的尺寸,那么效果就是全屏了。
===============华丽的分割线===============
最后就是Draw方法的实现了,既然前期工作已经做好了,我们只要简单地使用Draw方法就可以实现与Flip一样的效果了,还需要说明的是,上次的代码存在一个线程锁死的问题,可能会导致机器假死的问题,不过目前已经修改了文章,这一章也是使用修正后的代码:
While Running
Try
'将各小球重复地从离屏表面中绘制到后备表面上
For i As Integer = 0 To BALL_COUNT
vBackSurface.DrawFast(X(i) + Me.Left + PictureBox1.Left, Y(i) + Me.Top + PictureBox1.Top, vOffSurface, New Rectangle(0, 0, 20, 20), DrawFastFlags.SourceColorKey)
Next
'让主表面实现翻转,从而将后备表面中的图像绘制到主表面上
vPrimarySurface.Draw(vBackSurface, DrawFlags.Wait)
'清楚后备表面中的图片
vBackSurface.ColorFill(Color.Black)
Catch ex As Exception
MsgBox("程序出现异常,被迫退出。")
ExitProg(False)
Exit Sub
End Try
'小球处理位置
For i As Integer = 0 To BALL_COUNT
X(i) += SpeedX(i)
Y(i) += SpeedY(i)
If X(i) <= 0 Then
X(i) = 0
SpeedX(i) = -SpeedX(i)
End If
If X(i) >= 620 Then
X(i) = 620
SpeedX(i) = -SpeedX(i)
End If
If Y(i) <= 0 Then
Y(i) = 0
SpeedY(i) = -SpeedY(i)
End If
If Y(i) >= 460 Then
Y(i) = 460
SpeedY(i) = -SpeedY(i)
End If
Next
'线程延迟休息
Thread.Sleep(50)
End While
===============华丽的分割线===============
在这里再把修正后的ExitProg函数贴出来一次:
Private Sub ExitProg(ByVal Wait As Boolean)
'结束程序
Running = False
'等待线程结束 If Wait Then
While DrawThread.ThreadState <> ThreadState.Stopped
End While
End If
'释放内存
vOffSurface.Dispose()
vOffSurface = Nothing
vBackSurface.Dispose()
vBackSurface = Nothing
vPrimarySurface.Dispose()
vPrimarySurface = Nothing
vDev.Dispose()
vDev = Nothing
X = Nothing
Y = Nothing
SpeedX = Nothing
SpeedY = Nothing
Running = Nothing
DrawThread = Nothing
'退出程序
End
End Sub
上次的代码是由于当Draw函数发生错误时ExitProg被调用的话,在等待线程结束时两个线程会发生锁死情况,程序也会陷入假死的现象,因此我们在这里加入一个Wait参数,决定是否等待线程结束,凡是来自Draw函数的错误我们则屏蔽这个等待的过程。
===============华丽的分割线===============
程序的其它部分和上一章相同,全部代码如下:
Imports Microsoft.DirectX
Imports Microsoft.DirectX.DirectDraw
Imports System.Threading
'获取系统组件尺寸
Public Class Form1
Inherits System.Windows.Forms.Form
Dim vDev As Device
Dim vClipper As Clipper '剪切对象
Dim vPrimarySurface As Surface
Dim vBackSurface As Surface
Dim vOffSurface As Surface
Public Const BALL_COUNT As Integer = 50
Dim X(BALL_COUNT) As Integer
Dim Y(BALL_COUNT) As Integer
Dim SpeedX(BALL_COUNT) As Integer
Dim SpeedY(BALL_COUNT) As Integer
Dim Running As Boolean
Dim DrawThread As Thread
#Region " Windows 窗体设计器生成的代码 "
Public Sub New()
MyBase.New()
'该调用是 Windows 窗体设计器所必需的。
InitializeComponent()
'在 InitializeComponent() 调用之后添加任何初始化
End Sub
'窗体重写 dispose 以清理组件列表。
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Windows 窗体设计器所必需的
Private components As System.ComponentModel.IContainer
'注意: 以下过程是 Windows 窗体设计器所必需的
'可以使用 Windows 窗体设计器修改此过程。
'不要使用代码编辑器修改它。
Friend WithEvents PictureBox1 As System.Windows.Forms.PictureBox
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.PictureBox1 = New System.Windows.Forms.PictureBox
Me.SuspendLayout()
'
'PictureBox1
'
Me.PictureBox1.Location = New System.Drawing.Point(0, 0)
Me.PictureBox1.Name = "PictureBox1"
Me.PictureBox1.Size = New System.Drawing.Size(640, 480)
Me.PictureBox1.TabIndex = 0
Me.PictureBox1.TabStop = False
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(6, 14)
Me.ClientSize = New System.Drawing.Size(634, 476)
Me.Controls.Add(Me.PictureBox1)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle
Me.MaximizeBox = False
Me.Name = "Form1"
Me.Text = "动画"
Me.ResumeLayout(False)
End Sub
#End Region
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'获取屏幕尺寸
Dim ScreenWidth As Integer = Screen.PrimaryScreen.WorkingArea.Width
Dim ScreenHeight As Integer = Screen.PrimaryScreen.WorkingArea.Height
'初始化设备
vDev = New Device(CreateFlags.Default)
vDev.SetCooperativeLevel(PictureBox1, CooperativeLevelFlags.Normal)
'初始化剪切
vClipper = New Clipper(vDev)
vClipper.Window = PictureBox1 '指定控件为PictureBox1
'初始化主表面
Dim vPrimarySurfaceCaps As New SurfaceCaps
vPrimarySurfaceCaps.PrimarySurface = True
vPrimarySurfaceCaps.VideoMemory = True
Dim vPrimarySurfaceDesc As New SurfaceDescription(vPrimarySurfaceCaps)
vPrimarySurface = New Surface(vPrimarySurfaceDesc, vDev)
vPrimarySurface.Clipper = vClipper '指定剪切为vClipper
'初始化后备表面
Dim vBackSurfaceCaps As New SurfaceCaps
vBackSurfaceCaps.OffScreenPlain = True
vBackSurfaceCaps.VideoMemory = True
Dim vBackSurfaceDesc As New SurfaceDescription(vBackSurfaceCaps)
vBackSurface = New Surface(New Bitmap(ScreenWidth, ScreenHeight), vBackSurfaceDesc, vDev)
'初始化离屏表面
Dim vOffSurfaceCaps As New SurfaceCaps
vOffSurfaceCaps.OffScreenPlain = True
vOffSurfaceCaps.VideoMemory = True
Dim vOffSurfaceDesc As New SurfaceDescription(vOffSurfaceCaps)
vOffSurface = New Surface("C:\ball.bmp", vOffSurfaceDesc, vDev)
'设置关键色以去除背景
Dim vColorKey As New ColorKey
vColorKey.ColorSpaceHighValue = RGB(255, 0, 255)
vColorKey.ColorSpaceLowValue = RGB(255, 0, 255)
vOffSurface.SetColorKey(ColorKeyFlags.SourceDraw, vColorKey)
'随机初始化各小球位置
Randomize()
For i As Integer = 0 To BALL_COUNT
X(i) = Int(Rnd() * 620)
Y(i) = Int(Rnd() * 460)
SpeedX(i) = -5 + Int(Rnd() * 10)
SpeedY(i) = -5 + Int(Rnd() * 10)
Next
'初始化设置线程
Running = True
DrawThread = New Thread(AddressOf Draw)
DrawThread.Start()
End Sub
Private Sub Draw()
While Running
Try
'将各小球重复地从离屏表面中绘制到后备表面上
For i As Integer = 0 To BALL_COUNT
vBackSurface.DrawFast(X(i) + Me.Left + PictureBox1.Left, Y(i) + Me.Top + PictureBox1.Top, vOffSurface, New Rectangle(0, 0, 20, 20), DrawFastFlags.SourceColorKey)
Next
'让主表面实现翻转,从而将后备表面中的图像绘制到主表面上
vPrimarySurface.Draw(vBackSurface, DrawFlags.Wait)
'清楚后备表面中的图片
vBackSurface.ColorFill(Color.Black)
Catch ex As Exception
MsgBox("程序出现异常,被迫退出。")
ExitProg(False)
Exit Sub
End Try
'小球处理位置
For i As Integer = 0 To BALL_COUNT
X(i) += SpeedX(i)
Y(i) += SpeedY(i)
If X(i) <= 0 Then
X(i) = 0
SpeedX(i) = -SpeedX(i)
End If
If X(i) >= 620 Then
X(i) = 620
SpeedX(i) = -SpeedX(i)
End If
If Y(i) <= 0 Then
Y(i) = 0
SpeedY(i) = -SpeedY(i)
End If
If Y(i) >= 460 Then
Y(i) = 460
SpeedY(i) = -SpeedY(i)
End If
Next
'线程延迟休息
Thread.Sleep(50)
End While
End Sub
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
'按ESC退出程序
If e.KeyCode = Keys.Escape Then
ExitProg(True)
End If
End Sub
Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
ExitProg(True)
End Sub
Private Sub ExitProg(ByVal Wait As Boolean)
'结束程序
Running = False
'等待线程结束
If Wait Then
While DrawThread.ThreadState <> ThreadState.Stopped
End While
End If
'释放内存
vOffSurface.Dispose()
vOffSurface = Nothing
vBackSurface.Dispose()
vBackSurface = Nothing
vPrimarySurface.Dispose()
vPrimarySurface = Nothing
vDev.Dispose()
vDev = Nothing
X = Nothing
Y = Nothing
SpeedX = Nothing
SpeedY = Nothing
Running = Nothing
DrawThread = Nothing
'退出程序
End
End Sub
End Class
===============华丽的分割线===============
程序的运行效果如下:
DirectX与VB.NET编程(十四)窗口动画与剪切
===============华丽的分割线===============
下次的估计内容是色深理论和图片的智能加载,仅仅是估计。。。