获取Vector2之间的角度

6
我希望将星星(代码中的硬币)在汽车撞到星星时移动到屏幕的正右上角。在每次更新期间,星星和道路以恒定速度向下移动。汽车不会移动,但由于道路向下移动而看起来向上移动。尽管可以根据用户的指令左右移动车道。
因此,我使用以下方法计算了星星与屏幕右上角之间的角度。
public double AngleBetween(Vector2 a, Vector2 b)
{
    return Math.Atan2(b.Y - a.Y, b.X - a.X);
}

在我的Update方法中,以下代码计算移动速度并将其发送到屏幕右上角。
double angleBetween = coin.AngleBetween(coin.Position, new
   Vector2(currentGame.GraphicsDevice.Viewport.Bounds.Right, 0));              
collidedCoinVelocity = new Vector2((float)Math.Sin(angleBetween), 
   -(float)Math.Cos(angleBetween));

在我的“Draw”方法中,我使用以下代码更新了“coin.Position”。
coin.Position += collidedCoinVelocity * 10 ;

问题是星星(金币)没有被发送到我想要的右上角,而是在右侧屏幕边界的中间某个位置。
当星星在右车道被击中时,它与右上角之间的角度始终相同。
1.2196048576751 radians = 69.878211 degree

当星星在左车道时,角度为

0.952588487628243 radians = 54.5793 degree

我是否正确计算了角度?我可能忘记考虑星星的向下运动了吗?
编辑:我已更新图像以显示我正在尝试计算的角度,并编辑了我的问题以使其更清晰。
编辑2:添加了第二张图片,展示了星星被击中后的去处。
4个回答

4

看起来你不小心交换了正弦和余弦,而且似乎还有一个随机的负号。因此,这一行代码应该是

collidedCoinVelocity = new Vector2((float)Math.Sin(angleBetween), 
   -(float)Math.Cos(angleBetween));

应该是

collidedCoinVelocity = new Vector2((float)Math.Cos(angleBetween), 
   (float)Math.Sin(angleBetween));

虽然你并不需要计算那么多角度。为了获取一个没有角度的单位向量,只需使用以下方法:

double dx = b.X - a.X;
double dy = b.Y - a.Y;
double mag = Math.Sqrt(dx * dx + dy * dy);
collidedCoinVelocity = new Vector2(dx, dy) / mag;

我尝试按照你的建议交换sin和cos,但星星仍然没有被发送到屏幕的右上角。 - PutraKg
@PutraKg,我认为这可能与您如何否定y轴值有关。只要保持一致,您就不必这样做。 - Zong
好的,移除负的y轴值。现在星星被发送到了屏幕的右上角,但还没有完全到达那里。看起来计算出的角度比实际应该的要小? - PutraKg
@PutraKg 你还在使用 (cos, sin) 吗?如果没有的话,你可以尝试简单的归一化方法。 - Zong
你说得对,我不必计算那个角度,但我认为我应该选择另一个答案,因为我觉得它对我来说更清晰一些。 - PutraKg

2

嗯...只是提醒一下,将笛卡尔坐标转换为使用角度,然后再转回笛卡尔坐标在这里没有意义。

可以按照以下方式推导出最终速度:

Vector2 direction = new Vector2(currentGame.GraphicsDevice.Viewport.Bounds.Right, 0) - coin.Position;
direction.Normalize();

Velocity = direction * 10;
Position += Velocity;

此外,永远不要在绘图中更新位置。绘图是用于绘制而不是更新的。更新应该在Update中完成!

另外,您应该将代码通用化。所有移动对象都应该继承相同的基类,其中包括速度、位置和加速度以及处理这些内容的代码。这样,您只需更改操作速度和/或加速度的逻辑即可使物体移动。

MovingObject.Update:

Velocity += Acceleration * deltaTime;
Positioin += Velocity * deltaTime;

(deltaTime = 距离上一帧的时间(以秒为单位),或(float)游戏时间.ElapsedGameTime.TotalSeconds)

然后,在你的子类更新的末尾使用base.Update(),位置和速度就会起作用 :) 只要设置正确的值。


1

我认为你应该这样做:

public double AngleBetween(Vector2 a, Vector2 b)
{
    return Math.Atan2(-(b.Y - a.Y), b.X - a.X);
}

考虑到反向的Y轴,你需要取反Y分量。
然后,正如 Zong Zheng Li 所写,您需要交换Sin和Cos:
collidedCoinVelocity = new Vector2((float)Math.Cos(angleBetween), 
   -(float)Math.Sin(angleBetween));

但是由于Y轴被反转,你仍然需要否定Vector2.Y组件。


是的,图形表面的y轴是“反向”的,但只要您在游戏中使用该轴,它就不应该有影响。 - Zong
我总是这样做,如果我不否定Y轴,我总会发现某些东西无法正常工作。 - pinckerman
好的,只有当你选择将游戏视为具有“正常”的y轴,而图形具有“反转”的y轴时,才需要否定它(并来回执行)。如果你始终保持一致,完全可以避免这种情况。 - Zong
是的,我更喜欢将 Y 轴视为正常轴,这样在处理三角函数时会更容易。 - pinckerman

0

Vector2 提供了一个用于在点之间进行插值的静态函数。

star_pos = Vector2.Lerp(position_when_hit, destinatin, percent);
                percent += 0.005f;

使用此方法,当星星被击中时,设置点A和点B。
将其传递给Vector2.Lerp函数。
包括一个Percentage_of_distance_moved浮点值。

这是修改后的星级类:

class star
    {
        Texture2D starT;
        public Vector2 star_pos;
        Vector2 position_when_hit;
        Vector2 destinatin;
        float percent;
        bool star_moving_up;

        public star(Random rand,Texture2D star)
        {
            this.starT = star;
            star_moving_up = false;
            destinatin = new Vector2(800, 0);
            star_pos = new Vector2(rand.Next(500), rand.Next(500));
        }
//Try to call this only once
        public void on_hit(){
            if (!star_moving_up)
            {
                position_when_hit = star_pos;
                star_moving_up = true;
            }
        }

        public void update(GameTime gameTime)
        {
            if (star_moving_up)
            {
                star_pos = Vector2.Lerp(position_when_hit, destinatin, percent);
                //a larger percent value will move the star faster.
                percent += 0.001f;
            }
            else
            {
                //Your Logic for moving it down
            }
        }

        public void draw(SpriteBatch sp)
        {
            sp.Draw(starT, star_pos, Color.White);
        }
}

这是我如何使用它的方式:

public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D star;
        Random rand;
        star STAR;



public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }


    protected override void Initialize()
    {
        graphics.PreferredBackBufferWidth = 800;
        graphics.PreferredBackBufferHeight = 600;

        base.Initialize();
    }


    protected override void LoadContent()
    {
        rand = new Random();
        spriteBatch = new SpriteBatch(GraphicsDevice);            
        star = Content.Load<Texture2D>("Bitmap1");
        STAR = new star(rand, star);

    }


    protected override void UnloadContent()
    {

    }


    protected override void Update(GameTime gameTime)
    {
        // Allows the game to exit
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();
//calls on_hit every update call after 4 seconds. just for demo purpose. 
        if (gameTime.TotalGameTime > TimeSpan.FromSeconds(4))
            STAR.on_hit();
        STAR.update(gameTime);
        base.Update(gameTime);
    }


    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        spriteBatch.Begin();
        STAR.draw(spriteBatch);
        spriteBatch.End();
        base.Draw(gameTime);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接