Beschleunigung einer PictureBox via Pfeiltasten

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 31 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    2.) Ich finde, dass das noch sehr ruckelig aussieht, ist das nur Einbildung oder ist das wirklich so?
    Es ist tatsächlich so, ich habe es ausprobiert.

    Ich habe noch

    C#-Quellcode

    1. x_pos = Math.Round(x_pos + velocity * dt, 0);
    versucht, aber es wird kaum merklich besser.

    4.) Warum funktionieren die Formeln nicht?
    Die Formeln sind richtig. Siehe Anhang. Es sieht ja auch alles vollkommen logisch & richtig aus. Die Beschleunigung ist konstant, das heißt, die Geschwindigkeit steigt linear an und der Weg quadratisch. Du hast jetzt etwas gemacht, was ich noch nicht gesehen habe, nämlich – um die Variable a (Beschleunigung) zu vermeiden und dennoch die Kraft miteinzubeziehen – dieses Konstrukt erschaffen (force / mass) * dt;. Von den Einheiten kommt es hin.
    Also von
    $$F = m \cdot a$$
    mit
    $$a = \frac{v}{t}$$

    ergibt das
    $$F = m \cdot \frac{v}{t} $$

    nach v umgestellt
    $$v = \frac{F\cdot t}{m}$$

    Prima!
    Bilder
    • IMG_20201104_0001.jpg

      1,73 MB, 2.552×3.508, 65 mal angesehen

    VB.neter0101 schrieb:

    Was würdet ihr verbessern?
    dt wird hier fragwürdigerweise als konstante angenommen. Tatsächlich ticken windows-Timer nicht wirklich konstant.
    Statt dt als konstante solltest du beim Timer_Tick die tatsächliche Zeit aufnehmen.



    Ruckelig ist nunmal so. Evtl kann man den Timer schneller machen, aber iwann ist Windows.Forms nicht mehr schnell genug, um das Control umzuplazieren. Flüssige Bewegung ist ein Gimmick, was enorm Prozessorlast beansprucht.
    Tatsächlich ticken windows-Timer nicht wirklich konstant.


    Oh stimmt, das Problem hatte ich auch vor einiger Zeit. Es ging darum, dass der Timer alle 40ms feuern sollte, tatsächlich war von 39 bis 50ms alles dabei :rolleyes: Einigermaßen gelöst haben wir das mit Async / Await
    An die Neulinge: Nutzt Option Strict On und Option Infer Off. Dadurch kommt ihr mit Datentypumwandlungen nicht durcheinander und der Code verbessert sich um Einiges! Solche Fehler à la Dim Beispiel As Integer = "123" können nicht mehr passieren.

    Bartosz schrieb:

    C#-Quellcode

    1. x_pos = Math.Round(x_pos + velocity * dt, 0);
    bringt nix, da die Darstellung selbst auf ganze Pixel rundet.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich hatte an so etwas gedacht:
    Spoiler anzeigen

    C#-Quellcode

    1. private Vector direction;
    2. private double velocity = 500; // Pixel/s
    3. private Vector position = new Vector(50, 50);
    4. private TimeSpan elapsed;
    5. private DateTime lastUpdate;
    6. [DllImport("user32.dll")]
    7. private static extern short GetAsyncKeyState(Keys vKey);
    8. private void UpdateInputDirection()
    9. {
    10. direction = new Vector(0, 0);
    11. if ((GetAsyncKeyState(Keys.Up) & 0x8000) == 0x8000)
    12. direction.Y -= 1;
    13. if ((GetAsyncKeyState(Keys.Down) & 0x8000) == 0x8000)
    14. direction.Y += 1;
    15. if ((GetAsyncKeyState(Keys.Left) & 0x8000) == 0x8000)
    16. direction.X -= 1;
    17. if ((GetAsyncKeyState(Keys.Right) & 0x8000) == 0x8000)
    18. direction.X += 1;
    19. if (direction != new Vector(0, 0))
    20. direction.Normalize();
    21. }
    22. private void Form3_KeyDown(object sender, KeyEventArgs e)
    23. {
    24. UpdateInputDirection();
    25. }
    26. private void Form3_KeyUp(object sender, KeyEventArgs e)
    27. {
    28. UpdateInputDirection();
    29. }
    30. private void Form3_Paint(object sender, PaintEventArgs e)
    31. {
    32. System.Drawing.Point pos = new System.Drawing.Point((int)position.X, (int)position.Y);
    33. e.Graphics.FillRectangle(System.Drawing.Brushes.CornflowerBlue, pos.X, pos.Y, 50, 50);
    34. }
    35. private void tmrUpdate_Tick(object sender, EventArgs e)
    36. {
    37. elapsed = DateTime.Now - lastUpdate;
    38. lastUpdate = DateTime.Now;
    39. position += velocity * direction * elapsed.TotalSeconds;
    40. Invalidate();
    41. }

    Der Timer steht auf 10. Je schneller, desto flüssiger ist die Bewegung. Normalerweise nimmt man eine Schleife, der Timer ist aber ausreichend und nicht so CPU lastig. Ob du eine Masse brauchst um den Cursor gleichmäßig zu bewegen glaube ich nicht ;) Für die Beschleunigung bist du zuständig.
    Guten Abend,

    @Gonger96 der Code gefällt mir sehr! Das ist eine tolle Umsetzung. Die user32.dll wollte ich auch bei mir nutzen. Ich habe den Code mal angepasst und die Beschleunigung hinzugefügt. Zum Code sei noch soviel zu sagen, dass ich hier auf die Gravitation verzichtet habe. Interessieren würde mich noch die Schleifen Implementierung, würde man dort mit einem Sleep bzw. Delay arbeiten? Was jetzt noch stark wäre, wäre eine negative Beschleunigung als Abbremseffekt, wenn der Key losgelassen wird. Die Mathematik ist ja da, aber wie würde man das einbauen? Ggf. über einen weiteren Timer?

    Gonger96 schrieb:

    Je schneller, desto flüssiger ist die Bewegung

    Das habe ich bei meinem ersten Versuch zur gleichmäßig beschleunigten Bewegung nun auch festgestellt, soll heißen, oben war schon so gut wie alles korrekt, bis auf das Timer Intervall...?!



    Code mit Beschleunigung:
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Runtime.InteropServices;
    3. using System.Windows;
    4. using System.Windows.Forms;
    5. namespace BeschlOhneGegenkraft
    6. {
    7. public partial class Form1 : Form
    8. {
    9. public Form1()
    10. {
    11. InitializeComponent();
    12. }
    13. private Vector velocity = new Vector(0, 0);
    14. private Vector position = new Vector(50, 50);
    15. private TimeSpan elapsed;
    16. private DateTime lastUpdate;
    17. // Kraft & Masse
    18. private double mass = 0.00423; // Je kleiner desto schneller :)
    19. private Vector force = new Vector(0, 0);
    20. [DllImport("user32.dll")]
    21. private static extern short GetAsyncKeyState(Keys vKey);
    22. private void UpdateInputDirection()
    23. {
    24. force = new Vector(0, 0);
    25. if ((GetAsyncKeyState(Keys.Up) & 0x8000) == 0x8000)
    26. force.Y -= 1;
    27. if ((GetAsyncKeyState(Keys.Down) & 0x8000) == 0x8000)
    28. force.Y += 1;
    29. if ((GetAsyncKeyState(Keys.Left) & 0x8000) == 0x8000)
    30. force.X -= 1;
    31. if ((GetAsyncKeyState(Keys.Right) & 0x8000) == 0x8000)
    32. force.X += 1;
    33. if ((GetAsyncKeyState(Keys.Space) & 0x8000) == 0x8000)
    34. velocity = new Vector(0, 0);
    35. if (force != new Vector(0, 0))
    36. force.Normalize();
    37. }
    38. private void timer1_Tick(object sender, EventArgs e)
    39. {
    40. elapsed = DateTime.Now - lastUpdate;
    41. lastUpdate = DateTime.Now;
    42. velocity = velocity + (force / mass) * elapsed.TotalSeconds;
    43. position = position + velocity * elapsed.TotalSeconds;
    44. Invalidate();
    45. }
    46. private void Form1_KeyDown(object sender, KeyEventArgs e)
    47. {
    48. UpdateInputDirection();
    49. }
    50. private void Form1_KeyUp(object sender, KeyEventArgs e)
    51. {
    52. UpdateInputDirection();
    53. //velocity2 = new Vector(0, 0);
    54. }
    55. private void Form1_Paint(object sender, PaintEventArgs e)
    56. {
    57. System.Drawing.Point pos = new System.Drawing.Point((int)position.X, (int)position.Y);
    58. e.Graphics.FillRectangle(System.Drawing.Brushes.CornflowerBlue, pos.X, pos.Y, 50, 50);
    59. }
    60. }
    61. }

    Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „VB.neter0101“ ()

    VB.neter0101 schrieb:

    Abbremseffekt, wenn der Key losgelassen wird. Die Mathematik ist ja da, aber wie würde man das einbauen? Ggf. über einen weiteren Timer?
    nein - nur ein Timer-Tick.
    Da wird der Zustand des Systems neu ausgerechnet, und die Darstellung desselben angestossen.
    Mir persönlich gefällt auch nicht gut, dass du KeyDown-Event verwendest, und somit quasi quer in den Zustand eingrätschst.
    Wie gesagt: Imo muss das alles im selben Tick erfolgen.

    Aber ist nicht unbedingt das letzte Wort. Sicherlich kann man auch beliebige andere Events heranziehen, um den Zustand des Systems zu manipulieren - nur habich nicht besonders gutes Gefühl dabei.

    VB.neter0101 schrieb:

    Interessieren würde mich noch die Schleifen Implementierung

    Das könnte z.B. so aussehen:
    Spoiler anzeigen

    C#-Quellcode

    1. CancellationTokenSource tokenSource;
    2. private async void Form3_Load(object sender, EventArgs e)
    3. {
    4. tokenSource = new CancellationTokenSource();
    5. await Task.Run(() => Loop(tokenSource.Token));
    6. }
    7. private void Form3_FormClosing(object sender, FormClosingEventArgs e)
    8. {
    9. tokenSource.Cancel();
    10. tokenSource.Dispose();
    11. }
    12. [DllImport("user32.dll")]
    13. private static extern short GetAsyncKeyState(Keys vKey);
    14. Vector position = new Vector(50, 50);
    15. private const double force = 1.0;
    16. private const double mass = 0.00423; // Je kleiner desto schneller :)
    17. private Vector currentForce = new Vector(0, 0);
    18. private Vector velocity = new Vector(0, 0);
    19. private readonly object syncRoot = new object();
    20. private void UpdateInputDirection(TimeSpan elapsed)
    21. {
    22. currentForce = new Vector(0, 0);
    23. double increment = force * elapsed.TotalSeconds;
    24. if ((GetAsyncKeyState(Keys.Up) & 0x8000) == 0x8000)
    25. currentForce.Y -= increment;
    26. if ((GetAsyncKeyState(Keys.Down) & 0x8000) == 0x8000)
    27. currentForce.Y += increment;
    28. if ((GetAsyncKeyState(Keys.Left) & 0x8000) == 0x8000)
    29. currentForce.X -= increment;
    30. if ((GetAsyncKeyState(Keys.Right) & 0x8000) == 0x8000)
    31. currentForce.X += increment;
    32. if ((GetAsyncKeyState(Keys.Space) & 0x8000) == 0x8000)
    33. velocity = new Vector(0, 0);
    34. if (currentForce != new Vector(0, 0))
    35. currentForce.Normalize();
    36. }
    37. private void Loop(CancellationToken cancellationToken)
    38. {
    39. TimeSpan elapsed;
    40. DateTime lastUpdate = DateTime.Now;
    41. // Kraft & Masse
    42. while (!cancellationToken.IsCancellationRequested)
    43. {
    44. elapsed = DateTime.Now - lastUpdate;
    45. lastUpdate = DateTime.Now;
    46. UpdateInputDirection(elapsed);
    47. velocity = velocity + (currentForce / mass) * elapsed.TotalSeconds;
    48. var pos = position + velocity * elapsed.TotalSeconds;
    49. lock (syncRoot)
    50. position = pos;
    51. Invalidate();
    52. Thread.Sleep(20);
    53. }
    54. }
    55. private void Form3_Paint(object sender, PaintEventArgs e)
    56. {
    57. System.Drawing.Point pos;
    58. lock (syncRoot)
    59. pos = new System.Drawing.Point((int)position.X, (int)position.Y);
    60. e.Graphics.FillRectangle(System.Drawing.Brushes.CornflowerBlue, pos.X, pos.Y, 50, 50);
    61. }

    Wobei das Zeichnen bzw. der Invalidate() Aufruf eher suboptimal ist. Zum Probieren reichts aber allemal.
    @VB.neter0101 Hatten wir doch bereits in Post #4:

    RodFromGermany schrieb:

    Start bei KeyDown, Stop bei KeyUp und Senden eines Events im Timer-Takt.
    Wenn der Timer durch läuft, kannst Du KeyDown und KeyUp natürlich rausschmeißen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!