Overview
My role
Enemies & Combat
Engine
MonoGame (C#)
Genre
Top-down shooter
Platform
Windows
Hell Space is a top-down survival shooter on a lunar surface. Aliens approach from all sides and the player fights back with a randomly assigned starting weapon — Minigun, Shotgun, or Lasergun — upgraded through a DNA collection system.
I built all four enemy types with their individual behaviours, the timed spawn system that escalates difficulty over the session, and the player's full shooting mechanic — aim, fire rate, and bullet damage falloff.
Enemy Types
Four classes inherit from a shared Enemy base. Each overrides movement and behaviour while reusing the base spawn, separation, and health logic.
Enemy
Basic chaser
HP
10
Speed
140
Dmg
1
Spawns from a random screen edge from the start. Chases the player directly, separates from other enemies to avoid clumping.
BigEnemy
Tank
HP
45
Speed
80
Dmg
3
Unlocks after 30 seconds. Slower but absorbs far more damage — forces the player to commit more firepower.
ShooterEnemy
Ranged
HP
15
Speed
75
Dmg
5
Spawns every 3 seconds from the start. Keeps 200 units of distance and fires a bullet every 4 seconds. Threatens the player even at range.
SuicideEnemy
Kamikaze bomber
HP
5
Speed
150
Dmg
20
Unlocks after 60 seconds. Low health but deadly — within 150 units it boosts speed 2.5× and detonates 0.8s later for 20 damage.
public class Enemy : Lifeform, IDestroyable
{
protected Player player;
private float avoidDistance = 20;
public List<Enemy> otherEnemies = new List<Enemy>();
protected int damage = 1;
public Enemy(...) : base(...)
{
position = CalculateSpawnPosition();
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
MoveTowardsPlayer();
AvoidOtherEnemies();
}
public virtual void MoveTowardsPlayer()
{
if (player.camouflage == null)
{
Vector2 direction = Vector2.Normalize(
player.Position - position);
velocity += direction * speed;
if (velocity.Length() > speed)
{
velocity.Normalize();
velocity *= speed;
}
}
else velocity = Vector2.Zero; // player is hidden
}
private void AvoidOtherEnemies()
{
foreach (var other in otherEnemies)
{
if (other != this &&
Vector2.Distance(position, other.Position) < avoidDistance)
{
Vector2 dir = Vector2.Normalize(position - other.Position);
position += dir * (avoidDistance
- Vector2.Distance(position, other.Position));
}
}
}
}Spawn System
Enemies spawn from a random screen edge so the player can never camp one direction. Harder types are gated behind elapsed time — the session stays manageable early and escalates as it goes on.
private Vector2 CalculateSpawnPosition()
{
Random random = new Random();
int side = random.Next(4); // 0=Top 1=Right 2=Bottom 3=Left
switch (side)
{
case 0: return new Vector2(random.Next(Screen.X), -800);
case 1: return new Vector2(Screen.X + 100,
random.Next(Screen.Y));
case 2: return new Vector2(random.Next(Screen.X),
Screen.Y + 100);
case 3: return new Vector2(-800, random.Next(Screen.Y));
}
}// GameState.cs — Update()
enemySpawnTimer += elapsed;
if (enemySpawnTimer >= enemySpawnInterval) // every 2s
{
spawnEnemy(); // basic — from 0:00
if (elapsed >= 30)
spawnBigEnemy(); // tank — from 0:30
if (elapsed >= 60)
spawnSuicideEnemy(); // bomber — from 1:00
enemySpawnTimer = 0;
}
ShooterEnemySpawnTimer += elapsed;
if (ShooterEnemySpawnTimer >= ShooterSpawnInterval) // every 3s
{
SpawnShooterEnemy(); // ranged — from 0:00
ShooterEnemySpawnTimer = 0;
}ShooterEnemy & SuicideEnemy
Both types override the base MoveTowardsPlayer() with distinct behaviour patterns that force different player responses.
public class ShooterEnemy : Enemy
{
private float ShootCooldown = 4f;
private float ShootTimer = 0f;
private int DistanceFromPlayer = 200;
public override void MoveTowardsPlayer()
{
Vector2 dir = player.Position - position;
float distance = dir.Length();
if (distance > DistanceFromPlayer)
{
dir.Normalize();
velocity = dir * speed; // close the gap
}
else velocity = Vector2.Zero; // hold position and shoot
}
public void ShootAtPlayer()
{
if (player.camouflage != null) return;
Vector2 dir = Vector2.Normalize(player.Position - Position);
Bullet b = new Bullet(dir, BulletSpeed=200, BulletDamage=5,
new MoveStraightBehavior(), "EnemyBullet");
b.Position = this.Position;
bullets.Add(b);
}
}public class SuicideEnemy : Enemy
{
private bool isCharging = false;
private float detonationTime = 0.8f; // explodes 0.8s after charge
public override void MoveTowardsPlayer()
{
if (!isCharging)
{
base.MoveTowardsPlayer();
float dist = (player.Position - position).Length();
if (dist < 150)
{
velocity *= 2.5f; // dash toward the player
isCharging = true;
}
}
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
if (isCharging)
{
detonationTime -= (float)gameTime.ElapsedGameTime
.TotalSeconds;
if (detonationTime < 0) hp = 0; // triggers destroy
}
}
}Player Combat
The gun rotates toward the mouse cursor every frame. Left-click fires when the cooldown allows. Bullet damage applies a falloff reduction past a distance threshold, rewarding close-range play.
// GameState.cs — CheckForPlayerInput()
Vector2 aimDir = new Vector2(
input.MousePosition.X - Screen.X / 2,
input.MousePosition.Y - Screen.Y / 2
);
// Rotate the gun sprite toward the mouse
player.Gun.UpdateRotation(input.MousePosition);
// Left-click fires if the gun's cooldown has elapsed
if (input.MouseLeftButtonDown && player.Gun.readyToFire)
{
player.currentState = PlayerState.State.Shooting;
Bullet[] bullets = player.Gun.ShootBullet(aimDir);
foreach (Bullet b in bullets)
Add(b);
}
// Releasing the button restores walk state + fire rate
if (!input.MouseLeftButtonDown && !player.IsOverloading)
{
player.Gun.Firerate = player.Gun.StartFirerate;
player.currentState = PlayerState.State.Walking;
}// Gun.cs — ShootBullet (base implementation)
public virtual Bullet[] ShootBullet(Vector2 direction)
{
currentTime = timeToWait;
Bullet b = (Bullet)bullet.Clone();
b.Position = position;
b.Direction = direction;
return new Bullet[] { b };
}
// Each subclass (Shotgun, Minigun, Lasergun) overrides
// this to change spread, count, or bullet type.
// Bullet damage falls off past falloffDistance:
private void BulletDamagesEnemy(Bullet bullet, Enemy enemy)
{
if (bullet.TraveledTime > player.Gun.FalloffDistance)
bullet.Damage -= player.Gun.DamageReduction;
enemy.Hp -= bullet.Damage;
}Lasergun
High fire rate, moderate damage. Continuous beam-style fire.
Shotgun
Fires a spread of pellets. High burst damage up close.
Minigun
Rapid single shots. Fastest DPS but needs sustained aim.