|
Introduction
The title is
scary huh^^?
This tutorial will start to confuse you a lot :D. Don't worry about
it, I'll try to make this tutorial as easy as possible for you guys.
Just please try to keep an open mind and a positive attitude (i.e.
don't say "I give up" right when I give you an explanation).
Heh, lets start
with a few Q and A to get the ball rolling.
Q&A
The obvious question:
"How do you move things in Direct3D??"
The n00b responses (which I expect from some of you at this stage):
"Well, the first thing to do is recreate the vertices - its kinda
simple, if you want to move a square right, all you gotta do is
this:
vertex(0) = new customvertex.positionTextured(<1 unit to the right>)
and do the same with the other vertices"
Sorry, wrong answer... Direct3D doesn't allow this... why?
Because you're changing data in the vertex buffer. The vertex buffer
is loaded and ready to go (there is a way around this but this is
not for now), and you can't change it!
Another n00b comes up with a response to that:
"Well all you gotta do is recreate the vertex buffer and load in a
new set of vertices"
Well... yes that could work... but isn't that a TOTAL waste of
memory? You're creating a TOTALLY new vertex buffer and you're
filling it with ANOTHER set of vertices = lag fest & flicker mania.
So .... that's wrong ;p.
And then a geek
comes up with a response:
"Well, its rather simple actually, Direct3D has its own built-in
functions for doing this, it involves a complex system of matrices.
The world gets translated in the direction which you want your
objects to move and the result is a mass-movement of all objects in
that specified direction."
Uhh... yeah that's the correct answer..... In English please?? Here
it is:
-Every object is in the WORLD. Just like humans are on the earth.
-These objects can't move on their own (by changing the vertices or
recreating the vertex buffer..etc) because it would be too slow....
imagine moving a 3D Character this way...do you realize how many
vertices we'd have to move in order to move him? Even if this was
just 1 pixel to the right.. its a CRAZY way of doing this - again -
this would lead to lag fest and LOTS of flicker ;p.
So, the
ingenious Microsoft came up with a way of doing this:
Move the world to move an object.
Scary huh? Again, keep an optimistic outlook and an open mind
towards this concept, it might seem weird but everything has its
reasons.
If an object wants to move left... what do you do? Make the world
move left! :-P. The same goes for the other directions.. whichever
direction the object wants to go in, the world goes in.
So to move that high polygon 3D character model, you move the world
rather than move each of the vertices over... you sort of understand
what I mean?
How do we move the world?
Well... here's
where it gets a bit confusing,
Microsoft stores the world in a Matrix which is stored in your
Device. The process of moving the world is called Translating...
there are other transformations as well, such as Rotation and
Scaling, but Translating is just moving it in a direction.
Well here's how to translate the world 1 unit to the right..
assuming d3ddev is your device:
d3ddev.Transform.World = Matrix.Translation(1,0,0).
The 3 arguments are X, Y, and Z respectively. That 1 indicates that
the world is being moved 1 unit to the right.
Now that our character has moved 1 unit to the right, how do we move
him back to his original position?
d3ddev.Transform.World = Matrix.Translation(0,0,0)
Lets try experimenting with this.
The Programming Part
Open up the
previous project (Rendering a Sprite)... Go to GameClass and then go
to the RenderScene sub.
Right before Alex.Render, type in
d3ddev.transform.World = Matrix.Translation(30,0,0)
Run it...
edit: Before you run it, do 1 thing, it is an error on my part in
the previous tutorial, in the Initialize sub in GameClass, please
change
D3Ddev.Transform.View = Matrix.OrthoOffCenterLH(0, DP.Width,
DP.Height, 0, 0,
10)
to
D3Ddev.Transform.View = Matrix.OrthoOffCenterLH(0, DP.Width,
DP.Height, 0, 1,
10)
Due to some mathematical reason, the zNearPlane argument must be 1
(don't worry if you don't understand this line anyways, I will
explain later in the tutorial.
The character just
shifted 30 units to the right! Be sure to realize one thing,
remember that the top left of the character is position (0,0,0),
which is the top left of the screen? Well now, the point (0,0,0) has
now shifted 30 units to the right, it is now where point (30,0,0)
used to be.. the character's top left is still at point (0,0,0) ...
and the top left of the screen is now -30,0,0 :) ... starting to
understand a bit?
Now let's try something different.
How do we translate the world 1 unit to the right each frame [each
time the object is rendered]?
Well, first of all, we would have d3ddev.transform.world =
matrix.translation(1,0,0) right? How would we make it move 1 unit to
the right of its previous position, meaning how do we make it keep
moving right 1 unit??
We'd simply do this [replace this line with the translation(30,0,0)
line]:
d3ddev.transform.world = Matrix.Multiply(d3ddev.transform.world,
matrix.translation(1,0,0))
Don't worry, the line may seem tricky, but just replace that line
with your other translation line and run it... So now the guy moves
REALLY quickly across the screen. For those of you with fast
computers, I'd suggest that you change the 1 into a 0.1 or a 0.01.
Depending on the amount of FPS (Frames Per Second) you get, the
faster he'll go.
So Multiply simply translates the world from the PREVIOUS position..
and without Multiply, it would translate the world from position
(0,0,0)... got all that? If you don't - play around with it for a
while and it might sink in :D.
Basic Controlled Movement using
Matrix.Multiply and Matrix.Translation
Now that you've understood that (I don't expect you to understand it
completely, but get the general gist of what it does), lets discuss
a bit on how to move him.
The first response that people come up with is usually
"Simple, use the Multiply along with Translation to animate the
character - except use variables... for example:
d3ddev.transform.world = Matrix.Multiply(d3ddev.transform.world,
matrix.Translation(x,y,0)) since Z doesn't matter in a 2d game set
it to 0. When the character presses (for example) right, set x = 1
so he can start animating, when there is no key pressed, set x = 0
so he can STOP animating"
- I'll tell you right now that this is the wrong way, but.. I like
to teach people why we do things as opposed to simply how
we do things... let's experiment:
In GameClass:
Public X As
Single
Public Y As Single
Replace the translation line (the one before Alex.Render - you
should only have 1 "Translation line") with:
d3ddev.transform.world = Matrix.Multiply(d3ddev.transform.world,
matrix.Translation(x,y,0))
Now go back to form1.. add the following code
If
e.KeyCode = Keys.Right Then
Game.X = 0.1
End If
If e.KeyCode = Keys.Left
Then
Game.X = -0.1
End If
If e.KeyCode = Keys.Up
Then
Game.Y = -0.1
End If
If e.KeyCode = Keys.Down
Then
Game.Y = 0.1
End If
And in form1_keyup:
game.X = 0
game.Y = 0
Now test out the code :p...
You see that it
works right? Here's my reasoning behind why we shouldn't do this...
the Matrix.Multiply thing is based on time (FPS) because it moves
the world based on frame count.. So basically, the slower the
computer, the slower the guy will move :(.
Here's how it works... You move it .1 in whatever direction, lets
say the Frames Per Second is 1 (REALLY bad lol), that means that it
moves the world by .1 (in whatever direction) every second. Based on
the FPS only you'll know how much you're actually moving the world
by - if the FPS jumps to 2 (woohoo, a really small increase!) then
he'll move twice as fast and you wouldn't be able to keep track of
his position :(.
This leads to confusion and is something you probably want to avoid
if you want to keep track of the character's position
So there's another technique which does not use multiply and
translation, it just uses translation < I bet you guys now know what
it is >
Basic Controlled Movement using
Matrix.Translation
The other technique is really simple - We simply use Translation and
use variables.
It works something like this [ replace the Multiply line with this ]
d3ddev.transform.world = matrix.Translation(x,y,0)
Now, the translation is not based on FRAME RATE, its simply based on
the amount that X and Y is.
Go back to form 1 and revise the code in form1_keydown:
If
e.KeyCode = Keys.Right Then
Game.X += 1
End If
If e.KeyCode =
Keys.Left Then
Game.X -= 1
End If
If e.KeyCode = Keys.Up
Then
Game.Y -= 1
End If
If e.KeyCode =
Keys.Down Then
Game.Y += 1
End If
Its pretty simple,
since its not based on frame rate, we can use "normal sized" amounts
as opposed to using .01 or .1.
Oh yeah, delete the code in form1_keyup
http://www.dotnetforums.net/images/smilies/wink.gif.
Now run the thing
http://www.dotnetforums.net/images/smilies/biggrin.gif, it
works. As a matter of fact, you might actually have to increase the
1 to maybe 5 or 10.
Well, here comes
another question which people will ask:
Remember, you have to move the world to move an object...
"What if you had Two objects??? What if object1 wanted to go
right and object2 wanted to go left? Oh no! is this a limitation of
Direct3D????"
Of course its not ;P. This is a confusing explanation, and I gotta
explain this through experimentation (and you'll probably understand
it better that way).
Moving Multiple Objects using
Matrix.Translation
Ok, let's get
started on this confusing concept :).
In GameClass,
Private Alex2 As
clsSprite
at the end of Initialize,
Alex2 = new clsSprite(d3ddev, "down1.bmp", new point(5,5), 32,32)
and in RenderScene after Alex.Render:
Alex2.Render
Run it, as expected, you should see both Alex and Alex2 moving
together in the same direction because you're moving the WORLD...
Well, how do you move 2 objects at once? You translate the world
again before rendering the 2nd object!
In GameClass,
Dim X2 as Single
Dim Y2 As Single
In form1_keydown,
add this to your previously existing code:
If e.KeyCode = Keys.W
Then
Game.y2 -= 1
End If
If e.KeyCode = Keys.S
Then
Game.y2 += 1
End If
If e.KeyCode = Keys.A
Then
Game.x2 -= 1
End If
If e.KeyCode = Keys.D
Then
Game.x2 += 1
End
If
Ok, now
before alex2.Render:
d3ddev.Transform.World = Matrix.Translation(x2,y2,0)
Now run it (don't worry if you don't understand the code yet, I'll
explain soon), push W S A D and UP DOWN LEFT RIGHT to move the
guys... they move SEPARATELY :).
----
Now its time for an
explanation :D.
Here's how the
program "reads" at the code
Translates the world to Alex's position
Renders Alex
Translates the world to Alex2's position
Renders Alex2
Its a weird concept to sink in :s, its really hard to explain.. but
here's 1 other thing you should realize,
After you render Alex, any more translating after that won't really
do anything to Alex's position... he's already been rendered, and he
"stays" there :), so when you translate the world to alex2's
position - you're not affecting Alex's position.
Experiment a bit with this, and please try to understand it before
moving on, it took me a month to understand this concept (I learned
DirectX right when it came out, I had no books and there was
virtually no internet resources lol, I learned it with a few other
people through trial and error... and then I picked up .NET Game
Programming with DirectX 9 and it explained it all beautifully
:D)
The View Matrix
Here
is a question which I said I would answer in the previous tutorial
(Rendering a Sprite).
I asked you earlier to type in the line D3Ddev.Transform.View =
Matrix.OrthoOffCenterLH(0, DP.Width, DP.Height, 0, 1, 10) near
the end of GameClass, and I told you I would explain it in the next
tutorial.
The World matrix stores the objects, and the View matrix stores what
you can see IN the world.
Here are arguments of the function
In this OrthoOffCenterLH View matrix, we define the left of the
screen to have an x of 0, we define the right of the screen to be
DP.Width (ex: if it was 800x600, the right of the screen would be
800), we define the bottom of the screen to be DP.Height (600 in our
case), we define the Top of the screen to have a y of 0.
The next 2 arguments tell us zNear plane and zFar plane. Here's what
those things do (its not important in 2d but it certainly is
important in 3d):
-It basically says "If any object has a Z which is less than 1,
don't render it, If any object has a Z greater than 10, don't render
it". Its sort of a memory manager so that you don't render your
ENTIRE world (imagine a HUGE 3d world), at the same time - you
render it as you approach there.
If we HADN'T
speficied the View matrix, it would be by default:
Left: -1
Right: 1
Top: -1
Bottom: 1
ZNearPlane: 1
ZFarPlane: <im not too sure, I think it may be 2>
This means that point (0,0,0) is the CENTER of the screen, we
certainly don't want that to happen. This also means that we can
only see a tiny section of the world (2 by 2)! I recommend that your
View Matrix "matches" with your resolution (the OrthoOffCenterLH
matrix defined in your program "matches" with your resolution)
Now to explain what OrthoOffCenterLH means, let's dissect it :D;
Ortho: No matter what Z value an object has, it's not going
to matter - ideal for 2d, bad for 3d... the Z value of an object
only does 1 thing: determines what object renders on top of the
other object. If and object had a z value of 1 and an other object
had a z value of 5, then the object with a zvalue of 1 would be
rendered on top.
OffCenter: This means that the center of the screen isn't
0,0,0
LH: Left handed coordinate system, this means that the more Z
an object has, the farther away it is. If we used RH, then the more
Z an object has, the closer it is to the screen - this gets
confusing because it's better to have a "Start Point" for where the
beginning of the scene is - imagine taking the years in BC and
switching it with the years in AD.... We would be counting DOWN :-P,
so RH is a little weird, I prefer LH.
The reason why I said replace the 0 with a 1 < look way above, I'm
talking about the ZNearPlane argument> was for mathematical reasons.
Never have a zNear of 0 - I honestly don't know why/how, and I don't
think it will affect this 2d program, but - always use good
programming practices and fix your problems early rather than
wondering why something was not working when you do it later.
That's all for this tutorial. Mess around with some stuff, and learn
more - It took me a while to understand this concept and I learned
it by messing around.
The
Source Code for this tutorial is located
here:
You can
also locate this by logging in to vbProgramming Forums and going
to:
Tutorials > Tutorial Source Code > Source Code |