转向行为已经被各种语言实现过多次了,其最底层是用向量来描述的(也是最常见的实现方式)。
概括的看,一个向量由两部分组成:一个方向和一个大小。比如,一个运动中对象的速度由它要去哪里(方向)和移动快慢(大小)两部分组成。因此,把速度看作一 个向量是最贴切不过的。加速度——任何改变对象速度的作用力——同样也是由力的方向和大小组成(另一个向量)。向量同样也可以用来描述对象间的位置关系, 其中大小代表距离,方向代表角度。
向量还可以用来表示一个角色(脸)的朝向,这种情况下就只管方向,而忽视大小,也可以说大小等于1。这样的向量叫做单位向量(unit vector)。实际上,只有一单位长度的向量,在数学运算上能起到很大的优化作用。
向量的所有这些特性对转向行为来说都很有用,因为速度,队伍方向,对象间距离,对象的朝向都会被大量的使用。
代码语言:javascript复制 public class Vector2D
{
private double _x;
private double _y;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public Vector2D(double x, double y)
{
_x = x;
_y = y;
}
public double x
{
get
{
return _x;
}
set
{
_x = value;
}
}
public double y
{
get
{
return _y;
}
set
{
_y = value;
}
}
/// <summary>
/// 克隆
/// </summary>
/// <returns></returns>
public Vector2D clone()
{
return new Vector2D(x, y);
}
/// <summary>
/// 初始化向量
/// </summary>
/// <returns></returns>
public Vector2D zero()
{
_x = 0;
_y = 0;
return this;
}
/// <summary>
/// 这个向量是否等于零,即x,y,长度为零。
/// </summary>
/// <returns></returns>
public bool isZero()
{
return _x == 0 && _y == 0;
}
/// <summary>
/// 向量大小
/// </summary>
public double length
{
get
{
return Math.Sqrt(lengthSQ);
}
set
{
double a = angle;
_x = Math.Cos(a) * value;
_y = Math.Sin(a) * value;
}
}
/// <summary>
/// 得到这个向量长度的平方。
/// </summary>
public double lengthSQ
{
get
{
return _x * _x _y * _y;
}
}
/// <summary>
/// 得到这个向量角度。
/// </summary>
public double angle
{
get
{
return Math.Atan2(_y, _x);
}
set
{
double len = length;
_x = Math.Cos(value) * len;
_y = Math.Sin(value) * len;
}
}
/// <summary>
/// 单位化向量,设定长度为一,更有效率。
/// </summary>
/// <returns></returns>
public Vector2D normalize()
{
if (length == 0)
{
_x = 1;
return this;
}
double len = length;
_x/=len;
_y/=len;
return this;
}
/// <summary>
/// 截断
/// </summary>
/// <param name="max"></param>
/// <returns></returns>
public Vector2D truncate(double max)
{
length = Math.Min(max, length);
return this;
}
/// <summary>
/// 倒置
/// </summary>
/// <returns></returns>
public Vector2D reverse()
{
_x = -_x;
_y = -_y;
return this;
}
/// <summary>
/// 是否单位化
/// </summary>
/// <returns></returns>
public bool isNormalized()
{
return length == 1.0;
}
/// <summary>
/// 积
/// </summary>
/// <param name="v2"></param>
/// <returns></returns>
public double dotProd(Vector2D v2)
{
return _x * v2.x _y * v2.y;
}
/// <summary>
/// 差
/// </summary>
/// <param name="v2"></param>
/// <returns></returns>
public double crossProd(Vector2D v2) {
return _x * v2.y - _y * v2.x;
}
/// <summary>
/// 两个向量之差
/// </summary>
/// <param name="v1"></param>
/// <param name="v2"></param>
/// <returns></returns>
public static double angleBetween(Vector2D v1, Vector2D v2)
{
if (!v1.isNormalized())
{
v1 = v1.clone().normalize();
}
if (v2.isNormalized())
{
v2 = v2.clone().normalize();
}
return Math.Acos(v1.dotProd(v2));
}
/// <summary>
/// 确定给定向量的方向
/// </summary>
/// <param name="v2"></param>
/// <returns></returns>
public int sign(Vector2D v2)
{
return perp.dotProd(v2) < 0 ?-1: 1;
}
/// <summary>
/// 垂直与这个向量的向量
/// </summary>
public Vector2D perp
{
get
{
return new Vector2D(-y, x);
}
}
/// <summary>
/// 两个向量间的距离
/// </summary>
/// <param name="v2"></param>
/// <returns></returns>
public double dist(Vector2D v2)
{
return Math.Sqrt(distSQ(v2));
}
/// <summary>
/// 计算向量到另一个给定向量的距离
/// </summary>
/// <param name="v2"></param>
/// <returns></returns>
public double distSQ(Vector2D v2)
{
double dx = v2.x - x;
double dy = v2.y - y;
return dx * dx dy * dy;
}
/// <summary>
/// 和
/// </summary>
/// <param name="v2"></param>
/// <returns></returns>
public Vector2D add(Vector2D v2)
{
return new Vector2D(_x v2.x, _y v2.y);
}
/// <summary>
/// 差
/// </summary>
/// <param name="v2"></param>
/// <returns></returns>
public Vector2D subtract(Vector2D v2)
{
return new Vector2D(_x - v2.x, _y - v2.y);
}
/// <summary>
/// 翻倍
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public Vector2D multiply(double value)
{
return new Vector2D(_x * value, _y * value);
}
/// <summary>
/// 减倍
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public Vector2D divide(double value)
{
return new Vector2D(_x / value, _y / value);
}
/// <summary>
/// 是否相等
/// </summary>
/// <param name="v2"></param>
/// <returns></returns>
public bool equals(Vector2D v2)
{
return _x == v2.x && _y == v2.y;
}
public override string ToString()
{
return "[Vector2D(x:" _x ",y:" _y ")]";
}
}
对于实现这样的类,在架构上就存在着挑战,比如决定类的方法该如何工作。对这些方法truncate,normalize,reverse,add,substrat,multiple和divide有两种考虑情况:是直接对调用对象做改变呢,还是返回一个新的对象。
举个例子,假设有vectorA(3,2)意思是x等于3,y等于2,和vectorB等于(4,5),然后执行以下代码:
vectorA.add(vectorB);
根据第一种情况,vectorB不变,而vectorA等于(7,7)。
而另一种情况,vectorA和vectorB都不变,但是产生一个新的等于(7,7)的向量,我们让它等于vectorC。
vectorC = vectorA.add(vectorB);
那么哪种做法才合适呢?对此,我前前后后进行了多番审视,最终发现在很多数学运算时,需要把一个对象——比如位置和速度——用向量来表示,而无所谓运算后对象本身的改变。所以,加、减、乘、除不对原对象进行修改。
然而truncate(截断),reverse(倒置)和normalize(单位化)则更注重对象本身的改变,所以这些操作用来直接改变对象要比返回一个新的更有用。
互换以上操作方式也不是很难。如果想把vectorB加在vectorA上,可以这样:
vectorA = vectorA.add(vectorB);
而如果想让normalize返回一个新的对象,可以使用clone(克隆)函数:
normalizedA = vectorA.clone().normalize();
现在,有了向量类可以表示角色的位置,速度和各种群体。还需要一个类用来表示角色。