Scripting
Your scripts are the game.
Every .cs file under the project folder is compiled, except trees named bin, obj, .git, .vs, and any subfolder that has its own .csproj. Scripts/ is convention, not a boundary. Files outside the project folder are never compiled.
The editor compiles in memory when you open a project, when the window regains focus, when the Add Component list opens, when a script is dropped on the Inspector, and when a script is renamed or deleted. Unchanged sources are not recompiled. A failed compile keeps the last good assembly; the Console lists the compiler errors.
A build checks the scripts, then compiles them into the player. No script DLL is loaded at runtime. Do not reference editor assemblies (WillEngineEditor, ImGui.NET, Silk.NET) outside the project’s Editor/ folder — that folder compiles in the editor and fails the build.
Usings
The compile already imports System, System.Collections.Generic, System.IO, System.Linq, System.Net.Http, System.Threading, and System.Threading.Tasks. Add using System.Numerics; for vectors, and a using for each engine namespace you touch, such as WillEngine.Core.Components.
Lifecycle
There is no Awake, Start, Update, FixedUpdate, or OnDestroy. The overrides are these, and they run only while PlayState.IsPlaying:
| Callback | When |
|---|---|
Initialize() | Once per instance, the first time it is attached while playing, before the first OnEnabled. Runs even if the component or object is inactive. |
OnEnabled() | Each time the component becomes effectively active: Enabled and the object and its parents are active. |
OnDisabled() | Each time it leaves that state: disabled, deactivated, removed, or the scene unloaded. Only if OnEnabled had run. |
OnUpdate(float deltaTime) | Every game step while effectively active. deltaTime is GameTime.StepSeconds. |
Order on Play is parents before children, components in list order: Initialize, then OnEnabled. On Stop it is the reverse. AddComponent during play runs Initialize before it returns, so fields you set after that call are not visible inside Initialize. Scene loads finish building the scene before any callback.
Exceptions in Initialize, OnEnabled, and OnDisabled are logged and the game continues. An exception in OnUpdate propagates; the editor marks that object.
Objects
Create objects with scene.CreateGameObject(name) so they are already in the scene. new GameObject() is sceneless until you parent it into a scene. Owner.RemoveComponent(this) and Owner.Scene?.RemoveGameObject(Owner) remove immediately. There is no deferred destroy.
GetComponent<T>() matches base classes and interfaces. The array overloads allocate; the List<T> overloads do not. SetParent is cycle-safe. Spawn a prefab with Owner.InstantiatePrefab(guid).
What gets saved
A public field, a public property with a public getter and setter, or a non-public member marked [SerializeField] is scene data. Get-only properties, static, readonly, and delegates are not. [HideInInspector] still saves and stays off the Inspector. [TextArea] makes a string a multi-line box.
Enums save by name. List<T> and arrays are the editable collections. Dictionary keys that round-trip are string, int, enum, bool, and Guid. Asset references are GUID strings, or the typed Sound and Sequence structs. Object references are GameObject or Component fields you pick in the Inspector.
Keep runtime state private. It is never written.
Hot reload
A recompile during Play migrates each live component onto the new type by member name and shape, including fields typed as your own script classes. Renaming a serialized member parks the old value in LostValues. Renaming a class makes existing scenes load that component as a missing script. Shipping components need a public parameterless constructor.
OnUpdate runs every step. Reuse lists and arrays there. The engine’s own hot paths allocate nothing once they are warm.