Damit das Spiel möglichst wie sein Scratch-Zwilling aussieht, kannst du die gleichen Grafiken und Sounds verwenden:
Du benötigst lokal:
Sobald das alles installiert ist, richten wir die dotnet new
- Templates (Vorlagen) für MonoGame ein:
dotnet new install "MonoGame.Templates.CSharp"
Dann können wir schon loslegen und ein neues, leeres MonoGame-Projekt erstellen.
Das geht relativ einfach, indem man entweder in der IDE ein Template auswählt.
Alternativ kann man das auch von Hand machen (z. B. mit VS Code). Schaut euch einfach die verfügbaren Templates an:
> dotnet new list monogame
These templates matched your input: 'monogame'
Template Name Short Name Language Tags
------------------------------------------- ----------- -------- ------------------------------------------
MonoGame Android Application mgandroid [C#] MonoGame/Games/Mobile/Android
MonoGame Content Pipeline Extension mgpipeline [C#] MonoGame/Games/Extensions
MonoGame Cross-Platform Desktop Application mgdesktopgl [C#] MonoGame/Games/Desktop/Windows/Linux/macOS
MonoGame Game Library mglib [C#] MonoGame/Games/Library
MonoGame iOS Application mgios [C#] MonoGame/Games/Mobile/iOS
MonoGame Shared Library Project mgshared [C#] MonoGame/Games/Library
MonoGame Windows Desktop Application mgwindowsdx [C#] MonoGame/Games/Desktop/Windows/Linux/macOS
Legt ein neues Verzeichnis an und wählt dann das passende Template aus:
> mkdir csmoonhack
> cd csmoonhack
> dotnet new mgwindowsdx
Dann könnt ihr euer Projekt mit eurer Entwicklungsumgebung (IDE, Integrated Development Environment) öffnen.
Die Vorlage für das Spiel ist ja nun fertig. Zuerst benennen wir die Datei Game1.cs
in LunarLanderGame.cs
um. Anschließend die Datei öffnen und dort die Zeile:
public class Game1 : Game {
in
public class LunarLanderGame : Game {
ändern.
In Program.cs müssen wir auch dafür sorgen, dass dort die neue Klasse verwendet wird. Dort wird die Zeile
using var game = new csmoonhack.Game1();
zu
using var game = new csmoonhack.LunarLanderGame();
Kopiere die im ersten Abschnitt genannten Dateien (.png
und .wav
) in das Unterverzeichnis Content
deines Projekts. Dazu gehören:
backdrop1.png
lunar_lander_sheet.png
thruster.wav
landed.wav
problem.wav
Öffne anschließend die Datei Content.mgcb
, die sich ebenfalls im Content
-Ordner befindet. Dort kannst du die Assets zum Projekt hinzufügen und verwalten.
Info:
Eine.mgcb
-Datei ist eine Textdatei, die Informationen darüber enthält, welche Dateien vom Content-Pipeline-Tool verarbeitet werden sollen.
Jede hinzugefügte Datei wird mit einem#begin
-Eintrag eingeleitet, gefolgt von Optionen wie z. B.Importer
,Processor
undBuild
.
Beispiel:#begin backdrop1.png /importer:TextureImporter /processor:TextureProcessor /build:backdrop1.png
Diese Datei kannst du entweder mit einem Editor oder bequem mit dem MGCB-Editor bearbeiten, der in Visual Studio als Erweiterung verfügbar ist.
Die Datei sollte dann in der Sektion Content wie unten aussehen. Den Rest lassen wir einfach, wie er ist.
#---------------------------------- Content ---------------------------------#
#begin backdrop1
/importer:TextureImporter
/processor:TextureProcessor
/build:backdrop1.png
#begin lunar_lander_sheet
/importer:TextureImporter
/processor:TextureProcessor
/build:lunar_lander_sheet.png
#begin thruster
/importer:WavImporter
/processor:SoundEffectProcessor
/build:thruster.wav
#begin problem
/importer:WavImporter
/processor:SoundEffectProcessor
/build:problem.wav
#begin landed
/importer:WavImporter
/processor:SoundEffectProcessor
/build:landed.wav
Ab hier werden wir die Datei LunarLanderGame.cs
mit code füllen. Als Erstes brauchen wir einige Variablen, um den aktuellen Spielzustand zu halten.
Der Anfang der Klasse LunarLanderGame
sollte dann so aussehen:
public class LunarLanderGame : Game {
private const int GAME_UNIT_HEIGHT = 200;
// wird vom Framework benötigt
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
// Grafiken die von "Content" geladen werden
private Texture2D _lunarLanderSpriteSheet;
private Texture2D _backgroundTexture;
private Rectangle[] _spriteFrames;
// SoundEffects
private SoundEffectInstance _thrusterSoundInstance;
private SoundEffectInstance _landedSoundInstance;
private SoundEffectInstance _problemSoundInstance;
// Variablen für das den Spielzustand
private int _currentSprite;
private float _speed;
private float _position;
private int _absoluteYPos;
private bool _gameOver;
Ausserdem müssen wir im Konstruktor, den wir auch noch auf LunarLanderGame
umbenennen sollten, noch das Spielfeld einstellen:
public LunarLanderGame() {
_graphics = new GraphicsDeviceManager(this) {
PreferredBackBufferWidth = 1024,
PreferredBackBufferHeight = 720,
GraphicsProfile = GraphicsProfile.HiDef,
PreferMultiSampling = true
};
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
In der Initialize
Methode sollten die Zustände für den Spielanfang eingetragen werden:
protected override void Initialize() {
// Initiale Werte für das Spiel
_speed = 0;
_gameOver = false;
_absoluteYPos = 0;
_currentSprite = 0;
base.Initialize();
}
Die Inhalte, die in Content.mgcb
eingefügt wurden können wir nun einfach laden. Das Spritesheet müssen wir allerdings noch zerlegen:
protected override void LoadContent() {
_spriteBatch = new SpriteBatch(GraphicsDevice);
_lunarLanderSpriteSheet = Content.Load<Texture2D>("lunar_lander_sheet");
_backgroundTexture = Content.Load<Texture2D>("backdrop1");
// Lade Audio-Dateien
SoundEffect thrustersSound = Content.Load<SoundEffect>("thruster");
SoundEffect landedSound = Content.Load<SoundEffect>("landed");
SoundEffect problemSound = Content.Load<SoundEffect>("problem");
_thrusterSoundInstance = thrustersSound.CreateInstance();
_thrusterSoundInstance.IsLooped = true;
_landedSoundInstance = landedSound.CreateInstance();
_landedSoundInstance.IsLooped = false;
_problemSoundInstance = problemSound.CreateInstance();
_problemSoundInstance.IsLooped = false;
SliceSpriteSheet();
}
private void SliceSpriteSheet() {
int frameHeight = _lunarLanderSpriteSheet.Height;
_spriteFrames = new Rectangle[3];
// Zerlege das SpriteSheet in einzelne Sprites
int pos = 30;
_spriteFrames[0] = new Rectangle(pos, 0, 525, frameHeight);
pos += 525 + 30;
_spriteFrames[1] = new Rectangle(pos, 0, 931, frameHeight);
pos += 931 + 30;
_spriteFrames[2] = new Rectangle(pos, 0, 526, frameHeight);
}
Unsere Update
-Methode sieht ungefähr so aus:
protected override void Update(GameTime gameTime) {
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape)) {
Exit();
}
if (!_gameOver) {
TouchCollection touchCollection = TouchPanel.GetState();
bool isThrusting =
GamePad.GetState(PlayerIndex.One).Buttons.BigButton == ButtonState.Pressed ||
(touchCollection.Count > 0 && touchCollection[0].State == TouchLocationState.Pressed) ||
Keyboard.GetState().IsKeyDown(Keys.Space);
if (isThrusting) {
if (_thrusterSoundInstance.State != SoundState.Playing) {
_thrusterSoundInstance.Play();
}
} else {
if (_thrusterSoundInstance.State == SoundState.Playing) {
_thrusterSoundInstance.Stop();
}
}
CalculateSpritePosition(gameTime, isThrusting);
CheckLanding();
// Aktuelle Position des Lunar-Lander berechnen
_absoluteYPos = (int)(_position * (_graphics.PreferredBackBufferHeight / GAME_UNIT_HEIGHT));
} else {
if (_thrusterSoundInstance.State == SoundState.Playing) {
_thrusterSoundInstance.Stop();
}
}
}
Die Hilfsmethoden CalculateSpritePosition
und CheckLanding
haben wir ausgelagert und du findest sie hier:
private void CheckLanding() {
if (_position > GAME_UNIT_HEIGHT * 0.95f) {
// ende nach 95% des spielfelds
_gameOver = true;
_position = GAME_UNIT_HEIGHT * 0.95f;
if (_speed > 10) { // Das war wohl zu schnell
_currentSprite = 1;
_problemSoundInstance.Play();
} else { // Super - wir sind gelanded
_currentSprite = 0;
_landedSoundInstance.Play();
}
_speed = 0;
}
}
private void CalculateSpritePosition(GameTime gameTime, bool isThrusting) {
if (isThrusting) {
_currentSprite = 2;
_speed -= gameTime.ElapsedGameTime.Milliseconds * 0.05f;
} else {
_currentSprite = 0;
_speed += gameTime.ElapsedGameTime.Milliseconds * 0.01f;
}
_position += _speed * gameTime.ElapsedGameTime.Milliseconds * 0.001f;
}
Die Draw
-Methode kümmert sich um die Skalierung der Figuren und die Darstellung des Spielfelds
protected override void Draw(GameTime gameTime) {
GraphicsDevice.Clear(Color.CornflowerBlue);
// Hintergrund zeichnen
_spriteBatch.Begin();
_spriteBatch.Draw(_backgroundTexture, GraphicsDevice.Viewport.Bounds, Color.White);
// skalieren des Landers
float scale = (_graphics.PreferredBackBufferHeight / 5f) / _spriteFrames[_currentSprite].Height;
int targetWidth = (int)(_spriteFrames[_currentSprite].Width * scale);
int targetHeight = (int)(_spriteFrames[_currentSprite].Height * scale);
Rectangle destinationRectangle = new Rectangle(
(_graphics.PreferredBackBufferWidth - targetWidth) / 2,
_absoluteYPos,
targetWidth,
targetHeight
);
// und zeichnen des Landers an seiner aktuellen Position
_spriteBatch.Draw(_lunarLanderSpriteSheet, destinationRectangle, _spriteFrames[_currentSprite], Color.White);
_spriteBatch.End();
base.Draw(gameTime);
}
Damit ist das Spiel nun fertig. Viel Spaß damit und viel Erfolg beim Landen auf dem Mond.
Wenn du noch eine Herausforderung suchst, bau doch bitte einen Treibstoffzähler ein, der während des Triebwerkseinsatzes Treibstoff verbraucht. Wie auch in der echten Mondlandefähre ist Treibstoff immer knapp und sollte mit Bedacht eingesetzt werden.