An interactive learning tool I designed and built in Unity — letting beginners write real C#, compile it at runtime using Roslyn, and see it affect a live scene immediately.
Solo project
Interactive C# learning tool
Unity · C# · Roslyn runtime compiler
12-stage lesson system
The Unity C# Trainer is a fully interactive learning tool I built to teach beginners the core principles of C# inside Unity. Unlike static tutorials, it lets players write real C#, compile it at runtime, and see the results immediately in a live scene.
Unity has no built-in support for runtime code injection. Integrating Roslyn made it possible, but ensuring compilation didn't cause frame hitches required careful async handling and memory management throughout.
Making injected scripts behave like native MonoBehaviours — especially Update() — required me to link the user's implementation into the Roslyn controller's own update loop. Since only one user script runs at a time, I fetch and invoke it from there, giving seamless frame-by-frame behaviour with no extra wiring from the player's side.
To convey lessons clearly, I built a manager and a Lesson class that injects pre-authored code into the editor at the right moment. I integrated this with the Fungus interactive storytelling asset — with small modifications to fit how my lesson manager was structured.
Surface-level Unity console access wasn't available for injected runtime code. Given time constraints, I used Quantum Console by QFSW to deliver the readable, real-time error and log output the experience depended on.
Building a full syntax-highlighted IDE from scratch wasn't feasible in the available time. I used InGame Code Editor by Trivial Interactive as a foundation, which freed up enough development time to expand the lesson count from the original 6 to 12.
The core technical challenge was making real C# compilation work safely inside a running Unity build. I integrated the Roslyn compiler to enable true runtime code execution — then built the lesson system, editor, and console around it. Below are the key systems I designed and implemented.
I integrated the Roslyn compiler into Unity to dynamically compile user-written code at runtime. This was the foundational decision that made the whole project possible — Unity has no native support for runtime code injection, so I had to set up the Roslyn pipeline, manage assembly references, and ensure compilation didn't block the main thread.
When a player clicks Compile, I parse their script into a syntax tree and feed it into the Roslyn compilation pipeline. I also dispose of any previously running runtime script instances before each new compile — a step I added early on after spotting memory growth during playtests.
I use Roslyn to emit an in-memory DLL from the player's script. I include all currently loaded assemblies as references so the user's code can access Unity APIs without any extra setup on their part — keeping the experience beginner-friendly while still running real compiled code.
Once the in-memory DLL is ready, I search it for the first class that extends
MonoBehaviour. That becomes the live script running inside the game world —
a simple reflection step that lets beginners write natural Unity-style code without any
wrapper boilerplate.
For the final challenge, I spawn a fully interactable character and attach the player's compiled script as its controller. This gives beginners a direct, tangible connection between their code and a character they can play — the payoff of the whole lesson arc.
I solved Unity's lifecycle problem for injected scripts by manually invoking the user's
Update() from the Roslyn controller's own Update(). Since only
one user script runs at a time, I can fetch and invoke its implementation cleanly — giving
beginners seamless frame-by-frame behaviour without them needing to know any of this
is happening.
I built a template injection system that pushes pre-authored starter code into the editor at the start of each lesson, guiding beginners through the challenge without overwhelming them with a blank slate. I also support loading scripts from external files for more advanced users.
I designed a Lesson class holding each lesson's block name, sample code,
and a DisableNextButton flag for gating progression. A manager I built
handles loading these into both the code editor and the Fungus dialogue system, keeping
the lesson content and the tutorial flow in sync.