Skip to main content
Reading time: ~35 minutes
This guide covers essential Roblox Studio concepts for developers using CORP, especially those new to the Roblox platform. Understanding these fundamentals will help you build games more effectively with CORP.

Table of Contents

Roblox Studio Basics

Roblox Studio is the development environment for creating Roblox games. It provides a 3D editor, scripting environment, and testing tools.

Key Concepts

  • Place: A Roblox game world/level
  • Experience: A published game (can contain multiple places)
  • DataModel: The root container for all game objects
  • Instance: Any object in the game (Parts, Scripts, Models, etc.)

Opening Your Project

  1. Open Roblox Studio
  2. Create a new place or open an existing one
  3. Your compiled CORP code will be synced to the game using roblox-ts

The Instance Hierarchy

Everything in Roblox is an Instance - a hierarchical object with properties and methods. Instances form a tree structure called the DataModel.

Common Instance Types

Containers

Folder          - Organize instances (no visual representation)
Model           - Group of parts with a primary part
Actor           - Container for parallel execution (used by CORP for GameObjects)
Configuration   - Store configuration data

Parts and Models

Part            - Basic 3D object
MeshPart        - Part with a custom mesh
UnionOperation  - Boolean combination of parts
Model           - Collection of parts

Scripts

Script          - Server-side script
LocalScript     - Client-side script
ModuleScript    - Shared reusable code module

UI

ScreenGui       - Container for 2D UI elements
Frame           - Rectangular UI container
TextLabel       - Display text
TextButton      - Clickable button
ImageLabel      - Display images

Hierarchy Example

Workspace               (World container)
├── Baseplate (Part)
├── GameScene (Folder)
│   ├── Player (Actor)
│   ├── Enemy (Actor)
│   └── Props (Folder)
│       ├── Tree (Model)
│       └── Rock (Part)

Players                 (Player management)

ReplicatedStorage       (Shared between server/client)
├── Assets (Folder)
└── Shared (Folder)

ServerStorage           (Server-only storage)

ServerScriptService     (Server scripts)
├── GameServer (Script)

StarterPlayer           
├── StarterPlayerScripts (Client scripts)
│   └── GameClient (LocalScript)
└── StarterCharacterScripts (Per-character scripts)

Important Services

Roblox organizes core functionality into Services - singleton instances that manage specific aspects of the game.

Essential Services

Workspace

The 3D world where visible game objects exist.
import { Workspace } from "@rbxts/services";

const part = Workspace.FindFirstChild("Baseplate") as Part;

Players

Manages connected players.
import { Players } from "@rbxts/services";

// Server: when a player joins
Players.PlayerAdded.Connect((player) => {
    print(`${player.Name} joined!`);
});

// Client: get local player
const localPlayer = Players.LocalPlayer;

ReplicatedStorage

Storage accessible by both server and client. Use for shared assets and modules.
import { ReplicatedStorage } from "@rbxts/services";

const assets = ReplicatedStorage.FindFirstChild("Assets");

ServerStorage

Server-only storage. Clients cannot access this.
import { ServerStorage } from "@rbxts/services";

// Only server can access
const serverAssets = ServerStorage.FindFirstChild("Weapons");

ServerScriptService

Where server Scripts execute.
import { ServerScriptService } from "@rbxts/services";

// CORP automatically places server scripts here

RunService

Access to game loop and environment checks.
import { RunService } from "@rbxts/services";

// Check if code is running on server or client
if (RunService.IsServer()) {
    print("Running on server");
}

if (RunService.IsClient()) {
    print("Running on client");
}

// Game loop
RunService.Heartbeat.Connect((deltaTime) => {
    // Update every frame
});

Data Types

Roblox has unique data types for 3D game development.

Vector3

Represents a 3D point or direction.
// Position
const position = new Vector3(0, 10, 0); // x=0, y=10, z=0

// Direction
const forward = new Vector3(0, 0, -1);

// Operations
const sum = position.add(forward);
const scaled = position.mul(2);
const magnitude = position.Magnitude;

CFrame

Represents position AND rotation (Coordinate Frame).
// Position only
const cframe = new CFrame(0, 10, 0);

// Position with lookAt
const lookAt = CFrame.lookAt(
    new Vector3(0, 5, 0),  // from
    new Vector3(10, 5, 0)  // to
);

// Rotation
const rotated = cframe.mul(CFrame.Angles(0, math.rad(90), 0));

// Get position from CFrame
const pos = cframe.Position;

// CORP GameObjects use CFrame for transforms
gameObject.setTransform(new CFrame(0, 10, 0));

Color3

RGB color with values from 0 to 1.
// RGB values (0-1)
const red = new Color3(1, 0, 0);
const green = new Color3(0, 1, 0);
const blue = new Color3(0, 0, 1);

// From 0-255 values
const orange = Color3.fromRGB(255, 165, 0);

// Common colors
const white = new Color3(1, 1, 1);
const black = new Color3(0, 0, 0);

Other Important Types

// Enum values
const material = Enum.Material.Plastic;
const partType = Enum.PartType.Block;

// Ray for raycasting
const ray = new Ray(origin, direction);

// NumberRange, NumberSequence, etc.

Tags System

Roblox’s CollectionService allows you to tag instances. CORP uses tags extensively to identify GameObjects and scene roots.

Installing Tag Editor

  1. In Roblox Studio, go to PluginsPlugin Marketplace
  2. Search for “Tag Editor”
  3. Install the official Tag Editor plugin

Using Tags

import { CollectionService } from "@rbxts/services";

// Add a tag
CollectionService.AddTag(instance, "GameObject");

// Check if tagged
const hasTag = CollectionService.HasTag(instance, "GameObject");

// Get all instances with tag
const gameObjects = CollectionService.GetTagged("GameObject");

// Listen for tagged instances
CollectionService.GetInstanceAddedSignal("GameObject").Connect((instance) => {
    print(`GameObject added: ${instance.Name}`);
});

CORP Tags

CORP recognizes these special tags:
  • GameObjectSceneRoot: Marks the root folder of a scene in Workspace

Adding Tags in Studio

  1. Select an instance in the Explorer
  2. Open the Tag Editor plugin (View → Tag Editor)
  3. Type the tag name (e.g., “GameObjectSceneRoot”)
  4. Click the + button or press Enter

Client-Server Architecture

Roblox games use a client-server architecture. Understanding this is crucial for multiplayer games.

The Server

  • Authoritative: Makes final decisions about game state
  • Validates: Checks client inputs for cheating
  • Manages: Game logic, physics, NPC AI
  • Runs: Script instances in ServerScriptService
import { RunService } from "@rbxts/services";

if (RunService.IsServer()) {
    // This code only runs on the server
    print("Server-side logic");
}

The Client

  • Displays: Renders the game world
  • Inputs: Captures player input (keyboard, mouse, touch)
  • Predicts: Can predict movement for responsiveness
  • Runs: LocalScript instances in player-specific locations
import { RunService } from "@rbxts/services";

if (RunService.IsClient()) {
    // This code only runs on clients
    print("Client-side logic");
}

When Code Runs Where

// ModuleScript - Runs wherever it's required
export class SharedLogic {
    public static calculate(value: number): number {
        return value * 2;
    }
}

// In a Script (ServerScriptService)
if (RunService.IsServer()) {
    // Server-only logic
    Players.PlayerAdded.Connect((player) => {
        // Give player tools, load data, etc.
    });
}

// In a LocalScript (StarterPlayerScripts)
if (RunService.IsClient()) {
    // Client-only logic
    const camera = Workspace.CurrentCamera;
    // Handle input, update UI, etc.
}

CORP’s Approach

CORP abstracts some of this complexity:
  • NetworkObject: Automatically replicates GameObjects
  • NetworkBehavior: Components that sync state
  • RPC: Easy server-client communication
  • Authority Models: Choose server or client authoritative
import { Unreal } from "CORP/shared/unreal";

// Check who has authority
if (Unreal.isAuthority()) {
    // Server (or client in client-authoritative mode)
    this.spawnEnemy();
}

Replication

Replication is how Roblox syncs game state between server and clients.

What Replicates

Automatically Replicated:
  • Instances in Workspace
  • Properties of replicated instances (Position, Size, Color, etc.)
  • Instance hierarchy changes (parenting)
Does NOT Replicate:
  • Local variables in scripts
  • Instances in ServerStorage or ServerScriptService
  • Client-created instances (unless parent is replicated)

Replication Direction

Server → Clients:    Always works (server is source of truth)
Client → Server:     Never automatic (use RemoteEvents/RemoteFunctions)
Client → Client:     Never (must go through server)

CORP’s Network System

CORP handles replication for you:
@RequiresComponent(NetworkObject)
export class PlayerSync extends NetworkBehavior {
    // Automatically replicates to all clients
    public readonly position = new NetworkedVariable<Vector3>(Vector3.zero);
    
    public onStart(): void {
        if (RunService.IsServer()) {
            // Only server modifies
            this.position.setValue(new Vector3(0, 10, 0));
        } else {
            // Clients automatically receive updates
            this.position.changed.connect((newPos) => {
                print(`Position updated to: ${newPos}`);
            });
        }
    }
}

RemoteEvents and RemoteFunctions

Roblox’s built-in communication system (CORP’s RPC system uses these internally):
// Server creates RemoteEvent
const event = new Instance("RemoteEvent");
event.Name = "MyEvent";
event.Parent = ReplicatedStorage;

// Server fires to client
event.FireClient(player, "Hello!");

// Client listens
event.OnClientEvent.Connect((message) => {
    print(message); // "Hello!"
});

// Client fires to server
event.FireServer("Hi from client!");

// Server listens
event.OnServerEvent.Connect((player, message) => {
    print(`${player.Name} says: ${message}`);
});
With CORP RPCs, this is simplified:
@RPC.Method({
    endpoints: [RPC.Endpoint.CLIENT_TO_SERVER],
    accessPolicy: RPC.AccessPolicy.OWNER
})
public clientMessage(message: string): void {
    print(`Client says: ${message}`);
}

Streaming

StreamingEnabled controls whether the entire game world loads at once or streams in based on player location. CORP is designed to work seamlessly with streaming enabled.

Streaming Modes

StreamingEnabled = false

  • Entire world loads immediately
  • Best for: Single-player games, small maps, client-authoritative games
  • Memory: Higher memory usage (entire map loaded)
  • Performance: Consistent, but limited by total content size

StreamingEnabled = true

  • World streams based on player position
  • Best for: Multiplayer games, large open worlds, server-authoritative games
  • Memory: Lower memory footprint per client
  • Performance: Better for large-scale games

CORP’s Streaming Support

CORP is fully compatible with StreamingEnabled. It provides built-in tools to handle streaming gracefully:

NetworkObject Streaming Awareness

export class MyComponent extends NetworkBehavior {
    private networkObject!: NetworkObject;

    public onStart(): void {
        this.networkObject = this.getComponent(NetworkObject)!;

        // Check if instance is currently streamed in
        if (this.networkObject.isStreamedIn()) {
            print("Instance is loaded");
        }

        // Listen for streaming status changes
        this.networkObject.isLoadedChanged.connect((isLoaded) => {
            if (isLoaded) {
                print("Instance streamed in!");
                this.onInstanceLoaded();
            } else {
                print("Instance streamed out!");
                this.onInstanceUnloaded();
            }
        });
    }

    private onInstanceLoaded(): void {
        // Access Roblox instance safely
        const instance = this.getInstance();
        // Work with instance...
    }

    private onInstanceUnloaded(): void {
        // Clean up any instance-specific references
    }
}

InstanceNetworkVariable

For networked Instance references, CORP provides InstanceNetworkVariable which handles streaming automatically:
import { InstanceNetworkVariable } from "CORP/shared/networking/instance-network-variable";

@RequiresComponent(NetworkObject)
export class TargetSystem extends NetworkBehavior {
    // Automatically handles streaming for instance references
    public readonly targetPart = new InstanceNetworkVariable<Part | undefined>(undefined);

    public onStart(): void {
        // Check streaming status
        if (this.targetPart.isStreamedIn()) {
            const part = this.targetPart.getValue();
            // Use part...
        }

        // Listen for streaming changes
        this.targetPart.streamingStatusChanged.connect((isStreamedIn) => {
            if (isStreamedIn) {
                print("Target is now available!");
            }
        });

        // Wait for instance to stream in if needed
        task.spawn(async () => {
            const part = await this.targetPart.waitForValue();
            print("Got the part:", part.Name);
        });
    }
}

For Multiplayer Games (Recommended: StreamingEnabled = true)

// In Roblox Studio: Set Workspace.StreamingEnabled = true

// Benefits:
// ✅ Better performance for large worlds
// ✅ Lower memory per client
// ✅ Supports massive player counts
// ✅ CORP handles streaming automatically

// Use CORP's streaming-aware features:
// - NetworkObject.isStreamedIn()
// - NetworkObject.isLoadedChanged
// - InstanceNetworkVariable

For Single-Player Games (StreamingEnabled = false)

// In Roblox Studio: Set Workspace.StreamingEnabled = false

// Benefits:
// ✅ Simpler development (everything always loaded)
// ✅ No streaming concerns
// ✅ Better for smaller, contained experiences

// Required for client-authoritative games

Setting Streaming

In Roblox Studio:
  1. Select Workspace in Explorer
  2. In Properties panel, find StreamingEnabled
  3. Set based on your game type:
    • true for multiplayer/large worlds
    • false for single-player/client-authoritative

Client-Authoritative Games

Important: For client-authoritative games, Workspace.StreamingEnabled must be set to false to ensure all game content loads immediately. See Unreal System - Authority Models for details.
Client-authoritative mode requires the full game state to be available on the client, which is incompatible with streaming.

Working with the Workspace

The Workspace is where your 3D game world exists.

Basic Operations

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

// Create a part
const part = new Instance("Part");
part.Size = new Vector3(10, 1, 10);
part.Position = new Vector3(0, 0, 0);
part.Anchored = true;
part.Parent = Workspace;

// Find instances
const folder = Workspace.FindFirstChild("GameScene") as Folder;
const actor = Workspace.WaitForChild("Player") as Actor;

// Raycasting
const origin = new Vector3(0, 10, 0);
const direction = new Vector3(0, -20, 0);
const raycastResult = Workspace.Raycast(origin, direction);

if (raycastResult) {
    const hitPart = raycastResult.Instance;
    const hitPosition = raycastResult.Position;
}

Workspace Organization

Organize your Workspace for CORP:
Workspace/
├── Terrain
├── Baseplate (Part)
├── GameScene (Folder) [Tag: GameObjectSceneRoot]
│   ├── Players (Folder)
│   │   └── SpawnPoint (Actor)
│   │       └── $components (Folder)
│   │           └── spawn_point (ModuleScript)
│   ├── Enemies (Folder)
│   │   ├── Enemy1 (Actor)
│   │   │   ├── $components (Folder)
│   │   │   │   ├── enemy_ai (ModuleScript)
│   │   │   │   └── health_system (ModuleScript)
│   │   │   └── Model (MeshPart or Model)
│   │   └── Enemy2 (Actor)
│   │       └── $components (Folder)
│   │           └── enemy_ai (ModuleScript)
│   └── Environment (Folder)
│       ├── Trees (Folder)
│       └── Buildings (Folder)

Studio Workflow with CORP

  1. Set Up Development Tools
  2. Set Up Project
    • Clone CORP-TEMPLATE
    • Initialize submodules: git submodule update --init --recursive
    • Install dependencies: npm install
    • Configure Gamepacks in GAMEPACKS/ folder
  3. Start Development Servers
    • Terminal 1: Start Rojo server
      rojo serve default.project.json
      
    • Terminal 2: Watch TypeScript compilation
      npx rbxtsc --watch
      
    • In Roblox Studio: Click Rojo plugin → Connect to localhost:34872
  4. Code in TypeScript
    • Write components in your Gamepack’s src/ folder
    • Export components in exports/components.ts
    • Save files to auto-compile and sync to Studio
  5. Build Scenes in Studio
    • Create Folders and Actors in Workspace
    • Tag the scene root folder with GameObjectSceneRoot
    • Add $components folders to Actors for component configuration
  6. Test in Studio
    • Click Play (F5) to test
    • Check Output window for prints and errors
    • Iterate on code and scene design
    • Changes sync automatically when you save TypeScript files
  7. Export Assets
    • Place models, sounds, UI in your Gamepack’s assets/ folder
    • Reference with %assets%/path/to/asset

Component Configuration in Studio

CORP stores component data inside a special $components folder within each Actor. Here’s the proper hierarchy:
Actor: Player
├── $components (Folder)
│   ├── player_controller (ModuleScript or StringValue)
│   └── health_system (ModuleScript or StringValue)
└── ... other child instances

Creating Component Configuration

Method 1: ModuleScript (for complex configuration)
  1. Select your Actor in the Explorer
  2. Insert a Folder and name it exactly $components
  3. Inside $components, insert a ModuleScript
  4. Name it with your component’s mapping name (e.g., player_controller)
  5. Set the ModuleScript content to return a configuration table:
-- ModuleScript: player_controller
return {
    speed = 16,
    jumpHeight = 50,
    maxHealth = 100
}
Method 2: StringValue (for JSON configuration)
  1. Inside the $components folder, insert a StringValue
  2. Name it with your component’s mapping name (e.g., enemy_ai)
  3. Set the Value to a JSON string:
{
    "attackDamage": 25,
    "moveSpeed": 10,
    "attackRange": 15
}

Component References

To reference other instances, GameObjects, or components, add ObjectValue children to the component and use the Data.factory methods:
Actor: Enemy
├── $components (Folder)
│   └── target_follower (ModuleScript)
│       └── targetRef (ObjectValue)  ← References another Actor
└── ... other children
In the component ModuleScript, use the appropriate Data.factory reference type:
-- Reference to an Instance (Part, Model, etc.)
return {
    target = { ["$type"] = "instanceReference", ["$referenceId"] = "targetRef" }
}

-- Reference to a GameObject
return {
    target = { ["$type"] = "gameObjectReference", ["$referenceId"] = "targetRef" }
}

-- Reference to a Component on another GameObject
return {
    target = { ["$type"] = "componentReference", ["$referenceId"] = "targetRef" }
}
The $referenceId must match the name of the ObjectValue child. CORP will resolve the ObjectValue to the actual Instance/GameObject/Component based on the $type.

Testing Workflow

// In your component
export class DebugComponent extends Behavior {
    public onStart(): void {
        // Use print for debugging
        print("Component started!");
        print(`GameObject name: ${this.gameObject.getName()}`);
        
        // Warn for non-critical issues
        warn("This is a warning");
        
        // Error for critical issues
        error("This is an error");
    }
}
Check the Output window in Studio to see your prints.

Hot Reloading with Rojo

With Rojo and roblox-ts watch mode running:
  1. Save your TypeScript file in VS Code
  2. roblox-ts compiles automatically (Terminal 2)
  3. Rojo syncs changes to Studio (Terminal 1)
  4. Stop and restart the play session to see changes
Benefits:
  • ✅ Real-time code synchronization
  • ✅ No manual file copying
  • ✅ Work in your favorite code editor
  • ✅ Version control friendly (edit source, not .rbxl files)
Troubleshooting:
  • If changes don’t sync, check Rojo connection status (green = connected)
  • If compilation fails, check Terminal 2 for TypeScript errors
  • Restart Rojo server if connection is lost

Best Practices

✅ Do

  • Enable Streaming for multiplayer/large world games
  • Disable Streaming for single-player/client-authoritative games
  • Use CORP’s streaming features (isStreamedIn(), isLoadedChanged, InstanceNetworkVariable)
  • Use Tags to mark CORP scene roots with GameObjectSceneRoot
  • Organize Workspace with folders and meaningful names
  • Test Frequently in Studio play mode
  • Use $components folders for component configuration
  • Check RunService.IsServer() when needed
  • Use CFrame for GameObject transforms
  • Keep Server Authoritative for multiplayer

❌ Avoid

  • Don’t access Roblox instances without checking isStreamedIn() when streaming is enabled
  • Don’t use StreamingEnabled = true with client-authoritative games
  • Don’t access Workspace directly in most CORP code (use GameObjects)
  • Don’t create instances manually when CORP can handle it
  • Don’t forget to anchor Parts in the Workspace (if static)
  • Don’t hardcode paths to instances (use VFS for assets)
  • Don’t mix LocalScripts and Scripts unnecessarily (let CORP handle it)

Common Issues

”Scene root not found”

  • Cause: Missing GameObjectSceneRoot tag
  • Fix: Tag a Folder in Workspace with GameObjectSceneRoot

”Component not spawning”

  • Cause: Component not exported or name mismatch
  • Fix: Check exports/components.ts for correct mapping

”Client can’t access instance”

  • Cause: Instance is in ServerStorage or ServerScriptService
  • Fix: Move to ReplicatedStorage or Workspace

”Instance not found” (with streaming enabled)

  • Cause: Instance not yet streamed in to client
  • Fix: Use NetworkObject.isLoadedChanged or InstanceNetworkVariable.waitForValue()

”Streaming error with client authority”

  • Cause: StreamingEnabled is true with client-authoritative game
  • Fix: Set Workspace.StreamingEnabled = false in Studio (required for client authority)

Next Steps

Now that you understand Roblox Studio basics:
Master Roblox Studio to build amazing CORP games! 🎮