DirectDraw7 / Direct3D
Hybrid Engine
Sample Project
There comes a point in most 2D game projects
using DirectDraw when you start wondering
how you can do all those cool alpha blending
and rotation effects you've seen in [insert
your favorite 2D game here]. Now, you could
switch over to DirectGraphics8 to do all
your 2D graphics, but the major drawback
with that is that a.) It is more complicated,
and you have a learn a whole different
structure (No DirectDraw!), and b.) You
can't easily do surfaces of any size
- D3D surfaces need to be square and a power
of 2 size no bigger than 256x256 (this is
due to hardware limitations of your users).
But! What if you could use DirectDraw for
the parts of your game that need big
surfaces, and use Direct3D in conjunction
for the parts that need alpha blending,
rotation, scaling, etc.? You can! This
tutorial will explain (hopefully) simply how
to do this.
Initializing DirectX
You initialize DirectX almost the same as
a normal DirectDraw project, (See how in
this tutorial) with a few exceptions:
1.) Declaring the D3D variables:
Dim d3d As Direct3D7
Public dev As Direct3DDevice7
2.) Initializing the primary surface
to be D3D capable:
ddsdPrimary.ddsCaps.lCaps =
DDSCAPS_PRIMARYSURFACE Or DDSCAPS_3DDEVICE
Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX Or
DDSCAPS_VIDEOMEMORY
3.) Initializing Direct3D:
Set d3d = dd.GetDirect3D
Set dev =
d3d.CreateDevice("IID_IDirect3DHALDevice",
BackBuffer)
Loading Surfaces
For loading surfaces, you will now have
to take into account two kinds of surfaces:
Normal DirectDraw Surfaces:
- Can be any size - they don't need to
be square or have power of 2 dimensions
- Can't use alpha-blending, scaling,
rotation, or vertex coloring
Direct3D Surfaces
- Must be square, must have a power of
2 size, and can't be bigger than
256x256 (Possible sizes: 2x2, 4x4, 8x8,
16x16, 32x32, 64x64, 128x128, 256x256)
- You can blit different sized
portions from the square surface, if you
need a non-square sprite.
- Supports Alpha Blending - This makes
the the surface semi-transparent, for
window or smoke type effects.
- Supports Rotation - You can
manipulate the vertices of a sprite to
rotate it.
- Supports Scaling - You can
manipulate the vertices of a sprite to
scale it bigger or smaller. In fact,
you can manipulate the vertices to do
all kinds of things, like skewing,
distorting, and stretching.
- Supports Vertex Coloring - You can
assign any color to each of the four
corners of a D3D sprite, to "colorize"
it.
So you will need a surface-loading
routine that can load both types of surfaces.
For the sample project, I first create a
dynamic array of surfaces:
'The element type of the Surfaces() array
Public Type Surface
Surface As DirectDrawSurface7
Width As Integer
Height As Integer
D3DSurface As Boolean
End Type
'Array of surfaces
Public Surfaces() As Surface
'Current number of surfaces in Surfaces()
Public NumSurfaces As Long
This organizes the surfaces you use
nicely. Here is my routine to load surfaces:
Public Sub LoadSurface(File As String,
Optional D3DSprite As Boolean)
Dim CKey As DDCOLORKEY
Dim SurfaceDesc As DDSURFACEDESC2
On Error GoTo ErrOut
NumSurfaces = NumSurfaces + 1
ReDim Preserve Surfaces(NumSurfaces)
'If this is a D3D sprite, load it
accordingly
If D3DSprite = True Then
SurfaceDesc.lFlags = DDSD_CAPS Or
DDSD_WIDTH Or DDSD_HEIGHT Or DDSD_CKSRCBLT
SurfaceDesc.ddsCaps.lCaps =
DDSCAPS_TEXTURE
SurfaceDesc.ddsCaps.lCaps2 =
DDSCAPS2_TEXTUREMANAGE
'Set the color key
SurfaceDesc.ddckCKSrcBlt.high = ColorKey
SurfaceDesc.ddckCKSrcBlt.low = ColorKey
'Create the surface
Set Surfaces(NumSurfaces).Surface =
dd.CreateSurfaceFromFile(File, SurfaceDesc)
'Set the information for this surface
Surfaces(NumSurfaces).Width =
SurfaceDesc.lWidth
Surfaces(NumSurfaces).Height =
SurfaceDesc.lHeight
Surfaces(NumSurfaces).D3DSurface = True
'Normal DirectDraw surface
Else
SurfaceDesc.lFlags = DDSD_CAPS Or
DDSD_WIDTH Or DDSD_HEIGHT
SurfaceDesc.ddsCaps.lCaps =
DDSCAPS_VIDEOMEMORY Or
DDSCAPS_OFFSCREENPLAIN
'Create the surface
Set Surfaces(NumSurfaces).Surface =
dd.CreateSurfaceFromFile(File, SurfaceDesc)
'Set up the color key
CKey.low = ColorKey
CKey.high = ColorKey
Surfaces(NumSurfaces).Surface.SetColorKey
DDCKEY_SRCBLT, CKey
'Set the information for this surface
Surfaces(NumSurfaces).Surface.SetForeColor
vbBlack
Surfaces(NumSurfaces).Width =
SurfaceDesc.lWidth
Surfaces(NumSurfaces).Height =
SurfaceDesc.lHeight
End If
ErrOut:
Exit Sub
End Sub
So whether or not the surface created is
a D3D surface is determined by the second
argument passed to the LoadSurface
subroutine. An example of using this
routine would be:
Private Sub LoadSurfaces()
'Load the sprite surface - D3D Surface is
true so we can use alpha blending, etc.
LoadSurface App.Path & "\object.bmp", True
'Load the background image surface - Not D3D
surface so it can be any size (This one is
640x480)
LoadSurface App.Path & "\stars.bmp", False
End Sub
Drawing Surfaces
Now that we have loaded our surfaces, we
need to display them on the screen! To do
this, we must again take into account the
two surface kinds. Drawing D3D surfaces is
going to be much more complicated than
normal DirectDraw surfaces. Bltting a
normal DirectDraw surface is simple:
BackBuffer.Blt DestRect, Surfaces(SurfIndex).Surface,
SrcRect, DDBLT_KEYSRC Or DDBLT_WAIT
D3D surfaces work differently. They are
actually made up of two triangles that form
the rectangle of the surface:

The corners are called vertices (Singular,
vertex), and these are what allow us to do
rotation and scaling. To set up for drawing
a D3D sprite, we must first set up some
vertices:
'The vertices that will define this sprite
Dim TempVerts(3) As D3DTLVERTEX
I created a beefy little subroutine
called SetUpGeom() that will set up a
supplied D3DTLVERTEX array according to the
passed information:
Public Sub SetUpGeom(Verts() As D3DTLVERTEX,
SurfIndex As Integer, Src As RECT, Dest As
RECT, R As Single, G As Single, B As Single,
A As Single, Angle As Single)
'This sub sets up the vertices for a sprite,
taking into account
'width, height, vertex color, and rotation
angle
'NOTE: R, G, and B dictate the color that
the sprite will be -
'1, 1, 1 is normal, lower values will
colorize the vertices
Dim SurfW As Single
Dim SurfH As Single
Dim XCenter As Single
Dim YCenter As Single
Dim Radius As Single
Dim XCor As Single
Dim YCor As Single
'Width of the surface
SurfW = Surfaces(SurfIndex).Width
'Height of the surface
SurfH = Surfaces(SurfIndex).Height
'Center coordinates on screen of the sprite
XCenter = Dest.Left + (Dest.Right -
Dest.Left - 1) / 2
YCenter = Dest.Top + (Dest.Bottom - Dest.Top
- 1) / 2
'Calculate screen coordinates of sprite, and
only rotate if necessary
If Angle = 0 Then
XCor = Dest.Left
YCor = Dest.Bottom
Else
XCor = XCenter + (Dest.Left - XCenter) *
Sin(Angle) + (Dest.Bottom - YCenter) *
Cos(Angle)
YCor = YCenter + (Dest.Bottom - YCenter)
* Sin(Angle) - (Dest.Left - XCenter) *
Cos(Angle)
End If
'0 - Bottom left vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
Src.Left / SurfW, _
(Src.Bottom + 1) / SurfH, _
Verts(0)
'Calculate screen coordinates of sprite, and
only rotate if necessary
If Angle = 0 Then
XCor = Dest.Left
YCor = Dest.Top
Else
XCor = XCenter + (Dest.Left - XCenter) *
Sin(Angle) + (Dest.Top - YCenter) *
Cos(Angle)
YCor = YCenter + (Dest.Top - YCenter) *
Sin(Angle) - (Dest.Left - XCenter) *
Cos(Angle)
End If
'1 - Top left vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
Src.Left / SurfW, _
Src.Top / SurfH, _
Verts(1)
'Calculate screen coordinates of sprite, and
only rotate if necessary
If Angle = 0 Then
XCor = Dest.Right
YCor = Dest.Bottom
Else
XCor = XCenter + (Dest.Right - XCenter)
* Sin(Angle) + (Dest.Bottom - YCenter) *
Cos(Angle)
YCor = YCenter + (Dest.Bottom - YCenter)
* Sin(Angle) - (Dest.Right - XCenter) *
Cos(Angle)
End If
'2 - Bottom right vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
(Src.Right + 1) / SurfW, _
(Src.Bottom + 1) / SurfH, _
Verts(2)
'Calculate screen coordinates of sprite, and
only rotate if necessary
If Angle = 0 Then
XCor = Dest.Right
YCor = Dest.Top
Else
XCor = XCenter + (Dest.Right - XCenter)
* Sin(Angle) + (Dest.Top - YCenter) *
Cos(Angle)
YCor = YCenter + (Dest.Top - YCenter) *
Sin(Angle) - (Dest.Right - XCenter) *
Cos(Angle)
End If
'3 - Top right vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
(Src.Right + 1) / SurfW, _
Src.Top / SurfH, _
Verts(3)
End Sub
To do scaling, simply supply a bigger or
smaller destination width and/or height to
this sub.
Now let's go through one of the vertices,
step by step, to understand what's going on:
'Calculate screen coordinates of sprite, and
only rotate if necessary
If Angle = 0 Then
XCor = Dest.Left
YCor = Dest.Bottom
Else
XCor = XCenter + (Dest.Left - XCenter) *
Sin(Angle) + (Dest.Bottom - YCenter) *
Cos(Angle)
YCor = YCenter + (Dest.Bottom - YCenter)
* Sin(Angle) - (Dest.Left - XCenter) *
Cos(Angle)
End If
This part calculates the correct
destination coordinates of this vertex and
puts them in the XCor and YCor variables.
This is where rotation calculations are
made. If angle is 0, then no rotation
calculations are made. If a non-zero angle
is supplied the destination coordinates will
be rotated by the amount in the Angle
variable, which is in radians. This is just
a simple matter of trigonometry
calculations, which, if you don't understand
them, don't worry, you don't have to for
them to work :) (BTW, thanks to W-Buffer for
the trig. code here ;) Remember, if you
want to rotate a sprite to a specific angle
in degrees, multiply the degree angle by (pi
/ 180) to get the correct radian angle to
supply to this subroutine.
'0 - Bottom left vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
Src.Left / SurfW, _
Src.Bottom / SurfH, _
Verts(0)
These are the coordinates on the screen of
this vertex, calculated previously to take
into account rotation.
'0 - Bottom left vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
Src.Left / SurfW, _
Src.Bottom / SurfH, _
Verts(0)
These will just always be set this way,
don't ask why :)
'0 - Bottom left vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
Src.Left / SurfW, _
Src.Bottom / SurfH, _
Verts(0)
The R, G, and B variables sent to this
sub determine what color the sprite will be
made. If they are all 1, the color will be
normal. If R is 1, and the rest are 0, the
sprite will be tinted red, and so on,
according to RGB color standards. The A
variable determines what level of visibility
the sprite will have for alpha blending, 1
being completely solid, and 0 being
invisible. So .5 would be a
half-translucent state.
'0 - Bottom left vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
Src.Left / SurfW, _
Src.Bottom / SurfH, _
Verts(0)
Always gonna be zero :)
'0 - Bottom left vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
Src.Left / SurfW, _
Src.Bottom / SurfH, _
Verts(0)
These are the TU and TV values. These
show where the source coordinates for this
sprite are on it's surface - TU is the X
coordinate, and TV is the Y coordinate.
This is calculated by dividing the source
coordinate by the size of the surface, which
will give a number between 0 and 1. So for
this vertex, which is vertex 0; the bottom
left corner, the TU will be 0 (0 / 64 =
0), and the TV will be 1 (64 / 64 = 1). If
the sprite started half-way across the
surface, the TU would be .5 (32 / 64 = .5)
If this is a little confusing, you probably
don't have to worry about it, because you
don't need to understand it to use the
subroutine.
'0 - Bottom left vertex
dx.CreateD3DTLVertex _
XCor, _
YCor, _
0, _
1, _
dx.CreateColorRGBA(R, G, B, A), _
0, _
Src.Left / SurfW, _
Src.Bottom / SurfH, _
Verts(0)
This is the vertex array that this
information will all be stored in.
Whew! Now that the geometry is all set
up, we can actually draw the surface
(Remember to call Dev.BeginScene before
drawing any D3D surfaces, and call
Dev.EndScene when you're all done):
'Enable alpha-blending
dev.SetRenderState
D3DRENDERSTATE_ALPHABLENDENABLE, True
'Enable color-keying (ColorKey is drawn
transparent)
dev.SetRenderState
D3DRENDERSTATE_COLORKEYENABLE, True
dev.SetRenderState
D3DRENDERSTATE_COLORKEYBLENDENABLE, True
'Use Alpha Blend One alpha blending if the
ABOne variable is true (you can use any
variable)
If ABOne = True Then
dev.SetRenderState
D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE
dev.SetRenderState
D3DRENDERSTATE_DESTBLEND, D3DBLEND_ONE
'Or Alpha blend to a certain fade value (0 -
1)
Else
dev.SetRenderState
D3DRENDERSTATE_SRCBLEND, D3DBLEND_SRCALPHA
dev.SetRenderState
D3DRENDERSTATE_DESTBLEND,
D3DBLEND_INVSRCALPHA
dev.SetRenderState
D3DRENDERSTATE_TEXTUREFACTOR,
dx.CreateColorRGBA(1, 1, 1, Alpha)
Dev.SetTextureStageState 0,
D3DTSS_ALPHAOP, D3DTA_TFACTOR
End If
'Set the texture on the D3D device
dev.SetTexture 0,
Surfaces(SurfIndex).Surface
dev.SetTextureStageState 0,
D3DTSS_MIPFILTER, 3
'Draw the triangles that make up our square
texture
dev.DrawPrimitive D3DPT_TRIANGLESTRIP,
D3DFVF_TLVERTEX, TempVerts(0), 4,
D3DDP_DEFAULT
'Turn off alphablending after we're done
dev.SetRenderState
D3DRENDERSTATE_ALPHABLENDENABLE, False
The alpha blending is specified here -
either Alpha Blend One, or blending to a
certain fade value (Determined by the Alpha
variable - a fraction from 0 to 1, 0 being
invisible, and 1 being solid). You can
check the
sample project to see how these
different techniques look. Then the texture
is set on the device, the triangles that
make up the surface are drawn, and we're
done!
Check out the
sample project with source code, which
demonstrates all these concepts in action,
using a cool sprite engine demo! :)
-Matt Hafermann |