Who Wants Some Math?

One of the most simple principles in 2D games is an object that bounces around in a container.

This little set of math knowledge is really important for any game that has some basic physics in it, for example, Pong.

An easy way to reflect a vector about a certain axis is just to negate a component of the vector, for example, x = (-1)x. But what if you had a game like Pong where you could rotate the paddles?

In this case, the math becomes a little harder. In XNA, you have a great mini-physics library available with data types like Vector2 and methods like Reflect that do all the work for you. There are physics engines out there for Silverlight that will do this for you, but it's helpful to know what you're doing in the first place. Come with me for a little trig lesson!

Basic Trig & Vectors

Disclaimer: Some of this may not be 100% textbook correct. I minored in math in college and this took a lot of digging!

Vectors. A vector, in two dimensions, has an X component, a Y component, and a magnitude or length. Any vector with a magnitude of 1 is a unit vector. <1, 0> is the X unit vector and <0, 1> is the Y unit vector. Normalizing is the act of creating a unit vector from any other vector.

You can use vectors to represent the general direction or heading of an object. An object that is traveling along the X axis might have a direction vector of <1, 0>.

Vectors have different operations commonly associated with them.

  • Addition. Adding two vectors generates a resultant vector. If A = <1,0> and B = <0, 1>, then A + B = <1, 1>.
  • Normalization. A normalized vector is any vector with the length 1. To find the normal vector of any given vector, divide both components of the vector by the vector's length.
  • Multiplication. Multiplying a vector by a scalar factor will increase the magnitude of the vector. An easy way to do this is to multiply both components by the scalar factor. If you were to multiply <1, 0> by a factor of 3, you would have <3, 0>.
  • Dot product. The dot product of a vector is different than multiplication in that you are multiplying two vectors together. If you had a vector A<1, 2> and a vector B<2, 4>, the dot product A·B would be equal to <Ax*Bx, Ay*By> or <2, 8>.
  • Reflection. As mentioned above, if you are reflecting on a surface that can be represented by a unit vector (basically an axis), then you could accomplish reflection just by negating one of the components. But what if you are reflecting off of some weirdly rotated platform, or the vector <2, 3>, or a line at a 45 degree angle?

    In either case, you can use the formula below to accomplish reflection:

    Let R be the vector result of the reflection
    Let N be the normal vector of the vector you are bouncing off of
    Let V be the vector that represents the incoming object's direction

    R = 2 * (V · N) - V

    Example 1
    So if V is <1, 2> and you are bouncing off the Y axis (the normal unit vector is <0, 1>) then V·N would be <1, 2> · <0, 1> = <0, 2>
    Multiplying this by 2 would yield <0, 4>
    Subtracting the original vector V would yield <0, 4> - <1, 2> = <-1, 2>

    See the difference? Before: <1, 2>. After: <-1, 2>. The X component is negative which means that the object will bounce off of a left, or right, wall.

    The nice part is that this formula works if you are bouncing off of some non-planar vector, such as <2, 1>.

    Example 2
    Let V be <1, 2> and let our bouncy vector be <2, 1>. The length of <2, 1> can be calculated like the hypotenuse of a triangle: Sqrt(2*2 + 1*1) = Sqrt(4 + 1) = Sqrt(5) = 2.236.
    The normal vector N would be <2, 1> / 2.236 = <0.894, 0.447>. If you again take the length of this resulting normal vector, you'd get 0.999 or 1, which means our work is accurate.
    The dot product of V and N, V·N yields <0.894, 0.894>
    Multiplying this by 2 would yield <1.788, 1.788>
    Subtracting the original vector V would yield <.788, -.212> for the reflection vector. See how that works? Well, probably not; those numbers don't mean anything, so here's a bad scan of some graph paper.

    IMG_2245 

    Notice the original direction vector, V, indicates a heading of up and to the right. Vector B, the one we are bouncing off of, is also up and to the right but at a lesser degree (also note this vector is translated somewhere in space). The box you see is the right angle between V and R, the reflection of V. Notice that this makes perfect sense, because our vector R indicates the direction "to the right and down a little" ( <.788, -.212> ).

Applications in Silverlight

In Silverlight, there are no useful physics / trig / math stuff built in aside from what's included in the compact framework. I had to roll my own MathHelper class and include it in my project to do a lot of these operations. This is the Math Helper you see being used in the Silverlight applications at the top of the post.

   1: using System;
   2:  
   3: namespace MathHelper
   4: {
   5:     public class Vector2
   6:     {
   7:         private float _x;
   8:         private float _y;
   9:  
  10:         /// <summary>
  11:         /// Gets or sets the X component of the vector
  12:         /// </summary>
  13:         public float X
  14:         {
  15:             get
  16:             {
  17:                 return _x;
  18:             }
  19:             set
  20:             {
  21:                 _x = value;
  22:             }
  23:         }
  24:  
  25:         /// <summary>
  26:         /// Gets or sets the Y component of the vector
  27:         /// </summary>
  28:         public float Y
  29:         {
  30:             get
  31:             {
  32:                 return _y;
  33:             }
  34:             set
  35:             {
  36:                 _y = value;
  37:             }
  38:         }
  39:  
  40:         /// <summary>
  41:         /// Gets the length of the vector
  42:         /// </summary>
  43:         public float Length
  44:         {
  45:             get
  46:             {
  47:                 return (float)Math.Sqrt(_x * _x + _y * _y);
  48:             }
  49:         }
  50:         
  51:         /// <summary>
  52:         /// Gets a Vector2 object containing X=1, Y=0
  53:         /// </summary>
  54:         public static Vector2 UnitX
  55:         {
  56:             get
  57:             {
  58:                 return new Vector2(1.0f, 0.0f);
  59:             }
  60:         }
  61:  
  62:         /// <summary>
  63:         /// Gets a Vector2 object containing X=0, Y=1
  64:         /// </summary>
  65:         public static Vector2 UnitY
  66:         {
  67:             get
  68:             {
  69:                 return new Vector2(0.0f, 1.0f);
  70:             }
  71:         }
  72:  
  73:         public Vector2()
  74:         {
  75:             _x = 0;
  76:             _y = 0;
  77:         }
  78:  
  79:         public Vector2(float x, float y)
  80:         {
  81:             _x = x;
  82:             _y = y;
  83:         }        
  84:  
  85:         /// <summary>
  86:         /// Reflects a vector based on incidence with another vector
  87:         /// </summary>
  88:         /// <param name="unitVector">The unit vector to reflect on</param>
  89:         /// <returns>The new reflected Vector2 object</returns>
  90:         public Vector2 Reflect(Vector2 unitVector)
  91:         {
  92:             float dotProduct = this.Dot(unitVector);
  93:             Vector2 reflection = unitVector.Multiply(2 * dotProduct).Subtract(this);
  94:             return reflection;
  95:         }
  96:  
  97:         /// <summary>
  98:         /// Subtracts one vector from another
  99:         /// </summary>
 100:         /// <param name="vector">The vector to subtract from this one</param>
 101:         /// <returns>The resultant vector</returns>
 102:         public Vector2 Subtract(Vector2 vector)
 103:         {
 104:             Vector2 result = new Vector2();
 105:             result.X = this.X - vector.X;
 106:             result.Y = this.Y - vector.Y;
 107:             return result;
 108:         }
 109:  
 110:         /// <summary>
 111:         /// Adds one vector to another
 112:         /// </summary>
 113:         /// <param name="vector">The vector to add to this one</param>
 114:         /// <returns>The resultant vector</returns>
 115:         public Vector2 Add(Vector2 vector)
 116:         {
 117:             Vector2 result = new Vector2();
 118:             result.X = this.X + vector.X;
 119:             result.Y = this.Y + vector.Y;
 120:             return result;
 121:         }
 122:  
 123:         /// <summary>
 124:         /// Multiplies the vector by a scalar value
 125:         /// </summary>
 126:         /// <param name="scaleFactor">The magnitude of the new vector</param>
 127:         /// <returns>The resultant vector</returns>
 128:         public Vector2 Multiply(float scaleFactor)
 129:         {
 130:             Vector2 result = new Vector2();
 131:             result.X = this.X * scaleFactor;
 132:             result.Y = this.Y * scaleFactor;
 133:             return result;
 134:         }
 135:  
 136:         /// <summary>
 137:         /// Gets the dot product (a scalar value) between this vector and another
 138:         /// </summary>
 139:         /// <param name="vector">The vector to dot with this one</param>
 140:         /// <returns>The scalar dot product</returns>
 141:         public float Dot(Vector2 vector)
 142:         {
 143:             float result = 0.0f;
 144:             result = this.X * vector.X + this.Y * vector.Y;
 145:             return result;
 146:         }
 147:     }
 148: }

 

Applications in XNA

XNA is the best! All this stuff is already built in in XNA so you don't need to mess with the above math at all! Just use Vector2.Reflect, Vector2.Dot, and other lovely methods that are at your disposal. Makes Pong Easier.

Conclusion

This is some of the most basic stuff you need to know to understand what exactly is going on behind all of that bouncing. Hopefully it was moderately easy to understand!