Skip to main content
Reading time: ~30 minutes
Scenes in CORP organize your game world into manageable units. They can be defined using the Roblox instance hierarchy (recommended) or programmatically via TypeScript/JSON.

Table of Contents

What are Scenes?

A scene is a container for GameObjects and represents a level, menu, or any other distinct state in your game. The recommended approach is to define scenes using Roblox Studio’s instance hierarchy, though programmatic definition is also supported.

Key Features

  • Roblox Hierarchy: Build scenes directly in Studio using Actors and Attributes (recommended)
  • Declarative Structure: Define your game world in code or JSON
  • Serialization: Save and load scenes
  • Hierarchy Support: Organize GameObjects in parent-child relationships
  • Component Configuration: Pre-configure component properties
  • Runtime Flexibility: Load scenes dynamically

Scene from Roblox Hierarchy

The primary and recommended way to create scenes is using Roblox Studio’s instance hierarchy. CORP can parse Actor instances in the workspace and convert them to GameObjects with components.

Setting Up a Scene in Studio

  1. Create Actors: Each Actor in Workspace becomes a GameObject
  2. Add Attributes: Configure component properties using Roblox attributes
  3. Use Folders: Organize your scene structure
  4. Add Components: Use ModuleScripts or StringValues to specify components

Example Studio Structure

Workspace/
├── GameScene (Folder)
│   ├── Player (Actor)
│   │   ├── $network_object (ModuleScript or Attribute)
│   │   └── player_controller (ModuleScript or Attribute)
│   ├── Enemy (Actor)
│   │   ├── $network_object
│   │   └── enemy_ai (ModuleScript)
│   │       └── attributes: attackDamage, moveSpeed
│   └── SpawnPoints (Folder)
│       ├── SpawnPoint1 (Actor)
│       └── SpawnPoint2 (Actor)

Loading from Workspace

import { CORP } from "CORP/shared/CORP";
import { Workspace } from "@rbxts/services";

// Start CORP from workspace hierarchy
// This loads and parses all Actors in Workspace
CORP.startFromWorkspace();
Or start from a specific folder:
import { SceneManager } from "CORP/shared/scenes/scene-manager";

// Get a specific scene folder
const sceneFolder = Workspace.FindFirstChild("GameScene") as Folder;

// Load scene from that folder
if (sceneFolder) {
    SceneManager.currentScene?.startFromWorkspace(sceneFolder);
}

Component Mapping in Hierarchy

Components are specified using attributes or child instances:
// In Studio, on an Actor:
// Add Attribute: "player_controller" = true
// Add Attribute: "speed" = 16
// Add Attribute: "health" = 100

// This creates a GameObject with a PlayerController component
// where speed and health are set to those values
Note: Component names should use snake_case. Components prefixed with $ are core CORP components (e.g., $network_object).

Scene Structure

When defining scenes programmatically, they follow the SceneDescription interface:
interface SceneDescription {
    children: GameObjectDescription[];
}

interface GameObjectDescription {
    name: string;
    transform?: SerializedTransform;
    components: ComponentDescription[];
    children: GameObjectDescription[];
}

interface ComponentDescription {
    // ExportPath can be:
    // - [path, exportName]: ["src/components/MyComponent", "MyComponent"]
    // - [path]: ["src/components/MyComponent"] (uses default export)
    path: SceneSerialization.ExportPath;
    data: Record<string, any>;
}

// ExportPath type
type ExportPath = [string, string] | [string];

Creating Scenes Programmatically

Basic Scene

Create a simple scene with a few GameObjects:
import { SceneSerialization } from "CORP/shared/serialization/scene-serialization";

const MyScene: SceneSerialization.SceneDescription = {
    children: [
        {
            name: "Player",
            components: [
                {
                    path: ["src", "shared", "components", "PlayerController"],
                    data: {}
                },
                {
                    path: ["src", "shared", "components", "HealthSystem"],
                    data: {
                        maxHealth: 100,
                        currentHealth: 100
                    }
                }
            ],
            children: []
        },
        {
            name: "GameManager",
            components: [
                {
                    path: ["src", "shared", "components", "GameManager"],
                    data: {
                        roundDuration: 300,
                        maxPlayers: 10
                    }
                }
            ],
            children: []
        }
    ]
};

export default MyScene;

Scene with Hierarchy

Create GameObjects with parent-child relationships:
const HierarchyScene: SceneSerialization.SceneDescription = {
    children: [
        {
            name: "Vehicle",
            components: [
                {
                    path: ["src", "shared", "components", "VehicleController"],
                    data: {
                        speed: 50,
                        turnSpeed: 2
                    }
                }
            ],
            children: [
                {
                    name: "Wheel_FrontLeft",
                    components: [
                        {
                            path: ["src", "shared", "components", "Wheel"],
                            data: { position: "front-left" }
                        }
                    ],
                    children: []
                },
                {
                    name: "Wheel_FrontRight",
                    components: [
                        {
                            path: ["src", "shared", "components", "Wheel"],
                            data: { position: "front-right" }
                        }
                    ],
                    children: []
                }
            ]
        }
    ]
};

export default HierarchyScene;

Scene with Transforms

Specify initial positions for GameObjects:
const SpawnScene: SceneSerialization.SceneDescription = {
    children: [
        {
            name: "SpawnPoint_1",
            transform: {
                position: { x: 0, y: 5, z: 0 },
                rotation: { x: 0, y: 0, z: 0 }
            },
            components: [
                {
                    path: ["src", "shared", "components", "SpawnPoint"],
                    data: { teamId: 1 }
                }
            ],
            children: []
        },
        {
            name: "SpawnPoint_2",
            transform: {
                position: { x: 50, y: 5, z: 0 },
                rotation: { x: 0, y: 180, z: 0 }
            },
            components: [
                {
                    path: ["src", "shared", "components", "SpawnPoint"],
                    data: { teamId: 2 }
                }
            ],
            children: []
        }
    ]
};

export default SpawnScene;

Scene with Network Objects

Create a scene with networked GameObjects:
const MultiplayerScene: SceneSerialization.SceneDescription = {
    children: [
        {
            name: "NetworkedPlayer",
            components: [
                {
                    path: ["CORP", "shared", "networking", "NetworkObject"],
                    data: {}
                },
                {
                    path: ["src", "shared", "components", "PlayerController"],
                    data: {}
                },
                {
                    path: ["src", "shared", "components", "NetworkedHealth"],
                    data: {
                        maxHealth: 100
                    }
                }
            ],
            children: []
        },
        {
            name: "GameState",
            components: [
                {
                    path: ["CORP", "shared", "networking", "NetworkObject"],
                    data: {}
                },
                {
                    path: ["src", "shared", "components", "GameState"],
                    data: {}
                }
            ],
            children: []
        }
    ]
};

export default MultiplayerScene;

Loading Scenes

Using SceneManager

The SceneManager class handles scene loading and management:
import { SceneManager } from "CORP/shared/scenes/scene-manager";
import { SceneSerialization } from "CORP/shared/serialization/scene-serialization";
import MainMenu from "./scenes/MainMenu";
import GameLevel from "./scenes/GameLevel";

// Load a scene
SceneManager.loadScene(MainMenu);

// Get the current scene
const currentScene = SceneManager.currentScene;

// Change to a different scene
SceneManager.changeScene(GameLevel);

// Unload the current scene
SceneManager.unloadScene();

Starting with CORP

Use CORP.start() to initialize your game with a starting scene:
import { CORP } from "CORP/shared/CORP";
import { Configurations } from "CORP/shared/configurations";
import MainScene from "./scenes/MainScene";

// Start with a scene object
const config: Configurations.GameConfiguration = {
    startingScene: MainScene
};

CORP.start(config);
Or load from a path:
// Start with a scene path
const config: Configurations.GameConfiguration = {
    startingScene: "src/shared/scenes/MainScene"
};

CORP.start(config);

Scene Events

Listen for scene changes:
import { SceneManager } from "CORP/shared/scenes/scene-manager";

// Subscribe to scene changes
SceneManager.sceneChanged.connect((newScene) => {
    print(`Scene changed to: ${newScene.description}`);
    
    // Perform scene-specific setup
    setupScene(newScene);
});

Scene Serialization

Exporting Scenes

Generate an Instance representation of the current scene:
import { SceneManager } from "CORP/shared/scenes/scene-manager";

// Generate scene instance description
const sceneInstance = SceneManager.generateSceneInstanceDescription();

// Can be used to save the scene state

Importing from JSON

Scenes can be stored as JSON and loaded at runtime:
{
    "children": [
        {
            "name": "Player",
            "components": [
                {
                    "path": ["src", "shared", "components", "PlayerController"],
                    "data": {
                        "speed": 16,
                        "jumpPower": 50
                    }
                }
            ],
            "children": []
        }
    ]
}
Load the JSON scene:
import sceneData from "./scenes/level1.json";

const scene = sceneData as SceneSerialization.SceneDescription;
SceneManager.loadScene(scene);

Component Path Format

Component paths use the ExportPath type:
// Format 1: [path, exportName]
{
    path: ["src/shared/components/PlayerController", "PlayerController"]
}

// Format 2: [path] (uses default export)
{
    path: ["src/shared/components/PlayerController"]
}

// For CORP built-in components
{
    path: ["CORP/shared/networking/network-object", "NetworkObject"]
}
Note: ExportPath is defined as [string, string] | [string], representing either [file path, export name] or [file path] for default exports.

Configuring Components

Pass initial data to components in scenes:
{
    name: "Enemy",
    components: [
        {
            path: ["src", "shared", "components", "EnemyAI"],
            data: {
                // These values are applied via Object.assign
                attackDamage: 25,
                detectionRange: 50,
                moveSpeed: 8,
                patrolPoints: [
                    { x: 0, y: 0, z: 0 },
                    { x: 10, y: 0, z: 10 }
                ]
            }
        }
    ],
    children: []
}
The component receives these values:
export class EnemyAI extends Behavior {
    @SerializeField
    public attackDamage: number = 10; // Will be 25 from scene

    @SerializeField
    public detectionRange: number = 30; // Will be 50 from scene

    @SerializeField
    public moveSpeed: number = 5; // Will be 8 from scene

    @SerializeField
    public patrolPoints: Vector3[] = []; // Will have 2 points from scene

    public onStart(): void {
        // Values are already applied
        print(`Enemy with ${this.attackDamage} damage`);
    }

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public willRemove(): void {}
}

Best Practices

Prefer defining scenes in Roblox Studio using the instance hierarchy. This approach provides:
  • Visual editing in Studio
  • Easier iteration and testing
  • Natural integration with Roblox’s tooling
  • Faster prototyping
Use programmatic scenes for:
  • Procedural generation
  • Dynamic content
  • Runtime scene construction

2. Organize Scenes by Purpose

Workspace/
├── MainMenu (Folder)
├── Lobby (Folder)
├── Level1 (Folder)
├── Level2 (Folder)
└── GameOver (Folder)
Or for programmatic scenes:
scenes/
├── MainMenu.ts
├── Lobby.ts
├── Level1.ts
├── Level2.ts
└── GameOver.ts

2. Use Scene Hierarchy

Group related GameObjects:
{
    name: "Level",
    components: [],
    children: [
        {
            name: "Spawners",
            components: [],
            children: [
                { name: "SpawnPoint_1", components: [...], children: [] },
                { name: "SpawnPoint_2", components: [...], children: [] }
            ]
        },
        {
            name: "Pickups",
            components: [],
            children: [
                { name: "HealthPack_1", components: [...], children: [] },
                { name: "HealthPack_2", components: [...], children: [] }
            ]
        }
    ]
}

3. Separate Configuration

Keep configuration separate from scene structure:
// config.ts
export const PlayerConfig = {
    maxHealth: 100,
    speed: 16,
    jumpPower: 50
};

// scene.ts
import { PlayerConfig } from "./config";

const scene: SceneSerialization.SceneDescription = {
    children: [
        {
            name: "Player",
            components: [
                {
                    path: ["src", "shared", "components", "PlayerController"],
                    data: PlayerConfig
                }
            ],
            children: []
        }
    ]
};

4. Choose the Right Format

For most cases, use Roblox hierarchy in Studio. Use TypeScript/JSON for:
  • Procedural content generation
  • Runtime dynamic scenes
  • Complex scripted setups
When using TypeScript for scenes:
  • Get type safety and IntelliSense
  • Enable refactoring support
  • Use import statements for organization

5. Scene Transitions

Handle scene transitions gracefully:
export class SceneTransition extends Behavior {
    public async transitionToScene(newScene: SceneSerialization.SceneDescription): Promise<void> {
        // Fade out
        await this.fadeOut();

        // Clean up current scene
        if (SceneManager.currentScene) {
            SceneManager.unloadScene();
        }

        // Load new scene
        SceneManager.loadScene(newScene);

        // Fade in
        await this.fadeIn();
    }

    private async fadeOut(): Promise<void> {
        // Fade animation
    }

    private async fadeIn(): Promise<void> {
        // Fade animation
    }

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public onStart(): void {}
    public willRemove(): void {}
}

6. Dynamic Scene Loading

Load scenes based on game state:
export class LevelManager extends Behavior {
    private levels: SceneSerialization.SceneDescription[] = [];

    public onStart(): void {
        // Register levels
        this.levels = [
            require("./scenes/Level1"),
            require("./scenes/Level2"),
            require("./scenes/Level3")
        ];
    }

    public loadLevel(levelIndex: number): void {
        if (levelIndex >= 0 && levelIndex < this.levels.size()) {
            SceneManager.changeScene(this.levels[levelIndex]);
        }
    }

    protected getSourceScript(): ModuleScript {
        return script as ModuleScript;
    }

    public willRemove(): void {}
}

Common Patterns

Procedural Scene Generation

Generate scenes programmatically:
export function generateDungeonScene(width: number, height: number): SceneSerialization.SceneDescription {
    const children: SceneSerialization.GameObjectDescription[] = [];

    for (let x = 0; x < width; x++) {
        for (let z = 0; z < height; z++) {
            children.push({
                name: `Tile_${x}_${z}`,
                transform: {
                    position: { x: x * 10, y: 0, z: z * 10 },
                    rotation: { x: 0, y: 0, z: 0 }
                },
                components: [
                    {
                        path: ["src", "shared", "components", "FloorTile"],
                        data: {}
                    }
                ],
                children: []
            });
        }
    }

    return { children };
}

// Use it
const dungeon = generateDungeonScene(10, 10);
SceneManager.loadScene(dungeon);

Scene Prefabs

Create reusable GameObject descriptions:
// prefabs.ts
export const PlayerPrefab: SceneSerialization.GameObjectDescription = {
    name: "Player",
    components: [
        {
            path: ["CORP", "shared", "networking", "NetworkObject"],
            data: {}
        },
        {
            path: ["src", "shared", "components", "PlayerController"],
            data: { speed: 16 }
        },
        {
            path: ["src", "shared", "components", "HealthSystem"],
            data: { maxHealth: 100 }
        }
    ],
    children: []
};

// Use in scenes
const scene: SceneSerialization.SceneDescription = {
    children: [
        PlayerPrefab,
        // ... other GameObjects
    ]
};

Next Steps


Master scene management to organize your game world effectively! 🎬