[Silverlight动画]转向行为 - 2D向量
转向行为已经被各种语言实现过多次了,其最底层是用向量来描述的(也是最常见的实现方式)。
概括的看,一个向量由两部分组成:一个方向和一个大小。比如,一个运动中对象的速度由它要去哪里(方向)和移动快慢(大小)两部分组成。因此,把速度看作一 个向量是最贴切不过的。加速度——任何改变对象速度的作用力——同样也是由力的方向和大小组成(另一个向量)。向量同样也可以用来描述对象间的位置关系, 其中大小代表距离,方向代表角度。
向量还可以用来表示一个角色(脸)的朝向,这种情况下就只管方向,而忽视大小,也可以说大小等于1。这样的向量叫做单位向量(unit vector)。实际上,只有一单位长度的向量,在数学运算上能起到很大的优化作用。
向量的所有这些特性对转向行为来说都很有用,因为速度,队伍方向,对象间距离,对象的朝向都会被大量的使用。
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();
现在,有了向量类可以表示角色的位置,速度和各种群体。还需要一个类用来表示角色。