Einstieg in die Spieleprogrammierung mit MonoGame und C# - Teil 3

Im dritten Artikel der Reihe wollen wir uns ein wenig mit Rotation beschäftigen. Ziel ist es, zu verstehen wie eine Spielerfigur rotiert und die entsprechende Ausrichtung verwendet werden kann. Wir bedienen uns des bereits vorhanden Codes der letzten beiden Artikel. Nichts desto trotz haben wir den Code ein wenig bereinigt und starten mit einer etwas abgespeckten Version. Du kannst entweder mit dem vorhanden Code weiter arbeiten, oder aber mit der verkürzten Version starten die du HIER findest.
Weiter benötigen wir 3 neue Grafiken; den Spieler (Rakete), das Ziel (Insekt) und das Projektil (Netz). Donwload

Anpassen der BaseTexture.cs

Die BaseTexture.cs erweitern wir um ein entsprechende float-Variable die unsere Rotation repräsentiert. Diese wird auch im entsprechenden Konstruktor initialisiert:

public class BaseTexture
{
    ...
    protected float imageRotation; 

    public BaseTexture(String imageName, Vector2 position, Vector2 size)
    {
        ...
        imageRotation = 0f;
    }

    public BaseTexture(String image)
    {
        ...
        imageRotation = 0f; 
    }
}

Auch die Draw( )–Methode passen wir an. Die SpriteBatch.Draw()-Methode verfügt glücklicher weise über eine entsprechende Überladung, so dass wir die Rotation direkt angeben können.
Die Variable org stellt den Mittelpunkt der Grafik dar. Diese benötigen wir, damit diese um ihren Mittelpunkt rotiert.

public void Draw()
{
    //Position und Größe der Textur
    Rectangle destRect = Camera.GetPixelRectangle(Position, Size);

    //Mittelpunkt der Textur
    Vector2 org = new Vector2(imageName.Width / 2, imageName.Height / 2);

    Game1.spriteBatch.Draw(imageName, destRect, null, Color.White, imageRotation, org, SpriteEffects.None, 0f);
}

Außerdem erweitern wie die BaseTexture-Klasse um eine statische Methode RotateVectorByAngle. Ein Vector kann, wie wir es bisher verwendet haben, einen Punkt repräsentieren. Verwenden wir zwei solcher Punkte, dann entsteht eine Gerade die entsprechend ausgerichtet werden kann. Mehr zum Thema Vectoren in der Spieleentwicklung gibt es zum Beispiel auf gamedev.net

static public Vector2 RotateVectorByAngle(Vector2 v, float angleInRadian)
{
    float sinTheta = (float)(Math.Sin((double)angleInRadian));
    float cosTheta = (float)(Math.Cos((double)angleInRadian));
    float x, y;
    x = cosTheta * v.X + sinTheta * v.Y;
    y = -sinTheta * v.X + cosTheta * v.Y;
    return new Vector2(x, y);
}

Anpassen der GameLogic.cs

In der GameLogic-Klasse erstellen wir uns eine Variable die unsere Spielerrotation beinhaltet.

class GameLogic
{
    //Player definitions
    BaseTexture player;
    Vector2 playerSize = new Vector2(3, 10);
    Vector2 playerPosition = new Vector2(10,10);
    float playerRotation = 0f;
    ...

Die Update( )-Methode erweitern wir um die Behandlung der Tasten Q und W mit denen wir unseren Spiele rotieren lassen können. Zu beachten ist hierbei, dass die Rotation in Radianten rechnet und nicht in Grad wie man es vermuten möchte. Hier hilft uns die MathHelper-Klasse aus.
Zum Abschluss passen wir den Update-Aufruf an um die Rotation an.

if (Keyboard.GetState().IsKeyDown(Keys.W))
    playerRotation += MathHelper.ToRadians(1f);
if (Keyboard.GetState().IsKeyDown(Keys.Q))
    playerRotation -= MathHelper.ToRadians(1f);

player.Update(playerPosition, playerRotation);

Wenn du den Code nun ausführst, solltest du in der Lage sein mit den Pfeiltasten die Rakete zu bewegen und deren Ausrichtung mit Q und W zu verändern.
Game 1

Als nächstes wollen wir diese Rotation nutzen um unser Projektil entsprechend abzufeuern und ein Insekt damit zu treffen.  

Anpassen der GameLogic.cs

Zuerst erstellen wir einige Klassenvariablen die uns unsere Projektil und das Insekt definieren, sowie zwei Variablen für den Punktestand:

class GameLogic
{
    //Player definitions
    BaseTexture player;
    Vector2 playerSize = new Vector2(10, 10);
    Vector2 playerPosition = new Vector2(10,10);
    Vector2 playerDirection = Vector2.UnitY;
    float playerRotation = 0f;

    //Projectile definitions
    BaseTexture projectile;
    bool projectileFired = false;
    Vector2 projectileVelocity = Vector2.Zero;
    float projectileSpeed = 0.5f;

    //Insct definitions
    BaseTexture insect;
    bool insectAlive = true;

    //Game Stats
    int insectsHit;
    int insectsMissed;

    ...
}

In dem Konstruktor initialisieren wir diese Variablen:

public GameLogic()
{
    player = new BaseTexture("Rocket", playerPosition, playerSize);

    playerDirection = Vector2.UnitY;
    projectile = new BaseTexture("Net", new Vector2(0, 0), new Vector2(10, 10));
    projectileFired = false;
    projectileVelocity = Vector2.Zero;
    projectileSpeed = 0.5f;

    insect = new BaseTexture("Insect", Vector2.Zero, new Vector2(10,10));
    insectAlive = false;

    insectsHit = 0;
    insectsMissed = 0;
}

Die Update( )-Methode wird nun ebenfalls erweitert. Die Taste A soll unser Netz abfeuern. Außerdem Setzen wir unser Insekt an eine zufällige Position und prüfen ob unser Projektil ein Insekt getroffen hat (Projektil und Insekt wird entfernt) oder ob wir es verfehlt haben (nur das Projektil wird entfernt)

...
//Fire Projectile
if (Keyboard.GetState().IsKeyDown(Keys.A))
{
    projectileFired = true;
    projectile.Position = player.Position;
    projectile.Rotation = player.Rotation;
    projectileVelocity = BaseTexture.RotateVectorByAngle(playerDirection, projectile.Rotation) * projectileSpeed;
}

//Update Insect
if (!insectAlive)
{
    float x = 15f + ((float)Game1.rand.NextDouble() * 30f);
    float y = 15f + ((float)Game1.rand.NextDouble() * 30f);
    insect.Position = new Vector2(x, y);
    insectAlive = true;
}

//Check Collision
if (projectileFired)
{
    projectile.Position += projectileVelocity;

    if(projectile.PrimitivesTouch(insect))
    {
        insectAlive = false;
        projectileFired = false;
        insectsHit++;
    }

    if(Camera.CollidedWithCameraWindow(projectile) != Camera.CameraWindowCollisionStatus.InsideWindow)
    {
        projectileFired = false;
        insectsMissed++;
    }
}
...

Zum Abschluss erweitern wir die DrawGame( )-Methode um unser Insekt und das Netz und eine kurze Anzeige des Punktestandes:

public void DrawGame()
{
    //Draw Player
    player.Draw();

    if (projectileFired)
        projectile.Draw();

    if (insectAlive)
        insect.Draw();

    Game1.spriteBatch.DrawString(Game1.usedFont, "Num insects hit: " + insectsHit + ", Missed: " + insectsMissed, new Vector2(5, 5), Color.Black);
}

Wenn du das Programm nun ausführst, solltest du in der Lage sein die Rakete mit den Pfeiltasten zu steuern, mit Q und W zu drehen und mit A ein kleines Netz abzufeuern.
Game Finished


Das war der dritte Teil unseres Tutorials zur Spieleentwicklung. Im nächsten Teil beschäftigen wir uns mit einer Implementierung von Physikeffekten
Solltest du Probleme bei der Umsetzung haben, kannst du jeder Zeit im Forum oder via Facebook um Unterstützung fragen.

Hier findest du außerdem die komplette Solution als .rar-File Download Solution