mIRC Home    About    Download    Register    News    Help

Print Thread
Linear Algebra - Tutorial for simple game design #254043 20/07/15 12:35 AM
Joined: Dec 2002
Posts: 201
T
Talon Offline OP
Fjord artisan
OP Offline
Fjord artisan
T
Joined: Dec 2002
Posts: 201
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! smile

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 smile 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.
Code:
       +
  -----^-----
 |     |  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 smile
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 smile 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:

Code:
   |  |
 --_--_--              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:

Code:
Figure 1       Figure 2         Figure 3
========       ========         ========
A ----->          ^             A <------
B ----->          |             B ------>
                 A|
                 B ---->


if AB = 1, then the directions are parallel. (figure 1)
if AB = 0, then the directions are perpendicular. (figure 2)
if AB = -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:

Code:
;==========================================================================================================================
; 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.

Code:
     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 smile

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.

Re: Linear Algebra - Tutorial for simple game design [Re: Talon] #254044 20/07/15 01:25 AM
Joined: Dec 2002
Posts: 201
T
Talon Offline OP
Fjord artisan
OP Offline
Fjord artisan
T
Joined: Dec 2002
Posts: 201
Super simple example here, the Asteroids ship! In this example I've mapped out the points it takes to make the original game ship in the grid world, and made a simple quick demonstration on how to plot these points to a picwin, assuming that our grid units are in pixels.

This is totally not optimized by any means, this is just for demonstration purposes. The points were setup so the ship is facing in the direction of 1,0 or 0 degrees.

You can try it out with /ship or /ship <degree> to rotate it by any angle you wish, 0-359 degrees.

Code:
alias ship {
  ;-- Ship polygon in points, connect the dots! hehe :) 6 points in total with the last being the same as the first.
  var %poly = 11 0;-11 -7;-7 -3;-7 3;-11 7;11 0 , %angle = $1

  ;-- make a window
  window -dpf +l @Ship -1 -1 320 240

  ;-- find half width & height to draw proper
  var %hw = $window(@Ship).dw / 2 , %hh = $window(@Ship).dh / 2

  ;-- Clear the screen (Mainly here for different color themes, not everyone uses a black background)
  drawrect -nf @Ship 1 1 0 0 $window(@Ship).dw $window(@Ship).dh

  ;-- convert polygon grid coords to picwin coords
  var %a = 0 , %coords
  while (%a < 6) {
    inc %a
    ;-- tokenize the vector, x = $1 , y = $2
    tokenize 32 $gettok(%poly,%a,59)

    if (%angle isnum) {
      ;-- Rotate the vector
      tokenize 32 $Vector2D($1,$2,%angle).Rot
    }

    ;-- Convert grid coords to picwin coords by adding half the display size.
    tokenize 32 $Vector2D($1,$2,%hw,%hh).Add

    ;-- store the values into a variable for use later to draw.
    var %coords = %coords $1-
  }
  ;-- draw the polygon
  drawline -n @Ship 0 1 %coords

  ;-- update the screen since all draw commands were with -n
  drawdot @ship
}

Re: Linear Algebra - Tutorial for simple game design [Re: Talon] #254156 25/07/15 07:18 AM
Joined: Dec 2002
Posts: 201
T
Talon Offline OP
Fjord artisan
OP Offline
Fjord artisan
T
Joined: Dec 2002
Posts: 201
I've seen all kinds of views on the forums but no comments or questions, so since this was about games, let's try tossing a simple game example out there first and see if it strikes any attention smile

I originally planned on breaking it down into steps and having the readers "finished" product wind up being this. But I guess we'll show off the goods first and if it gets attention, I'll explain it in some progressive examples.

Remember this needs the Vector2D alias from my first post! without it, it won't work!

To play around with it, type "/Asteroids" or you can specify your own window size by "/Asteroids <w> <h>", the window is also resizable by the edge borders. I wanted to demonstrate a sort-of "flexible" world, there's no error correction for shrinking it fast, so it may take a while for a rock to come back into view if you do, but they're always forced back to moving toward the middle, so it'll eventually get there smile

EDIT: forgot to mention controls... up = thrust, left/right = rotate, space = shoot, enter = new game if dead or you win.

Code:
alias Asteroids {
  Asteroids.End
  if (!$window(@Asteroids)) { window -Bdpf +tns @Asteroids -1 -1 $iif($1,$1,320) $iif($2,$2,240) }
  hadd -m Asteroids FPS 1 $ticks
  hadd -m Asteroids GameUpdate $ticks
  hadd -m Asteroids DrawUpdate $ticks
  hadd -m Asteroids Player.1.data Ship 0 0 0 -1 0 0 0
  Asteroids.AddObject largerock
  Asteroids.GameLoop
}
alias -l Asteroids.End { hfree -w Asteroids | .timerAsteroids off }
alias -l Asteroids.Polygons {
  if ($1 = ship) { return 11 0 -11 -7 -7 -3 -7 3 -11 7 }
  if ($1 = flame) { return -7 -2 -12 0 -7 2 -7 -2 }
  if ($1 = largeRock1) { return -39 -25 -33 -8 -38 21 -23 25 -13 39 24 34 38 7 33 -15 38 -31 16 -39 -4 -34 -16 -39 }
  if ($1 = largeRock2) { return -32 35 -4 32 24 38 38 23 31 -4 38 -25 14 -39 -28 -31 -39 -16 -31 4 -38 22 }
  if ($1 = largeRock3) { return 12 -39 -2 -26 -28 -37 -38 -14 -21 9 -34 34 -6 38 35 23 21 -14 36 -25 }
  if ($1 = medRock1) { return -7 -19 -19 -15 -12 -5 -19 0 -19 13 -9 19 12 16 18 11 13 6 19 -1 16 -17 }
  if ($1 = medRock2) { return 9 -19 18 -8 7 0 15 15 -7 13 -16 17 -18 3 -13 -6 -16 -17 }
  if ($1 = medRock3) { return 2 18 18 10 8 0 18 -13 6 -18 -17 -14 -10 -3 -13 15 }
  if ($1 = smallRock1) { return -8 -8 -5 -1 -8 3 0 9 8 4 8 -5 1 -9 }     
  if ($1 = smallRock2) { return -6 8 1 4 8 7 10 -1 4 -10 -8 -6 -4 0 }
  if ($1 = smallRock3) { return -8 -9 -5 -2 -8 5 6 8 9 6 7 -3 9 -9 0 -7 }
}
alias -l Asteroids.TranslatePolygon {
  var %c = $4 , %s = $5 , %ns = %s * -1 , %ox = $2 + $6 , %oy = $3 + $7
  return $regsubex($Asteroids.Polygons($1),/([^ ]+) ([^ ]+)/g,$calc($Vector2D(\1,\2,%c,%ns).dot + %ox) $calc($Vector2D(\1,\2,%s,%c).dot + %oy))
}
alias -l Asteroids.AddObject {
  var %w = $window(@Asteroids).dw , %h = $window(@Asteroids).dh / 2 , %hw = %w / 2 , %hh = %h / 2 , %a = 1 , %type = $1
  if (rock isin $1) {
    while ($hget(Asteroids,$+(Asteroid.,%a,.data))) { inc %a }
    if ($1 = largerock) { var %mag = 0.008 , %type = $+($1,$r(1,3)) }
    if ($1 = medrock) { var %mag = 0.011 , %type = $+($1,$r(1,3)) }
    if ($1 = smallrock) { var %mag = 0.022 , %type = $+($1,$r(1,3)) }
    var %ang = $r(0,360) + 15 , %x = $r(0,%w) - %hw , %y = $r(0,%h) - %hh , %dx = $sin(%ang).deg , %dy = $cos(%ang).deg , %vx = %dx * %mag , %vy = %dy * %mag
    hadd -m Asteroids $+(Asteroid.,%a,.data) %type $iif($0 = 3,$2-3,%x %y) %dx %dy %vx %vy
  }
  elseif ($1 = bullet) {
    var %a = 1 , %type = 2000 , %tot = $hfind(Asteroids,Bullet.*.data,0,w)
    if (%tot < 6) {
      while ($hget(Asteroids,$+(Bullet.,%a,.data))) { inc %a }
      var %vx = $4 * 0.15 , %vy = $5 * 0.15
      hadd -m Asteroids $+(Bullet.,%a,.data) %type $2-5 $Vector2D(%vx,%vy,$6,$7).Add
    }
  }
}
alias -l Asteroids.GameLoop {
  if (!$window(@Asteroids)) { Asteroids.End | return }
  var %hw = $window(@Asteroids).dw / 2 , %hh = $window(@Asteroids).dh / 2 , %GU = $hget(Asteroids,GameUpdate) , %gt = $ticks - %GU
  hadd -m Asteroids GameUpdate $ticks
  Asteroids.CheckKeys %gt
  noop $hfind(Asteroids,Asteroid.*.data.Translated,0,w,Asteroids.TestCollision $1)
  noop $hfind(Asteroids,*.*.data,0,w,Asteroids.UpdatePosition $1 %gt %hw %hh)
  Asteroids.Render %hw %hh
  tokenize 32 $hget(Asteroids,FPS)
  var %fs = $1 + 1 , %ft = $ticks - $2 , %ft = %ft / 1000
  if (%ft < 0.5) { hadd -m Asteroids FPS %fs $2 }
  else {
    titlebar @Asteroids Avg. FPS: $round($calc(%fs / %ft),0)
    hadd -m Asteroids FPS 0 $ticks
  }
  .timerAsteroids -m 1 0 Asteroids.GameLoop
}
alias -l Asteroids.CheckKeys {
  var %dt = $1 , %at = $1 / 15 , %dead = $hget(Asteroids,Dead) , %fired = $hget(Asteroids,Fired)
  tokenize 32 $hget(Asteroids,Player.1.data)
  var %type = $1 , %x = $2 , %y = $3 , %dx = $4 , %dy = $5 , %vx = $6 , %vy = $7
  var %e = $hget(Asteroids,Key.13) , %l = $hget(Asteroids,Key.37) , %u = $hget(Asteroids,Key.38) , %r = $hget(Asteroids,Key.39) , %sp = $hget(Asteroids,Key.32)
  if (%l || %r) && (!%dead) { tokenize 32 $Vector2D(%dx,%dy,$calc($+($iif(%l,-),%dt) * 0.27)).Rot | var %dx = $1 , %dy = $2 }
  if (%u && !%dead) { 
    tokenize 32 $Vector2D($calc(%dx * %at * 0.002),$calc(%dy * %at * 0.002),%vx,%vy).Add 
    if ($Vector2D($1,$2).MagSq < 0.1) { var %vx = $1 , %vy = $2 }
    else { tokenize 32 $Vector2D($1,$2).norm | var %vx = $1 * 0.32 , %vy = $2 * 0.32 }
  }
  if (%sp && !%dead) && (!%fired || $calc($ticks - %fired) >= 250) {
    Asteroids.AddObject bullet %x %y %dx %dy %vx %vy 
    hadd -m Asteroids Fired $ticks
  }
  if (%e) && (!$hfind(Asteroids,Asteroid.*.data,0,w) || %dead) { Asteroids | return }
  hadd -m Asteroids Player.1.data %type %x %y %dx %dy %vx %vy
}
alias -l Asteroids.TestCollision {
  var %asteroid = $1 , %bcol = $hfind(Asteroids,Bullet.*.data.translated,0,w,Asteroids.CheckCollision %asteroid $1)
  if (!%bcol) { Asteroids.CheckCollision $1 }
}
alias -l Asteroids.CheckCollision {
  var %a = $1 , %b = $2 , %AsteroidPoly = $regsubex($hget(Asteroids,$1),/ /g,$chr(44)) , %BulletVec = $regsubex($hget(Asteroids,$2),/ /g,$chr(44))
  if (%AsteroidPoly && !$hget(Asteroids,Dead)) {
    var %PlayerPoly = $regsubex($hget(Asteroids,Player.1.data.Translated),/ /g,$chr(44)) , %AsteroidCount = $numtok(%AsteroidPoly,44) / 2 , %PlayerCount = $numtok(%PlayerPoly,44) / 2
    if ($onpoly(%AsteroidCount,%PlayerCount, [ %AsteroidPoly ] , [ %PlayerPoly ] )) { 
      hadd -m Asteroids Dead 1
      hadd -m Asteroids Player.1.data Ship 0 0 0 -1 0 0 0
    }
  }
  if (%BulletVec && %AsteroidPoly) {
    if ($inpoly( [ %BulletVec ] , [ %AsteroidPoly ] )) {
      var %a = $deltok($1,4,46) , %b = $deltok($2,4,46)
      tokenize 32 $hget(Asteroids,%a)
      if (large isin $1) { var %x = 0 | while (%x < 2) { inc %x | Asteroids.AddObject medrock $2-3 } }
      elseif (med isin $1) { var %x = 0 | while (%x < 4) { inc %x | Asteroids.AddObject smallrock $2-3 } }
      hdel -w Asteroids %a $+ *
      hdel -w Asteroids %b $+ *
    }
  }
}
alias -l Asteroids.UpdatePosition {
  var %key = $1 , %dt = $2 , %hw = $3 , %hh = $4
  tokenize 32 $hget(Asteroids,$1)
  var %type = $1 , %x = $2 , %y = $3 , %dx = $4 , %dy = $5 , %vx = $6, %vy = $7
  if (%type isnum) { dec %type %dt }
  var %l = $Vector2D(%x,%y,1,0).dot , %r = %l * -1 , %u = $Vector2D(%x,%y,0,1).dot , %d = %u * -1
  var %vl = $Vector2D(%vx,%vy,1,0).dot , %vr = %vl * -1 , %vu = $Vector2D(%vx,%vy,0,1).dot , %vd = %vu * -1
  var %xp1 = %l + %hw , %xp2 = %r + %hw , %yp1 = %u + %hh , %yp2 = %d + %hh
  if (%xp1 < 0 && %vl < 0) || (%xp2 < 0 && %vr < 0) { var %x = %x * -1 }
  if (%yp1 < 0 && %vu < 0) || (%yp2 < 0 && %vd < 0) { var %y = %y * -1 }
  if (rock isin %type) {
    if (large isin %type) { var %div = 30 }
    elseif (med isin %type) { var %div = 20 }
    else { var %div = 10 }
    var %avx = %vx * %dt , %avy = %vy * %dt
    tokenize 32 $Vector2D(0,1,$Vector2D(%avx,%avy,0,1).Dot).MultScalar
    tokenize 32 $Vector2D(%avx,%avy,$1,$2).Sub
    var %rot = $calc($Vector2D($1,$2,0,1).Wedge / %div * 57.29578)
    tokenize 32 $Vector2D(%dx,%dy,%rot).Rot | var %dx = $1 , %dy = $2 
  }
  tokenize 32 $Vector2D(%x,%y,$calc(%vx * %dt),$calc(%vy * %dt)).Add
  hadd -m Asteroids %key %type $1-2 %dx %dy %vx %vy
  if (%type isnum) { hadd -m Asteroids $+(%key,.Translated) $Vector2D(%x,%y,%hw,%hh).Add }
  else { 
    hadd -m Asteroids $+(%key,.Translated) $Asteroids.TranslatePolygon(%type,$1,$2,%dx,%dy,%hw,%hh) 
    if (%type = ship) {
      if ($hget(Asteroids,Key.38)) { hadd -m Asteroids Player.2.Data.Translated $Asteroids.TranslatePolygon(flame,$1,$2,%dx,%dy,%hw,%hh) }
      else { hdel -w Asteroids Player.2.* }
    }
  }
  if (%type <= 0) { hdel -w Asteroids %key $+ * }
}
alias -l Asteroids.DrawTranslated {
  if (Player !isin $1 || !$hget(Asteroids,Dead)) {
    tokenize 32 $hget(Asteroids,$1)
    if ($0 > 8) { drawline -n @Asteroids 0 1 $1- $1-2 }
    elseif ($0 > 3) { drawline -n @Asteroids 7 1 $1- $1-2 }
    elseif ($0) { drawdot -n @Asteroids 9 2 $1- }
  }
}
alias -l Asteroids.Render {
  drawrect -nf @Asteroids 1 1 0 0 $window(@Asteroids).dw $window(@Asteroids).dh
  noop $hfind(Asteroids,*.*.data.translated,0,w,Asteroids.DrawTranslated $1)
  if ($hget(Asteroids,Dead)) { var %msg = You LOSE! Press Enter... }
  elseif (!$hfind(Asteroids,Asteroid.*.data.translated,0,w)) { var %msg = You Win! Press Enter... }
  if (%msg) {  
    var %ox = $width(%msg,$window(@Asteroids).font,$window(@Asteroids).fontsize) / 2
    drawtext -n @Asteroids 0 $calc($1 - %ox) $2 %msg
  }
  drawdot @Asteroids
}
on *:keydown:@Asteroids:*: { hadd -m Asteroids $+(Key.,$keyval) 1 }
on *:keyup:@Asteroids:*: { hdel -w Asteroids $+(Key.,$keyval) | if ($keyval = 32) { hdel -w Asteroids Fired  } }


Last edited by Talon; 25/07/15 07:22 AM.
Re: Linear Algebra - Tutorial for simple game design [Re: Talon] #254160 25/07/15 09:21 AM
Joined: Sep 2014
Posts: 259
S
Sakana Offline
Fjord artisan
Offline
Fjord artisan
S
Joined: Sep 2014
Posts: 259
Thanks for this, bookmarked it. I'll need a 2D vector space for a future project, though it's not game related