×

DirectX与VB.NET编程(十四)窗口动画与剪切

Kalet Kalet 发表于2009-03-20 12:00:14 浏览218 评论0

抢沙发发表评论

拖了很久的剪切终于要在这一章中被终结了,本章的主要内容是窗口模式下的绘图,相信看过上个教程的朋友一定对全屏下翻转的一些限制有所不满,这一章中也将介绍仿翻转方式,仿翻转模式是本人经过试验摸索出来的(但我不排除事先已经有这个技术),仿翻转不仅在上效果和翻转相差无几,而且甩开了诸多束缚,馋人的话就不说了,下面切入正题。


===============华丽的分割线===============


学习内容: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()

DirectX与VB.NET编程(十四)窗口动画与剪切

        '该调用是 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编程(十四)窗口动画与剪切

===============华丽的分割线===============


下次的估计内容是色深理论和图片的智能加载,仅仅是估计。。。


群贤毕至

访客