Alpha A text-dump rationale for Aether Edit

Discussion in 'Work in Progress' started by SupernaturalCow, Aug 6, 2019.

  1. SupernaturalCow

    SupernaturalCow Member

    Joined:
    Sep 11, 2016
    Posts:
    95
    Hi All,

    Up until now, I have explicitly remained in the Programming section of these forums, and usually pose niche questions to the community that is often met with, "why would you do that?". I thought it might be about time I etched out a little space on here for my latest GM project fascination, and explained exactly what I'm using all of these peculiar forum posts for.

    TL;DR: this, but ~100x better.


    Unveiling: a ridiculously ambitious Game Maker built in GameMaker. What started off as a way of helping our team's artists get in on the level creation process despite not knowing anything about code, or even having GM installed, eventuated into an amalgamation of a few different tools we'd designed/repurposed for helping us flesh out our mystery/puzzle game, Liminality. Our custom map editor, dialogue editor, menu D&D tool, character creation tool, and dynamic music cross-fader all came together under one roof to allow our team to build levels together, despite being thousands of miles apart. This process absolutely bolstered our ability to share ideas with each other and create stuff together, but came at the cost of being really expensive to run, and very memory intense. I understand that GM has a native 1.7gb cap on RAM, and we were managing to reach this roof routinely, as we tried to import custom assets in from our filesystems. The latest addition to this ambitious stack was a shader I wrote about a year ago, that compares 3 terrain masks for the side, top, and front view, in order to create volumetric shadows in real-time. We used this is in development to tailor our level design to the specifics of how shadows would creep around walls and such, and again, found it invaluable to the overall process.

    [​IMG]
    [​IMG]
    One of the earlier problems we ran into with the level editor, was not being able to add in detail behind something.
    [​IMG]
    So I created a way of rotating the viewpoint around the player (while still all 2D). In the earlier days however, this created quite a few depth-sorting headaches.
    [​IMG]
    This is a very early prototype of the system in action. It's come a pretty long way since then, but you can see how the shadows dynamically flick around corners, even in the beginning.
    An old blog post about the dialogue editor we were using: https://survansix.blogspot.com/2018/03/devlog-3-feburary.html.

    This gets us to about 7 months ago, when I embarked on the mission to bring all of these tools together, and create us something that was designed for this express use. The factors we needed to account for were well beyond just space constraints. Performance was an issue, edited map version-control was an issue, not understanding how the GM functions were working was becoming a real problem, and most importantly: nothing worked in unison. Everything was written slightly differently, and used slightly different systems. It needed a rewrite.

    What followed is a pretty lean project. It has 1 room and 1 object. That object has a map called root, that contains all the vars needed for the game within, stored in a structure familiar to a linux user (e.g. there's user specific vars for different instances, there's binary stuff for loading level data, and there's system files for general setup and maintenance). The only object vars that exist hold references to the significant locations in this hierarchy, and are designed in such a way that I can rename vars without having to go through tonnes of code changing the references. The object's code runs on a hierarchical finite state system (HFSM), and steps through the necessary setup actions before entering what is effectively just a big limbo space for the game's main step state.

    [​IMG]
    Sorry what did you say? "Camel case and underscore case"? I don't want to talk about it.
    [​IMG]

    The rest of the game's functionality comes from instances. These are not GM instances mind you, but rather have been distilled down to the smallest form I could find: the u8 fast buffer. They initialise what is effectively a hash-table of values, referenced with an indice lookup list from their 'class', which is a map of custom specifications that inherits from parent classes (as you would expect). Each class holds a script for all their related step code, which is run in the step part of the HFSM. This code ranges in utility from updating sprite information, to accepting user input, to interacting with other instances and creating helper instances (like hitboxes).

    The step code for each instance runs in order of creation, and is organised so that every variable is read once and operated on in order from beginning to end. An instance 'variable' is a value from 0-255 stored in a buffer, and the way the game knows what the value does, is by reference to its class's stored list of variable indices. Anything that needs to be a float (image index, normalized values, etc.), gets a reference to a map of temp vars, the values of which are reset between state transitions. Anything larger than 255 gets a second slot in the buffer. Anything in the negatives gets either absoluted, or a temp var. The philosophy is, 'everything gets a tiny allocation of memory, and exceptions are handled' - this is as opposed to GM's 'everything gets a huge allocation of memory'.

    Code:
    enum VAR {
        // All
        CLASS_ID,            // All instances have a class ID, which points them to their class data.
                          
        // Dimensions
        X_DIV, X_MOD,        // Both bytes for X.
        Y_DIV, Y_MOD,        // Both bytes for Y.
        Z,                    // Single byte value for Z.
        WIDTH,                // Width of the instance.
        HEIGHT,                // Height of the instance.
        DEPTH,                // Depth of the instance.
                          
        // Sprite
        SPRITE_INDEX,        // Sprite to display.
        SPRITE_IMAGE,        // Sub-image of the sprite to display.
        SPRITE_SCALE,         // Scale at which to draw the sprite.
        SPRITE_ANGLE,         // Angle of the sprite (0==east).
     
        // Colour
        C_RED,
        C_GREEN,
        C_BLUE,                // No alpha coz we hijack the channel for shader stuff.
     
        // Bool flags (!to-do: convert these into bitmask).
        HAS_BIND_ID,
        IN_WORLD,
        HAS_STRING,
        CONTENT_EDITABLE,
     
        // GUI stuff
        FONT_SIZE,
        STATE,
        ACTION_OFF,
        ACTION_HOV,
        ACTION_ON,
        ACTION_TOG,
     
        // Resources
        HEALTH,
        STAMINA,
        AETHERIUM,
                          
        // Player details
        //ACTIVE_PC,        // !to-do: create a player instance for each playable character.
                          
        ... etc.
    }
    When explicitly enumerated in this way, I was surprised to find there really weren't that many unique types of variables for a game! Even one as ambitious as this.
    Code:
    #region All
    classInitWith(CLASS.ALL) {  // Reminder: 'all' has no parent!
        classInitSetName("All");
        classInitSetState(class_state_all);
        classInitSetVars(
            VAR.CLASS_ID,        CLASS.ALL,
            // 3D dimensions
            VAR.X_DIV,            0,
            VAR.X_MOD,            0,
            VAR.Y_DIV,            0,
            VAR.Y_MOD,            0,
            VAR.Z,                0,
            VAR.WIDTH,            32,
            VAR.HEIGHT,            16,
            VAR.DEPTH,            8,
        );
    }
                      
                      
    #endregion
    #region Invisible
    classInitWith(CLASS.INVISIBLE) {
        classInitSetName("Invisible");
        classInitSetParent(CLASS.ALL);
        classInitSetState(class_state_invisible);
        classInitSetVars(
            VAR.CLASS_ID,        CLASS.INVISIBLE,
        );
    }
                      
                      
    #endregion
    #region Visible
    classInitWith(CLASS.VISIBLE) {
        classInitSetName("Visible");
        classInitSetParent(CLASS.ALL);
        classInitSetState(class_state_visible);
        classInitSetVars(
            VAR.CLASS_ID,        CLASS.VISIBLE,
            // Sprite
            VAR.SPRITE_INDEX,    spr_intro_van,
            VAR.IMAGE_INDEX,    0,
            // Colour
            VAR.C_RED,            255,
            VAR.C_GREEN,        255,
            VAR.C_BLUE,            255,
            // Orientation
            VAR.SCALE,            1,
            VAR.ANGLE,            0,
        );
    }
                      
    #endregion
    ...Etc. There's obviously a lot more classes than that.

    All the tools work with instances, and each instance falls into a broad category of classes that range between 'actors' (playable, friendly, hostile), 'props' (button, slider, textbox, etc.), and 'backdrops' (individual terrain cells that cover the screen, enabling dynamic depth sorting). Every tool now works (mostly, we're about 2/3 of the way there) in unison with each other, and all resources are drawn from the same pool (the 'root' directory). The biggest downside I am facing is a performance cost (I didn't realise that accessing buffers was so slow), but everything else is looking much much lighter and more manageable. I think one of the biggest benefits to this system is that everything is organised together, and easy to access. No longer am I tracing object references through dozens of instances, or trying to figure out where to place a piece of code that is similar, but not quite the same as other sections, and ending up with a mish-mash of code blocks without any real organisation. With this system, everything has a dedicated state with a descriptive name, or belongs to a dedicated 'instance' with a descriptive 'class', and is all stored linearly from beginning to end. No more loopy code :)

    I usually field a few questions after explaining this stuff, so the following is a short list of FAQs:
    • Why GM? The GMC is by far and away my favourite support network for game development. From Discord channels to Twitter tags, to the forum here, you GMers treat game dev like a hobby and a passion, which is something I have come to miss in the other related communities.
    • Okay, but why not Unity? Look, the other thing is that this stuff isn't my job. I use Unity for work, and when I come home and dev, I like the unique challenges associated with Game Maker. Sure, it's sometimes a bit guess-and-check, and people don't usually pursue GM-related issue with the same career-minded fervor that Unity-users do, but at the end of the day, GM is always going to be that engine that just gets me. I'm a weirdo, I know.
    • Alright, so if you love GM so much, why go to all the trouble of abandoning everything it offers you? There's something kind of great about having so much control over how the engine works. I handle all my own surfaces, so the views work exactly how I expect them to, because I wrote them to do that. I also handle my own class inheritance, so I can do branching inheritance and eliminate the need for superfluous objects purely because there's, for example, a dynamic type of crate (one you push), and a static type of crate (one that doesn't budge). Did you know GM instances come with about 70 built-in vars? How many of those do you think you actually use? However, perhaps the biggest advantage to all of this, is the organisation. Everything gets its place in the HFSM, and everything fits into a linear relationship with each other. Code (usually) runs top to bottom, so why shouldn't my code be positioned as such? The pseudo-OOP approach that GM adopted is usually great for starting off, but very quickly becomes a dog's breakfast once you get any real traction on a project. This structure eliminates that possibility, and I absolutely love that.

    We're planning on releasing an Aether Edit 2.0 in October. You can find a download link of AE 1.0 here, which contains nearly nothing of what I've described above :p
     
    Last edited: Aug 6, 2019
    trg601, GMWolf and Simon Gust like this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice