Chase's Techno Rants and Raves

Umm.. did I just hear that guy call it "C-Pound?!"

  Home  |   Contact  |   Syndication    |   Login
  19 Posts | 0 Stories | 22 Comments | 5 Trackbacks

News

Me

Archives

Image Galleries

Thursday, April 24, 2008 #

Updated 5/19/2008 to use code CSS files and show both posts as one

Recently I started playing around with the XNA framework.  I absolutely LOVE it.  So much simpler to use than raw DirectX.  Well while working on my first 2D game I came across a problem - there was no intrinsic support to move a sprite along a specified path starting at Point A and ending up at Point B (and then having it stop there) - after lots of digging - reading a lot of crappy forums and pouring through tutorial after tutorial - there was nothing readily simple out there to parse out this kind of (what should be) a relatively simple task. 

This to me seemed stupid that there would be nothing built in that does this, unit vectors and Rays alone will not help you, and trying to use a simple linear equation doesn't do it either - here's an example of that simple formula that doesn't work for all applications (works great if you have a straight horizontal line however).

B(t) = P0+(P1-P0)t

Which in code translates to...

   1:  public Vector3 PathUnit(Vector3 pos1, Vector3 pos2, float speed)
   2:  {
   3:             return pos1 + (pos2 - pos1) * speed;
   4:  }
 
Like I said - fine if you just want to go from point a to point b on a straight horizontal line - but what about diagonal lines?  This will fall flat on it's face if you just try to add the two vectors together (the Y keeps incrementing at a static rate regardless of the slope) - wait - there's that word - slope...  So that leads us to the Slope intercept formulas.

First - find the slope of your line using the slope formula you learned way back in 8th grade...

m = (y2-y1)
(x2-x1)

Which in code looks like this... 

lineSlope = (_endPoint.Y - _startPoint.Y) / (_endPoint.X - _startPoint.X);

Now that we have our slope we can get the slope intercept for the Y axis using the slope-intercept formula where the slope is "M" and b is our speed (in our case we are going to muliply it since it gives better results on the screen).

y = mx+b

Our code will look something like this (take the slope we just got and multiply it by our end point's X position - then multiply this by the speed - we will be using the value a lot later on.

For our X intercept we will use a Ray structure - as follows....

_direction = new Ray(_startPoint, _endPoint); 
_xIntercept = _direction.Direction.X*speed;

In some cases this will not work (In the case of vertical lines) this is because mathematically a slope of 0 is horizontal and a slope of Infinity is straight up and down.  So we have to do this when that happens....

   1:  if (float.IsNegativeInfinity(_lineSlope) || 
   2:      float.IsInfinity(_lineSlope)) //vertical line moving down 
   3:  { 
   4:       if (_startPoint.Y > _endPoint.Y) 
   5:            _yIntercept = -speed; 
   6:       else 
   7:            _yIntercept = speed; 
   8:            
   9:       _xIntercept = 0.0f; //duh - a vertical line moves neither left nor right 
  10:  } 
  11:  else if (float.IsNaN(_lineSlope)) //vertical line moving up 
  12:  { 
  13:       if (_startPoint.Y > _endPoint.Y) 
  14:            _yIntercept = -speed; 
  15:       else 
  16:            _yIntercept = speed; 
  17:       
  18:       _xIntercept = 0.0f; //see above... 
  19:  }


So this all works fine - now we need to come up with a way to tell it to stop when our point get's to where it is going...   The rectangle class in XNA - well - it sucks, why they use integers instead of floats like everything else makes no sense to me - oh well - enough ranting - making our own is easy enough and we can even make it smart enough to center itself over a specified vector as such...

   1:    public void Inflate(float size) 
   2:    { 
   3:            _top += size; 
   4:            _bottom -= size; 
   5:            _left -= size; 
   6:            _right += size; 
   7:            _front -= size; 
   8:            _back += size; 
   9:    }
  10:   
  11:    public bool VectorInRect(Vector3 position) 
  12:    { 
  13:         return (position.X >= _left && position.X <= _right && 
  14:                 position.Y <= _top && position.Y >= _bottom && 
  15:                 position.Z >= _front && position.Z <= _back); 
  16:    }
  17:   

Our inflate method gives us the ability to fractionally increase the size in all 4 directions (this is just like the worthless integer based rectangle I told you about earlier).

So when our point gets close to the end of the line - we can check to see if it's in this region - if it is - we can just set the point to the end and stop there. 

Here is the whole thing put together...

   1:  using System;
   2:  using Microsoft.Xna.Framework;
   3:   
   4:  namespace GameUtilities
   5:  {
   6:      public class LinearPathHelper
   7:      {
   8:          private Vector2 _startPoint;
   9:          private Vector2 _endPoint;
  10:          private float _lineSlope = 0.0f;
  11:          private float _yIntercept = 0.0f;
  12:          private float _xIntercept = 0.0f;
  13:          private Ray _direction;
  14:   
  15:          private StopRegion _stopRegion;
  16:   
  17:          public LinearPathHelper(Vector2 startPoint, Vector2 endPoint, float speed)
  18:          {
  19:              _startPoint = startPoint;
  20:              _endPoint = endPoint;
  21:  
            _lineSlope = (_endPoint.Y - _startPoint.Y) / (_endPoint.X - _startPoint.X);
  22:   
  23:              if (float.IsNegativeInfinity(_lineSlope) || float.IsInfinity(_lineSlope)) //vertical line moving down
  24:              {
  25:                  if (_startPoint.Y > _endPoint.Y)
  26:                      _yIntercept = -speed;
  27:                  else
  28:                      _yIntercept = speed;
  29:   
  30:                  _xIntercept = 0.0f;
  31:              }
  32:              else if (float.IsNaN(_lineSlope)) //vertical line moving up
  33:              {
  34:                  if (_startPoint.Y > _endPoint.Y)
  35:                      _yIntercept = -speed;
  36:                  else
  37:                      _yIntercept = speed;
  38:   
  39:                  _xIntercept = 0.0f;
  40:              }
  41:              else //diagonal line
  42:              {
  43:                  _yIntercept = (_lineSlope * _endPoint.X) * speed;
  44:                  _direction = new Ray(new Vector3(_startPoint,0.0f),new Vector3( _endPoint,0.0f));
  45:                  _xIntercept = _direction.Direction.X * speed;
  46:              }
  47:   
  48:              _stopRegion = new StopRegion(_endPoint, 10f, 10f);
  49:              _stopRegion.Inflate(speed);
  50:   
  51:          }
  52:   
  53:          private class StopRegion
  54:          {
  55:              private float _top;
  56:              private float _bottom;
  57:              private float _left;
  58:              private float _right;
  59:   
  60:              public StopRegion(Vector2 point, float width, float height)
  61:              {
  62:                  _top = point.Y - (height / 2);
  63:                  _left = point.X - (width / 2);
  64:   
  65:                  _bottom = point.Y + height;
  66:                  _right = point.X + width;
  67:              }
  68:   
  69:              public void Inflate(float size)
  70:              {
  71:                  if (size < 1)
  72:                      size = 50;
  73:   
  74:                  _top -= size;
  75:                  _bottom += size;
  76:                  _left -= size;
  77:                  _right += size;
  78:              }
  79:   
  80:              public bool VectorInRect(Vector2 position)
  81:              {
  82:                  return (position.X >= _left && position.X <= _right &&
  83:                          position.Y >= _top && position.Y <= _bottom);
  84:              }
  85:          }
  86:   
  87:          public void MoveVectorPoint(ref Vector2 currentPointPos)
  88:          {
  89:              //if we are within the boundaries of our stopping region - we need to snap to it
  90:              //so the move helper will stop the animation timer...
  91:              if (_stopRegion.VectorInRect(currentPointPos))
  92:              {
  93:                  currentPointPos = _endPoint;
  94:                  return;
  95:              }
  96:   
  97:              currentPointPos.X -= _xIntercept;
  98:              currentPointPos.Y -= _yIntercept;
  99:          }
 100:   
 101:          public bool IsAtDestination(Vector2 currentPointPos)
 102:          {
 103:              return currentPointPos == _endPoint;
 104:          }
 105:      }
 106:  }