Spread the love

While my original project, RapidXNA – built 11 and a half years ago, used to be available to the public, I decided to hide it as XNA fell to the ground. I felt this year I wanted to be more active with my game development again, so I tried several engines. First was Unity to try use my lifetime license I purchased on an older version, and it keeps forcing update to newer versions which don’t offer what I paid for. Second was Epic Games, which is super awesome, but I couldn’t solve my lighting issues with the first project I wanted to port. Third, I even tried out Godot. It just never felt comfy for me.

I’m not sure why, but I decided to take a step back. Revive my old days projects. The first of which is Fragmented Space, which I originally had alongside RapidXNA as a proof of concept which I was going to port over. XNA died just before I got to that, so it stagnated. Throwing together a quick port of RapidXNA to RapidMono was rather easy, it only took a few hours yesterday. Then, porting Fragmented Space I had to rough up art and handle the move.

A gameplay view, with arrows pointing to nearest pickups, with the enemies shooting. Shows score and health points.
Fragmented Space (ignore the gun not in the middle)

I roughed up the art assets myself, fresh, since I didn’t keep them 11 years back, and while it doesn’t have all the features yet, I originally wanted to add them to RapidXNA, it stands as a decent proof of concept. This was all Step 1. Now, I can plan and organise how I will advance it in the future, but for now, a super simple 2D game: Fragmented Space

Note: In Releases when you build, you will see a folder runtimes, if you add NuGet packages to your project, for instance I used EntityFramework.Sqlite. These are where the dependencies for other platforms are held.

Image showing the runtimes, e.g. linux-x86 for sqlite
Runtimes folder

While I would need to test properly on other platforms, I’m hopeful as I didn’t need to do anything to test in WSL2 Ubuntu 22.04. As a note, when focused on another window it hangs a little and struggles to keep FPS up, but that’s not a problem for this proof-of-concept game.

Introducing RapidMono

The idea behind RapidMono is to have an easy game state management system, in C# – my happy place, to build 2D and 3D games in. The architecture is simple, have easy ways to throw game states in, as well as popups, and you can manage your own game code in separated chunks with ease. Somewhat along the lines of scenes in other editors. I stuck to the naming of XNA days, they’re game states.

All the code of today is available through edg3/RapidMono, which I will slowly adjust as needed while I port all my past games into it for desktop first. You will note, I used a static class for ease of reference in the states:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using RapidMono.Services;

namespace RapidMono;

public static class Engine
{
    private static RapidEngine? _Instance;
    public static RapidEngine Instance
    {
        get => _Instance;
        set
        {
            if (_Instance is not null)
            {
                throw new System.Exception("Engine instance already set");
            }
            _Instance = value;
        }
    }

    public static void Exit() => _Instance?.Exit();
    public static ScreenService Screen => _Instance?.ServiceOf<ScreenService>() ?? throw new System.Exception("ScreenService not found");
    public static ScreenshotService Screenshot => _Instance?.ServiceOf<ScreenshotService>() ?? throw new System.Exception("ScreenshotService not found");
    public static KeyboardService Keyboard => _Instance?.ServiceOf<KeyboardService>() ?? throw new System.Exception("KeyboardService not found");
    public static MouseService Mouse => _Instance?.ServiceOf<MouseService>() ?? throw new System.Exception("MouseService not found");
    public static GamePadService GamePad => _Instance?.ServiceOf<GamePadService>() ?? throw new System.Exception("GamePadService not found");
    public static void AddService(IRapidService service, bool replace) => _Instance?.AddService(service, replace);
    // TODO: consider how to rework this a little
    public static object ServiceOf<T>() => _Instance?.Services.Where(it => typeof(T) == it.GetType()).First() ?? throw new System.Exception("Service not found");
    public static GameTime GameTime => _Instance?.GameTime ?? throw new System.Exception("GameTime not found");
    public static GraphicsDevice GraphicsDevice => _Instance?.GraphicsDevice ?? throw new System.Exception("GraphicsDevice not found");
    public static ContentManager Content => _Instance?.Content ?? throw new System.Exception("ContentManager not found");
    public static SpriteBatch SpriteBatch => _Instance?.SpriteBatch ?? throw new System.Exception("SpriteBatch not found");
}

The plan is to manage your own Services as well for the game, so you can partition code into functional segments you can call anywhere, however you will note that feature is too flawed at present. The static class gives reference to the RapidEngine, and direct references to the Services and the normal XNA style objects.

    // ...
    public override void Update()
    {
        UpdateStars();
        GameState.gameTime = Engine.GameTime;
        GameState.Update();

        if (GameState.PlayerHealth <= 0)
        {
            //Game over
            Engine.Screen.PopScreen();
            GameOver go = new GameOver();
            go.ScoreGained = GameState.PlayerScore;
            Engine.Screen.PushScreen(go);
        }
        else if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            Engine.Screen.PopScreen(); //Note: do something fancy to preserve game state
    }

    //...

    Vector2 v2_helper = new();
    Vector2 v2_bulletOrigin = new(10, 10),
            v2_pickupOrigin = new(25, 25);
    Vector2 v2_score = new(8, 8);
    public override void Draw()
    {
        // Draw all stars
        foreach (StarColor sc in smallStars)
        {
            v2_helper.X = sc.Pos.X - GameState.PlayerPosition.X + 400;
            v2_helper.Y = sc.Pos.Y - GameState.PlayerPosition.Y + 240;
            Engine.SpriteBatch.Draw(smallStar, v2_helper, sc.Colour);
        }
        foreach (StarColor sc in mediumStars)
        {
            v2_helper.X = sc.Pos.X - GameState.PlayerPosition.X + 400;
            v2_helper.Y = sc.Pos.Y - GameState.PlayerPosition.Y + 240;
            Engine.SpriteBatch.Draw(mediumStar, v2_helper, sc.Colour);
        }
        foreach (StarColor sc in largeStars)
        {
            v2_helper.X = sc.Pos.X - GameState.PlayerPosition.X + 400;
            v2_helper.Y = sc.Pos.Y - GameState.PlayerPosition.Y + 240;
            Engine.SpriteBatch.Draw(largeStar, v2_helper, sc.Colour);
        }

        // Draw all enemies
    // ---

Then if you have any experience with the idea of a state system, or XNA in general, you can easily work out how to structure your own game data and objects with some simpler methods to manage some of the needed actions.

The are two elements I will likely add in soon, first a new method to separate the content loading to avoid loading the same objects multiple times separately in memory, and the second a way to register event functions. Think along the lines of Engine.Keyboard.Register(some_fn_name, Key.Enter); which would trigger the function some_fn_name(EventCode clicked_or_held_or_released) and your logic for that event registration could go there. You could then also Deregister events, and register them again, if you want to.

That being said, this is a simple space shooter to show it in use for today: Fragmented Space

The menu showing New Game, Local Scores, Quit, and the logo
Fragmented Space menu