1
\$\begingroup\$

I've been working on my own game engine for some time, I ideally need a way to create global function implementations to make it easy to do things within the game loop.

Heres an example:

void Start()
{

}

This one is pulled from Unity, as you can see there is no need to override any methods.

The current method I have is just to implement a base class, say Loop and then have all classes that want to implement loop override the functions, like Render() and Init(), the problem with this is that you instantiate an Instance of the class when you implement it, meaning there may be multiple game loops running simultaneously and out of sync. Also since it's a class instance you have to start it somehow:

internal static class Loop
{
    public static void Start()
    {
        Init();

        Load();

        while (!Glfw.WindowShouldClose(WindowManager.Window))
        {
            Update();

            Render();
        }
    }

    public static void Init();
    public static void Load();
    public static void Update();
    public static void Render();
}

(By the way that code doesn't actually work)

See the normal game loop is in a seperate class that gets started when the application opens as you can see below:

internal abstract class Game
{
    protected int InitialWindowWidth { get; set; }
    protected int InitialWindowHeight { get; set; }
    protected string InitialWindowTitle { get; set; }

    public Game(int initialWindowWidth, int initialWindowHeight, string initialWindowTitle)
    {
        InitialWindowWidth = initialWindowWidth;
        InitialWindowHeight = initialWindowHeight;
        InitialWindowTitle = initialWindowTitle;
    }

    public void Run()
    {
        if (InitialWindowTitle == string.Empty)
        {
            InitialWindowTitle = $"{Application.AppName} {Application.AppVersion}";
        }

        Init();

        WindowManager.CreateWindow(InitialWindowWidth, InitialWindowHeight, InitialWindowTitle);

        Load();

        while (!Glfw.WindowShouldClose(WindowManager.Window))
        {
            Time.DeltaTime = (float)Glfw.Time - Time.ElapsedTime;
            Time.ElapsedTime = (float)Glfw.Time;
            Update();
            Glfw.PollEvents();
            Render();
        }

        WindowManager.CloseWindow();
    }

    protected abstract void Init();
    protected abstract void Load();
    protected abstract void Update();
    protected abstract void Render();
}

However this method also requires overrides, and also requires you to initialize or start it through 'public void run'.

So how can I make something similar to Unity's. I'm pretty sure Unity does it through external dlls or c++, but that's okay, I can also work slightly with c++ and creating dlls.

\$\endgroup\$
1

2 Answers 2

4
\$\begingroup\$

The key idea is that each class with game loop event/message methods does not implement its own game loop.

Instead, the engine core implements one central game loop. Then each component that needs to be updated goes through a registration process to add it to the collection of objects that get updated in that central game loop.

In Unity, this happens under the hood when you call AddComponent(), Instantiate(), or GameObject.CreatePrimitive() — and it's part of why component behaviours don't work correctly when you try to create them directly with a new constructor — they miss out on this registration step.

Here's a sketch of how we could implement this in a single pure C# assembly:

(This is far from the most optimal implementation — just keeping things simple to show the core concept of registering collections of objects that need to get certain method calls at certain times)

public class MyComponent : Component, IUpdatable, IRenderable {
    public void Update() {
        // TODO: Updating code here.
    }

    public void Render() {
        // TODO: Rendering code here.
    }
}

public interface IRenderable {
    void Render();
}

public interface IUpdatable() {
    void Update();
}

public partial class GameLoop {

    List<IUpdatable> _toUpdate = new();
    List<IRenderable> _toRender = new();

    public void Register(Component component) {
        var updatable = component as IUpdatable;
        if (updatable != null} _toUpdate.Add(updatable);

        var renderable = component as IRenderable;
        if (renderable != null) _toRender.Add(renderable);
    }

    public void Unregister(Component component) {
        _toUpdate.Remove(component);
        _toRender.Remove(component);
    }

    public void LoopUntilQuit() {
        while (!ShouldQuit()) {
            UpdateTimeStep();

            foreach (var updatable in _toUpdate)
               updatable.Update();

            foreach (var renderable in _toRender)
               renderable.Render();

            FinishFrameAndWaitForNext();
        }
    }
}

Unity doesn't work quite like this, since the core engine is written in C++, and it loads the managed assembly. In that process, it can inspect your types to figure out which ones have Start() / Update() / etc. methods without explicit interface markup. It can do this just once, at start-up, and cache function pointers to those methods so it can call them directly without going through as much indirection/type-checking as the naïve sketch above. And it can sneak past pesky public/private access control, which is why in Unity you don't have to declare these methods as public.

This past Q&A goes into some more detail about the kind of under-the-hood data you can get when inspecting a loaded managed assembly and how you can use it for this purpose.

\$\endgroup\$
2
  • \$\begingroup\$ Very interesting, I will have to look into that, I figured initially I would have to store a list but I figured that it may be better to try and do it with c++ as it may be faster. If I added a constructor to my component class, just an empty one, and add the Register function into the constructor, wouldnt that mean you could use the new keyword to Instantiate it? Since every time its instantiated the constructor would automatically register it. \$\endgroup\$
    – Pow
    Mar 6 at 1:01
  • 3
    \$\begingroup\$ Potentially, but I wouldn't recommend it. It's not unusual that you may want to be able to prep a set of objects before adding them to the game loop (e.g. for loading a level chunk on a background thread without impacting framerate on the main thread). So you'll at minimum want another way to call the constructor without automatic registration, and a separate manual register/unregister step you can do when you're ready to actually start ticking/drawing that content, or want to remove it without immediately destroying it. \$\endgroup\$
    – DMGregory
    Mar 6 at 1:04
1
\$\begingroup\$

You can create event handlers, that components can subscribe to; and that are called when that event occurs. This pattern is most suitable when you have multiple "subscribers" that "attach" (and maybe detach).

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-publish-events-that-conform-to-net-framework-guidelines

Or you can use a Delegate or Action, which is simpler to use for a single callback.

https://learn.microsoft.com/en-US/dotnet/csharp/programming-guide/delegates/using-delegates

https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=net-8.0

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .