2014-01-09 38 views
1

我需要實現以均勻圓周運動移動的球的簡單動畫。我已經嘗試了幾個公式,下面的版本似乎是迄今爲止最好的。但是,仍然有2個問題,我真的不知道有什麼問題。 首先,在節目開始後的幾秒鐘內,球不穩定地移動。我認爲theta(以弧度表示的角度)的值不能正確計算,但我不知道爲什麼。其次,運動在一段時間後變得更加均勻,但似乎隨着時間的推移而減少。 「速度」的值表示完成一整圈所花費的秒數。 我想要的是一個統一的,正確的圓形運動(根據速度的值),並且一開始就沒有急動。C#均勻圓周運動

到目前爲止我的代碼:

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到定時器,但我仍然得到這種不穩定的運動,一段時間後運動減慢。

+1

它可以使事情順利使用基於時間的動畫,而不是基於幀的動畫。時間自上次生成幀以來的時間,並將其用於時間相關的動畫。它會比假設每次的時間量相同更準確很多(即使您在'Thread.Sleep()'中指定了等待時間)。 –

+0

你需要處理你在'OnPaint'函數中創建的'Brush'。 (不是問題的根源,但它可能導致其他與GDI +相關的問題) –

+0

我丟棄了BackgroundWorker並使用了一個Timer(間隔100毫秒),但它仍然表現相同。如果我考慮弧度,θ達到> 100的值,不應該在[0,2 * PI]中? – joanna

回答

2

嘗試使用System.Windows.Forms.Timer而不是BackgroundWorker。我相信你會得到更一致的結果。這絕對不是使用BackgroundWorker的好例子。

這是一個或多或少完整的解決方案。請注意,我通過Form的大小縮放擺動半徑和球半徑。

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(); 
} 
+0

對於Corey的評論如上。無論採用何種方法,您都無法保證即時報價。你在這裏沒有做複雜的計算,所以如果你想得到最一致的輸出,計算'OnPaint'中的經過時間,並根據經過的時間計算出位置。你可以選擇在「OnPaint」之外進行計算(例如,在你的計時器的「Elapsed」事件處理程序中),但是在計算和執行「OnPaint」的時間之間會有一個延遲。 –

+0

非常感謝邁克爾,這正是我需要的。 – joanna