【问题标题】:Uniform circular motion匀速圆周运动
【发布时间】:2014-01-09 21:27:16
【问题描述】:

我需要实现一个球以匀速圆周运动移动的简单动画。我已经尝试了几个公式,到目前为止,以下版本似乎是最好的。但是,仍然存在 2 个问题,我真的不知道出了什么问题。

首先,在程序开始后的几秒钟内,球的移动不规律。我认为 theta 的值(以弧度为单位的角度)计算不正确,但我不知道为什么。

其次,一段时间后运动变得更加均匀,但似乎随着时间的推移而减少。

 The value for 'speed' indicates the number of seconds it takes to do a full revolution.

我想要的是一个均匀、正确的圆周运动(根据速度的值),并且在开始时没有急动。

到目前为止我的代码:

public partial class ServerForm : Form
{
    Stopwatch watch;

    //Angular velocity
    float angularVelocity;

    //Angle
    float theta = 20;

    //Speed - time to complete a full revolution, in seconds
    private float speed = 3;

    //Circle center
    private int centerX = 250;
    private int centerY = 200;

    //Circle radius
    private float R = 120;

    //Current position
    private LocationData currentLocation;

    public ServerForm()
    {
        InitializeComponent();
    }

    public void UpdateUI()
    {            
        currentLocation.CoordX = (float)(centerX + Math.Cos(theta) * R);
        currentLocation.CoordY = (float)(centerY + Math.Sin(theta) * R);
        currentLocation.Speed = speed;

        try
        {
            this.Invoke(new Action(() => { this.Invalidate(); }));
        }
        catch (Exception ex)
        {
            watch.Stop();
            Application.Exit();
        }

        theta += (float)((angularVelocity * 1000 / watch.ElapsedMilliseconds));            
        //Console.Out.WriteLine("elapsed miliseconds: " + watch.ElapsedMilliseconds + " theta = " + theta);

    }

    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        Brush color = new SolidBrush(Color.BlueViolet);

        g.FillEllipse(color, currentLocation.CoordX, currentLocation.CoordY, 30, 30);

        //Draw circle & center
        g.DrawEllipse(new Pen(color), centerX, centerY, 5, 5);

        float x = centerX - R;
        float y = centerY - R;
        float width = 2 * R;
        float height = 2 * R;
        g.DrawEllipse(new Pen(color), x, y, width, height);

        base.OnPaint(e);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        if (!String.IsNullOrEmpty(textSpeed.Text))
        {
            ResetValues(float.Parse(textSpeed.Text));                
        }
    }

    private void ResetValues(float newSpeed)
    {
        speed = newSpeed;
        angularVelocity = (float)(2 * Math.PI / speed);  // radians / sec

        //Start at the top
        currentLocation.CoordX = centerX;
        currentLocation.CoordY = centerY - R;

        theta = 90;

        watch.Restart();            
    }

    private void ServerForm_Load(object sender, EventArgs e)
    {
        watch = new Stopwatch();            

        timer1.Enabled = true;
        timer1.Interval = 100;
        timer1.Tick += timer1_Tick;            

        currentLocation = new LocationData();
        ResetValues(speed);          
    }

    void timer1_Tick(object sender, EventArgs e)
    {
        UpdateUI();
    }

}

LocationData 只是一个保存坐标和当前速度的类。 时间和角速度的单位(以及使用毫秒的转换)是否正确?

我将 BackgroundWorker 更改为 Timer,但我仍然会出现这种不稳定的运动,并且一段时间后运动会变慢。

【问题讨论】:

  • 使用基于时间的动画而不是基于帧的动画可能会使事情变得平滑。时间 自上次生成帧并将其用于与时间相关的动画以来已经过去了多长时间。这将比假设每次都是相同的时间要准确得多(即使您在 Thread.Sleep() 中指定了等待时间)。
  • 您需要处理您在OnPaint 函数中创建的Brush。 (不是问题的根源,但可能导致其他 GDI+ 相关问题)
  • 我删除了 BackgroundWorker 并使用了 Timer(100 毫秒间隔),但它的行为仍然相同。 Theta 达到 > 100 的值,如果我考虑弧度,它不应该在 [0, 2*PI] 中吗?

标签: c# animation uniform


【解决方案1】:

尝试使用 System.Windows.Forms.Timer 而不是 BackgroundWorker。我相信你会得到更一致的结果。这绝对不是使用 BackgroundWorker 的好案例。

这是一个或多或少完整的解决方案。请注意,我将挥杆半径和球半径按表格的大小进行缩放。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        SetStyle(ControlStyles.Opaque, true);
        SetStyle(ControlStyles.ResizeRedraw, true);
        SetStyle(ControlStyles.DoubleBuffer, true);
        SetStyle(ControlStyles.UserPaint, true);
    }

    private void Form1_Load(object sender, System.EventArgs e)
    {
        _stopwatch.Start();
    }

    private void Timer1_Tick(object sender, System.EventArgs e)
    {
        Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.Clear(BackColor);

        const float rotationTime = 2000f;

        var elapsedTime = (float) _stopwatch.ElapsedMilliseconds;

        var swingRadius = Math.Min(ClientSize.Width, ClientSize.Height) / 4f;

        var theta = Math.PI * 2f * elapsedTime / rotationTime;

        var ballRadius = Math.Min(ClientSize.Width, ClientSize.Height) / 10f;

        var ballCenterX = (float) ((ClientSize.Width / 2f) + (swingRadius * Math.Cos(theta)));

        var ballCenterY = (float) ((ClientSize.Height / 2f) + (swingRadius * Math.Sin(theta)));

        var ballLeft = ballCenterX - ballRadius;

        var ballTop = ballCenterY - ballRadius;

        var ballWidth = ballRadius * 2f;

        var ballHeight = ballRadius * 2f;

        e.Graphics.FillEllipse(Brushes.Red, ballLeft, ballTop, ballWidth, ballHeight);

        e.Graphics.DrawEllipse(Pens.Black, ballLeft, ballTop, ballWidth, ballHeight);
    }

    private readonly Stopwatch _stopwatch = new Stopwatch();
}

【讨论】:

  • +1 表示 Corey 的上述评论。无论采用何种方法,您都无法保证按时完成。您在这里没有进行复杂的计算,因此如果您想要最一致的输出,请在您的 OnPaint 中计算经过的时间,并根据经过的时间计算位置。您可以选择在 OnPaint 之外进行计算(例如,在您的计时器的 Elapsed 事件处理程序中),但是您将在该计算和 OnPaint 执行的时间之间存在延迟。
猜你喜欢
  • 2010-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多