Week 2 — C#, Systems and the Coding Masterclass · w2d4 · 5h 41m

W2D4: C# meets Unity

Unity's MonoBehaviour lifecycle end to end, component access from code, editor tooling, assembly definitions and a scriptable-object-driven Tetris build.

Lucian Lazar · 18 Jul 2024

Day four is where C# and Unity finally meet properly. Yesterday was pure language theory for six hours; today takes all of that and points it at the engine — script lifecycle, components, editor tooling, project structure, and a small Tetris prototype to tie it together. Lucian says outright this is the material he’s strongest at, and it shows: the pacing is quicker and the opinions are sharper.

What’s covered

Scripting backends and .NET profiles

  • Mono vs IL2CPP, where to find them in Player Settings, and why you build with IL2CPP for shipping but Mono when you’re building two or three times an hour just to test
  • Keeping the same backend across platforms so you get predictable bugs instead of platform-specific ones
  • .NET Standard vs .NET Framework — stay on Standard unless a plugin forces your hand

The MonoBehaviour lifecycle, demonstrated not just listed

  • Awake, OnEnable, Start, Update, FixedUpdate, LateUpdate, OnDisable, OnApplicationPause, OnApplicationQuit, OnDestroy
  • The one rule you can actually rely on: all Awakes fire before all Starts. Everything else about ordering between objects is something you should never build logic on
  • What happens when a GameObject starts deactivated vs when only the script component is unchecked (Awake fires, OnEnable and Start don’t)
  • Passing a context object to Debug.Log so clicking the console message highlights the object that logged it
  • FixedUpdate tied to the fixed timestep in Project Settings (0.02 by default, so 50 calls a second regardless of frame rate)
  • Why camera movement belongs in LateUpdate, with a concrete “this will save you an hour of confusion” example

Collisions, triggers and physics setup

  • OnCollisionEnter/Stay/Exit fires on both objects; how to identify the other object by name, tag or component
  • collision.contacts[0].point for spawning sparks or effects at the impact point
  • Layer collision matrix, and building a cube by hand from MeshFilter + MeshRenderer + Collider + Rigidbody to see what the 3D menu actually does for you

Components from code

  • GetComponent, AddComponent at runtime, TryGetComponent with the out pattern, FindObjectsOfType and why it’s expensive
  • Playing an AudioSource added at runtime, disabling MeshRenderers in bulk and keeping a list so you can re-enable them

Editor code

  • [MenuItem] attributes to add custom build buttons, with a working Windows and macOS build executed live
  • A weekend build blocker using EditorUtility.DisplayDialog that quits the editor if you say no to overtime
  • [CustomEditor] with buttons in the inspector, casting target, and registering Undo so Ctrl+Z works
  • [ExecuteInEditMode] territory: Reset, #if UNITY_EDITOR, and why editor scripts must live in an Editor folder

Best practices block

  • Folder structure by meaning, not by file type
  • Assembly definitions, what they buy you, and how granular to get
  • Dependency injection, the service locator pattern, and which frameworks are worth your time
  • Scriptable objects as configuration files

Tetris walkthrough — a working minimal implementation reviewed class by class.

Timestamps

  • 00:07:30 — Mono vs IL2CPP, .NET Standard vs Framework, and when each one is the right call
  • 00:14:00 — What a MonoBehaviour actually is, why it’s named that, and public fields showing up in the inspector
  • 00:22:00 — Full lifecycle overview: the ten methods worth knowing out of the many that exist
  • 00:31:00 — Awake, OnEnable and Start ordering with two objects, instance IDs, and what you can and can’t rely on
  • 00:48:00 — Objects that start disabled, scripts that start unchecked, and logging with a context object
  • 01:02:00 — Update vs FixedUpdate, the fixed timestep setting, and the layer collision matrix
  • 01:14:00 — LateUpdate and camera follow: Camera.main.transform.LookAt demonstrated live
  • 01:27:00 — OnDisable, deactivating parents, and the enabled/disabled vs activated/deactivated distinction
  • 01:38:00 — OnApplicationPause and Quit, Run In Background, and how Android and iOS differ when the player switches apps
  • 01:50:00 — OnDestroy, destroying objects from Update with Input.GetKeyDown
  • 01:58:00 — OnCollisionEnter, contact points, and OnTriggerEnter with the GTA mission-marker analogy
  • 02:12:00 — Reset, #if UNITY_EDITOR, and a quick tour of built-in components including the line renderer
  • 02:28:00 — Components from code: AddComponent, AudioSource, coroutine-based Start, TryGetComponent, FindObjectsOfType
  • 02:50:00 — Editor tooling: custom build menu items, building for Windows and macOS live from a [MenuItem] method
  • 03:08:00 — The weekend build blocker dialog, then a custom inspector with Add One and Clone Me buttons plus Undo support
  • 03:40:00 — Project structure: group by feature, not by asset type, and keep the hierarchy flat
  • 03:58:00 — Dependency injection, service locator, poor man’s DI, and a look at zenject, VContainer-style alternatives and reflex
  • 04:14:00 — Assembly definitions: what they compile, how to reference them, and how granular is too granular
  • 04:34:00 — Scriptable objects, [CreateAssetMenu], and building a Tetris piece config from an ASCII block schema
  • 04:48:00 — Tetris walkthrough: level config, piece factory, mover, physics interface, gravity coroutine, signal bus and entry point
  • 05:28:00 — Live debugging with breakpoints when the piece refuses to fall

What you build

Two things, really. First, a lifecycle sandbox: a script with every callback logging its own name and context, attached to two objects so you can watch ordering play out in the console while you enable, disable, destroy and pause things. It’s crude and it’s the fastest way to internalise something that most tutorials just hand you as a diagram.

Second, a minimal Tetris. Pieces are defined as scriptable objects containing a text block schema — you literally type 010 / 010 / 011 in the inspector and a parser turns it into block positions. A level config holds world size, spawn position and gravity speed. A factory instantiates pieces and their child blocks. A mover asks a physics interface whether a move is legal before committing. Gravity runs on a coroutine with WaitForSeconds, not Update. When a piece lands, an event goes out on a signal bus and the level solidifies it and spawns the next one. An entry point class wires all of it together in Start.

Nothing in that list is exotic, but the separation is the point. The mover doesn’t know how collision is detected. The gravity doesn’t know how pieces are created. You can forget how any one class works and still use it.

private void LateUpdate()
{
    // camera moves after everything else has updated its position
    Camera.main.transform.LookAt(transform);
}
```csharp
[MenuItem("Build/Windows x64")]
private static void BuildWindows()
{
    var options = new BuildPlayerOptions
    {
        scenes = scenes,
        locationPathName = "build/mygame.exe",
        target = BuildTarget.StandaloneWindows64
    };

    var report = BuildPipeline.BuildPlayer(options);
    Debug.Log(report.summary.result);
    EditorUtility.RevealInFinder(options.locationPathName);
}
## Who should watch this

If you've done a few Unity tutorials and can make a cube move but have no idea why Start runs before Update or what LateUpdate is for, this fills that gap permanently. The editor tooling and assembly definition sections are aimed at people who intend to work on projects bigger than a weekend prototype, and the project structure rant — group by feature, not by file type — is the kind of thing most courses quietly get wrong. Lucian's take is that this session and the previous one are the two most valuable in the course, and he's not being modest about it.

Part of the full curriculum. Course overview.