Tutorial 4: Das erste 2D Game: Bewegung

    Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

    • Tutorial 4: Das erste 2D Game: Bewegung

      Im Letzten Tutorial wurde gezeichnet, aber ich denke jeder weiß, dass niemand ein spiel mit einem standbild will. D.h Geht es nun um bewegung

      Zu erst einmal zur Definition von bewegung(Physikalisch):

      Quellcode

      1. Bewegung bezeichnet eine strecke S die ein objekt in einer gewissen zeit Z überwindet.
      2. Die Formel lautet daher: S/Z.

      (aus Physikheft)

      Nun wäre das in SI einheiten meter/sekunden, allerdings haben wir bei der Programmierung keine meter, d.h. verwenden wir pixel also p/s.

      Für mehr dynamik machen wir das ganze auch noch mit beschleunigung:

      Quellcode

      1. Beschleunigung bezeichnet eine erreichte geschwindigkeit g über einen gewissen zeitraum z
      2. Die formel lautet g/z


      Wenn man das beides zusammen fügt so erhält man für die beschleunigung die formel
      m/s/s oder auch m/s²

      so viel zum kleinen physik exkurs...

      Nun geht es aber an die programmierung:
      Zu erst gehen wir an den gegner, da dieser eine konstante geschwindigkeit hat und d.h. keine beschleunigung braucht

      Wir müssen also einfach nur die Position (enemy.pos) mit der geschwindigkeit (enemy.speed) verrechnen
      solche berechnungen werden in der Update methode erledigt
      nun denkt man villeicht einfach mit

      Quellcode

      1. enemy.pos += enemy.speed;

      (ein vorteil an vectoren ist dass man sie direkt miteinander verrechnen kann)
      sei das problem behoben, allerdings hat man vergessen dass die geschwindigkeit auf p/s ausgelegt ist, allerdings die aktualisierung mehr mals pro sekunde aufgerufen wird, und dass auch nicht immer in regelmäßigen abständen.

      Aus diesem grund liefert die game Klasse beim aufruf der Methoden Update und Draw immer die spielzeit(gameTime) mit, die enthält sowohl die gesammte zeit seit dem spiel start, als auch die verstrichene zeit seit dem letzten aufruf (das letzte mal seit dem die methode aufgerufen wurde).

      Also verrechnen wir das nun, dafür multiplizieren wir einfach die geschwindigkeit mit der verstrichenen zeit seit dem letzten aufruf in sekunden, um somit auch eine geschwindigkeit von pixeln pro sekunde zu bekommen

      Quellcode

      1. protected override void Update(GameTime gameTime)
      2. {
      3. // Ermöglicht ein Beenden des Spiels
      4. if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
      5. this.Exit();
      6. [COLOR="red"]
      7. enemy.pos += enemy.speed*(float)gameTime.ElapsedGameTime.TotalSeconds;[/COLOR]
      8. base.Update(gameTime);
      9. }

      (float)gameTime.ElapsedGameTime.TotalSeconds ist der float wert von der verstrichenen zeit seit dem letzten aufruf in sekunden(Total seconds damit man kommazahlen erhält)

      Beim starten sollte sich nun eine bewegung sehen lassen. Mit der geschwindigkeit könnt ihr variiren wie ihr wollt.


      Tasten abfrage:

      Nun soll sich der spieler ja auch bewegen können, dafür müssen wir abfragen ob eine taste gedrückt ist.

      Für sowas gibt es in XNA den KeyboardState.
      Dafür deklarieren wir einfach eine variable vom typ KeyboardState und nennen die einfach mal ks und geben dieser den aktuellen keyboard status (Keyboard.GetState())

      Quellcode

      1. protected override void Update(GameTime gameTime)
      2. {
      3. // Ermöglicht ein Beenden des Spiels
      4. if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
      5. this.Exit();
      6. [COLOR="red"]KeyboardState ks = Keyboard.GetState();[/COLOR]
      7. enemy.pos += enemy.speed*(float)gameTime.ElapsedGameTime.TotalSeconds;
      8. base.Update(gameTime);
      9. }
      Alles anzeigen


      Mit diesem Code wird in die variable der aktuelle keyboard status geschrieben, sie enthält also den status jeder taste(ob gedrückt oder nicht).

      Mit dieser variable können wir nun über simple if abfragen überprüfen ob eine taste gedrückt ist.

      Quellcode

      1. if (ks.IsKeyDown(Keys.Up)) {
      2. }

      So wird überprüft ob die taste Hoch geklickt ist.
      Nun müssen wir das nur noch anwenden, dafür verwende ich 2 variablen eimal mX und einmal mY
      Wenn hoch geklickt ist wird mY = 1 sein und wenn runter dann wird mY = -1
      und bei mx ist es das gleiche mit rechts und links

      Quellcode

      1. protected override void Update(GameTime gameTime)
      2. {
      3. // Ermöglicht ein Beenden des Spiels
      4. if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
      5. this.Exit();
      6. KeyboardState ks = Keyboard.GetState();
      7. int mX = 0;
      8. int mY = 0;
      9. [COLOR="red"] if (ks.IsKeyDown(Keys.Up))
      10. mY += 1;
      11. if (ks.IsKeyDown(Keys.Down))
      12. mY -= 1;
      13. if (ks.IsKeyDown(Keys.Right))
      14. mX += 1;
      15. if (ks.IsKeyDown(Keys.Left))
      16. mX -= 1;[/COLOR]
      17. enemy.pos += enemy.speed*(float)gameTime.ElapsedGameTime.TotalSeconds;
      18. base.Update(gameTime);
      19. }
      Alles anzeigen

      Ich verwende einzelne abfragen und keine große mit else if, da ja auch 2 tasten gedrückt sein können
      und das += bzw -= verwende ich damit wenn 2 tasten geklickt sind sie sich aufheben können.

      Nun können wir diese variable verwenden um die richtung der geschwindigkeit zu errechnen(da eine zahl*-1= -zahl)

      Das machen wir erst mal mit 2 neuen float variablen: newX und newY, diese werden dann die geschwindigkeit die drauf gerechnet wird enthalten.

      Quellcode

      1. float newX = mX * (float)gameTime.ElapsedGameTime.TotalSeconds * 80;
      2. float newY = mY * (float)gameTime.ElapsedGameTime.TotalSeconds * 40;

      mit dem mX/mY wird die richtung(positiv oder negativ) angegeben
      (float)gameTime.ElapsedGameTime.TotalSeconds wird verwendet um den zeitfaktor reinzubringen(da ja beschleunigung=g/z), also um die geschwindigkeit auf die zeit anzupassen
      und 80 ist die kraft/geschwindigkeit(pixel pro sekunde) mit der gerechnet wird (9,81 wäre die erdanziungskraft) mit der muss man rumspielen um die beschläunigungszeit zu ändern.

      diese Beschleunigung wird jetzt auf die geschwindigkeit gerechnet:

      Quellcode

      1. //beschläunigung zur aktuellen geschwindigkeit dazu rechnen
      2. player.speed = new Vector2(player.speed.X + newX, player.speed.Y + newY);


      und dann wird wie gehabt die geschwindigkeit errechnet

      Quellcode

      1. player.pos += player.speed * (float)gameTime.ElapsedGameTime.TotalSeconds;


      Nun sollte euch beim debuggen auffallen das hoch und runter vertauscht sind, dass liegt daran dass wie bereits vorher erwähnt die y achse umgedreht ist.
      dieses problem lässt sich aber ganz einfach lösen indem ihr aus

      Quellcode

      1. float newX = mX * (float)gameTime.ElapsedGameTime.TotalSeconds * 80;
      2. float newY = mY * (float)gameTime.ElapsedGameTime.TotalSeconds * 40;

      das macht

      Quellcode

      1. [CODE] float newX = mX * (float)gameTime.ElapsedGameTime.TotalSeconds * 80;
      2. float newY = [COLOR="red"]-mY[/COLOR] * (float)gameTime.ElapsedGameTime.TotalSeconds * 40;
      [/CODE]

      Wer gut aufgepasst hat dem sollte aufgefallen sein dass ich die ganze zeit von /zeit gesprochen habe aber letztendlich *zeit genommen habe.
      Das liegt daran dass der wert (float)gameTime.ElapsedGameTime.TotalSeconds für die rechnung nicht zeit darstellt sondern 1/zeit, das bedeutet dass wenn man wenn man /(float)gameTime.ElapsedGameTime.TotalSeconds macht eigentlich *zeit rechnet, und wenn man *(float)gameTime.ElapsedGameTime.TotalSeconds eigentlich /zeit rechnet (Mathe 6te klasse bruchrechnung...)

      jetzt wird das teil aber irgendwann unkontrolierbar schnell, darum bauen wir noch eine geschwindigkeits begrenzung von 200px/s in jede richtung also etwa max 280px/s insgesammt
      dafür brauchen wir nur eine simple änderung:
      aus

      Quellcode

      1. player.speed = new Vector2(player.speed.X + newX, player.speed.Y + newY);

      wird

      Quellcode

      1. player.speed = new Vector2(Math.Max(Math.Min((player.speed.X + newX),200), -200), Math.Max(Math.Min((player.speed.Y + newY),200), -200));

      Was math.min und math.max ist erkläre ich hier nicht dafür gibts msdn(einfach googlen)
      es heißt nur wenn die neue geschwindigkeit größer als 200 ist so wird 200 rausgegeben und wenn sie kleiner als -200 ist wird -200 rausgegeben.

      Allerdings ist irgendwann der gegner und der spieler aus dem feld draußen und kommt auch nicht mehr rein, dafür wird eine kleine rand Kollision erstellt, damit wenn man an der rand kommt, die geschwindigkeit in die entgegengesetzte richtung umgedreht wird(wie beim ping pong, einfallswinkel=ausfallswinkel)

      Quellcode

      1. //rand kollision
      2. if (enemy.pos.X > environment.windowWidth- enemy.tex.Width)
      3. enemy.speed *= -1;
      4. if (enemy.pos.X < 0)
      5. enemy.speed *= -1;
      6. if (player.pos.X > environment.windowWidth - player.tex.Width)
      7. player.speed = new Vector2(-player.speed.X, player.speed.Y);
      8. if (player.pos.X < 0)
      9. player.speed = new Vector2(-player.speed.X, player.speed.Y);
      10. if (player.pos.Y > environment.windowHeight - player.tex.Height)
      11. player.speed = new Vector2(player.speed.X, -player.speed.Y);
      12. if (player.pos.Y < 0)
      13. player.speed = new Vector2(player.speed.X, -player.speed.Y);
      Alles anzeigen


      das heißt einfach nur wenn die position von gegner oder spieler größer als die fenster größe ist geht die bewegung grade in die andere richtung

      Nun nocheinmal die ganze update methode, bis jetzt:

      Quellcode

      1. protected override void Update(GameTime gameTime)
      2. {
      3. // Ermöglicht ein Beenden des Spiels
      4. if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
      5. this.Exit();
      6. //Variable die den tastenstatus enthält
      7. KeyboardState ks = Keyboard.GetState();
      8. //variablen für die richtung der beschläunigung
      9. int mX = 0;
      10. int mY = 0;
      11. //tasten abfrage
      12. if (ks.IsKeyDown(Keys.Up))
      13. mY += 1;
      14. if (ks.IsKeyDown(Keys.Down))
      15. mY -= 1;
      16. if (ks.IsKeyDown(Keys.Right))
      17. mX += 1;
      18. if (ks.IsKeyDown(Keys.Left))
      19. mX -= 1;
      20. float newX = mX * (float)gameTime.ElapsedGameTime.TotalSeconds * 80; //beschleunigungsfaktor für x errechnen
      21. float newY = -mY * (float)gameTime.ElapsedGameTime.TotalSeconds * 80; //beschleunigungsfaktor für y errechnen
      22. //beschläunigung zur aktuellen geschwindigkeit dazu rechnen
      23. player.speed = new Vector2(Math.Max(Math.Min((player.speed.X + newX),200), -200), Math.Max(Math.Min((player.speed.Y + newY),200), -200));
      24. //Neue position des players über die geschwindigkeit / sekunden(bzw geschindigkeit*1/zeit) rechnen
      25. player.pos += player.speed * (float)gameTime.ElapsedGameTime.TotalSeconds;
      26. //Neue position des gegners über die geschwindigkeit / sekunden(bzw geschindigkeit*1/zeit) rechnen
      27. enemy.pos += enemy.speed*(float)gameTime.ElapsedGameTime.TotalSeconds;
      28. //rand kollision
      29. if (enemy.pos.X > environment.windowWidth- enemy.tex.Width)
      30. enemy.speed *= -1;
      31. if (enemy.pos.X < 0)
      32. enemy.speed *= -1;
      33. if (player.pos.X > environment.windowWidth - player.tex.Width)
      34. player.speed = new Vector2(-player.speed.X, player.speed.Y);
      35. if (player.pos.X < 0)
      36. player.speed = new Vector2(-player.speed.X, player.speed.Y);
      37. if (player.pos.Y > environment.windowHeight - player.tex.Height)
      38. player.speed = new Vector2(player.speed.X, -player.speed.Y);
      39. if (player.pos.Y < 0)
      40. player.speed = new Vector2(player.speed.X, -player.speed.Y);
      41. base.Update(gameTime);
      42. }
      Alles anzeigen



      Das wars auch schon bis hier hin

      Nächstes mal kommen die bomben und Leben
    • Werbung zur Unterstützung des Forums ( Bitte AddBlocker deaktivieren )

    • Kwaggax;241262 schrieb:

      Schon lang nicht mehr so ein schlechtes Tutorial gelesen...
      Wenn du kein Bock hast dann lass es lieber anstatt so ne halbe Sache hinzuklatschen... --.--

      Zeitverschwendung... (Nimm mal lieber Nachhilfe, in Deutsch!)

      Btw:

      Dein Programmierstil ist auch ziemlich schlecht... o.O


      Machs besser?
    • @Lalox3
      Ich muss deinem Vorposter aber teilweise schon recht geben
      Er überlädt die Methode zu stark mit seinen ganzen Abfragen für Bewegung usw, das hätte sich weitaus schöner über Klassen regeln lassen.
      Außerdem verstößt er mit seiner 1-Datei-Technologie gegen die Konventionen von C#, die besagt, dass pro Datei lediglich eine Klasse/Struktur/Schnittstelle drin sein darf.
      Fakt ist jedenfalls, das das ganze etwas besser formatiert werden könnte.
    • internetfreak;241284 schrieb:

      @Lalox3
      Ich muss deinem Vorposter aber teilweise schon recht geben
      Er überlädt die Methode zu stark mit seinen ganzen Abfragen für Bewegung usw, das hätte sich weitaus schöner über Klassen regeln lassen.
      Außerdem verstößt er mit seiner 1-Datei-Technologie gegen die Konventionen von C#, die besagt, dass pro Datei lediglich eine Klasse/Struktur/Schnittstelle drin sein darf.
      Fakt ist jedenfalls, das das ganze etwas besser formatiert werden könnte.


      Drotzdem ist es ein Tutorial. Und ich denke/hoffe doch das es dann auch funktioniert wie es beschrieben wurde.
      Es gibt halt unübersichtliche und übersichtliche Tutorial's. Was für mich, wenn es funktioniert eigentlich nicht's dagegen
      Sprechen würde. Man könnte ja, wenn das Tutorial so Negativ beurteilt wird das Tutorial auch umschreiben. Damit
      man zeigt wie man es richtig macht.

      Denn nur Sagen das es schlecht ist, kann jeder aber zeigen wie es richtig ist, macht fast niemand.
    • Ich hab ja mit meiner Tutorialserie einen Weg eingeschlagen, der einem XNA Stück für Stück näher bringen soll durch kleinere Teile, in denen immer ein Gebiet behandelt wird, bevor es später an ein Spiel geht.
      Funktionieren sollte es normalerweise, aber eine gewisse Form sollte das Tutorial auch haben, da es sonst schnell für Anfänger usw unübersichtlich wird oder diese falsche Sachen haben.
    • Er geht auf manche Stellen viel zu wenig ein, dann schreibt er nicht wo er die Zeile einfügt usw.
      Grafiken fehlen 2. -..-

      Also da kann man echt besseres erwarten, vil. erstmal sein eigenes Wissen aufbessern bevor man anderen helfen will.

      Da ist das Tut von Internetfreak ein Augenschmaus dagegen, leider kenn ich das alles schon in der Welt von XNA, würde mich freuen, wenn du anfängst ein Spiel zu machen. (Jump´n Run)
      Wie MegaMan, des wär klasse. : )

      Da ich bei XNA erst neu bin und eig. mit Forms arbeite, in der Firma.
      Somit will ich daheim mit XNA spielen.

      Da sowas find ich klasse: Klick
      Vom Aufbau mein ich, aber Jump´n Run wär natürlich noch besser. : )


      Grüße