[翻译]XNA外文博客文章精选之twelve


PS:自己翻译的,转载请著明出处

                                                     RTS风格的移动:转向并向一个目标移动
                                                                                            Phantom
                                     在许多游戏里都会有一个共同的特点,它的RTS(即时战略),PRG(角色扮演)或者其他游戏类型,是使精灵旋转后面对一个目标;在RTS里这通常是一个敌人或者正好是要移动到的一个位置。XNA使它变的十分简单去旋转精灵或者单独的或者做为一组旋转,但是找到角度可以稍微更复杂使用三角法和弧度。
                                     这篇文章将会展示你如何去创建一个unit(个体),当给定一个地方旋转去面对它并且移动到那个位置。它同样会解释在后台的这个三角法和矩阵。
                                     这个unit它本身将会可拖拉的游戏组件部分,这主要是由于IM只有创造一个unit在这个例子中。
1 public class Unit:DrawableGameComponent
2 {
3         private Texture2D m_unit;
4         private SpriteBatch m_batch;
5         private Matrix m_matrix;
6         private float m_movementspeed;
7         private float m_turningspeed;
                                     这前面的一小片代码中,我们为这个unit包括5个基础的变量,它们都十分简单,这个unit的纹理,sprite batch去绘制它,这个矩阵,它将在后面被使用并且最后连个浮点型数去控制转向和unit的移动的速度。
                                     我们同样需要有一个变量为unit的当前的位置和旋转,但同样一个变量去保存位置,这个位置是我们象要精灵最终移动到的并且角度是面对这个位置。
 1 private Vector2 m_endposistion;
 2 public Vector2 EndPosition
 3 {
 4     get { return m_endposistion; }
 5     set
 6     {
 7         m_endposistion = value;
 8         UpdateEndDirection();           
 9     }
10 }
11 
12     private Vector2 m_position;
13     private float m_enddirection;
14     private float m_rotation;
15     private float m_direction
16     {
17       get { return m_rotation; }
18       set
19       {
20          m_rotation = value;
21          if (m_rotation < 0)
22          {
23              m_rotation += (float)(Math.PI * 2);
24          }
25          else if (m_rotation > Math.PI * 2)
26          {
27              m_rotation -= (float)(Math.PI * 2);
28          }
29       }
30 }
                                    这个m_endposition变量同样有一个公有属性作为当你希望发送这个unit到一个新的位置,每一次这个EndPosition被设置一个被调用的方法同样去更新m_enddirection变量。m_position保存这个units当前的位置和m_rotation保存当前的units的旋转,然而,这是通过m_direction属性被更新的,这个属性执行一个检查每一次它的更新去确保浮点值总是保持在0和2Pi之间;这是因为所有的C#使用弧度去测量在一个圆里的角度,一个整圆是2Pi的弧度,它近似于6.283,大于它或者小于也是可以使用的,但是产生了更多的麻烦,一会在计算上去结实这个数据。
1 private Vector2 m_origin;
                                    最后一个变量是初始点,这点是unit将会转向的,如果你想要unit开始在中心,因为是我选择要这样,然后设置你的unit的高度和宽度的一半。
                                    下一部分是结构器和加载内容,这都是非常直接的。因为这个unit是一个可拖拽的游戏组件部分,,我们必须传入到游戏对象中,但是我们同样传入unit的开始的位置。
 1 public Unit(Game game, Vector2 pos): base (game)
 2 {
 3       m_position = pos;
 4       m_endposistion = pos;
 5       m_origin = new Vector2(3232;
 6       m_movementspeed = 15f;
 7       m_turningspeed = 0.05f;
 8 }
 9 protected override void LoadContent()
10 {
11      m_unit = Game.Content.Load<Texture2D>("Unit"); 
12      m_batch = new SpriteBatch(GraphicsDevice);
13      base.LoadContent();
14 
                                    开始的位置传入到构造器中,我们设置units当前和结束的位置,这是为了让unit开始不动。这个构造器同样设置我们初始位置和为了速度的两个浮点型。
                                    这LoadContent方法是十分简单的,它加载我们的unit纹理和使用spritebatch去绘制它。
1 public override void Draw(GameTime gameTime)
2 {
3     m_batch.Begin();
4     m_batch.Draw(m_unit, m_position, null, Color.Red, m_direction, m_origin, 1f, SpriteEffects.None, 0.0f);
5     m_batch.End();
6     base.Draw(gameTime);
7 }
                                    Draw比平常略微不同,因为当我们调用draw方法在sprite batch上,我们使用第6个,这样我们可以传入m_direction作为第5个变量,这个变量绘制精灵面对的方向;我们同样传入我们的初始位置告诉它,旋转精灵去指向它。另外一个变量十分简单可以自我解释。
                                    纹理,位置,没有源矩形,风格,当前旋转,在旋转点,缩放,没有效果,没有深度。
                                    我喜欢保留update方法,简单放置大量代码在另外两个方法中。
 1 public override void Update(GameTime gameTime)
 2 {
 3     if (m_direction != m_enddirection)
 4     {// If your not currently facing the right direction turn
 5          TurnTo();
 6     }
 7     else if (m_position != m_endposistion)
 8     {// Else If your not at your end destination move
 9          MoveTo();
10     }
11     base.Update(gameTime);
12 }
                                   当update方法被调用,它首先检查当前的unit是否面对正确的方向,通过m_direction和m_enddirection的比较,如果它不是的,它调用TurnTo方法;如果unit当前是面对正如的方向,然后它把当前的位置(m_position)与结束的位置(m_endposition)相比较,如果unit不在结束的位置上它调用MoveTo方法。
                                   MoveTo方法被用来更新unit的位置,而这正是更复杂的观念在起作用。首先的事情是这个方法的是得到当前和结束位置之间的距离,通过使用Pythagoreantheorem,幸运的是XNA有一个方法处理这个,调用Length方法在一个Vector2上,将给你提供两个角之间的直线距离。通过在两个位置之间你得到的Vector2的当前位置,减去结束点位置,并且调用Length方法在你提供的距离的一个浮点型上。
 1 private void MoveTo()
 2 {
 3        float distance = (m_position - m_endposistion).Length();
 4        if (distance < m_movementspeed)
 5        {
 6            m_position = m_endposistion;
 7        }
 8        else
 9        { 
10           m_matrix = Matrix.CreateTranslation(-m_position.X, -m_position.Y, 0);
11           m_matrix = Matrix.CreateRotationZ(-m_direction);
12           m_matrix *= Matrix.CreateTranslation(0,-m_movementspeed, 0); 
13           m_matrix *= Matrix.CreateRotationZ(m_direction);
14           m_matrix *= Matrix.CreateTranslation(m_position.X, m_position.Y, 0);  
                                   一旦你有m_position和m_endposition之间的距离,首先检查的是unit是否达到结束的位置,这将通过m_movmentspeed是否比这个距离小来返回,如果是这种情况,没有做任何更多的计算你点只需要设置m_position与m_endposition相等。
                                   在这种情况下,unit不会达到结束位置,然后你需要unit以当前速度朝着这个结束位置移动。我选择使用一个矩阵去做这个,但是我同样还是要去解释这个方法,使用Pythagoreantheorem(勾股定理)
                                   使用这个Matrix方法,这里有5步在你可以得到unit的新的向量位置之前。你将会看见每一个阶段的显示出的效果,因为它可以影响你的unit在每一个阶段。

                 1.开始的位置。
                 2.矩阵总是旋转在0,0上,这样你需要使用你当前位置的负方向转变这个矩阵
                 3.然后你需要旋转矩阵,这样它很容易去添加你的移动,所以如果你旋转它到你当前方向的负方向时,他将会指向上。
                 4.一旦矩阵被旋转,你现在刚好可以通过准确的速度来转换它,你希望你的unit通过这个速度来传播。
                 5.现在你有效的移动你的unit,如你所希望的,并且现在可以再运用于旋转。
                 6.最后再运用你前面的位置的平移,并且以你最终的位置结束。
1 Vector3 dest = m_matrix.Translation;
2 m_position.X = dest.X;
3 m_position.Y = dest.Y;
                                   一旦你操作你的矩阵去产生渴望的向量,然后你可以使用这个矩阵的Translation属性去找到移动的总和,你创建并设置你的units位置到它们中。
                                   aulternative方法是用来找到两个位置之间的距离,并且使用移动速度除以它,这将产生更新需要得到结束位置的数量。一旦你有它,通过更新所需要的数量,你可以划分Vector2代表在这两个位置之间的距离,这将产生一个向量1更新了移动的值,并且所有你需要去做的是这个units的当前的位置。(译者:这是一位俄罗斯的人写的,十分难懂,可以参照原文看。)
1 Vector2 distance = m_position - m_endposistion;
2 float turns = distance.Length() / m_movementspeed;
3 distance /= turns;
4 m_position -= distance;
                                   当unit不是面对正确的方向,TurnTo被调用,这个方法更新units的方向。方法的第一部分是有点象MoveTo方法,首先它得到在m_direction和m_enddirection之间的距离,然后它测试去看看这是否较小,然后改变速度,但是如果这个距离是一个负的,它添加一个"-"在这个距离的前面让它变成正的。
 1 private void TurnTo()
 2 {
 3       float d = m_enddirection - m_direction
 4       if (d < 0 && -< m_turningspeed || d > 0 && d < m_turningspeed)
 5       {
 6             m_direction = m_enddirection;// Turn to the end 
 7       }
 8       else
 9       {
10            if (d > 0 && d > Math.PI)
11            {
12                m_direction -= m_turningspeed;
13            }
14            else if (d < 0 && (-d  < Math.PI))
15            {
16                m_direction -= m_turningspeed;
17            }
18            else
19            {
20                m_direction += m_turningspeed;
21            }
22      }
23 }
                                   方法的第二部分通过m_turnignspeed给定的数量更新当前的方向,但是它同样决定是否转向左或者右。这两个IF语句决定这个,如果这个距离是一个正数并且比Pi大,或者如果这个距离是个负数并且比Pi小,unit需要转向左因为这是短距离的左转;否则unit需要转向右;
                                   最后的方法是UpdateEndDirection,它每一次被调用一个新的结束位置,这个位置被给予这个unit,它同样是支持三角法的方法。
                                   为了得到结束的方法,你使用向量的X和Y坐标创建在当前和结束位置之间。该计算公式是:ATan(O/A)

                                   ATan是一个数学函数提供在C#中,这个O部分代表从你想要找到的角度的直角三角型的对边,这个A部分代表邻边。我选择去使用X作为邻边和Y作为对边,你可以从相反的方向转动它,但是其他的计算部分稍后也必须改变。
 1 private void UpdateEndDirection()
 2 {
 3      Vector2 d = m_endposistion - m_position;
 4      double a = 0;
 5      float m = 0.5f;
 6      if (d.X != 0 && d.Y != 0)
 7      {
 8           a = Math.Atan(d.Y / d.X);
 9      }
10      else if (d.X == 0 && d.Y > 0)
11      {
12           m = 0.0f;
13      }
14      if (d.X < 0)
15      {
16          a -= (Math.PI * m);
17      }
18      else
19      {
20          a += (Math.PI * m);
21      }
22      if (a < 0)
23      {
24          a += (Math.PI * 2);
25      }
26       m_enddirection = (float)a;
27 }
                                   这个方法的主要的问题是你只可以找到直角三角形的角度,也就是说你不能使用这个计算并且马上找到这个角。首先是它不能计算这个角度,如果这个方向是直接UP的,所以它只是计算不是在这种情况下。否则一个旋转的1/4的modifier既不增加也不减去从产生的这个角度,除非角度是直角。因为X被用来作为三角法的邻近部分计算它是同样对modifier作出决定,如果这个X部分的不同是负的,modifier被减否则它被加。这是因为如果你划分X和Y部分,并且他们中的一个是负的,那么另外的不是负的。你会产生一个负的角度,一个负的角度将使角度接近0的位置,一个负的角度将会使它接近Pi(旋转的一半),所以通过添加一半的Pi(旋转的四分之一)你可以计算角度在正X和通过减去你可以计算角度在一个负X。
                                  最后你检查这个角度是负的,如果它不是,你增加1去保证它是并且设置新的结束方向到这个值中。
                                  在这个例子里提供了,当你点击鼠标右键,它给这个unit一个新的目标去移动。

源代码:http://www.ziggyware.com/readarticle.php?article_id=153
(完)
原文地址:https://www.cnblogs.com/315358525/p/1563537.html