Bild per Pfeiltasten bewegen - Bewegungsschnelligkeit einstellbar

  • VB.NET

Es gibt 16 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    Bild per Pfeiltasten bewegen - Bewegungsschnelligkeit einstellbar

    Hallo,

    Ich möchte auf meiner Form ein Bild mit den Pfeiltasten Links, Rechts , Hoch und Runter bewegen können.

    Die Schnelligkeit wie sich das Bild in eine Richtung bewegt , soll aber anpassbar sein.

    Ebenfalls bin ich mir noch unklar wie ich das Bild überhaupt auf die Form machen soll und dann bewegen soll , denn ob das per PictureBox sinnvoll ist?

    Kann mir jemand ein paar Tipps geben wie ich das am besten Lösen kann? Und nein , ich möchte keinen fertigen Code =)
    Hi
    Das Bild kannst du über GDI zeichnen, bewegen kannst du es über die KeyUp/KeyDown-Events. Dazu verwendest du zum Beispiel eine Enumeration oder boolsche Werte, die, wenn sie gesetzt sind, angeben, in welche Richtungen der Charakter sich bewegt. Die frägst du dann in einem Timer bzw. Thread mit einer Schleife mit passender Bedingung und angepasstem Threading.Thread.Sleep (z.B. an die FPS) ab.

    Gruß
    ~blaze~
    Oder Du packst Dein Bild in eine PictureBox, dann musst Du nur noch im Takt deren Location anpassen.
    Da gibt es 2 Möglichkeiten:
    Konstantes Timer-Intervall und variable Schrittweite
    oder
    variables Timer-Intervall und konstante Schrittweite.
    Letzteres dürfte besser aussehen.
    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!

    ~blaze~ schrieb:

    Hi
    Das Bild kannst du über GDI zeichnen, bewegen kannst du es über die KeyUp/KeyDown-Events. Dazu verwendest du zum Beispiel eine Enumeration oder boolsche Werte, die, wenn sie gesetzt sind, angeben, in welche Richtungen der Charakter sich bewegt. Die frägst du dann in einem Timer bzw. Thread mit einer Schleife mit passender Bedingung und angepasstem Threading.Thread.Sleep (z.B. an die FPS) ab.

    Gruß
    ~blaze~

    Die Antwort scheint mir sehr gut zu sein , leider weiss ich aber nicht genau wie ich das nun umsetzen soll , vielleicht kannst Du mir das nochmal konkreter Erklären.

    Das : wenn sie gesetzt sind, angeben, in welche Richtungen der Charakter sich bewegt verstehe ich nicht.

    Kannst du Das nochmal genauer erklären?
    Ich erklärs dir anhand der Enumeration:

    VB.NET-Quellcode

    1. <Flags> _
    2. Public Enum Directions
    3. None = 0
    4. Left = 1
    5. Right = 2
    6. Top = 4
    7. Bottom = 8
    8. End Enum
    9. 'Deklaration in der Klasse:
    10. Private dirDirections As Directions = Directions.None


    Wenn du zum Beispiel A drückst setzt du die Flagge auf True:

    VB.NET-Quellcode

    1. dirDirections = dirDirections Or Directions.Left
    2. TimerInstance.Enabled = True


    Wenn du die Taste loslässt, setzt du sie auf False:

    VB.NET-Quellcode

    1. dirDirections = dirDirections And (Not Directions.Left)
    2. TimerInstance.Enabled = (dirDirections <> Directions.None)


    Nach dem Verändern der Tastenkombination setzt du immer den Timer auf aktiv bzw. deaktiviert. Da solltest du aber eventuell darauf achten, dass der Timer immer zu festen Zeitpunkten läuft, falls notwendig, dazu noch was weiter unten.
    Im Timer überprüfst du dann immer, ob die Flagge für die Richtung gesetzt ist:

    VB.NET-Quellcode

    1. playerPositionX += (If((dirDirections And Directions.Right) = Directions.Right, 1, 0) - If((dirDirections And Directions.Left) = Directions.Left, 1, 0)) * pxPerTick
    2. playerPositionX += (If((dirDirections And Directions.Right) = Directions.Bottom, 1, 0) - If((dirDirections And Directions.Top) = Directions.Right, 1, 0)) * pxPerTick


    Das ist jetzt nicht wirklich optimal, weil der Betrag vom Richtungsvektor nicht immer 1 ist, sondern auch mal Sqrt(2), das ist aber ganz einfach zu lösen (z.B. über eine zweidimensionale Vektor-Klasse, indem du anschließend durch die Länge teilst) und deshalb überlass ich das dir ;). Mit Flagge setzen meine ich einfach, dass das Bit in der Ganzzahl auf 1 gesetzt wird, wenn in die jeweilige Richtung gegangen werden soll. Anschließend wird das eben ausgewertet durch die If((dirDirections And Directions.Right) = Directions.Right, 1, 0)-Abfrage. Die gibt halt 1 zurück, wenn z.B. nach rechts gegangen werden soll und 0, wenn nicht. Anschließend wird es mit der gegenüberliegenden Richtung verrechnet, mit einem Faktor pxPerTick multipliziert (damit sich das Objekt nicht maximal 1 px bewegt) und die Position des Spielers gesetzt.

    Noch mal zum Timer: Der Timer ist relativ ungenau, da die Aufrufe nicht immer exakt zum Interval passen. Besser wäre es, wenn du das mit einer StopWatch kombinierst, da die die vergangene Zeit exakt anzeigt (kannst du ja mit dem Timer kombinieren, mit Start() startest du die StopWatch, mit Stop() stoppst du sie - wohl relativ logisch - und, oh Wunder, ... mit Reset() setzt du sie zurück und Elapsed* gibt dir halt die seit Start() vergangene Zeit unter Berücksichtigung von Pausen durch Stop() an). Für so kleine Aufgaben ist der Timer schon geeignet, da er den genutzten Thread nicht so lange blockiert, sonst müsste man wohl schon auf einen anderen Thread ausweichen, aber das ist jetzt nicht so relevant...

    Gruß
    ~blaze~

    ~blaze~ schrieb:

    Ich erklärs dir anhand der Enumeration:

    VB.NET-Quellcode

    1. _
    2. Public Enum Directions
    3. None = 0
    4. Left = 1
    5. Right = 2
    6. Top = 4
    7. Bottom = 8
    8. End Enum
    9. 'Deklaration in der Klasse:
    10. Private dirDirections As Directions = Directions.None


    Wenn du zum Beispiel A drückst setzt du die Flagge auf True:

    VB.NET-Quellcode

    1. dirDirections = dirDirections Or Directions.Left
    2. TimerInstance.Enabled = True


    Wenn du die Taste loslässt, setzt du sie auf False:

    VB.NET-Quellcode

    1. dirDirections = dirDirections And (Not Directions.Left)
    2. TimerInstance.Enabled = (dirDirections <> Directions.None)


    Nach dem Verändern der Tastenkombination setzt du immer den Timer auf aktiv bzw. deaktiviert. Da solltest du aber eventuell darauf achten, dass der Timer immer zu festen Zeitpunkten läuft, falls notwendig, dazu noch was weiter unten.
    Im Timer überprüfst du dann immer, ob die Flagge für die Richtung gesetzt ist:

    VB.NET-Quellcode

    1. playerPositionX += (If((dirDirections And Directions.Right) = Directions.Right, 1, 0) - If((dirDirections And Directions.Left) = Directions.Left, 1, 0)) * pxPerTick
    2. playerPositionX += (If((dirDirections And Directions.Right) = Directions.Bottom, 1, 0) - If((dirDirections And Directions.Top) = Directions.Right, 1, 0)) * pxPerTick


    Das ist jetzt nicht wirklich optimal, weil der Betrag vom Richtungsvektor nicht immer 1 ist, sondern auch mal Sqrt(2), das ist aber ganz einfach zu lösen (z.B. über eine zweidimensionale Vektor-Klasse, indem du anschließend durch die Länge teilst) und deshalb überlass ich das dir ;). Mit Flagge setzen meine ich einfach, dass das Bit in der Ganzzahl auf 1 gesetzt wird, wenn in die jeweilige Richtung gegangen werden soll. Anschließend wird das eben ausgewertet durch die If((dirDirections And Directions.Right) = Directions.Right, 1, 0)-Abfrage. Die gibt halt 1 zurück, wenn z.B. nach rechts gegangen werden soll und 0, wenn nicht. Anschließend wird es mit der gegenüberliegenden Richtung verrechnet, mit einem Faktor pxPerTick multipliziert (damit sich das Objekt nicht maximal 1 px bewegt) und die Position des Spielers gesetzt.

    Noch mal zum Timer: Der Timer ist relativ ungenau, da die Aufrufe nicht immer exakt zum Interval passen. Besser wäre es, wenn du das mit einer StopWatch kombinierst, da die die vergangene Zeit exakt anzeigt (kannst du ja mit dem Timer kombinieren, mit Start() startest du die StopWatch, mit Stop() stoppst du sie - wohl relativ logisch - und, oh Wunder, ... mit Reset() setzt du sie zurück und Elapsed* gibt dir halt die seit Start() vergangene Zeit unter Berücksichtigung von Pausen durch Stop() an). Für so kleine Aufgaben ist der Timer schon geeignet, da er den genutzten Thread nicht so lange blockiert, sonst müsste man wohl schon auf einen anderen Thread ausweichen, aber das ist jetzt nicht so relevant...

    Gruß
    ~blaze~

    Danke für die Antwort.

    Ich zeichne das Bild mit folgendem Code:

    VB.NET-Quellcode

    1. Dim g As Graphics
    2. Dim Bildschirmgröße As Rectangle = New Rectangle(0, 0, My.Computer.Screen.Bounds.Width, My.Computer.Screen.Bounds.Height)
    3. Dim Bildgröße, BildLocation As Rectangle
    4. Dim AktuellesBild As Image
    5. g = e.Graphics
    6. AktuellesBild = My.Resources.Hook
    7. Bildgröße = New Rectangle(New Point(0, 0), AktuellesBild.Size)
    8. BildLocation = New Rectangle(CInt((Bildschirmgröße.Width / 2) - (Bildgröße.Width / 2)), CInt((Bildschirmgröße.Height / 2) - (Bildgröße.Height / 2)), Bildgröße.Width - 1, Bildgröße.Height - 1)
    9. g.DrawImage(AktuellesBild, BildLocation)


    Ist das so gut gezeichnet , oder würdest du es anders machen?
    Ich würds direkt so machen:

    VB.NET-Quellcode

    1. With My.Resources.Hook
    2. g.Graphics.DrawImage(My.Resources.Hook, (Me.ClientSize.Width - .Width) \ 2, (Me.ClientSize.Height - .Height) \ 2, .Width, .Height)
    3. End With


    Vermutlich hast du dafür eine Form ohne Rand, oder? Sonst würde das mit der Bildschirmgröße nicht funktionieren.

    Gruß
    ~blaze~
    Gdi kann auch mit Singles arbeiten:

    VB.NET-Quellcode

    1. 'Resourcen sollten vorher geladen werden, weil das Zugreifen auf My.Resources langsam ist.
    2. Dim HookBitmap As Bitmap = My.Resources.Hook
    3. Sub Sonstwas_Paint(...)
    4. e.Graphics.DrawImage(HookBitmap, CSng((Me.ClientRectangle.Width - HookBitmap.Width) / 2, CSng((Me.ClientRectangle.Height - HookBitmap.Height) / 2))
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Dann lieber mit * 0.5F oder / 2.0F, sonst wird erst in Double gerechnet und dann in Single gecastet (außer der Compiler optimierts weg). Das mit den Resourcen macht irgendwie Sinn... Dann aber auf jeden Fall in der Klasse. Ich schätze, dass Single und Integer kaum einen Unterschied machen. Dann lieber Integer, denn das ist effizienter.

    Gruß
    ~blaze~
    Vielen Dank für die Info. Werd's mir merken.
    Single und Integer machen beim Zeichen von Bildern nur den Unterschied, dass das erste Pixel des Bildes möglicherweise nicht genau auf ein Pixel des Bildschirms gezeichnet wird, sondern interpoliert werden muss (Und alle weiteren deshalb auch), was, wenn das Bild sowiso nicht skaliert wird, unnötig ist.

    Das mit den Ressourcen ist mir aufgefallen als ich jedesmal in der Paint Sub grafiken aus den Ressourcen gezeichnet habe. Das hat das Ganze ziemlich verlangsamt. Ich verwende dann immer eine Klasse, die meistens BitmapResources o.ä. heißt, in der alle Ressourcen als Public Shared deklariert sind. So behält man bei mehreren auch ein bisschen die Übersicht.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Problematisch bei der Verwendung von Resourcen ist halt, dass die jedes mal geladen werden MÜSSEN, weil sie eventuell zur Laufzeit verändert werden. Das mit den Singles macht mehr Sinn, wenn man mit Transformationsmatrizen arbeitet und dabei skaliert. Bei Integern wäre da die Genauigkeit nicht da, die durch Singles möglich ist.

    Gruß
    ~blaze~