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
- Build the managed project (this now happens automatically when you compile a C# script):
dotnet build Scripts/Managed/ModuCPP.csproj -c Debug - In the Inspector, add a Script component and set:
- Language =
C# - Assembly Path = path to a
.csfile,.csproj, or the compiled.dll - Type =
Namespace.ClassName(or just class name if no namespace)
- Language =
- Enter play mode.
Assembly Path Resolution
- If
Assembly Pathis a.csor.csproj, the engine resolves to the managed output DLL automatically. - If
<ProjectRoot>/Scripts/Managed/ModuCPP.csprojis missing, the editor attempts to bootstrap it on first managed compile. - Engine expects
ModuCPP.Host.SetNativeApi(IntPtr)in the assembly (provided byScripts/Managed/ModuCPP.cs).
Requirements
- Requires Mono-enabled engine build (MODULARITY_USE_MONO=ON) and a valid Mono runtime.
- The
ModuCPP.runtimeconfig.jsonproduced bydotnet buildmust 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):
| Hook | Signature |
|---|---|
Begin / Script_Begin | void(IntPtr ctx, float dt) |
TickUpdate / Script_TickUpdate | void(IntPtr ctx, float dt) |
Update / Script_Update | void(IntPtr ctx, float dt) |
Spec / Script_Spec | void(IntPtr ctx, float dt) |
TestEditor / Script_TestEditor | void(IntPtr ctx, float dt) |
OnInspector / Script_OnInspector | void(IntPtr ctx) |
Note
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)
| 1 | include/mono-2.0/ |
| 2 | lib/ (or lib64/) with mono-2.0-sgen library |
| 3 | etc/mono/ (config files) |
| 4 | lib/mono/4.5/ (framework assemblies) |
Runtime Override at Startup
| 1 | MODU_MONO_ROOT=/path/to/mono ./ModularityEditor |
Build Configuration
- CMake cache variable
MONO_ROOTcontrols where headers/libs are found. - Managed scripts target
netstandard2.0and are built withdotnet 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:
| 1 | var c = new Context(ctx); |
Object Query
| 1 | c.ObjectId // native object id for this script context |
| 2 | c.SelectedObjectId // id of currently selected editor object |
| 3 | c.SceneObjectCount // total objects in scene |
| 4 | c.FindObjectByName(string name) // returns ModuObject |
| 5 | c.FindObjectById(int id) |
| 6 | c.GetObjectByIndex(int index) |
| 7 | c.GetObjectName(int id) |
Transform
| 1 | c.Position // Vec3 get/set |
| 2 | c.Rotation // Vec3 get/set (degrees) |
| 3 | c.Scale // Vec3 get/set |
| 4 | c.SetPosition2D(Vec2) // 2D position |
Object State, Tags & Layers
| 1 | c.IsObjectEnabled |
| 2 | c.Layer |
| 3 | c.Tag |
| 4 | c.HasTag(string) |
| 5 | c.IsInLayer(int) |
Input & Movement
| 1 | c.IsSprintDown() |
| 2 | c.IsJumpDown() |
| 3 | c.GetMoveInputWASD(float pitchDeg, float yawDeg) |
| 4 | c.ApplyMouseLook(ref pitch, ref yaw, sensitivity, maxDelta, dt, requireMouseButton: false) |
Rigidbody & Physics
| 1 | c.HasRigidbody |
| 2 | c.EnsureRigidbody(bool useGravity = true, bool kinematic = false) |
| 3 | c.EnsureCapsuleCollider(float height, float radius) |
| 4 | c.RigidbodyVelocity // Vec3 get/set |
| 5 | c.AddRigidbodyForce(Vec3) |
| 6 | c.AddRigidbodyImpulse(Vec3) |
| 7 | c.HasRigidbody2D |
| 8 | c.Rigidbody2DVelocity // Vec2 via Vec3 bridge |
| 9 | c.AddRigidbodyVelocity(Vec3) |
| 10 | c.SetRigidbodyAngularVelocity(Vec3) |
| 11 | c.TryGetRigidbodyAngularVelocity(out Vec3) |
| 12 | c.AddRigidbodyTorque(Vec3) |
| 13 | c.AddRigidbodyAngularImpulse(Vec3) |
| 14 | c.SetRigidbodyYaw(float) |
| 15 | c.SetRigidbodyRotation(Vec3) |
| 16 | c.TeleportRigidbody(Vec3 pos, Vec3 rotDeg) |
| 17 | c.RaycastClosestDetailed(origin, dir, distance, out RaycastHit hit) |
Animation
| 1 | c.HasAnimation |
| 2 | c.PlayAnimation(bool restart = true) |
| 3 | c.StopAnimation(bool resetTime = true) |
| 4 | c.PauseAnimation(bool pause = true) |
| 5 | c.ReverseAnimation(bool restartIfStopped = true) |
| 6 | c.SetAnimationTime(float) |
| 7 | c.GetAnimationTime() |
| 8 | c.IsAnimationPlaying |
| 9 | c.SetAnimationLoop(bool) |
| 10 | c.SetAnimationPlaySpeed(float) |
| 11 | c.SetAnimationPlayOnAwake(bool) |
UI
| 1 | c.IsUIButtonPressed() |
| 2 | c.IsUIInteractable |
| 3 | c.UISliderValue |
| 4 | c.SetUISliderRange(float min, float max) |
| 5 | c.SetUILabel(string) |
| 6 | c.SetUIColor(Vec3 color) // or overload with alpha |
| 7 | c.UITextScale |
| 8 | c.SetUISliderStyle(int) |
| 9 | c.SetUIButtonStyle(int) |
| 10 | c.SetUIStylePreset(string) |
| 11 | c.SetFPSCap(bool enabled, float cap = 120f) |
Sprite
| 1 | c.SpriteClipCount |
| 2 | c.SpriteClipIndex |
| 3 | c.GetSpriteClipName() |
| 4 | c.GetSpriteClipNameAt(int index) |
| 5 | c.SetSpriteClipIndex(int) |
| 6 | c.SetSpriteClipName(string) |
| 7 | c.SpriteAlpha |
| 8 | c.FadeSpriteAlpha(float targetAlpha, float duration, float dt) |
| 9 | c.FadeSpriteToClipIndex(int clipIndex, float fadeOut, float fadeIn, float dt) |
| 10 | c.FadeSpriteToClipName(string clipName, float fadeOut, float fadeIn, float dt) |
Audio
| 1 | c.HasAudioSource |
| 2 | c.PlayAudio() |
| 3 | c.StopAudio() |
| 4 | c.SetAudioLoop(bool) |
| 5 | c.SetAudioVolume(float) |
| 6 | c.SetAudioClip(string path) |
| 7 | c.PlayAudioOneShot(string clipPath = "", float volumeScale = 1f) |
Settings & Console
| 1 | c.GetSettingFloat(string key, float fallback) |
| 2 | c.SetSettingFloat(string key, float value) |
| 3 | c.GetSettingBool(string key, bool fallback) |
| 4 | c.SetSettingBool(string key, bool value) |
| 5 | c.GetSettingString(string key, string fallback) |
| 6 | c.SetSettingString(string key, string value) |
| 7 | |
| 8 | // Auto-binding helpers (load + save in one call) |
| 9 | c.AutoSetting("key", ref boolValue) |
| 10 | c.AutoSetting("key", ref floatValue) |
| 11 | c.AutoSetting("key", ref vec3Value) |
| 12 | c.AutoSetting("key", ref stringValue) |
| 13 | c.AutoSettingsFrom(object instance, bool save = true) |
| 14 | |
| 15 | c.AddConsoleMessage(string, ConsoleMessageType) |
| 16 | c.MarkDirty() |
ModuCPP.ImGui Helpers
| 1 | ImGui.Text(string) |
| 2 | ImGui.Separator() |
| 3 | ImGui.Button(string) // returns bool |
| 4 | ImGui.Checkbox(string, ref bool) // returns bool |
| 5 | ImGui.DragFloat(string, ref float, float speed, float min, float max) |
| 6 | ImGui.DragFloat3(string, ref Vec3, float speed, float min, float max) |
| 7 | ImGui.InputText(string, ref string) |
| 8 | ImGui.BeginCombo(string, string preview) |
| 9 | ImGui.EndCombo() |
| 10 | ImGui.Selectable(string) |
| 11 | ImGui.AcceptSceneObjectDrop(out int id) |
Auto Inspector Attributes
Apply attributes to class fields to control auto inspector generation via ModuCPP.Inspector.RenderAuto:
| Attribute | Effect |
|---|---|
[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
OnInspector is not implemented, the engine calls ModuCPP.Inspector.RenderAuto automatically. Implement OnInspector only when you need custom inspector logic.Examples
Manual Inspector + Runtime
| 1 | using System; |
| 2 | using ModuCPP; |
| 3 | |
| 4 | public 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
| 1 | using System; |
| 2 | using ModuCPP; |
| 3 | |
| 4 | [HeadText("Auto Inspector Demo")] |
| 5 | public 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
| 1 | using System; |
| 2 | using ModuCPP; |
| 3 | |
| 4 | public 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 inspectorScripts/Managed/SampleInspectorManaged.cs— managed bridge sample