Skip to main content
Reading time: ~20 minutes
Macros in CORP are powerful tools for transforming instance hierarchies during scene loading. They allow you to programmatically modify, replace, or generate Roblox instances based on tagged markers in your workspace or scene data.

Table of Contents

What are Macros?

Macros are classes that extend Macro and implement a transformInstanceHierarchy() method. When CORP encounters an instance tagged with a macro name, it invokes that macro to transform the instance hierarchy.

Key Features

  • Instance Transformation: Modify or replace instances during loading
  • Procedural Generation: Generate complex hierarchies from simple markers
  • Template Expansion: Convert placeholder instances into full structures
  • Dynamic Configuration: Create instances based on game state or data

How Macros Work

  1. You create instances in Workspace or scenes
  2. Tag them with a macro name (e.g., %spawn_point_macro)
  3. During loading, CORP applies the macro transformation
  4. The macro returns the transformed instance hierarchy

Creating a Macro

Basic Macro Class

import { Macro } from "CORP/shared/macros";

export class SpawnPointMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        // Create a new part for the spawn point
        const spawnPart = new Instance("Part");
        spawnPart.Name = instance.Name;
        spawnPart.Size = new Vector3(4, 1, 4);
        spawnPart.Color = new Color3(0, 1, 0);
        spawnPart.Anchored = true;
        spawnPart.CanCollide = false;
        spawnPart.Transparency = 0.5;
        
        // Copy position from original instance
        if (instance.IsA("BasePart")) {
            spawnPart.CFrame = instance.CFrame;
        }
        
        // Parent to the original instance's parent
        spawnPart.Parent = instance.Parent;
        
        // Destroy the original marker
        instance.Destroy();
        
        return spawnPart;
    }
}

Macro with Configuration

Read attributes from the tagged instance:
import { Macro } from "CORP/shared/macros";

export class TeamSpawnMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        // Read configuration from attributes
        const teamId = instance.GetAttribute("teamId") as number ?? 1;
        const spawnCount = instance.GetAttribute("spawnCount") as number ?? 1;
        
        const container = new Instance("Folder");
        container.Name = `Team${teamId}Spawns`;
        container.Parent = instance.Parent;
        
        for (let i = 0; i < spawnCount; i++) {
            const spawnPoint = new Instance("Part");
            spawnPoint.Name = `Spawn_${i}`;
            spawnPoint.Size = new Vector3(4, 1, 4);
            spawnPoint.Anchored = true;
            
            // Color based on team
            if (teamId === 1) {
                spawnPoint.Color = new Color3(1, 0, 0); // Red
            } else {
                spawnPoint.Color = new Color3(0, 0, 1); // Blue
            }
            
            // Position in a line
            if (instance.IsA("BasePart")) {
                spawnPoint.CFrame = instance.CFrame.mul(new CFrame(i * 10, 0, 0));
            }
            
            spawnPoint.Parent = container;
        }
        
        instance.Destroy();
        
        return container;
    }
}

Complex Macro with Children

Process child instances and generate complex structures:
import { Macro } from "CORP/shared/macros";

export class GridGeneratorMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        const width = instance.GetAttribute("width") as number ?? 10;
        const height = instance.GetAttribute("height") as number ?? 10;
        const tileSize = instance.GetAttribute("tileSize") as number ?? 10;
        
        // Find a template tile (first child)
        const template = instance.GetChildren()[0];
        
        const grid = new Instance("Folder");
        grid.Name = `Grid_${width}x${height}`;
        grid.Parent = instance.Parent;
        
        if (template && template.IsA("BasePart")) {
            const baseCFrame = instance.IsA("BasePart") ? instance.CFrame : new CFrame();
            
            for (let x = 0; x < width; x++) {
                for (let z = 0; z < height; z++) {
                    const tile = template.Clone();
                    tile.Name = `Tile_${x}_${z}`;
                    tile.CFrame = baseCFrame.mul(
                        new CFrame(x * tileSize, 0, z * tileSize)
                    );
                    tile.Parent = grid;
                }
            }
        }
        
        instance.Destroy();
        
        return grid;
    }
}

Using Macros

Tagging Instances in Studio

  1. Create an instance (Part, Model, Folder, etc.)
  2. Add a tag using the Tag Editor plugin
  3. Use the format %macro_name (e.g., %spawn_point_macro)
Example in Workspace:
  SpawnMarker (Part)
    - Tag: %spawn_point_macro
    - Attribute: teamId = 1

Programmatic Application

Macros are automatically applied when loading from workspace:
import { CORP } from "CORP/shared/CORP";

// Macros are applied automatically during bootstrap
CORP.startFromWorkspace();
Or apply macros manually to an instance tree:
import { Macro } from "CORP/shared/macros";

// Apply all registered macros to an instance hierarchy
Macro.applyMacros(workspace.GameScene);

Getting Macro Information

import { Macro } from "CORP/shared/macros";
import { SpawnPointMacro } from "./macros/SpawnPointMacro";

// Get the registered name of a macro class
const macroName = Macro.getMacroName(SpawnPointMacro);
print(macroName); // "spawn_point_macro"

// Get the tag format for a macro
const macroTag = Macro.getMacroTag(SpawnPointMacro);
print(macroTag); // "%spawn_point_macro"

Macro Registration

Macros must be registered in your Gamepack’s exports to be available.

Registering in Gamepack

Create exports/macros.ts:
import { Config } from "CORP/shared/unreal/gamepacks/config";
import { SpawnPointMacro } from "../src/macros/SpawnPointMacro";
import { TeamSpawnMacro } from "../src/macros/TeamSpawnMacro";
import { GridGeneratorMacro } from "../src/macros/GridGeneratorMacro";

const macros: Config.Macros = {
    mappings: {
        // Use snake_case for macro names
        "spawn_point_macro": SpawnPointMacro,
        "team_spawn_macro": TeamSpawnMacro,
        "grid_generator_macro": GridGeneratorMacro
    }
};

export = macros;
Naming Convention: Always use snake_case for macro mapping names. This ensures consistency across your codebase and makes tags easy to read.

Common Use Cases

1. Spawn Point Generation

export class SpawnPointMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        const spawnPoint = new Instance("Part");
        spawnPoint.Name = "SpawnPoint";
        spawnPoint.Size = new Vector3(5, 0.5, 5);
        spawnPoint.Transparency = 0.8;
        spawnPoint.Color = new Color3(0, 1, 0);
        spawnPoint.Anchored = true;
        spawnPoint.CanCollide = false;
        
        // Add a spawn location marker
        const attachment = new Instance("Attachment");
        attachment.Name = "SpawnAttachment";
        attachment.Parent = spawnPoint;
        
        if (instance.IsA("BasePart")) {
            spawnPoint.CFrame = instance.CFrame;
        }
        
        spawnPoint.Parent = instance.Parent;
        instance.Destroy();
        
        return spawnPoint;
    }
}

2. Prefab Instantiation

import { vfs } from "CORP/shared/utilities/vfs";

export class PrefabMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        // Read prefab path from attribute
        const prefabPath = instance.GetAttribute("prefabPath") as string;
        
        if (!prefabPath) {
            warn("PrefabMacro: No prefabPath attribute found");
            return instance;
        }
        
        // Load the prefab from assets
        const prefab = vfs.read<Model>(prefabPath, {
            classname: "Model"
        });
        
        if (prefab) {
            const clone = prefab.Clone();
            clone.Name = instance.Name;
            
            if (instance.IsA("BasePart")) {
                clone.SetPrimaryPartCFrame(instance.CFrame);
            }
            
            clone.Parent = instance.Parent;
            instance.Destroy();
            
            return clone;
        }
        
        return instance;
    }
}

3. Procedural Environment

export class ForestMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        const treeCount = instance.GetAttribute("treeCount") as number ?? 20;
        const radius = instance.GetAttribute("radius") as number ?? 100;
        
        const forest = new Instance("Folder");
        forest.Name = "Forest";
        forest.Parent = instance.Parent;
        
        const center = instance.IsA("BasePart") 
            ? instance.Position 
            : new Vector3(0, 0, 0);
        
        for (let i = 0; i < treeCount; i++) {
            const angle = (i / treeCount) * math.pi * 2;
            const distance = math.random() * radius;
            
            const tree = new Instance("Part");
            tree.Name = `Tree_${i}`;
            tree.Size = new Vector3(2, 10, 2);
            tree.Position = new Vector3(
                center.X + math.cos(angle) * distance,
                center.Y + 5,
                center.Z + math.sin(angle) * distance
            );
            tree.Anchored = true;
            tree.Color = new Color3(0.4, 0.2, 0.1);
            tree.Parent = forest;
        }
        
        instance.Destroy();
        
        return forest;
    }
}

4. Dynamic Layout

export class LayoutMacro extends Macro {
    public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
        const layoutType = instance.GetAttribute("layoutType") as string ?? "grid";
        const spacing = instance.GetAttribute("spacing") as number ?? 10;
        
        const children = instance.GetChildren();
        const container = new Instance("Folder");
        container.Name = instance.Name;
        container.Parent = instance.Parent;
        
        if (layoutType === "grid") {
            const columns = instance.GetAttribute("columns") as number ?? 5;
            
            children.forEach((child, index) => {
                if (child.IsA("BasePart")) {
                    const row = math.floor(index / columns);
                    const col = index % columns;
                    
                    const cloned = child.Clone();
                    cloned.CFrame = new CFrame(
                        col * spacing,
                        0,
                        row * spacing
                    );
                    cloned.Parent = container;
                }
            });
        } else if (layoutType === "circle") {
            children.forEach((child, index) => {
                if (child.IsA("BasePart")) {
                    const angle = (index / children.size()) * math.pi * 2;
                    
                    const cloned = child.Clone();
                    cloned.CFrame = new CFrame(
                        math.cos(angle) * spacing,
                        0,
                        math.sin(angle) * spacing
                    );
                    cloned.Parent = container;
                }
            });
        }
        
        instance.Destroy();
        
        return container;
    }
}

Best Practices

1. Use snake_case for Names

// ✓ Good - consistent snake_case
"spawn_point_macro": SpawnPointMacro,
"team_spawn_macro": TeamSpawnMacro,
"grid_generator_macro": GridGeneratorMacro

// ✗ Bad - inconsistent casing
"SpawnPointMacro": SpawnPointMacro,
"teamSpawn": TeamSpawnMacro

2. Clean Up Original Instances

Always destroy the original instance after transformation:
public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
    const newInstance = this.createTransformed(instance);
    
    // ✓ Clean up the original
    instance.Destroy();
    
    return newInstance;
}

3. Handle Edge Cases

public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
    // Validate attributes
    const value = instance.GetAttribute("value") as number;
    if (value === undefined || value < 0) {
        warn(`Invalid value for macro on ${instance.Name}`);
        return instance; // Return unchanged if invalid
    }
    
    // Proceed with transformation
    // ...
}

4. Use Attributes for Configuration

// In Studio, on the tagged instance:
// Attribute: teamId = 1
// Attribute: count = 5
// Attribute: enabled = true

public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
    const teamId = instance.GetAttribute("teamId") as number ?? 1;
    const count = instance.GetAttribute("count") as number ?? 1;
    const enabled = instance.GetAttribute("enabled") as boolean ?? true;
    
    if (!enabled) return instance;
    
    // Use configuration...
}

5. Preserve Hierarchy

Maintain parent relationships when transforming:
public transformInstanceHierarchy(instance: Instance, root: Instance): Instance {
    const transformed = this.createNew();
    
    // ✓ Preserve the parent relationship
    transformed.Parent = instance.Parent;
    
    // Copy children if needed
    instance.GetChildren().forEach(child => {
        child.Parent = transformed;
    });
    
    instance.Destroy();
    
    return transformed;
}

6. Document Your Macros

/**
 * Generates a grid of tiles based on configuration attributes.
 * 
 * Required Attributes:
 * - width: number (default: 10)
 * - height: number (default: 10)
 * - tileSize: number (default: 10)
 * 
 * Expected Children:
 * - First child should be a BasePart to use as tile template
 * 
 * Example tag: %grid_generator_macro
 */
export class GridGeneratorMacro extends Macro {
    // ...
}

Next Steps


Master macros for powerful procedural generation and instance transformation! 🔧