• Stars
    star
    163
  • Rank 231,141 (Top 5 %)
  • Language
    C#
  • License
    MIT License
  • Created over 2 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A small 2D C# game engine I made in about 24 hours.

Mirage Logo

Mirage

Mirage is a small 2D game engine written in 24 hours, because why not? It's definitely not perfect but it's usable, and it's the first complete game engine I've ever made.

Support me: https://www.patreon.com/n8dev

Watch the devlog: https://youtu.be/hysfq_xJAw0

Join my Discord server for help: https://discord.gg/f8B6WW7YrD

Specifications

  • Runs on Windows exclusively because of System.Drawing :/
  • Built and used with C# and .NET 6
  • 2D sprite rendering with OpenGL (Silk.NET bindings)
  • Keyboard inputs
  • A beautiful and easy-to-use API
  • An API that's also very extendable if you wanna put in the work

How to Install

  1. Create a new .NET 6 Console application (here's how if you don't know)

  2. Install the NateCurtiss.Mirage NuGet package (here's how if you've never installed NuGet packages before)

  3. You're done :D

Examples

You'll find some sample projects in the repository, prefixed with Sample. For now there's Flappy Bird and Pong. Controls for both are pretty self-explanatory - just use WASD/Arrows and Space for everything.

Just like everything else, the game art and color palettes are under the MIT License so feel free to use those for whatever you want WITH or WITHOUT credit - it's up to you (although credit is always nice :p).

How to Use

Creating a Game

After adding the NuGet package to your project, create a file called Program.cs with either a top-level statement or Main method, and then create a new Game and Start() it.

new Game().Start();

The Game class takes in a few arguments in its constructor, so let's create those. Start with the Window, passing in a title, width, height and optionally whether the path to a custom Window Icon, the Color to use the for Window's background, and/or whether it should maximized and/or resizable.

var window = new Window("If you can read this you don't need glasses.", 1920, 1080, maximized: true);
new Game(window).Start();

Next we'll need the other arguments, so create the Keyboard...

var window = new Window("If you can read this you don't need glasses.", 1920, 1080, maximized: true);
var keyboard = new Keyboard();
new Game(window, keyboard).Start();

the Graphics object, which acts as the wrapper for OpenGL....

var window = new Window("If you can read this you don't need glasses.", 1920, 1080, maximized: true);
var keyboard = new Keyboard();
var graphics = new Graphics();
new Game(window, keyboard, graphics).Start();

the Camera, passing in the Window...

var window = new Window("If you can read this you don't need glasses.", 1920, 1080, maximized: true);
var keyboard = new Keyboard();
var graphics = new Graphics();
var camera = new Camera(window);
new Game(window, keyboard, graphics).Start();

the Renderer, passing in the Camera and the Window...

var window = new Window("If you can read this you don't need glasses.", 1920, 1080, maximized: true);
var keyboard = new Keyboard();
var graphics = new Graphics();
var camera = new Camera(window);
var renderer = new Renderer(camera, window);
new Game(window, keyboard, graphics, renderer).Start();

and finally, the World, which contains all of the Entities in the Game. You'll need to pass in everything to this.

var window = new Window("If you can read this you don't need glasses.", 1920, 1080, maximized: true);
var keyboard = new Keyboard();
var graphics = new Graphics();
var camera = new Camera(window);
var renderer = new Renderer(camera, window);
var world = new World(window, keyboard, graphics, camera, renderer);
new Game(world, window, keyboard, graphics, renderer).Start();

Now if we run our application we should get a blank Window with a title and icon!

Basics

A "thing" in the World is called an Entity; let's create one! First let's create a new file in our project called Player.cs, and make that class inherit from Entity.

class Player : Entity
{

}

Entities have a set of "event methods" called at different times at different frequencies that can be overriden. Here's a brief explanation of all of them.

  • OnAwake(): called BEFORE the first frame of the Entity's lifetime; use this for initializing variables and event handling
  • OnStart(): called ON the first frame of the Entity's lifetime; use this for game logic that should run on the first frame
  • OnKill(): called when the Entity is killed.`
  • OnUpdate(float deltaTime): called every frame

Simply override any of the event methods to have your Entity receive callbacks.

class Player : Entity
{
  protected override void OnStart()
  {
    Console.WriteLine("The Game has started lol.");
  }
  
  protected override void OnUpdate(float deltaTime)
  {
    Console.WriteLine("The Game has updated lmao.");
  }
}

Spawning an Entity is just as easy. To spawn an Entity we need to go through the World first, as that's where Entities live. Back in our Program.cs file we have a reference to the World, so let's spawn in our Player there.

var world = new World(window, keyboard, graphics, camera, renderer).Spawn<Player>();

That's it! The Spawn<T>() method takes in a type parameter T, which is just the type of Entity we'd like to spawn (in this case: Player). World.Spawn<T>() returns the World so that we can chain these as much as we want, which makes it look hella pretty.

var world = new World(window, keyboard, graphics, camera, renderer)
  .Spawn<Player>()
  .Spawn<Enemy>()
  .Spawn<Enemy>()
  .Spawn<Floor>()
  .Spawn<GameManager>()
  // ...

Resolving Dependencies

In literally every single video game ever developed, objects depend on each other. Mirage is code-only, so there's no drag-and-drop visual editor like Unity or Godot, but there are still a few good ways to resolve dependencies.

1) After Spawning

There's an overload for World.Spawn<T>() that takes in an argument to output the spawned Entity of type T.

var world = new World(window, keyboard, graphics, camera, renderer)
  .Spawn<Enemy>(out var enemy) // The Enemy needs to know where the Player is to follow them.
  .Spawn<Player>(out var player);

We can then do stuff to this Entity by chaining a World.OnAwake() or World.OnStart() call. These two methods act just like Entity.OnAwake() Entity.OnStart(), but are called after every single Entity has received the callback for the corresponding event method.

var world = new World(window, keyboard, graphics, camera, renderer)
  .Spawn<Enemy>(out var enemy)
  .Spawn<Player>(out var player)
  .OnAwake() =>  // Called after Enemy.OnAwake() and Player.OnAwake().
  {
    enemy.Target = player;
  };

Note: a World.OnUpdate(float deltaTime) callback also exists.

2) Before Spawning

Sometimes you'll want to pass in a lot of simple values, and something like

var world = new World(window, keyboard, graphics, camera, renderer)
  .Spawn<Player>(out var player)
  .OnAwake() => 
  {
    player.Speed = 1f;
    player.Jump = 5.5f;
    player.Height = 20f;
    player.ShouldLick = true;
    player.IsSubscribedToN8Dev = true;
    player.Pants = new Pants(Jeans.Good);
    player.EyeColor = Eyes.Green;
    // ...
  };

just isn't gonna cut it.

Instead we can have Player inherit from Entity<T> like so.

class Player : Entity<float>
{
    
}

This gives us an extra method, OnConfigure(T config) which looks like this in our Player.

class Player : Entity<float>
{
  protected override void OnConfigure(float config)
  {
  
  }
}

This method is special because it allows us to pass values in when we spawn in the Entity. In this case we're passing in the _moveSpeed of the player, so we'd use it like this.

class Player : Entity<float>
{
  float _moveSpeed;
  
  protected override void OnConfigure(float config)
  {
    _moveSpeed = config;
  }
}

To pass in our _moveSpeed we'll need to go back to our main file and use a different overload of the Spawn<T>() method.

var world = new World(window, keyboard, graphics, camera, renderer)
  .Spawn<Player, float>(5f);

All we're doing here is telling the World that

  • A: we're spawning in an Entity of type Player
  • B: we're passing in a float to its OnConfigure method
  • and C: we want that float to be equal to 5

And now we've given our Player a speed of 5! So now if we have multiple Players we can easily tweak values to our liking.

var world = new World(window, keyboard, graphics, camera, renderer)
  .Spawn<Player, float>(1f)
  .Spawn<Player, float>(0.5f); // Player 2 will be slower.
  .Spawn<Player, float>(100f); // Player 3 just drank some Red Bull.

Here's that example with the enemy from earlier.

var world = new World(window, keyboard, graphics, camera, renderer)
  .Spawn<Player>(out var player)
  .Spawn<Enemy, Player>(player);

Much more elegant.

"But Nate..." I hear you ask, "What if I have, for example, a bunch of weapons that have multiple properties I'd like to tweak per instance? This way only allows me to pass in a single argument to an Entity."

Well, you're right...in a way, but there's a pretty simple workaround. To fix this we can just create a struct that's something like this.

struct WeaponConfig
{
  public string Name;
  public float Power;
  public float Range;
  // ...
}

And pass THAT into our Entity.

class Weapon : Entity<WeaponConfig>
{
  float _name;
  float _power;
  float _range;
    
  protected override void OnConfigure(WeaponConfig config)
  {
    _name = config.Name;
    _power = config.Power;
    _range = config.Range;
  }
}
var world = new World(window, keyboard, graphics, camera, renderer)
  .Spawn<Weapon, WeaponConfig>(new("sword", 100f, 5f))
  .Spawn<Weapon, WeaponConfig>(new("spear", 30f, 30f))
  .Spawn<Weapon, WeaponConfig>(new("club", 200f, 2f))
  // ...

Even better, we can still get the Entity spawned through another overload of the Spawn<TE, TC>() method.

var world = new World(window, keyboard, graphics, camera, renderer)
  .Spawn<Weapon, WeaponConfig>(new("sword", 100f, 5f), out var sword)
  .Spawn<Player, Weapon>(sword);

To Get you Going...

Entities have access to all those modules we created at the start (Window, Keyboard, etc)

class Player : Entity<float>
{
  float _moveSpeed;
  
  protected override void OnConfigure(float config)
  {
    _moveSpeed = config;
  }
  
  protected override void OnUpdate(float deltaTime)
  {
    if (Keyboard.IsDown(Key.RightArrow))
      Position += new Vector2(_moveSpeed, 0f);
  }
}

There's no physics engine because I wrote this in 24 hours, but you can emulate collisions with Entity.Bounds

class Enemy : Entity<Player>
{
  Player _player
  
  protected override void OnConfigure(Player config)
  {
    _player = player;
  }
  
  protected override void OnUpdate(float deltaTime)
  {
    if (Bounds.Overlaps(_player.Bounds))
      World.Kill(_player);
  }
}

And...that's about it. Check out the sample projects in the repo for further guidance on the parts of the API I didn't talk about and don't hesitate to ask for help in my Discord server.

Dependencies

License

Mirage is under the MIT License which gives you the freedom to do pretty much whatever you want with the engine; every game you make with Mirage is 100% yours down to the very last semicolon.

More Repositories

1

n8engine

An open-source C# game engine that's going to be the best thing ever.
C#
50
star
2

n8dev.net

My website.
HTML
12
star
3

n8sprite

A pixel art sprite editor previously used to make sprites for my open-source C# console game engine. n8engine.
C#
12
star
4

discord-bot

bot for my server lmao.
C#
5
star
5

n8engine-terminal

An unfinished C# game engine for the terminal that I made for a YouTube video.
C#
4
star
6

natecurtiss.github.io

HTML
2
star
7

scarecrow-horror-game

C#
2
star
8

Brackeys-Game-Jam-2021

A game made for the Brackeys Game Jam 2021, with the theme of "Stronger Together".
C#
2
star
9

blackjack-cli

A small command like Blackjack game to pass the time with.
C#
2
star
10

tic-tac-toe-in-c

My second C program ever :D
C
2
star
11

fizzbuzz-in-c

My first C program lmao.
C
2
star
12

scriptable-object-architecture

A Unity package for a scriptable-object-based-workflow similar to that of Ryan Hipple's (https://www.youtube.com/watch?v=raQ3iHhE_Kk)
C#
2
star
13

sdl-game

lol
C#
2
star
14

brackeys-game-jam-2021

C#
1
star
15

bouncy-platformer

A platformer I'm making to revive my game dev career.
C#
1
star
16

minimalist-platformer

what
C#
1
star
17

gitignores

A collection of my own gitignore files for easier reference.
1
star
18

n8sprite-converter

A tool previously used for converting image files (jpeg, png, etc) to a usable format in my c# game engine, n8engine.
C#
1
star
19

gmtk-2022

lmao
C#
1
star
20

snake-in-c

It's pretty terrible but I was bored in class so I wrote it anyway
C
1
star
21

mango

Java
1
star
22

wheel

A wheel you can spin; made with Unity.
ShaderLab
1
star
23

utils

Helpful stuff for starting new Unity projects.
C#
1
star