First off, to sum it up in a nutshell, linear algebra is the study of vectors. If you plan on using anything in your game such as position, velocity, acceleration, direction, etc... You probably know that you need vectors. The better you understand them, the more control you'll have over implementing features and what they do within your game.
-------------------------------------------------------------
Section 1: The basics of a vector
-------------------------------------------------------------
Vectors are used commonly to store positions, directions, accelerations and velocities in most game designs. In this guide I will be illustrating all in Two-Dimensional examples although everything in here applies to 3D as well, just with an added z component. Food for thought if you decide to gander in the 3D world!
A 2D Vector has two components. an X coordinate and a Y coordinate. If you ever did plotting points on a grid, you've already used this kind of vector before. Before we dive into velocities and acceleration forces, directions and what have you, let's first understand a vector used for position, just like plotting points on a grid.
When we think about our grid as our universe, we need to be greedy like the ancient civilizations before us and think we're the center of the universe
so the very center of our "universe" would be the vector (0,0) Everything we place in our "universe" is a distance measure of how far away it is from the center.
In the code block below I've drawn a grid, and marked the center (0,0) and also marked a point (3,3) which is 3 units to the right on the X axis, and three units up on the Y axis away from the center.
+
-----^-----
| | 3,3| Since all this math has been around well
| | | before computers, it's been fixed to this
| | | style grid, on the X axis (horizontal)
-<----0,0---->+ anything left is negative and anything right
| | | is positive.
| | | on the Y axis (vertical) anything up is positive
| | | and anything down is negative. This is a problem
----- ----- with computer graphics, we'll cover later.
-
Subsection 1: Grid Units
----------------------------------
when we slice our universe on the X and Y and keep drawing equal distant lines above and below, left and right, we wind up with a bunch of equal squares. One of these squares is a Unit. It's very important to remember what your Unit represents. This is everywhere. The easiest example is a map. Often times you will see a "Key" or "Legend" which shows you what the grid units represent. It could be 1 mile, or 1 meter, or 1 foot, or 1 inch, etc..
Even tho I'm from the USA, I'm going to treat the above grid example as 1 meter, because metric is base 10, and standard is base 12, and I know I can easily go 10,20,30,40,etc... but 12,24,36,48,etc... is WAY harder to spit out off the top of your head! (We should adopt the metric system, but it'll never happen)
Subsection 2: Vector as a velocity
----------------------------------
Using the same coordinates from the grid above, if we treat the coordinate (3,3) as a velocity, that means whatever we're tracking moves 3 units to the right and 3 units up at whatever interval we're tracking. Speed intervals are almost always measured in a per second basis, so with that in mind, and previously stated 1 unit is 1meter by 1meter, then (3,3) translates to a speed of travel being 3 meters up, and 3 meters right per second.
-------------------------------------------------------------
Section 2: Vector Mathematics
-------------------------------------------------------------
In this section I will be illustrating the most common mathematical computations that you do with a vector or multiple vectors such as adding, subtracting, multiplying, dividing, determining length, distance between two vectors, etc.... There are a lot, but I'll cover the most commonly used ones in SIMPLE game development.
Vector Addition, Subtraction, Multiplication & Division:
--------------------------------------------------------
When dealing with mathematical operations on two vectors, you're actually doing simultaneous calculations. You seperate it by it's components, X & Y, and combine like terms.
Examples using (X1,Y1) and (X2,Y2):
Addition: (X1+X2,Y1+Y2)
Subtraction: (X1-X2,Y1-Y2)
Multiplication: (X1*X2,Y1*Y2)
Division: (X1/X2,Y1/Y2)
Specialty Case: Scalar-Vector operations:
-----------------------------------------
First of all, what is a scalar? The term is an individual number. For example, 5 is a Scalar. (2,3) is not, it is a Vector. How do we deal with a scalar? It's quite simple actually. treat it as both X and Y.
Example: (2,3)*5 is a Vector being multiplied by a scalar, treating our scalar as both X & Y, we can translate this to (2*5,3*5) for a final result of (10,15)
Length of a Vector (Magnitude):
-------------------------------
Now let's take a slice of time here and examine one second from the example above in subsection 2, how far did it actually go? this double coordinate things confusing! Well remember back in school you had two lengths of a triangle and 1 unknown? You solved that with the "Pythagorean Theorem" most commonly written as a^2+b^2=c^2
We already have our two knowns, it's 3 meters right and 3 meters up, so let's solve it.
a = 3, b = 3
3^2+3^2=c^2
9+9=c^2
18=c^2
sqrt(18)=c
4.242641=c
Our object moved 4.24 meters in a single second. Lucky for us, mIRC already has a function that does this for us, rather than computing it ourself, and it's called $hypot(). Try it yourself:
//echo -a $hypot(3,3)
result: 4.242641
Distance Between Two Vectors:
-----------------------------
Okay so we understand how to find the length of a single vector, but sometimes we may need to find the length between two vectors. How do we achieve this? We need to get back to our simple triangle to be able to use $hypot() so one of these needs to become (0,0) our origin like $hypot() assumes.
How do we get one to (0,0)? Simple! subtract it from itself, BUT REMEMBER what you do to one you do to the other, so say I have (1,1) and (4,5) and we want the distance between them. it really doesn't matter which we zero out, so lets do both just to demonstrate.
(1,1)-(1,1) = (0,0) <-- zero out this one..
(4,5)-(1,1) = (3,4) <-- do the same to the other
(4,5)-(4,5) = (0,0) <-- zero out this one..
(1,1)-(4,5) = (-3,-4) <-- do the same to the other
Now if you notice, we effectively shifted them both the same amount, so now one lies on the origin, and the other is still the same distance away as before, but now we know how far away it is from the origin. YAY! We can use $hypot() now!
//echo -a $hypot(3,4) = $hypot(-3,-4)
result: 5 = 5
We now know that the distance bewteen (1,1) and (4,5) is 5 meters.
For clarity, I demonstrated doing the subtraction to both to hopefully visually aid on understanding why the order of subtraction doesn't matter, but we never use the one we set to (0,0), this can totally be skipped since it's never used.
So to recap: Simplified answer to find the distance between VecA and VecB, Get a single vector by subtracting the two (VecA-VecB or vice versa) use that new returned vector and use $hypot() with the X & Y values.
Normalization of a Vector:
--------------------------
To normalize a vector, we must set it's length equal to 1, but how do we do this? It's actually quite simple, divide vector by it's scalar length (remember scalar is treated as both x & y) so let's re-use (3,4) since it's length computes evenly. (3,4)/5 = (3/5,4/5) = (0.6,0.8) which is our normalized vector!
Wait a minute, (0.6,0.8) doesn't even have a 1 in it... how's that work? Well remember it's the LENGTH (magnitude) so let's prove it mathematically leaving it as a fraction solving for the length.
a = 3/5, b = 4/5
(3/5)^2 + (4/5)^2 = c^2
9/25 + 16/25 = c^2
25/25=c^2 <-- It should be clear here that 25/25 = 1
sqrt(25/25)=c
1=c
But what's the use of this? Remember in the distance between vectors we subtracted one from the other? we had a new vector that contained the length between the two, but this also contains more information! it's also the rise and the run to reach from one to the other. This is a direction represented as a vector, rather than a degree (0-359 degrees, cause 360=0 or as you probably heard the expression turned full circle) but directions ABSOLUTELY NEED to have a length of 1.
So if we assume it's a motion vector and break down the vector (3,4) we know that it has a direction of (0.6,0.8) and a velocity of 5.
Why does a direction vector need to have a length of 1? In order to generate a motion vector, we first need a velocity, and a direction. the formula for a motion vector is the direction vector multiplied by the scalar velocity.
(0.6,0.8)*5=(3,4) <-- motion vector
But why is this useful you might ask? Well lets say in your game you have your good guy at (1,1), and a bad guy at (4,5). How does the bad guy know which way to shoot? since we know the distance between the two has both a length and a direction we already have what we need!
If you just took that subtracted vector, and used it as our direction without normalizing it, when you applied a velocity to the bullet he shot, it would go WAY TOO FAST because the direction has a length > 1!
If you normalized it first so it is just solely a direction vector, then your formula for finding the motion vector of the bullet would hold true to what you expect.
Side note: Although finding the length (distance) between the two the subtraction step doesn't matter, but if you're going for DIRECTION not length, then it really matters which one you subtract from the other, if you do it wrong, the bullet will fire in opposite direction away from the good guy, rather than towards him
a quick flop of the subtraction step will fix the issue.
Cross Product of a Vector:
--------------------------
The cross product is meant to give you a vector that is perpendicular to BOTH vectors involved. It theoretically doesn't work in two dimensions. In many 2D games, the cross product is repurposed with the idea that the Z component is equal to 1. Let's break out of 2D and check out the algorithm for the cross product of two 3D vectors called A and B:
(A.y * B.z - A.z * B.y, A.z * B.x - A.x * B.z, A.x * B.y - A.y * B.x)
To break it down:
X = A.y * B.z - A.z * B.y
Y = A.z * B.x - A.x * B.z
Z = A.x * B.y - A.y * B.x
Now, the repurposed 2D method to return the cross product of a vector is generally summed up by rotating that vector clockwise by 90 degrees. Which in fact does give us a vector perpendicular to original vector! Rather than rotating the vector, to avoid the whole $sin() and $cos() functions, it can quite simply be stated as: flip the two components, and change the sign of the 1st component. Example: if we have vector (2,1) and we wanted to rotate it by 90 degrees, we'd flip them (1,2) and now change the sign of the 1st component (-1,2) but how do we prove this mathematically? Remember how we said the Z component was assigned the value of 1? well let's re-visit our 3D formula, and replace all Z components with 1 and shave off our Z calculation, since we don't need it.
X = A.y * 1 - 1 * B.y
Y = 1 * B.x - A.x * 1
since Anything times 1 is itself, we can reduce this a bit, and be left with:
X = A.y - B.y
Y = B.x - A.x
Ok well we only have a vector of (2,1) so where is the other vector to solve this? Simple! since in 2D all of our coordinates are based on a distance away from the origin, our 2nd set of coordinates is (0,0) the origin! Now lets say A = origin, and B = (2,1) and plug in our values to solve:
X = 0 - 1
Y = 2 - 0
X = -1
Y = 2
We're left with a vector of (-1,2) You can now also find the inverse perpendicular vector by multiplying both components by -1, or by re-arranging A and B, lets flop A and B:
A = (2,1) , B = (0,0)
X = 1 - 0
Y = 0 - 2
X = 1
Y = -2
and we get: (1,-2)
Why is this useful? Well think about an old pirate ship, the ship has cannons off it's sides to attack other ships in the water. This allows you to know the directions the cannons fire solely from the direction of the ships stern, without any expensive rotational calculations. Here's another code block:
| |
--_--_-- This is a very crude pirate ship in ascii art I know hehe, but you can
/ \ ----> see, in this case the ship is at a direction of (1,0) so how do the
\__-__-__/ cannons fire? in (0,1) and (0,-1)! Nice! we got it without any expensive
| | rotational calculations! rock on! :)
Dot Product of Two Vectors:
---------------------------
The dot product is an amazing computation and has COUNTLESS useful applications, so much in fact it would make this section practically triple this entire tutorial! Since it's so diverse, I'll just show a few simplistic examples. Side note: A Dot product's return is a Scalar, not a Vector!
So how do we compute the dot product of two vectors? Here's how it'd look written on paper, left side is formula, right side is how to do it:
(x1,y1)•(x2,y2) = x1*x2 + y1*y2
So lets go back to directions, which have a length of 1. Taking the dot product of direction vectors will give you some nice information back. Lets say we have vectors A and B, and we use the dot product on them. Let's look at them on a chart first, rather than using values, CODE BLOCK! hehe:
Figure 1 Figure 2 Figure 3
======== ======== ========
A -----> ^ A <------
B -----> | B ------>
A|
B ---->
if A•B = 1, then the directions are parallel. (figure 1)
if A•B = 0, then the directions are perpendicular. (figure 2)
if A•B = -1, then the directions are opposite. (figure 3)
There are WAY more useful scenereos for the dot product of two vectors, like for instance finding out if a monster is in a players field of view, or figuring out if an object passes a plane. I plan on having some nice example game simulations that follow in this thread and I'll try to elaborate on cases such as these.
-------------------------------------------------------------
Section 3: The math, in a useful alias!
-------------------------------------------------------------
For every example I post I will assume you have this alias in your aliases tab (or in remotes with an alias prefix). I tried to incorporate the most commonly used functions, even some I haven't documented in this tutorial, but here it is, with no further ado:
;==========================================================================================================================
; Vector2D Examples:
;Vector Returns:
; $Vector2D(vec1.x,vec1.y,vec2.x,vec2.y).Add <== Adds two vectors together
; $Vector2D(vec1.x,vec1.y,vec2.x,vec2.y).Sub <== Subtracts one vector from the other
; $Vector2D(vec1.x,vec1.y,vec2.x,vec2.y).Mult <== Multiplies two vectors together
; $Vector2D(vec1.x,vec1.y,vec2.x,vec2.y).Div <== Divides one vector by the other
; $Vector2D(vec1.x,vec1.y,scalar).AddScalar <== Adds a scalar to a vector
; $Vector2D(vec1.x,vec1.y,scalar).SubScalar <== Subtracts a scalar from a vector
; $Vector2D(vec1.x,vec1.y,scalar).MultScalar <== Multiplies a vector by a scalar
; $Vector2D(vec1.x,vec1.y,scalar).DivScalar <== Divides a vector by a scalar
; $Vector2D(vec1.x,vec1.y,vec2.x,vec2.y).Cross <== Returns the cross product between two vectors
; $Vector2D(vec1.x,vec1.y).Cross <== Returns the cross product between two vectors, using 0,0 as the 1st vector
; $Vector2D(vec1.x,vec1.y,degree).Rot <== returns the rotated vector (newx newy), degree is 0-359
; $Vector2D(vec1.x,vec1.y).Norm <== returns the normalized vector
;Scalar Returns:
; $Vector2D(vec1.x,vec1.y,vec2.x,vec2.y).Wedge <== returns the wedge product between two vectors
; $Vector2D(vec1.x,vec1.y,vec2.x,vec2.y).InvDot <== returns the inverse dot product between two vectors
; $Vector2D(vec1.x,vec1.y,vec2.x,vec2.y).Dot <== returns the dot product between two vectors
; $Vector2D(vec1.x,vec1.y).MagSq <== returns the magnitude squared (length squared)
; $Vector2D(vec1.x,vec1.y).Mag <== returns the magnitude (length)
; $Vector2D(vec1.x,vec1.y).Angle <== returns the angle of a vector in degrees (0-359)
;==========================================================================================================================
Vector2D {
if ($prop = Add) { return $calc($1 + $3) $calc($2 + $4) }
if ($prop = Sub) { return $calc($1 - $3) $calc($2 - $4) }
if ($prop = Mult) { return $calc($1 * $3) $calc($2 * $4) }
if ($prop = Div) { return $calc($1 / $3) $calc($2 / $4) }
if ($prop = AddScalar) { return $calc($1 + $3) $calc($2 + $3) }
if ($prop = SubScalar) { return $calc($1 - $3) $calc($2 - $3) }
if ($prop = MultScalar) { return $calc($1 * $3) $calc($2 * $3) }
if ($prop = DivScalar) { return $calc($1 / $3) $calc($2 / $3) }
if ($prop = Rot) { return $calc($1 * $cos($3).deg - $2 * $sin($3).deg) $calc($2 * $cos($3).deg + $1 * $sin($3).deg) }
if ($prop = Cross) { if ($3- = $null) { tokenize 32 0 0 $1- } | return $calc($2 - $4) $calc($3 - $1) }
if ($prop = InvDot) { return $calc($1 * $3 - $2 * $4) }
if ($prop = Dot) { return $calc($1 * $3 + $2 * $4) }
if ($prop = Wedge || $prop = CrossScalar) { return $calc($1 * $4 - $2 * $3) }
if ($prop = MagSq) { return $calc($1 ^ 2 + $2 ^ 2) }
if ($prop = Mag) { return $hypot($1,$2) }
if ($prop = Norm) { var %mag = $hypot($1,$2) | return $calc($1 / %mag) $calc($2 / %mag) }
if ($prop = Angle) { return $atan2($2,$1).deg }
}
----------------------------------------------
Section 4: About the grid vs computer graphics
----------------------------------------------
All the way back to section 1, we made mention about how since this math has been around a while, it doesn't quite translate nicely to computer graphics. Why you might ask? Well grid coordinates are based on the distance from the origin, which is the center, where graphic coordinates are based on the distance from the top left of the drawable surface. This also means theres NO NEGATIVE coordinates in computer graphics.
So how do we get our grid coordinates to wind up being in the right spot for our visual representation? The answer is actually quite simple, You need to add half the drawable surface to each coordinate you plan on drawing. If you had a surface the size of 640x480, the middle is then 640/2 by 480/2 or 320x240.
But wait a minute. I've tried this and my Y axis stuff is wrong! Stuff that's supposed to go up goes down, and stuff that's supposed to go down goes up! On a grid, you'd be going right for positive X, and up for positive Y, in computer graphics it's an inverted Y axis grid. You're going right for positive x, and down for positive y.
Normal Inverted Y
+ -
-----^----- -----^-----
| | 3,3| | | |
| | | | | |
| | | | | |
-<----0,0---->+ -<----0,0---->+
| | | | | |
| | | | | |
| | | | | 3,3|
----- ----- ----- -----
- +
THE MOST MAJOR THING TO REMEMBER when developing a game, is that you need to think opposite with the Y values because of how translating to computer graphics works. if you want an object to go down, it's positive, and up is negative. by simply remembering to flip the sign during setup, it'll work fine. You could actually do it normally and just multiply the Y component by -1, but this adds extra calculations and it's just simpler to flop it before hand and avoid that extra mathematical calculation.
THIS ALSO AFFECTS HOW YOU PRECIEVE 0-359 DEGREES! 45 degrees in the normal grid would be right and up on an equal climb for both x & y, but since we flopped the Y for computer graphics, now our travel is right and down also on an equal climb for both x & y, which is actually 315 degrees or -45 degrees.
----------------------------------------------
Section 5: Conclusion
----------------------------------------------
I hope this helps you get your foot in the door for linear algebra
I plan to continue this tutorial with useful small snippet examples as new posts rather than an edit to this main topic, but if the examples merit changes to my Vector2D alias for other functions, I plan on re-editing this post to reflect the new alias rather than just re-posting the new one every time. It might be a good idea if you use this alias to re-check from time to time if there has been any additions to it's functionality.