C# Scripting

Managed C# scripting via Mono using the ModuCPP managed bridge, with auto-inspector support and bridge ABI version 6.

Overview

Modularity can host managed C# scripts via Mono using the ModuCPP managed bridge. Managed project files live under Scripts/Managed (separate from Assets/Scripts used for native scripts).

The current managed bridge ABI is version 6, which includes expanded object state, 2D motion helpers, sprite fade, audio, and detailed raycast results. ModuCPP.cs binds newer API blocks conditionally via Api.Version, so older native layouts fail soft rather than crashing when newer delegates are absent.

Managed scripts support object/transform, physics, animation, UI, sprite, audio, settings, and inspector helpers from ModuCPP.Context.

Setup

  1. Build the managed project (this now happens automatically when you compile a C# script): dotnet build Scripts/Managed/ModuCPP.csproj -c Debug
  2. In the Inspector, add a Script component and set:
    • Language = C#
    • Assembly Path = path to a .cs file, .csproj, or the compiled .dll
    • Type = Namespace.ClassName (or just class name if no namespace)
  3. Enter play mode.

Assembly Path Resolution

  • If Assembly Path is a .cs or .csproj, the engine resolves to the managed output DLL automatically.
  • If <ProjectRoot>/Scripts/Managed/ModuCPP.csproj is missing, the editor attempts to bootstrap it on first managed compile.
  • Engine expects ModuCPP.Host.SetNativeApi(IntPtr) in the assembly (provided by Scripts/Managed/ModuCPP.cs).

Requirements

  • Requires Mono-enabled engine build (MODULARITY_USE_MONO=ON) and a valid Mono runtime.
  • The ModuCPP.runtimeconfig.json produced by dotnet build must sit next to the DLL.
  • If runtime setup is missing, the engine reports the error in the inspector.

Hook Signatures

Recognized method names (with or without Script_ prefix):

HookSignature
Begin / Script_Beginvoid(IntPtr ctx, float dt)
TickUpdate / Script_TickUpdatevoid(IntPtr ctx, float dt)
Update / Script_Updatevoid(IntPtr ctx, float dt)
Spec / Script_Specvoid(IntPtr ctx, float dt)
TestEditor / Script_TestEditorvoid(IntPtr ctx, float dt)
OnInspector / Script_OnInspectorvoid(IntPtr ctx)

Note

Methods can be instance or static. If OnInspector is missing, the engine tries auto inspector via ModuCPP.Inspector.RenderAuto.

Mono Embedding Setup

Modularity uses Mono embedding for managed C# scripts. The project expects the Mono runtime to be installed or vendored in a specific location.

Expected Layout (Vendored)

src/ThirdParty/mono/
TEXT
1include/mono-2.0/
2lib/ (or lib64/) with mono-2.0-sgen library
3etc/mono/ (config files)
4lib/mono/4.5/ (framework assemblies)

Runtime Override at Startup

Bash
1MODU_MONO_ROOT=/path/to/mono ./ModularityEditor

Build Configuration

  • CMake cache variable MONO_ROOT controls where headers/libs are found.
  • Managed scripts target netstandard2.0 and are built with dotnet build.
  • Project-managed C# sources live under <ProjectRoot>/Scripts/Managed/.
  • Default managed output: <ProjectRoot>/Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.dll.

ModuCPP.Context API

Create a context from a hook's IntPtr ctx parameter:

C#
1var c = new Context(ctx);

Object Query

C#
1c.ObjectId // native object id for this script context
2c.SelectedObjectId // id of currently selected editor object
3c.SceneObjectCount // total objects in scene
4c.FindObjectByName(string name) // returns ModuObject
5c.FindObjectById(int id)
6c.GetObjectByIndex(int index)
7c.GetObjectName(int id)

Transform

C#
1c.Position // Vec3 get/set
2c.Rotation // Vec3 get/set (degrees)
3c.Scale // Vec3 get/set
4c.SetPosition2D(Vec2) // 2D position

Object State, Tags & Layers

C#
1c.IsObjectEnabled
2c.Layer
3c.Tag
4c.HasTag(string)
5c.IsInLayer(int)

Input & Movement

C#
1c.IsSprintDown()
2c.IsJumpDown()
3c.GetMoveInputWASD(float pitchDeg, float yawDeg)
4c.ApplyMouseLook(ref pitch, ref yaw, sensitivity, maxDelta, dt, requireMouseButton: false)

Rigidbody & Physics

C#
1c.HasRigidbody
2c.EnsureRigidbody(bool useGravity = true, bool kinematic = false)
3c.EnsureCapsuleCollider(float height, float radius)
4c.RigidbodyVelocity // Vec3 get/set
5c.AddRigidbodyForce(Vec3)
6c.AddRigidbodyImpulse(Vec3)
7c.HasRigidbody2D
8c.Rigidbody2DVelocity // Vec2 via Vec3 bridge
9c.AddRigidbodyVelocity(Vec3)
10c.SetRigidbodyAngularVelocity(Vec3)
11c.TryGetRigidbodyAngularVelocity(out Vec3)
12c.AddRigidbodyTorque(Vec3)
13c.AddRigidbodyAngularImpulse(Vec3)
14c.SetRigidbodyYaw(float)
15c.SetRigidbodyRotation(Vec3)
16c.TeleportRigidbody(Vec3 pos, Vec3 rotDeg)
17c.RaycastClosestDetailed(origin, dir, distance, out RaycastHit hit)

Animation

C#
1c.HasAnimation
2c.PlayAnimation(bool restart = true)
3c.StopAnimation(bool resetTime = true)
4c.PauseAnimation(bool pause = true)
5c.ReverseAnimation(bool restartIfStopped = true)
6c.SetAnimationTime(float)
7c.GetAnimationTime()
8c.IsAnimationPlaying
9c.SetAnimationLoop(bool)
10c.SetAnimationPlaySpeed(float)
11c.SetAnimationPlayOnAwake(bool)

UI

C#
1c.IsUIButtonPressed()
2c.IsUIInteractable
3c.UISliderValue
4c.SetUISliderRange(float min, float max)
5c.SetUILabel(string)
6c.SetUIColor(Vec3 color) // or overload with alpha
7c.UITextScale
8c.SetUISliderStyle(int)
9c.SetUIButtonStyle(int)
10c.SetUIStylePreset(string)
11c.SetFPSCap(bool enabled, float cap = 120f)

Sprite

C#
1c.SpriteClipCount
2c.SpriteClipIndex
3c.GetSpriteClipName()
4c.GetSpriteClipNameAt(int index)
5c.SetSpriteClipIndex(int)
6c.SetSpriteClipName(string)
7c.SpriteAlpha
8c.FadeSpriteAlpha(float targetAlpha, float duration, float dt)
9c.FadeSpriteToClipIndex(int clipIndex, float fadeOut, float fadeIn, float dt)
10c.FadeSpriteToClipName(string clipName, float fadeOut, float fadeIn, float dt)

Audio

C#
1c.HasAudioSource
2c.PlayAudio()
3c.StopAudio()
4c.SetAudioLoop(bool)
5c.SetAudioVolume(float)
6c.SetAudioClip(string path)
7c.PlayAudioOneShot(string clipPath = "", float volumeScale = 1f)

Settings & Console

C#
1c.GetSettingFloat(string key, float fallback)
2c.SetSettingFloat(string key, float value)
3c.GetSettingBool(string key, bool fallback)
4c.SetSettingBool(string key, bool value)
5c.GetSettingString(string key, string fallback)
6c.SetSettingString(string key, string value)
7
8// Auto-binding helpers (load + save in one call)
9c.AutoSetting("key", ref boolValue)
10c.AutoSetting("key", ref floatValue)
11c.AutoSetting("key", ref vec3Value)
12c.AutoSetting("key", ref stringValue)
13c.AutoSettingsFrom(object instance, bool save = true)
14
15c.AddConsoleMessage(string, ConsoleMessageType)
16c.MarkDirty()

ModuCPP.ImGui Helpers

C#
1ImGui.Text(string)
2ImGui.Separator()
3ImGui.Button(string) // returns bool
4ImGui.Checkbox(string, ref bool) // returns bool
5ImGui.DragFloat(string, ref float, float speed, float min, float max)
6ImGui.DragFloat3(string, ref Vec3, float speed, float min, float max)
7ImGui.InputText(string, ref string)
8ImGui.BeginCombo(string, string preview)
9ImGui.EndCombo()
10ImGui.Selectable(string)
11ImGui.AcceptSceneObjectDrop(out int id)

Auto Inspector Attributes

Apply attributes to class fields to control auto inspector generation via ModuCPP.Inspector.RenderAuto:

AttributeEffect
[HeadText("...")]Header text above the class or field
[Label("...")]Custom label for the field
[DragSpeed(0.25f)]Drag speed for numeric/vector fields
[InspectorIgnore]Skip this field in auto inspector

Note

If OnInspector is not implemented, the engine calls ModuCPP.Inspector.RenderAuto automatically. Implement OnInspector only when you need custom inspector logic.

Examples

Manual Inspector + Runtime

SpinScript.cs
C#
1using System;
2using ModuCPP;
3
4public class SpinScript {
5 private bool enabled = true;
6 private Vec3 speed = new Vec3(0f, 60f, 0f);
7
8 public void TickUpdate(IntPtr ctx, float dt) {
9 var c = new Context(ctx);
10 if (!enabled) return;
11 c.Rotation = c.Rotation + speed * dt;
12 }
13
14 public void OnInspector(IntPtr ctx) {
15 var c = new Context(ctx);
16 c.AutoSetting("enabled", ref enabled);
17 c.AutoSetting("speed", ref speed);
18
19 ImGui.Text("Spin Script");
20 ImGui.Checkbox("Enabled", ref enabled);
21 ImGui.DragFloat3("Speed", ref speed, 0.1f, -720f, 720f);
22 }
23}

Auto Inspector with Attributes

AutoInspectorDemo.cs
C#
1using System;
2using ModuCPP;
3
4[HeadText("Auto Inspector Demo")]
5public class AutoInspectorDemo {
6 [Label("Auto Rotate")]
7 private bool autoRotate = true;
8
9 [DragSpeed(0.5f)]
10 private Vec3 spinSpeed = new Vec3(0f, 45f, 0f);
11
12 [InspectorIgnore]
13 private float runtimeOnly;
14
15 public void Begin(IntPtr ctx) {
16 var c = new Context(ctx);
17 c.AutoSettingsFrom(this, save: false);
18 }
19
20 public void TickUpdate(IntPtr ctx, float dt) {
21 var c = new Context(ctx);
22 if (autoRotate) {
23 c.Rotation = c.Rotation + spinSpeed * dt;
24 }
25 }
26 // No OnInspector: engine falls back to ModuCPP.Inspector.RenderAuto
27}

Raycasting with RaycastHit

RaycastExample.cs
C#
1using System;
2using ModuCPP;
3
4public class RaycastExample {
5 public void TickUpdate(IntPtr ctx, float dt) {
6 var c = new Context(ctx);
7 var origin = c.Position;
8 var dir = new Vec3(0f, 0f, 1f);
9
10 if (c.RaycastClosestDetailed(origin, dir, 20f, out RaycastHit hit)) {
11 c.AddConsoleMessage(
12 $"Hit object {hit.ObjectId} at {hit.Distance:F2}m",
13 ConsoleMessageType.Info
14 );
15 }
16 }
17}

Managed Sample Script Reference

  • Scripts/Managed/SampleInspector.cs — managed hooks + auto inspector
  • Scripts/Managed/SampleInspectorManaged.cs — managed bridge sample