Game Loops & Events: Best Practices

I’ve been working on re-implementing a simple Java game in OpenFL. Because I’ve been basing my Haxe/OpenFL code on (very old) Java sources, I’m concerned that I’m not grasping the best practices regarding OpenFL’s event-driven architecture.

At this point, many of my game objects follow this pattern:

class Game extends Sprite {
    var entity:Tile;
    var entities:Tilemap;

    public function new() {
        init();
    }

    function init() {
        entities = new Tilemap(...);
        addChild(entities);

        entity = new Tile(...);
        entities.addChild(image); // Do this many times
    }

    function update() {
        // Game logic at a fixed interval
    }

    // A few examples of common code in these functions
    function render() {
        // The alpha technique
        if (state1) {
            entity.alpha = 0;
        } else {
            entity.alpha = 1;
            entity.x = xMouse;
            entity.y = yMouse;
        }

        // The add/remove technique
        if (state2 && !state3) {
            entities.removeChild(anotherEntity);
        } else {
            entities.addChild(anotherEntity);
        }

        // I want my tiles to render in a certain overlapping order that changes
        // every frame
        entities.sortTiles(comparisonFunction);
        // But I always want my cursor tile in the front
        entities.addChild(cursorEntity);
    }
}

In the main class of the program, the game loop is handled as follows:

class Main {
    public function new() {
        addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }

    // update() and render() trickle down all game objects
    function onEnterFrame(event) {
        var elapsedTime = Timer.stamp();

        if (lastTime < 0) {
            lastTime = elapsedTime;
            update();
        } else if (elapsedTime - lastTime > SECONDS_PER_TICK) {
            var count = 0;

            while (elapsedTime - lastTime > SECONDS_PER_TICK) {
                if (count > MAX_TICKS_PER_FRAME) {
                    lastTime = elapsedTime;
                    break;
                }

                update();
                lastTime += SECONDS_PER_TICK;
                ++count;
            }
        }

        render((elapsedTime - lastTime) / SECONDS_PER_TICK);
    }
}

As a result of this architecture, I find myself (a) passing Tilemaps and other “rendering management” objects all over the place, and (b) fooling around with render() functions that are either trivially simple or (seemingly) heavy-handed (removing a child every frame, rather than just getting rid of it once). Interestingly, this architecture was actually recommended in a thread in this forum community a few years ago.

I found this thread on another forum with someone asking similar architectural questions, and the answers seem very useful conceptually. However, the pseudocode is written with C-style languages (and events) in mind, which obviously differs greatly from a framework like OpenFL with pre-existing event systems in place.

I’ve subsequently searched for tutorials or documented examples of similar game architectures in OpenFL, but to no avail so far. Could someone more knowledgeable than myself point me in the right direction for further learning/research, or be so kind as to present their own understanding of game loop best practices in Haxe/OpenFL?

I feel that questions like this are asked far too often by unfamiliar developers like myself, so I apologize for any noise I am adding to the forum.

1 Like

There are a lot of game engines for Haxe. You may wish to inspect some of them to see what they do. I have bookmarked some entity/component systems in there.


Popular ones are HaxeFlixel, HaxePunk, Awe6, Heaps, Kha.
2 Likes

Hello!

You should be aware by now that OpenFL runs on a framework called Lime. This is where most of the platform-specific code is handled.

On HTML5, Lime listens to requestAnimationFrame where the browser tells our application when it is a good time to render. On native C++, we have a main loop written (not unlike your own) where we will sleep, process events or request a render based upon the application frame rate. All of this calls either an update() or a render() event on the lime.app.Application class.

OpenFL handles all of the rendering automatically. It is a passive renderer. Creating a Tilemap (or any other visible object) will result in it being drawn for you.

To update game logic, there is no direct update() method (like Application) however there is Event.ENTER_FRAME dispatched from the OpenFL Stage and all objects that have been added as children. You can use this to know that we’ve reached a new logical frame, so you can update your objects.

If possible I recommend using a mixture of ENTER_FRAME as well as a timer in order to handle a delta time with each frame. This will help if your application runs faster than expected (requestAnimationFrame might run 144 FPS or more on a fast monitor) or slower if frames are dropped. There is an example in the “CreatingAMainLoop” sample

openfl create CreatingAMainLoop
cd CreatingAMainLoop
openfl test html5

You can see the example code here:

openfl-samples/Main.hx at master · openfl/openfl-samples (github.com)

3 Likes

Thank you both for the information!

@Confidant I’ll definitely be inspecting those engines more closely in the coming weeks. I actually started trying to work in HaxeFlixel on a similar project a year or two ago, but I found that a lot of the “lower-level” stuff that I wanted to do (or was familiar with) was abstracted away to the point that I got myself confused. Ironically, now that I’m working with OpenFL I’m having an easier time learning. Hopefully inspecting these other engines now will be more beneficial.

@singmajesty Thank you for the detailed response! The “passive render” concept is a key point that I think I’m still wrapping my head around. It makes sense on paper of course, but trying to keep it in mind when building projects is still difficult for me. The “CreatingAMainLoop” sample clearly illustrates how much I’ve been overthinking a lot of the work!

1 Like

So looking at the sample code, I see that openfl.Lib.getTimer() is used. This function returns a number of milliseconds (integer) since a canonical start time (depending on the flash runtime), if I am reading the documentation correctly.

Other examples that I’ve seen use haxe.Timer.stamp(), which returns a number of seconds (floating point) since starting. Currently I’m using this method in my code.

To be honest, I would actually prefer the integer return value of the OpenFL API solution, but that’s simply a personal preference. Are there any objective trade-offs to be aware of when choosing between these two timers (or other alternatives that I’ve neglected to mention)?

Lib.getTimer() should be as fast (or faster) than Timer.stamp().

I agree that comparing integers for standard game code is my preference, making it simpler to create deltaTime values you can feed into game logic functions. If an application goes to sleep and comes back 10,000 milliseconds later it’s ideal to skip all the missed frames rather than grinding them out in a loop :joy:

As a total aside: In fact most cases I have seen where developers say they must have 60 FPS are cases where the game/application does not handle dropped frames so there’s notable “jerk” when the frame rate fluctuates. That IMHO is usually the real problem :sweat_smile:

Great, thank you for the confirmation there!

I’m actually quite enjoying the sometimes tedious process of figuring out where “jerk” is coming from when developing my little hobby project. I suppose it’s because I don’t yet have the experience of a developer who has wrestled with game loops hundreds of times and is sick and tired of dealing with the same old hanging/fluctuation issues!

Learning something the first time is a pleasure, but after the third or fourth time it probably gets old :laughing:

Have you tried enabling <window vsync="true" />?

That will probably end up helping! For now I’ve been able to iron out all the kinks and am working on polishing sound/art for publishing in #showcase for feedback. At that point I’ll probably go back to experimenting with vsync and other strategies that folks might recommend. I’m sure that once some more experienced eyes take a look at the code, they’ll have quite a bit to advise regarding performance and game-loop best practices.