This site uses cookies to ensure you get the best experience. More info
Got it!

Engine Upgrades



Upgrades

As the Praedium game is made with the usage of custom-tailored engine, the engine is upgraded, changed and created on-demand for any needs that arise during the development.

As a plenty of these small refinements happened over time I decided to highlight some of the more interesting changes and plans for the engine itself.

Creating game objects on demand

Previously, the objects were needed to be initialized on the initial level load manually. As obviously such approach is good, sooner or later I would have to initialize new game objects on the fly, and do so correctly.
This means, that every game object will need to be created along with any components defined by the RequireComponent attributes and every callback method would need to be called and in proper time.

Fortunately, this is actually very easy to implement, and - once again - provides a clean API, similar to the one, known from Unity Engine.

public void AddGameObject(GameObject gameObject)
{
    entities.Add(gameObject);
    gameObject.Game = this;
    gameObject.Start();
}

public GameObject Instantiate<T>()
    where T : GameObject
{
    var obj = Activator.CreateInstance(typeof(T)) as GameObject;
    AddGameObject(obj);

    return obj;
}

We use the already implemented AddGameObject method and, by using reflection, simply instantiate object of type T, which is constrained to inherit from the GameObject class.

Destroying game objects

Whatever is created should also expose some API to be properly destroyed. In this case, we expose another overrideable callback - OnDestroy.

protected virtual void OnDestroy() { }

public void Destroy()
{
    OnDestroy();
    Game.Destroy(this);
}

The public method Destroy is exposed to other game objects, which could force destruction of any other instantiated game object.

While calling this method, the object processes the internal callback and after that, requests removal from the Game by calling Game.Destroy(this).

The Game.Destroy method for now simply removes the object from the entities collection.

Finding and accessing game objects

Another small yet important thing is the ability to easily access other game objects from within the game’s logic code.

So in order to comply with it, I have introduced two simple methods.

The first one accepts a Bramble.Core.Rect instance to indicate the area from within it will return all game objects.

public IEnumerable<GameObject> GetObjectsWithin(Rect rectangle)
{
    foreach (var obj in entities)
    {
        if (rectangle.Contains(obj.Position))
            yield return obj;                
    }
}

And the second one is to find the game objects by name. The Name is a new property introduced to game objects, which can be optionally set, by the developer. By default it’s an empty string.

public IEnumerable<GameObject> GetObjectsByName(string name)
{
    foreach(var obj in entities)
    {
        if (obj.Name == name)
            yield return obj;
    }
}

Disabling components on the fly

If we would like to, let’s say disable a single component so it does not get updated (or does not render if it’s a renderer) I have also added a simple property with a public access in order to do so.
It is then checked during Update/Render call and if the component is not enabled, no action is committed.

Fancy renderers

I have also introduced two new renderers - BoxRenderer and PathRenderer, although their logic is probably going to change because of several different reasons.

BoxRenderer

Let’s start with the BoxRenderer that I used for mouse selection handling.

public class BoxRenderer : Renderer
{
    public DrawBoxOptions Options { get; set; }

    public Vector2D Position { get; set; }

    public Vector2D Size { get; set; }

    public TermColor Foreground { get; set; }

    public TermColor Background { get; set; }

    public override void Render(Malison.Core.ITerminal terminal)
    {
        if (Size.Length == 0)
            return;

        if(Units == Components.Units.World)
        {
            //TODO - Malison library at this moment doesn't support
            //rendering a box that doesn't fit terminal bounds
        }
        else
        {
            terminal[Foreground, Background][Position, Size].DrawBox(Options);
        }
    }

    protected override void OnStart()
    {
        Foreground = TermColor.White;
        Background = TermColor.Black;
    }

    public override void Update() { }
}

The BoxRenderer class is very simple and uses API of the Malison library.

Unfortunately, whenever we would want to use the worldspace coordinates to draw the rectangle, Malison wouldn’t render it properly on the terminal if the coordinates would be outside of it’s bounds, so for now, the renderer does not support it.

I will need to change the Malison library in order to allow such rendering, so, as much as it hurts me to show some TODOs in the snippets I want to highlight the issue, which will probably not be that easy to resolve.

The DrawBoxOptions enumeration type here is used in order to allow simple/double line characters for the box rendering.

PathRenderer

The PathRenderer was used by me to create a nice visualisation of generated paths for my pathfinding implementation.

It’s a very straightforward renderer that accepts a collection of coordinates and draws a given character on each of them.

public class PathRenderer : Renderer
{
    public IEnumerable<Vector2D> Nodes { get; set; }

    public Character Character { get; set; }

    public override void Render(Malison.Core.ITerminal terminal)
    {
        foreach (var node in Nodes)
        {
            if (Units == Components.Units.Viewport)
            {
                terminal[node].Write(Character);
            }
            else
            {
                terminal[node - Game.ViewPortOffset].Write(Character);
            }
        }
    }

    protected override void OnStart()
    {
        if (Nodes == null)
            Nodes = new List<Vector2D>();
    }

    public override void Update() { }
}

This renderer might be changed to support edges of lines instead of complete path of all the points and rendering it that way, but for now it was sufficient.

Wrapping up

This pretty much sums up most of the smaller details I have until now not covered in my posts.

As you can see, the engine is slowly getting more features and starting to look better and offer more utilities commit by commit.

I also will need to upgrade the Malison library again. Not only because of the weird behaviour of box rendering outside of terminal bounds, but also to support different type of terminals, and a multi-layer support.

Layering will be sooner or later a must-have, due to more complex rendering, or separating the GUI layer from the game layer.
It will also provide some kind of Z-index support to allow rendering order manipulation.

Meanwhile, I have been playing with threads to use more CPU cores for the game loop, but for now I have not yet fully figured it. So progress in this matter is rather stale.

Other than that I have been also playing around with designing some GUI elements and soon I’ll probably drop a new creative highlight.

That’s it for today’s post. See you next time!