🔍
Using an external text editor in GameMaker Studio 2

(Despite the post image, this will work for any external editor, not just Vim.)

Although GameMaker Studio 2 is a massive improvement over GameMaker: Studio 1.x in every just about single way, including the capabilities of its built-in code editor, programmers who are strongly attached to a particular external editor, be it vim, emacs, VS Code or another, may lament the removal of the feature that let you set an external editor for code. But, thanks to GMS2’s new project format, there is a workaround that will let you keep using your favourite editor.

Back when the software was just called Game Maker, projects were stored as single opaque files containing all game logic and resources. With GameMaker Studio 1.x, projects got separated out into directories with subdirectories containing (relatively) human-readable XML files describing rooms and objects and everything else. As I wrote a few years ago, this allowed projects to be managed with traditional source control tools such as git.

GameMaker Studio 2 has taken the human readability of its project files even further – at least if you choose to work with a GML project rather than a Drag ‘n’ Drop project. Now, instead of each object being an XML file as in GMS 1.x, each object is subdirectory of objects/ containing a few text files: obj_name.yy, a JSON structure with general object metadata; and a file for each event, named something like Create_0.gml or Step_0.gml. These .gml files contain only the code for that event with no preamble or other weird boilerplate. Scripts are simpler still: each script has a name.yy file and a name.gml file, the latter of which contains the script code.

You can probably see where I’m going with this. All you have to do to use your favourite editor with GMS2 is open its .gml files in that editor and edit them (remember to save!). To prevent GMS from getting out of sync with the edits you’re making externally, you just have to check Automatically reload changed files in the General Preferences dialogue. This setting makes file checking quite regular, so you can actually watch GMS reload your objects’ event code right before your eyes.

My workflow has a couple of added complications. Because I use Vim as my external editor, and because I run a GPU passthrough setup, I put my GM project folders on a shared drive (my Ubuntu host sees this as a regular internal NTFS disk, and my Windows VM sees it as a VFIO network drive) and have GMS2 open in my Windows VM while I edit code files using Vim on my host. I have two monitors so I can see both at the same time. A simpler, one-PC version of this setup could be achieved by just using a text editor installed on the same operating system (maybe even Vim through Windows Subsystem for Linux).

Obviously this workaround has its downsides and limitations – your text editor will be even less integrated than external editors were in older versions of GM. You’ll have to keep the editor in a separate window and make it responsible for handling file opens and closes. You’ll need to source a GML code highlighting plugin or syntax file. And of course, you won’t have GMS2 IntelliSense. But these are probably things you’ve decided you can live without if you’re committed enough to your favourite editor to have read this far down.

You’ll also need to create events and scripts in GMS2 before opening them in your external editor, but based on a some experimenting with the .yy files, I think this could be surmounted with a text editor plugin. To create a new script, you’d just need to create a subdirectory in scripts/ with a .gml file containing the code and a .yy file with the following contents:

{
    "id": "<GENERATED GUID>",
    "modelName": "GMScript",
    "mvc": "1.0",
    "name": "<SCRIPT NAME>",
    "IsCompatibility": false,
    "IsDnD": false
}

To generate a new object you need a .yy file in objects/objectname/ that looks something like this:

{
    "id": "<RANDOM GUID>",
    "modelName": "GMObject",
    "mvc": "1.0",
    "name": "<OBJECT NAME>",
    "eventList": [],
    "maskSpriteId": "00000000-0000-0000-0000-000000000000",
    "overriddenProperties": null,
    "parentObjectId": "00000000-0000-0000-0000-000000000000",
    "persistent": false,
    "physicsAngularDamping": 0.1,
    "physicsDensity": 0.5,
    "physicsFriction": 0.2,
    "physicsGroup": 0,
    "physicsKinematic": false,
    "physicsLinearDamping": 0.1,
    "physicsObject": false,
    "physicsRestitution": 0.1,
    "physicsSensor": false,
    "physicsShape": 1,
    "physicsShapePoints": null,
    "physicsStartAwake": true,
    "properties": null,
    "solid": false,
    "spriteId": "00000000-0000-0000-0000-000000000000",
    "visible": true
}

And to add an event with code to this object you’d need to append one of these to its eventList:

{
    "id": "<RANDOM GUID>",
    "modelName": "GMEvent",
    "mvc": "1.0",
    "IsDnD": false,
    "collisionObjectId": "00000000-0000-0000-0000-000000000000",
    "enumb": 0,
    "eventtype": 0,
    "m_owner": "<OBJECT GUID>"
},

eventtype is the overall type of event (Create, Destroy, Step, Key Pressed, etc) and enumb is the subtype (Begin Step, Up Arrow Pressed, etc) and the rest should be self-explanatory or things you can leave as-is.

I might just write this Vim plugin myself.