Erstellen eines einfachen WP7 Spiels mit dem Framework

Öffnen des Projekts Demo_Start

Das Projekt enthält bereits die benötigten Referenzen (GameLogic.dll) und einige Grafik- und Sound-Assets.

Anlegen des Spielinhalts

Zu allererst legen wir das eigentliche Spiel als UserControl mit dem Namen GameContent.xaml an. In diesem Spiel soll der Spieler einen sich bewegenden Ball möglichst oft auf einen Schläger prallen lassen, ohne dass der Ball das untere Ende des Spielfelds berührt.

Erstellen der Spielelemente

Bevor wir die Spielelemente erstellen, ändern wir den Layout Type des LayoutRoot-Elements von Grid auf Canvas. Dann zeichnen wir die Spielelemente: Einen Ball, der sich später bewegen soll, und einen Schläger, der später vom Spieler gesteuert werden soll.
1) Für den Ball zeichnen wir eine Ellipse in der Mitte des Spielfelds und nennen sie _ball.
2) Den Schläger (Paddle) zeichnen wir im unteren Bereich des Spielfelds und nennen ihn _player.
3) Damit der Ball später nicht aus dem Spielfeld fliegt, zeichnen wir außerdem noch Spielfeldbegrenzungen in Form eines Rectangles an jede Seite des Spielfeldes (left, right, top, bottom).

Zuweisen der Spiellogik

Damit sich der Ball und der Schläger des Spielers auch bewegen können, müssen wir ein GameLoop Behavior einfügen, das für Animation und Kollisionsbehandlung zuständig ist und die Zustände der entsprechenden Spielelemente in regelmässigen Abständen aktualisiert.


1) GameLoop:
a. Einfügen eines GameLoop-Behaviors. Wir können die GameLoop einem beliebigen Element zuordnen, der Übersicht halber ordnen wir es dem Element LayoutRoot zu.

2) Bewegung und Kollision des Balls:
a. Damit der Ball sich bewegen und gleichzeitig auch mit anderen Objekten kollidieren kann, weisem wir ihm ein MovingCollidableBehavior zu.
b. Unter den Properties aktivieren wir die Kollision indem wir ein Häkchen bei CollisionEnabled machen.
c. Der Ball soll bei Kollision abprallen, deshalb stellen wir den CollisionType auf Bounce.
d. Außerdem soll sich der Ball bei Spielbeginn mit einer festen Geschwindigfkeit nach unten bewegen. Daher machen wir ein Häkchen bei AutoStart, geben bei Direction 90 (für eine Bewegungsrichtung von 90 Grad nach unten) und bei Speed eine 6 ein.

3) Kollision des Paddles:
a. Der Ball soll mit dem Spieler-Paddle kollidieren können. Da wir das Paddle nicht animieren wollen, weisen wir ihm ein StaticCollidableBehavior zu.
b. Damit das Paddle der Mausbewegung auf der X-Achse folgt, weisen wir ihm ein FollowMouseBehavior zu und stellen unter Properties die FollowPosition auf X.
c. Außerdem soll der Spieler einen Punkt dazubekommen, wenn der Ball mit dem Paddle kollidiert. Daher weisen wir dem Paddle eine ChangeGamePropertiesAction zu. Damit die Action korrekt funtioniert, müssen wir in den Properties noch den Trigger einstellen, der die Action auslöst. Wir wählen den CollisionTrigger aus. Damit der Spieler bei jeder Kollision einen Punkt gutgeschreiben bekommt, machen wir ein Häkchen bei ChangeScoresOnAction und stellen den Wert von ScoresIncrementValue auf 1.

4) Kollision der Spielfeldbegrenzung:
a. Damit der Ball mit der Spielfeldbegrenzung kollidieren kann, weisen wir den jeweiligen Elementen ebenfalls ein StaticCollidableBehavior zu.
b. Damit dem Spieler ein Leben abgezogen wird, sobald der Ball die untere Spielfeldbegrenzung berührt, weisen wir der unteren Spielfeldbegrenzung _bottom eine ChangeGamePropertiesAction zu, die durch den CollisionTrigger ausgelöst werden soll. Wir machen ein Häkchen bei ChangeLivesOnAction und setzen LivesIncrementValue auf -1.

Einfügen des GameContainers in die MainPage und Zuweisen des Spielinhalts

Wir haben die Spielelemente erstellt und die grundlegende Spiellogik zugewiesen. Nun müssen wir ein GameContainer-Control in die Hauptseite unserer Anwendung einfügen und den Spielinhalt zuweisen.

1) Einfügen des GameContainers:
Wir fügen ein GameContainer-Control aus GameLogic.Controls in das LayoutRoot-Element ein und stellen sicher, dass es die gesamte Anwendung ausfüllt (AutoSize = Fill).

2) Zuweisen des Spielinhalts:
Um den Spielinhalt zuzuweisen, wählen wir den GameContainer aus und weisen in den Properties unter GameContainer die Datei GameContent.xaml als GameControl zu.

3) Test:
Nun kompilieren wir das Projekt und Starten die Anwendung. Es erscheint ein Warnhinweis: Wir haben im GameContainer zwar ein Spiel angegeben, das Spiel implementiert aber nicht das Interface IGameFeatures. Ohne dieses Interface ist unser Spiel nicht funktionsfähig!

Implementation der Spiellogik (IGameFeatures)

Damit der GameContainer mit dem Spiel kommunizieren kann und die von uns bereits zugewiesene Spiellogik funktioniert, muss unser Spiel das Interface IGameFeatures implementieren. Dieses Interface definiert die Informationen, Events und Methoden, die ein Spiel anbieten muss, damit der GameContainer und die Behaviors mit ihm kommunizieren können. Daher passen wir GameContent.xaml.cs wie folgt an:

Um auf die Controls zugreifen zu können, müssen wir den Namespace GameLogic.Controls und GameLogic .Logic verwenden. Wir erweitern die Using-Statements um folgenden Zeilen:

Als nächstes müssen wir angeben, dass unser Spiel das Interface IGameFeatures implementiert. Wir erweitern die Klassendefinition, so dass sie wie folgt aussieht:

using GameLogic.Controls;
using GameLogic.Logic;

 

namespace Demo_Start
{
public partial class GameContent : UserControl, IGameFeatures
{

Dann lassen wir uns die vom Interface geforderten Methoden und Events anlegen, indem wir mit der rechten Maustaste auf IGameFeatures klicken und Implemnt Interface – ImplementInterface auswählen.
Nun befüllen wir die vorgegebenen Methoden mit Leben. Die CompanyName, Description, DevloperName und GameName werden vom GameContainer ausgelesen und in der About-Seite des Spiels angezeigt. Sie erlauben dem Spieleentwickler, Informationen über sich und sein Spiel anzugeben.

public string CompanyName
{
get { return "Microsoft Deutschland GmbH"; }
}
public string Description
{
get { return String.Empty; }
}
public string DeveloperName
{
get { return "Dennis Sasse"; }
}
public string GameName
{
get { return "SillyTestGame"; }
}

Falls das Spiel dem Spieler die Möglichkeit geben soll, Einstellungen (wie etwa Schweirigkeitsgrad oder Lautstärke der Soundeffekte und Musik) vorzunehmen, dann kann über SettingsPage die entsprechende Einstellungsseite angegeben werden. Falls eine SettingsPage definiert ist, wird diese auch um Hauptmenü angezeigt. In unserem Fall benötigen wir jedoch keine weiteren Einstellungen.

public Uri SettingsPage
{
get { return null; }
}

IsLifeBased und IsTimeBased geben an, ob ein Spiel von diesen Ressourcen abhängig ist und z.B. durch Ablauf der Zeit oder verleiren aller Leben verloren werden kann. Unser Spiel ist abhängig von Leben, benötigt jedoch keinen Timer, so dass wir den Code entsprechend anpassen.

public bool IsLifeBased
{
get { return true; }
}
public bool IsTimeBased
{
get { return false; }
}

Falls ein Spiel durch das Ablaufen eines Timers verloren werden kann, so gibt GameDuration an, wie viel Zeit dem Spieler zur Verfügung steht. Da unser Spiel keinen Timer benötigt, geben wir einfach eine TimeSpan der Länge 0 an.

public TimeSpan GameDuration
{
    get { return new TimeSpan(); }
}

Die Events GameOver, LiveChanged, ScoresChanged und TimeChanged erlauben es dem Spiel, mit dem GameContainer zu kommunizieren um ihm mitzuteilen, dass das Spiel beendet ist oder um Punkte, Leben oder Zeit zu manipulieren.

public event EventHandler<GameOverEventArgs> GameOver;
public event EventHandler<LivesChangedEventArgs> LiveChanged;
public event EventHandler<ScoresChangedEventArgs> ScoresChanged;
public event EventHandler<TimeChangedEventArgs> TimeChanged;


Die OnLivesChanged und OnScoresChanged erlauben es Behaviors und Actions, auf die Events zum manipulieren von Punkten und Leben zuzugreifen.

public void OnLivesChanged(int lives)
{
// Fire LivesChanged event
if (LivesChanged != null)
{
LivesChangedEventArgs args = new LivesChangedEventArgs();
args.Lives = lives;
LivesChanged(this, args);
}
}
public void OnScoresChanged(int scores)
{
// Fire ScoresChanged event
if (ScoresChanged != null)
{
ScoresChangedEventArgs args = new ScoresChangedEventArgs();
args.Scores = scores;
ScoresChanged(this, args);
}
} 

Die Methoden Start, Stop, Pause und Resume erlauben es dem GameContainer, mit dem Spiel zu kommunizieren. Sie dienen dazu, das Spiel von Aussen zu starten, zu pausieren, fortzusetzen oder zu beeden. Um diese Methoden mit Leben befüllen zu können, müssen wir auf die das GameLoop-Behavior zugreifen können, das wir der LayoutRoot zugewiesen haben.
Wir erweitern den Constructor unseres Spiels um einen EventHandler, der sobald das Spiel geladen ist mit Hilfe des GameLoopFinders das entsprechende GameLoop-Behavior findet und in einer lokalen Variablen abspeichert.

GameLoop gameLoop; // GameLoop behavior for collision and animation

public GameContent()
{
// Required to initialize variables
InitializeComponent();

this.Loaded += new RoutedEventHandler(OnLoaded);
}

void OnLoaded(object sender, RoutedEventArgs e)
{
// Find the GameLoop
GameLoopFinder glf = new GameLoopFinder(LayoutRoot);
gameLoop = glf.GameLoop;
}

Nun können wir die entsprechenden Methoden mit Leben befüllen und die GameLoop bei Bedarf Starten, Pausieren oder Zurücksetzen.

public void Start()
{
if (gameLoop != null)
{
// Start GameLoop
gameLoop.StartGameLoop();
}
}
public void Stop()
{
if (gameLoop != null)
{
// Stop GameLoop and reset collision elements
gameLoop.ResetGameLoop();
}
}
public void Pause()
{
if (gameLoop != null)
{
// Pause GameLoop
gameLoop.PauseGameLoop();
}
}
public void Resume()
{
if (gameLoop != null)
{
// Resume GameLoop
gameLoop.StartGameLoop();
}
}

Damit haben wir alle durch das Interface IGameFeatures geforderten Events und Methoden implementiert und können das Spiel nun testen.

Test des Spiels

Um das Spiel zu testen, können wir das Projekt kompilieren und die Anwendung ausführen. Wenn wir das Spiel starten ist es noch pausiert und wir können es über die Option play im Menü (…) am unteren Bildschirmrand fortsetzen/starten.



Last edited Oct 29, 2010 at 8:57 AM by Tzirrit, version 9

Comments

No comments yet.