C# Interop#
The CS global gives you access to any C# type.
Basic Usage#
Access types through their full namespace:
// Static methods
CS.UnityEngine.Debug.Log("Hello from JS!")
// Static properties
const deltaTime = CS.UnityEngine.Time.deltaTime
// Create instances
const vec = new CS.UnityEngine.Vector3(1, 2, 3)
// Access instance properties
console.log(vec.x, vec.y, vec.z)
// Call instance methods
const normalized = vec.normalizedYour Own Code#
Access your game code the same way:
// C# code in your project
namespace MyGame {
public class GameManager {
public static int Score { get; set; }
public static void AddScore(int points) { Score += points; }
}
}// JavaScript
CS.MyGame.GameManager.AddScore(100)
const score = CS.MyGame.GameManager.ScoreCreating Objects#
Use new to instantiate C# classes:
// Create a GameObject
const go = new CS.UnityEngine.GameObject("MyObject")
// Add a component — returns the correct type
const rb = go.AddComponent(CS.UnityEngine.Rigidbody) // Type: Rigidbody
// Set properties
rb.mass = 2.0
rb.useGravity = true
// GetComponent also returns the correct type
const renderer = go.GetComponent(CS.UnityEngine.MeshRenderer) // Type: MeshRenderer
renderer.material = myMaterialGeneric Types#
Create bound generic types with function syntax:
// List<int>
const IntList = CS.System.Collections.Generic.List(CS.System.Int32)
const numbers = new IntList()
numbers.Add(1)
numbers.Add(2)
numbers.Add(3)
// Dictionary<string, int>
const StringIntDict = CS.System.Collections.Generic.Dictionary(
CS.System.String,
CS.System.Int32
)
const scores = new StringIntDict()
scores.set_Item("player1", 100)Async Methods#
C# async Task methods return Promises in JavaScript:
// C#
public class DataLoader {
public static async Task<string> LoadDataAsync(string url) {
// ... async loading
return data;
}
}// JavaScript
const data = await CS.MyGame.DataLoader.LoadDataAsync("/api/data")
console.log(data)See Async C# Methods for error handling, return types, and React patterns.
Events and Delegates#
Subscribe to C# events:
// Get an event
const button = CS.UnityEngine.UI.Button.FindObjectOfType(CS.UnityEngine.UI.Button)
// Subscribe
button.onClick.AddListener(() => {
console.log("Button clicked!")
})Create delegates for callbacks:
// Create an Action delegate
const action = new CS.System.Action(() => {
console.log("Action invoked!")
})
// Pass to C# methods that expect Action
SomeApi.DoSomething(action)Extension Methods#
C# extension methods (like PointerCaptureHelper.CapturePointer) appear as instance methods in C# but aren't automatically available in JS. Use useExtensions to register them:
// Register at module level (like C#'s "using" statement)
useExtensions(CS.UnityEngine.UIElements.PointerCaptureHelper)
// Now extension methods work as instance methods
const el = ref.current
el.CapturePointer(0) // PointerCaptureHelper.CapturePointer(el, 0)
el.HasPointerCapture(0) // PointerCaptureHelper.HasPointerCapture(el, 0)
el.ReleasePointer(0) // PointerCaptureHelper.ReleasePointer(el, 0)useExtensions(CS.UnityEngine.ImageConversion)
const tex = new CS.UnityEngine.Texture2D(2, 2)
tex.LoadImage(bytes) // ImageConversion.LoadImage(tex, bytes)Call useExtensions once per static class. The runtime discovers methods marked with [ExtensionAttribute] and makes them callable on the target type.
Enums#
Access enum values directly:
// Access enum values
const space = CS.UnityEngine.Space.World
// Use in method calls
transform.Translate(new CS.UnityEngine.Vector3(1, 0, 0), space)
// Compare
if (keyCode === CS.UnityEngine.KeyCode.Space) {
console.log("Space pressed!")
}Arrays and Collections#
Work with C# collections:
// C# arrays
const renderers = go.GetComponentsInChildren(CS.UnityEngine.Renderer)
for (let i = 0; i < renderers.Length; i++) {
renderers[i].enabled = false
}
// Lists
const list = new (CS.System.Collections.Generic.List(CS.System.String))()
list.Add("one")
list.Add("two")
console.log(list.Count) // 2
// Access by index
console.log(list[0]) // "one"Converting to JS Arrays#
C# collections have .Count/.Length and indexers but no .map(), .filter(), etc. Use toArray from onejs-react to convert them:
import { toArray } from "onejs-react"
// Convert C# List for use with array methods
const items = toArray(inventory.Items)
items.map(item => console.log(item.Name))
// Convert C# arrays
const resolutions = toArray(CS.UnityEngine.Screen.resolutions)
// Safe with null — returns []
const npcs = toArray(currentPlace?.NPCs)Type Checking#
Check types at runtime:
const component = go.GetComponent(CS.UnityEngine.Component)
// Check type
if (component.GetType().Name === "Rigidbody") {
// It's a Rigidbody
}
// Or use typeof
const rigidbodyType = CS.UnityEngine.Rigidbody
if (component instanceof rigidbodyType) {
// It's a Rigidbody
}Common Patterns#
Finding Objects#
// Find by type
const player = CS.UnityEngine.GameObject.FindWithTag("Player")
// Find all
const enemies = CS.UnityEngine.GameObject.FindGameObjectsWithTag("Enemy")
// Find component
const camera = CS.UnityEngine.Camera.mainCoroutine Alternative#
Since JS has async/await, you often don't need coroutines:
async function fadeOut(renderer, duration) {
const material = renderer.material
let alpha = 1.0
const startTime = CS.UnityEngine.Time.time
while (alpha > 0) {
await new Promise(resolve => requestAnimationFrame(resolve))
const elapsed = CS.UnityEngine.Time.time - startTime
alpha = 1.0 - (elapsed / duration)
material.color = new CS.UnityEngine.Color(1, 1, 1, Math.max(0, alpha))
}
}Input Handling#
function checkInput() {
if (CS.UnityEngine.Input.GetKeyDown(CS.UnityEngine.KeyCode.Space)) {
console.log("Jump!")
}
const horizontal = CS.UnityEngine.Input.GetAxis("Horizontal")
const vertical = CS.UnityEngine.Input.GetAxis("Vertical")
}
// Call every frame
function update() {
checkInput()
requestAnimationFrame(update)
}
requestAnimationFrame(update)Performance Tips#
- Cache type references: Don't look up types repeatedly in hot loops
- Batch property access: Get multiple values at once when possible
- Use fast paths: Common operations like
Time.deltaTimeare optimized - Avoid reflection in loops: Pre-resolve types outside the loop
za API eliminates all managed heap allocations after initial setup.