Skip to content

Code Generation

ScaffScript uses two constructs to define what code gets written and where:

  • #[blockName], named content blocks.
  • intg, keyword to map blocks to GM asset paths.

A block is a named section of content written to a specific GM asset. It’s reusable and can be integrated to multiple assets.

index.ss
#[blockName]
// GML / ScaffScript code here...
index.ss
#[main]
show_debug_message("Hello, World!");
#[other_block]
var xx = 10;
var yy = 20;
#[setYourOwnBlockName]
function my_function() {
show_debug_message("Hello, from my_function in setYourOwnBlockName!");
}

Blocks extend until the next #[...] header or end of file.

index.ss - main
show_debug_message("Hello, World!");

Later, you can map these blocks to a specific GM asset using the intg statement.


You can set an object event for an integration block. This is useful when you want to write code for a specific event of an object.

#[blockName as <EventType>]
// code here...
#[blockName as <EventType>[:EventSubtype]]
// code here...

Use as keyword to target an event.

index.ss
#[objCreate as create]
show_debug_message("create event!");
#[press_enter as keypress:keyboard_enter]
show_debug_message("F5 pressed!");
#[create event] // block name = `create`
show_debug_message("create event!");
#[keydown:keyboard_f5 Ev] // block name = `keydown:keyboard_f5`
show_debug_message("F5 held!");
#[coll_player as collision:objPlayer] // `objPlayer` is an existing object in the GameMaker IDE
show_debug_message("collided with objPlayer!");

Configure block behavior with flags after -- in the block header name or event. Multiple flags are space-separated.

#[blockName -- flag1 flag2 ...]
#[blockName as EventType -- flag1 flag2 ...]
#[blockName as EventType:EventSubtype -- flag1 flag2 ...]
#[EventType Event -- flag1 flag2 ...]
#[EventType:EventSubtype Ev -- flag1 flag2 ...]
FlagEffect
debugEmptied when debugLevel >= 1
dev | developmentEmptied when production: true
prod | productionEmptied when production: false
skip | disabledAlways emptied
excludeWritten to .out/, but won’t be integrated to the GM project
<platform>Included only when targetPlatform matches
!<platform>Excluded when targetPlatform matches
index.ss
#[main -- dev]
var x = 10;
show_debug_message("Only in development!");
#[main_android as create -- android exclude]
show_debug_message("Android only, and excluded from GM project integration!");
#[keypress:keyboard_enter Event -- !html5]
// some code here that won't work in HTML5

Multiple blocks with the same name are merged, their bodies concatenated with a blank line separator. The order of merged blocks is preserved.

index.ss
#[main]
show_debug_message("From first main block!");
#[main]
show_debug_message("This is from another 'main' block!");
index.ss
#[main]
show_debug_message("From first main block!");
show_debug_message("This is from another 'main' block!");

Maps blocks to a GM asset path.

intg myBlock to "./path/to/asset" // single block
intg { block1, block2 } to "./path/to/asset" // multiple blocks
intg * to "./path/to/asset" // all blocks

The path points to the target GM asset relative to the project root. Omit the .gml extension, ScaffScript appends it for you.

index.ss
intg { definition, main } to "./scripts/myScript"
intg { objCreate, keydown:keyboard_f1, definition } to "./objects/objController"
intg * to "./scripts/My Folder/myAllScript"
#[main]
show_debug_message("Hello, from main block!");
function print() {
show_debug_message("Hello, from print function!");
}
#[definition]
var my_var = 10;
var vec2 = function(x, y) {
return { x, y };
}
#[objCreate as create]
show_debug_message("Hello, from objController create event!");
var my_obj = {
x: 0,
y: 0
};
#[keydown:keyboard_f1 Event]
show_debug_message("Showing game instructions...");
function show_instructions() {
show_debug_message("Game instructions shown!");
}
show_instructions();
scripts/myScript/myScript.gml
var my_var = 10;
var vec2 = function(x, y) {
return { x, y };
}
show_debug_message("Hello, from main block!");
function print() {
show_debug_message("Hello, from print function!");
}
  1. The used asset is the last segment of the path. Based on the example above, myScript and objController are the GameMaker assets. They will be created automatically by ScaffScript if they don’t exist yet, but see the note for the asset creation.

  2. The intg statement with * is a catch-all. It integrates all blocks to the specified asset, either non-event blocks or blocks with events. Use it wisely.

  3. The integration blocks and integration statement is scoped to the file it’s defined in. You can’t integrate a block from another file.

  4. The order of the blocks is preserved based on the order of the intg statement. In the example above, the intg { definition, main } to "./scripts/myScript" statement, the main block is integrated after the definition block.

  5. If you integrate a block with an event, you should integrate it to the object asset itself, not to an event file. ScaffScript will handle the event integration for you.

    index.ss
    intg { myCreateEvent } to "./objects/My Object/objController/create"
    intg { myCreateEvent } to "./objects/My Object/objController/Create_0"

If you integrate an asset with multiple intg statements, the used block content is the last intg statement. For example:

index.ss
intg { main } to "./scripts/myScript"
intg * to "./scripts/myScript"
#[main]
show_debug_message("Hello, from main block!");
#[definition]
var my_var = 10;

The myScript asset contains all blocks, because the last intg statement is intg * to "./scripts/myScript".

scripts/myScript/myScript.gml
show_debug_message("Hello, from main block!");
var my_var = 10;

If you encounter this scenario, that’s mean your code is not organized well.


All files are staged in .out/ first. The purpose of this directory is to serve as a preview of what will be integrated to the GameMaker project.

Set clearOutputDir: true to wipe it before each run.

Set noIntegration: true to generate to .out/ only, without touching the GameMaker project.

index.ss
intg * to "./scripts/My-Folder/myScript"
intg { definition } to "./objects/objController"
#[main]
show_debug_message("Hello, from main block!");
#[definition as create]
var my_var = 10;
var vec2 = function(x, y) {
return { x, y };
}
  • Directory.out/
    • Directoryscripts/
      • DirectoryMy-Folder/
        • myScript.gml
    • Directoryobjects/
      • DirectoryobjController/
        • Create_0.gml

When the path has more than 2 segments, the segment(s) between the root and the asset name become folders/groups in the GameMaker IDE. We call this a virtual path, because they’re not a physical folders in the file system.

index.ss
intg { myBlock } to "./scripts/Scripts/scrHello"
intg { myBlock } to "./scripts/Scripts/My-Folder/scrOther"
intg { createEv } to "./objects/Objects/Players/objPlayer1"
intg { createEv, destroyEv } to "./objects/Objects/Enemies/objEnemy1"
intg { createEv } to "./objects/Singleton/objController"
  • Directory.out/
    • Directoryobjects/
      • DirectoryObjects/
        • DirectoryPlayers/
          • DirectoryobjPlayer1/
            • Create_0.gml
        • DirectoryEnemies/
          • DirectoryobjEnemy1/
            • Create_0.gml
            • Destroy_0.gml
      • DirectorySingleton/
        • DirectoryobjController/
          • Create_0.gml
    • Directoryscripts/
      • DirectoryScripts/
        • DirectoryMy-Folder/
          • DirectoryscrOther/
            • scrOther.gml
        • DirectoryscrHello/
          • scrHello.gml

ScaffScript generates the output based on the intg statement, including the virtual path.


To understand the relationship between intg and #[...] better, let’s make an analogy with bottles, various types of water, and shelves.

  1. The intg { ... } are the bottles. They acts as a container for the water (code). The bottle name is the last segment of the path. For example, ./scripts/myScript, so myScript is the bottle name.
  2. The integration blocks (#[...]) are the types of water. They define what kind of water (code) will be poured into the bottle. The bottle labels are the event defined in the integration block. For example, as create is the bottle label.
  3. The non-last segment(s) of the path (./scripts/, ./objects/, ./scripts/My Folder/) are the shelves. They acts as a container for the bottles.
  4. You can have multiple types of water (integration blocks) in a single bottle (integration statement). For example, you can have a myScript bottle that contains both #[main] water and #[objCreate] water.
  5. You can have multiple bottles in a shelf (virtual path). For example, you can put myScript and myOtherScript bottles in the ./scripts/My Folder/ shelf.
  6. The types of water (integration blocks) isn’t tied to a specific bottle. For example, you can pour #[main] water into myScript and myOtherScript bottles.
  7. The bottle (asset) and shelf (virtual path) is created automatically by ScaffScript if they don’t exist yet.
  8. The shelf (virtual path) is only visible in the GameMaker IDE, not on disk.
  9. The bottle (asset) is a physical file on disk, and it’s the one that’s being edited when you open the asset in the GameMaker IDE.
  10. The integration process to GameMaker IDE is like when you’re distributing your bottles to the shelves in the supermarket.
  11. ./objects/* shelves is special, so it can only accept labelled bottles (integration blocks with event). Unlabelled bottles (integration blocks without event) will only be put in the shelf without being distributed to the supermarket (GameMaker IDE).