Frühere Arbeit
AsteroidShooter, mein Abschlussprojekt am Gymnasium
Ein kleiner Weltraum-Shooter, 2018 von Grund auf in C# auf Microsofts XNA-Framework geschrieben, als mein Abschlussprojekt am Gymnasium, mit einer schriftlichen Arbeit, die erklärt, wie er funktioniert.
- Rolle
- Code, Spieldesign und die schriftliche Arbeit
- Stack
- C#, XNA 4.0
- Kontext
- Abschlussprojekt in Informatik, Gimnazija, Mai 2018
- Status
- Frühere Arbeit
Worum es geht
Man fliegt ein Schiff und schießt mit Lasern auf Asteroiden. Das erste Level ist absichtlich leicht und endet damit, dass das Spiel einem gratuliert, als wäre das schon das ganze Spiel. Dann beginnt es, mit einem zu sprechen, und das zweite Level ist weitaus schwerer als das erste. Wer verliert, bekommt einen Level-Code, den man im Hauptmenü eingeben kann, um direkt zu dem Level zurückzuspringen, an dem man gescheitert ist.
Es ist klein: zwei Level. Ich programmierte damals schon seit über zehn Jahren, deshalb war für mich der interessante Teil, die Struktur ohne Engine aus dem Nichts aufzubauen.
Screenshot folgt in Kürze
Gameplay im zweiten Level.
assets/projects/xna-game/gameplay.png (bereits hinzugefügt)
Screenshot folgt in Kürze
Das Hauptmenü.
assets/projects/xna-game/main-menu.png (bereits hinzugefügt)
Screenshot folgt in Kürze
Der Bildschirm nach dem Tod mit dem Level-Code.
assets/projects/xna-game/game-over.png (bereits hinzugefügt)
Wie es gebaut ist
- Szenen. Die Game-Klasse verwaltet nur die aktive Szene und delegiert die Update- und Draw-Aufrufe von XNA an sie. Hauptmenü und Spiel sind getrennte Szenen.
- Objekte. Schiff, Asteroiden, Laser und Explosionen sind alle Spielobjekte, die ihr eigenes Verhalten und Zeichnen besitzen. Die Szene erzeugt und zerstört sie nur und entscheidet, wann sie laufen. Neue Objekte werden in eine Warteschlange gestellt und zu Beginn des nächsten Updates hinzugefügt, sodass nie etwas zur Liste hinzukommt, während sie durchlaufen wird.
- Kollisionen. Jedes Objekt liefert ein konvexes Kollisionspolygon. Ein Durchlauf über die Szene paart Objekte nach Typ, sodass nur relevante Paare getestet werden (Asteroiden gegen das Schiff und Laser, nie gegeneinander), und jedes Polygon wird einmal pro Frame gebaut. Der Polygontest selbst ist das Separating-Axis-Theorem, angepasst aus dem CodeProject-Artikel 2D Polygon Collision Detection.
- Zeichenreihenfolge. Objekte werden nach Ebene gezeichnet. Die eingebaute Sortierung ist nicht stabil, sodass Objekte auf derselben Ebene zwischen Frames die Reihenfolge tauschten und flackerten. Der Wechsel auf eine stabile Sortierung hat das behoben.
- Alles andere. Eine Kamera, die dem Schiff mit weichem Zoom folgt, überspringbare Story-Übergänge und ein begrenzter Pool an Sound-Instanzen, damit sich bei Dauerfeuer keine Sounds aufstauen.
public void HandleCollisions()
{
PlayerObject pObj = null;
List<Vector2> pPoints = null;
if (PObjRef.IsAlive && (PObjRef.Target as PlayerObject).Scene == this) { pObj = (PlayerObject)PObjRef.Target; pPoints = pObj.BuildCollisionPoints(); }
if (pObj != null && pPoints.Count < 3) pObj = null;
List<Tuple<AsteroidObject, List<Vector2>>> Asteroids = new List<Tuple<AsteroidObject, List<Vector2>>>();
List<Tuple<LaserObject, List<Vector2>>> Lasers = new List<Tuple<LaserObject, List<Vector2>>>();
foreach (GameObject GObj in GameObjects)
{
if (GObj.Scene != this) continue;
if (GObj is Explosion) continue;
if (GObj is PlayerObject) continue;
List<Vector2> CPoints = GObj.BuildCollisionPoints();
if (CPoints.Count < 3) continue;
if (GObj is AsteroidObject)
{
if (pObj != null) CheckCollision(GObj, CPoints, pObj, pPoints);
foreach (Tuple<LaserObject, List<Vector2>> tLP in Lasers)
CheckCollision(GObj, CPoints, tLP.Item1, tLP.Item2);
Asteroids.Add(new Tuple<AsteroidObject, List<Vector2>>((AsteroidObject)GObj, CPoints));
}
else if (GObj is LaserObject)
{
foreach (Tuple<AsteroidObject, List<Vector2>> tAP in Asteroids)
CheckCollision(GObj, CPoints, tAP.Item1, tAP.Item2);
Lasers.Add(new Tuple<LaserObject, List<Vector2>>((LaserObject)GObj, CPoints));
}
}
}
public void CheckCollision(GameObject A, List<Vector2> PointsA, GameObject B, List<Vector2> PointsB)
{
if (!SATCollisions.CheckSATCollision(PointsA, PointsB)) return;
OnCollision(A, B);
}Ein kompletter Durchlauf
Das ganze Spiel vom Hauptmenü über das erste Level und den Monolog, bis mich das deutlich schwerere zweite Level geschlagen hat. Ich habe es während der Entwicklung leichter gemacht, und acht Jahre später schaffe ich es immer noch nicht zuverlässig.
Video folgt in Kürze
Ein vollständiger Durchlauf, von Anfang bis Ende.
Paste the YouTube video ID into data-youtube on this figure in work/xna-game.html
Rückblick
Es ist ein Schülerprojekt und liest sich stellenweise auch so: Eine globale Klasse hält gemeinsamen Zustand, und die Level-Logik steckt in der Spielszene statt in Daten. Was ich weiterhin verteidigen würde, sind die Trennung von Szenen und Objekten, die Objekterzeugung per Warteschlange und die stabile Sortierung, die aus der Jagd auf einen echten Flacker-Bug entstand und nicht aus einem Tutorial.
Video (optional) folgt in Kürze
Etwa 20 Sekunden des zweiten Levels: fliegen, schießen und die Kamera zoomen.
assets/projects/xna-game/gameplay.mp4 (etwa 20 Sekunden)