2014-01-09 2 views
1

Мне нужно реализовать простую анимацию шара, движущегося в равномерном круговом движении. Я пробовал несколько формул, и следующая версия кажется лучшей до сих пор. Однако есть еще две проблемы, и я действительно не могу понять, что не так. Сначала, через пару секунд сразу после запуска программы, шар перемещается беспорядочно. Я думаю, что значения для 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 просто класс держит координаты & текущую скорость. Являются ли единицы измерения для времени & угловой скоростью (и преобразованиями использования милисекунд) правильно?

UPDATE: изменил BackgroundWorker на Timer, но я все еще получаю это неустойчивое движение, и движение замедляется через некоторое время.

+1

Это может сгладить вещи, чтобы использовать анимацию, основанную на времени, а не анимацию на основе кадра. Время, сколько времени прошло с момента создания кадра и использования его для анимаций, зависящих от времени. Это будет намного более точной, чем предполагать, что это было такое же количество времени каждый раз (даже если вы указываете ожидание в свой 'Thread.Sleep()'). –

+0

Вам нужно избавиться от своей «кисти», которую вы создаете в своей функции «OnPaint». (а не источник вашей проблемы, но это может привести к другим проблемам, связанным с GDI +). –

+0

Я сбросил BackgroundWorker и использовал таймер (интервал 100 мс), но он по-прежнему ведет себя одинаково. Тета достигает значений> 100, разве это не должно быть в [0, 2 * PI], если я рассматриваю радианы? – joanna

ответ

2

Попробуйте использовать 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(); 
} 
+0

+1 для комментария Кори выше. Независимо от того, какой подход, вы не можете гарантировать выбор времени. Здесь вы не выполняете сложные вычисления, поэтому, если вы хотите получить самый согласованный вывод, вычислите прошедшее время в вашем «OnPaint» и вычислите местоположение на основе прошедшего времени. Вы можете выбрать вычисление вне 'OnPaint' (например, в обработчике событий« Elapsed »вашего таймера), но тогда у вас будет отставание от этого вычисления и время' OnPaint' выполняется. –

+0

Большое спасибо, Майкл, это то, что мне нужно. – joanna

Смежные вопросы