If you would like to program procedurally in the object-oriented language that is Haxe, this can be done by using the macro system. A procedural language is defined "procedural" because it is setup to allow the defining of structures/classes and functions tend to sit outside of these structures or classes.

Everything we do in these tutorials will be procedural-style. There will be no functions that sit inside of classes that serve as our data structures. Like in C, most structures that are created tend to be passed around a lot. A LOT!

To make use of this procedural-style and way of coding, we can take advantage of the class twinspire.macros.StaticBuilder class.

First, let's create a file inside our Sources folder and call it Game.hx. This is where our ENTIRE game's code will be.

You heard (saw?) that correctly. All of our game logic, event and render loops, and data storage will exist inside just ONE class. To make navigating our code easier, we will use comments to create markers we can quickly navigate to, and as our codebase grows larger, we will want to do this.

Why is this better? Honestly, there is no benefit to the compiler in splitting up our code across files or keeping it in one file. This is primarily a matter of preference. The only reason to do this is being able to use Find in the editor to navigate to functions quicker than searching for a file that may or may not contain the code we are looking for. It is a convenience method for searching for code more than anything. Now you know why I code procedurally -- for this reason, among others. But a rant on this can come later.

Setting up the Game Class

Inside our new Game.hx class, we will write the following:

package;

@:build(twinspire.macros.StaticBuilder.build())
class Game
{

}

StaticBuilder contains a function called build which is called against the @:build meta tag. If you are not familiar with meta tags, these are effectively data that can attach itself onto a class, field or function in any part of our code. The build meta tag in particular asks the Haxe compiler to generate code using the provided function we pass in.

The StaticBuilder.build() function takes all the fields and functions we have defined in our class and marks ALL of them static. This means they can be globally accessed from any other class. But we can control this.

Let's define some variables we will need for later.

@:local
var mouseX:Int;
var mouseY:Int;
var mouseDown:Bool;
var mouseReleased:Bool;

Now let's define our functions we need:

@:global
function init()
{

}

function handleEvent(e:Event)
{

}

function render(g2:Graphics)
{

}

So, let's explain what's going on.

The meta tags we have just added, local and global, are picked up by our StaticBuilder which define if any and all fields and functions that fall below it are either public or private. That means that all our variables starting from mouseX to mouseReleased will become private static, meaning they can only be accessed inside the Game class, while all our functions starting from init to render become public static which CAN be accessed outside the Game class.

To make sure this compiles, we need to add some import types above where we defined our Game class.

package;

import twinspire.events.Event;
import twinspire.events.EventType;

import kha.graphics2.Graphics;

@:build(twinspire.macros.StaticBuilder.build())
class Game

This is our full class:

package;

import twinspire.events.Event;
import twinspire.events.EventType;

import kha.graphics2.Graphics;

@:build(twinspire.macros.StaticBuilder.build())
class Game
{

    @:local
    var mouseX:Int;
    var mouseY:Int;
    var mouseDown:Bool;
    var mouseReleased:Bool;

    @:global
    function init()
    {

    }

    function handleEvent(e:Event)
    {

    }

    function render(g2:Graphics)
    {

    }

}

Updating Main

The code above won't do anything until we update our Main class.

package;

import twinspire.Application;

import kha.System;
import kha.Framebuffer;
import kha.Color;

class Main
{
    public static function main()
    {
        Application.create(
        {
            title: "New Project",
            width: 1280,
            height: 720
        }, () ->
        {
            Game.init();

            System.notifyOnFrames(render);
        });
    }

    static function render(buffers:Array<Framebuffer>)
    {
        var app = Application.instance;
        var g2 = buffers[0].g2;

        while (app.pollEvent())
        {
            Game.handleEvent(app.currentEvent);
        }

        g2.begin(true, Color.Black);

        Game.render(g2);

        g2.end();
    }
}

Compiling our code might work but nothing has really changed. In the next tutorial, we will create a back buffer and start drawing stuff onto the screen.

Previous Tutorial -> Introducing Twinspire

Next Tutorial -> Creating a Back Buffer