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
- You create instances in Workspace or scenes
- Tag them with a macro name (e.g.,
%spawn_point_macro)
- During loading, CORP applies the macro transformation
- 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
- Create an instance (Part, Model, Folder, etc.)
- Add a tag using the Tag Editor plugin
- 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);
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! 🔧