[Silverlight动画]转向行为 - 2D向量

2018-01-16 11:58:09 浏览数 (1)

转向行为已经被各种语言实现过多次了,其最底层是用向量来描述的(也是最常见的实现方式)。

概括的看,一个向量由两部分组成:一个方向和一个大小。比如,一个运动中对象的速度由它要去哪里(方向)和移动快慢(大小)两部分组成。因此,把速度看作一 个向量是最贴切不过的。加速度——任何改变对象速度的作用力——同样也是由力的方向和大小组成(另一个向量)。向量同样也可以用来描述对象间的位置关系, 其中大小代表距离,方向代表角度。

向量还可以用来表示一个角色(脸)的朝向,这种情况下就只管方向,而忽视大小,也可以说大小等于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();

现在,有了向量类可以表示角色的位置,速度和各种群体。还需要一个类用来表示角色。

0 人点赞