Enums, constructors, and teleports - oh my!


It has been a busy post-release for the demo of Nycto! Conversely, there have been very few substantial, tangible changes to the game. That's right - we've been refactoring!!! 🤮

One of the challenges I've always experienced with fixed-time projects like game jams is trying to find the compromise between writing good, efficient code and fast, fast, fast code. When a deadline is creeping up, it's very easy to start being lazy and hacking code together to ship something that doesn't break the second our users look at it the wrong way. To improve code readability, and clear the room of the horrible code smell in the office, I've spent this last week taking my garbage code and polishing it until it ✨sparkles✨. Here's some thoughts on my process so far.


Enums

Sometimes, one might structure their code a certain way because... well, it just makes sense to do it that way at the time. When the clock is ticking, it might seem logical to, say, assign a players "facing" direction to a string: "up", "down", "left", "right", etc. It makes the code readable, right? If I need to check the direction a player is facing, I can just:

if(player_facing == "up") { // Do Up Stuff, Dog (What's Up Stuff Dog?) }

and viola! But I ran into an issue during development by doing something like this - for some reason, I'd decided to start capitalizing the directions. In a lot of places. Went to compile and 💥 errors galore. It was an easy enough fix, but it got me thinking that maybe string-comparisons for specific, known values might not be the best. So I did a little research, and found out GameMaker has enums! For the uninitiated, an enum is instantiated thusly:

enum PLAYER_DIRECTIONS {
    LEFT,
    RIGHT,
    UP,
    DOWN
}
if(player_facing == PLAYER_DIRECTIONS.UP) { // Up Stuff, BUT WITH ENUMS! }

This is where the magic happens. When the project is built, some build-magic happens and anywhere the enums are referenced, they're replaced by Real number values from the enumerated object. So the above is essentially the same as:

enum PLAYER_DIRECTIONS {
    LEFT = 1,
    RIGHT = 2,
    UP = 3,
    DOWN = 4
}
if(player_facing == 3) { // PLAYER_DIRECTIONS.UP == 3 }

I've been using these everywhere. Persistent object UIDS, room teleport object references, key items - and they make everything a breeze! No more "oh shoot, what did I name that one specific game state string - was it begin_dialogue or begin_talking?" Instead of digging through my object code trying to find that one specific string, I just consult my handy dandy DIALOGUE_STATE enum and see it's DIALOGUE_STATE.START_DIALOGUE. And it's even better because the IDE can autofill enum values - so I don't even need to cross-reference the enum itself. I swear, IDEs make everything too easy these days!


Constructors

I'll be honest, I've been programming in GameMaker for the past 15 years, and a lot of the time I use old hacky solutions from ye olde days. I'm glad to report I no longer need to catch myself up on creating several persistent controller objects to place in my rooms, as I have found the holy grail of functional programming techniques is in GameMaker. They snuck it in right under my nose! If you're not aware of structs and constructors in GameMaker, allow me to elucidate you. Let's say you have an inventory system in your game, and you want to track unique items - they'd have an ID, a description, and a durability. One way of creating an item like this might just be to stick the values in an array, like so:

inventory = [[3,"Stone Pick",90],[ //Other items to follow]]

The problem with this solution is that you have no idea, looking at the item object itself, what any of those values mean. Instead, try creating objects via a constructor, like so:

function Item(_id, _name, _durability) constructor {
    id = _id;
    name = _name;
    durability = _durability;
}
inventory = [new Item(3, "Stone Pick", 90)]

There are some fun tricks you can pull by doing things this way. Two things I've been playing with are accessors and the nullish coalescing operator.

function Item(_id, _name, _durability) constructor {
    id = _id;
    name = _name ?? "Unnamed";
    durability = _durability ?? -1
    static get_durability = function() {
        return durability;
    }
    static repair = function(n) {
        durability += n;
    }
}

Having your objects be represented by actual objects, versus abstract data structures that force you to remember more than you need to, is a godsend, and just better programming practice in general. Most importantly, it lets me get rid of code in my controllers, and standardize everything in my game's scripts.

global.key_items = new function Inventory() constructor {
    held_items = [];
    
    static add_item = function(item) {
        // Check if item exists in held_items, and add it if not.
    }
    static remove_item = function(item_id) {
        // Remove an item with a specific ID from the held_items
    }
    static contains = function(item_id) {
        // Return bool true if item with ID item_id exists in held_items
    }
}()
global.key_items.add_item(new Item(6,"Skeleton Key",5)); // Add a skeleton key with 5 uses
                                                         // to the key items inventory

These concepts are, of course, nothing new. But I had no idea they existed and were as versatile in GameMaker as they are now. The IDE support for custom functions and structs is incredible to me - up there with some professional software development IDEs like Visual Studio, imho!


Room Teleports

One of the last big updates to Nycto so far is refactoring how the room movement code works. In the version pushed to the jam, when a player interacts with a door, the door object tells the game controller where to send the player - which room, as well as the specific X/Y coordinates to drop them off at. This was tedious and silly.

The new code relies on what are essentially teleport pads. Using enums, each teleport pad gets a unique ID in its instance variable definition (ROOM_TELEPORT_ID.MANSION_FOYER_TO_HALLWAY_01 for example). The door then tells the game controller to send the player to a specific room, but instead of passing X/Y coordinates, it passes in the ID of the teleport pad to put the player on. Now, when building a room, if the doors need to move due to a design/layout change, I don't need to find the new X/Y to send the player to - I just move the teleport pad and ZOOP that's where the player will spawn in the room.

It's like magic, but way cooler, and made me feel way better about the future design of the game, which ultimately is what this refactoring was all about.


Other Organizational Improvements

I'll admit I got a little burnt from refactoring and updating old code to be more streamlined. So to take a break and decompress, I thought more about the project management side of things.

Our team really likes using Trello for kanban, but it's also a little too featured for what we need, and setting up views for tracking different metrics just took time and, most importantly, didn't feel fun. So I did some poking around and discovered Awesome-Selfhosted - a GitHub repo of different business apps you can run at home, either standalone or (as I have been doing) in Docker containers.

We found three extremely helpful apps that I was able to get up and running virtually instantly (less than 8 hours), and I already feel like they are making a huge difference in how we operate as a dev team. I thought I'd share them for anyone else who is looking for good, free project-management resources.

FileGator - self-hosted web application for managing files and folders. We set up a user account for our playtester, and push nightly builds of Nycto to a special directory for them to download. Access rights are set up so they can only download, no uploads or deletes.

Trudesk - Helpdesk web app. Again, we created user accounts for our playtester, who can submit tickets for any bugs they find. Bugs can be tagged by the nightly build that they are currently playing, and we can link the tickets to Focalboard (below) for more efficient tracking.

And finally, Focalboard - a self-hosted project management tool that's an alternative to Trello. It certainly does not have all the automation and workflow bells and whistles of Trello, but it works much better for our team as a whole. It's simple, it has the features we need, and it feels faster to use than Trello.

Right now we only have a single playtester, so this is overkill and a half for a team of our size, but it's stuff I love doing when I need a break from code. Hopefully it helps someone else out there as well!


Conclusion

I don't think every weekly devlog for Nycto will be quite this, uh, involved, but we appreciate you sticking around and hopefully learning from our plight! If you're interested in seeing more devlogs, including status updates for Nycto and other CoyoteFox Games, be sure to follow our page on itch.io. If you use Tumblr/Twitter, you can follow us there for updates as well (although we will be phasing out updated to Twitter on account of the dumpster fire it has become)! Finally, if you found any information here helpful, or found errors/bad information, feel free to leave a comment and we'll correct as needed!

Thanks for reading!

Cover Image by Pexels from Pixabay


Get Nycto

Leave a comment

Log in with itch.io to leave a comment.