Earlier work
AsteroidShooter, my high school final project
A small space shooter written from scratch in C# on Microsoft’s XNA framework in 2018, for my final high school project, with a written thesis explaining how it works.
- Role
- Code, game design and the written thesis
- Stack
- C#, XNA 4.0
- Context
- Final project for computer science, gimnazija, May 2018
- Status
- Earlier work
What it is
You fly a ship and shoot asteroids with lasers. The first level is deliberately easy and ends with the game congratulating you, as if that were the whole game. Then it starts talking to you, and the second level is far harder than the first. Losing gives you a level code you can type on the main menu to jump straight back to the level you lost on.
It’s small: two levels. I’d been programming for over a decade by then, so the interesting part for me was building the structure from nothing, without an engine.
Screenshot coming soon
Second level gameplay.
assets/projects/xna-game/gameplay.png (already added)
Screenshot coming soon
The main menu.
assets/projects/xna-game/main-menu.png (already added)
Screenshot coming soon
The screen after dying, showing the level code.
assets/projects/xna-game/game-over.png (already added)
How it’s built
- Scenes. The game class only manages the active scene and delegates XNA’s update and draw calls to it. The main menu and the game are separate scenes.
- Objects. The ship, asteroids, lasers and explosions are all game objects that own their own behaviour and drawing. The scene only creates them, destroys them and decides when they run. New objects are queued and added at the start of the next update, so nothing is added to the list while it’s being iterated.
- Collisions. Each object provides a convex collision polygon. One pass over the scene pairs objects by type, so only pairs that matter are tested (asteroids against the ship and lasers, never against each other), and each polygon is built once per frame. The polygon test itself is the separating axis theorem, adapted from the CodeProject article 2D Polygon Collision Detection.
- Draw order. Objects are drawn by layer. The built-in sort isn’t stable, so objects on the same layer swapped order between frames and flickered. Switching to a stable sort fixed it.
- Everything else. A camera that follows the ship with smooth zoom, skippable story transitions, and a capped pool of sound instances so rapid fire can’t pile up sounds.
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);
}A full run
The whole game from the main menu, through the first level and the monologue, to the end of the much harder second level.
Video coming soon
A full playthrough, start to finish.
Paste the YouTube video ID into data-youtube on this figure in work/xna-game.html
Looking back
It’s a student project and reads like one in places: one global class holds shared state, and level logic lives in the game scene rather than in data. The parts I’d still defend are the separation between scenes and objects, the queued object creation, and the stable sort, which came from tracking down a real flickering bug rather than from a tutorial.
Video (optional) coming soon
About 20 seconds of the second level, flying, shooting and zooming the camera.
assets/projects/xna-game/gameplay.mp4 (about 20 seconds)