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):
Bewegung bezeichnet eine strecke S die ein objekt in einer gewissen zeit Z überwindet.
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:
Beschleunigung bezeichnet eine erreichte geschwindigkeit g über einen gewissen zeitraum z
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
(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
protected override void Update(GameTime gameTime)
{
// Ermöglicht ein Beenden des Spiels
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
[COLOR="red"]
enemy.pos += enemy.speed*(float)gameTime.ElapsedGameTime.TotalSeconds;[/COLOR]
base.Update(gameTime);
}
Alles anzeigen
(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())
protected override void Update(GameTime gameTime)
{
// Ermöglicht ein Beenden des Spiels
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
[COLOR="red"]KeyboardState ks = Keyboard.GetState();[/COLOR]
enemy.pos += enemy.speed*(float)gameTime.ElapsedGameTime.TotalSeconds;
base.Update(gameTime);
}
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.
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
protected override void Update(GameTime gameTime)
{
// Ermöglicht ein Beenden des Spiels
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
KeyboardState ks = Keyboard.GetState();
int mX = 0;
int mY = 0;
[COLOR="red"] if (ks.IsKeyDown(Keys.Up))
mY += 1;
if (ks.IsKeyDown(Keys.Down))
mY -= 1;
if (ks.IsKeyDown(Keys.Right))
mX += 1;
if (ks.IsKeyDown(Keys.Left))
mX -= 1;[/COLOR]
enemy.pos += enemy.speed*(float)gameTime.ElapsedGameTime.TotalSeconds;
base.Update(gameTime);
}
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.
float newX = mX * (float)gameTime.ElapsedGameTime.TotalSeconds * 80;
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:
//beschläunigung zur aktuellen geschwindigkeit dazu rechnen
player.speed = new Vector2(player.speed.X + newX, player.speed.Y + newY);
und dann wird wie gehabt die geschwindigkeit errechnet
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
float newX = mX * (float)gameTime.ElapsedGameTime.TotalSeconds * 80;
float newY = mY * (float)gameTime.ElapsedGameTime.TotalSeconds * 40;
das macht
[CODE] float newX = mX * (float)gameTime.ElapsedGameTime.TotalSeconds * 80;
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
wird
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)
//rand kollision
if (enemy.pos.X > environment.windowWidth- enemy.tex.Width)
enemy.speed *= -1;
if (enemy.pos.X < 0)
enemy.speed *= -1;
if (player.pos.X > environment.windowWidth - player.tex.Width)
player.speed = new Vector2(-player.speed.X, player.speed.Y);
if (player.pos.X < 0)
player.speed = new Vector2(-player.speed.X, player.speed.Y);
if (player.pos.Y > environment.windowHeight - player.tex.Height)
player.speed = new Vector2(player.speed.X, -player.speed.Y);
if (player.pos.Y < 0)
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:
protected override void Update(GameTime gameTime)
{
// Ermöglicht ein Beenden des Spiels
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//Variable die den tastenstatus enthält
KeyboardState ks = Keyboard.GetState();
//variablen für die richtung der beschläunigung
int mX = 0;
int mY = 0;
//tasten abfrage
if (ks.IsKeyDown(Keys.Up))
mY += 1;
if (ks.IsKeyDown(Keys.Down))
mY -= 1;
if (ks.IsKeyDown(Keys.Right))
mX += 1;
if (ks.IsKeyDown(Keys.Left))
mX -= 1;
float newX = mX * (float)gameTime.ElapsedGameTime.TotalSeconds * 80; //beschleunigungsfaktor für x errechnen
float newY = -mY * (float)gameTime.ElapsedGameTime.TotalSeconds * 80; //beschleunigungsfaktor für y errechnen
//beschläunigung zur aktuellen geschwindigkeit dazu rechnen
player.speed = new Vector2(Math.Max(Math.Min((player.speed.X + newX),200), -200), Math.Max(Math.Min((player.speed.Y + newY),200), -200));
//Neue position des players über die geschwindigkeit / sekunden(bzw geschindigkeit*1/zeit) rechnen
player.pos += player.speed * (float)gameTime.ElapsedGameTime.TotalSeconds;
//Neue position des gegners über die geschwindigkeit / sekunden(bzw geschindigkeit*1/zeit) rechnen
enemy.pos += enemy.speed*(float)gameTime.ElapsedGameTime.TotalSeconds;
//rand kollision
if (enemy.pos.X > environment.windowWidth- enemy.tex.Width)
enemy.speed *= -1;
if (enemy.pos.X < 0)
enemy.speed *= -1;
if (player.pos.X > environment.windowWidth - player.tex.Width)
player.speed = new Vector2(-player.speed.X, player.speed.Y);
if (player.pos.X < 0)
player.speed = new Vector2(-player.speed.X, player.speed.Y);
if (player.pos.Y > environment.windowHeight - player.tex.Height)
player.speed = new Vector2(player.speed.X, -player.speed.Y);
if (player.pos.Y < 0)
player.speed = new Vector2(player.speed.X, -player.speed.Y);
base.Update(gameTime);
}
Alles anzeigen
Das wars auch schon bis hier hin
Nächstes mal kommen die bomben und Leben