# Migration from Unity / Unreal

Concept map for teams coming from Unity or Unreal Engine. Canonical behaviour lives in the linked docs; this page only translates vocabulary and mental models.

## Quick map

| Concern | Unity | Unreal | Hikari |
|---|---|---|---|
| Placeable object | GameObject + components | Actor + components | **Actor** (`ActorRef`; `EntityId` is an alias). Archetype + open component list + optional Kawa scripts (entity-level and/or per component) |
| Transform | `Transform` | `RootComponent` / scene component | Always present on every entity |
| Mesh / material | MeshRenderer + Material | StaticMeshComponent + Material slots | `hi.ComponentRender` + scene `components.render` (`mesh`/`material` or multi-part `model`) |
| Rigid body | Rigidbody + Collider | Primitive / Physics body | `hi.ComponentPhysics` + scene `components.physics` |
| Character move | CharacterController / CharacterController2D | Character Movement Component | Capsule CCT flag on physics (`is_character_controller`) |
| Script on object | MonoBehaviour (C#) | Actor Blueprint / C++ | Zig `defineComponent` logic and/or Kawa `.kawa` (may mix) |
| Global / cross-level state | DontDestroyOnLoad, singleton managers | GameInstance, World Subsystems | **`GameSubsystem`** (session services) |
| Level | Scene (`.unity`) | Level / World Partition cell | Scene JSON (`kind: com.hikari.scene`, anywhere under `assets/`) |
| Additive load | Additive scene load | Level streaming / sublevels | `requestSceneLoad(ref, .{ .mode = .additive })` / layers |
| Persistent free spawn | DontDestroyOnLoad object | Spawn outside streaming level | **Loose** actor (`SpawnDesc.layer = .loose`) |
| Prefab | Prefab asset | Blueprint class / DataAsset | `.prefab.json` + variants — [Prefabs](/docs/systems/prefabs.md) |
| Project settings | Project Settings / ScriptableObject | Project Settings / ini / DataAsset | Zig `ProjectConfig` defaults + `configs/*.json` overlays (`hikari.project.json` is identity only) |
| Input | Input System / old Input Manager | Enhanced Input | Named actions in `input_actions.json` → `ActionState` |
| UI (game) | uGUI / UI Toolkit | UMG / Common UI | Immediate-mode `hi.ui()` / `world.ui` |
| UI (editor) | Editor IMGUI / UI Toolkit | Editor Slate / UMG tools | Retained editor chrome (`editor/ui/`) — separate from game UI |
| Messaging | SendMessage / UnityEvents / C# events | Gameplay Tags / Event Dispatchers / interfaces | Language-agnostic **message bus** (`send` / `emit`) |
| Coroutines / delays | `IEnumerator` / `async` UniTask | Latent nodes / timers / async tasks | `hi.Routine` — stackful waits; **Temporal Kit** for simple polled delays |
| Tweens | DOTween / LeanTween | Timeline / custom | **Motion Kit** (`Tween`, `Spring`, …) |
| Asset cook | AssetDatabase / Addressables | UAsset cook / Pak | **Shinra** → `.shin*` + bundles; refs are `asset://` |
| Hot reload (code) | Domain reload / Enter Play Mode options | Live Coding / Hot Reload (limited) | Editor recompiles `libgame` and dlopens it |
| Editor without project | Hub → open/create | Epic Launcher / project browser | Standalone `kaji editor` → **project selector** |

## Entities and components

Hikari uses an **open component list** on each archetype (not Unity’s free-form bag, not a full Flecs/Bevy ECS).

- An entity is authored with `defineActor`: `.components = .{ hi.ComponentRender, hi.ComponentPhysics, MyLogic, … }` plus optional Kawa `.script`. A game `defineComponent` may carry its own `.script` too — one script slot per component instance, `data` seeded from that component's fields.
- Engine capabilities are `hi.ComponentRender`, `hi.ComponentPhysics`, `hi.ComponentCamera`, `hi.ComponentLight`, … — not bool flags.
- Game logic is `defineComponent` types listed next to capabilities. `.behaviour` is rejected at compile time.
- Every entity has a transform. Scenes are **`com.hikari.scene` version `1`** documents; capability params live under `"components": { "render": …, "physics": … }` (open map; no parallel top-level capability fields).
- Logic components expose lifecycle: `awake`, `start`, `update`, `onCollision`, `onMessage`, `onChanged`, `onDestroy` (and optional `deinit`).
- Game modules never hold `Entity*` / `World*` layout. Handles are opaque `EntityId`s; calls go through `HostApi` (`hi.world()`, `hi.render()`, `hi.ui()`, …).

| Unity | Unreal | Hikari |
|---|---|---|
| `AddComponent<T>()` at runtime | `CreateDefaultSubobject` / add component | `target.component(C).add()` / `remove()` for attachable components; embedded membership is fixed |
| `GetComponent<T>()` | `FindComponentByClass` | `actor.get(C)` / `require(C)` inside callbacks; `target.component(C)` for copied field access |
| `GameObject.SetActive` / `activeInHierarchy` | `SetActorHiddenInGame` / tick enable (split) | `hi.world().setActive` — one entity switch; systems AND it with component switches |
| `Behaviour.enabled` / `Collider.enabled` | component tick / collision enable | `hi.world().setPhysicsActive` (`components.physics.is_active`) |
| `Renderer.enabled` | `SetVisibility` | `hi.render().setVisible` (`components.render.is_visible`) |
| Serialize fields on MonoBehaviour | UPROPERTY on Actor/Component | Supported component data fields → inspector and scene `components`; metadata customizes presentation |
| Prefab instance overrides | Blueprint defaults + instance | Scene JSON `components` blocks override params; Zig archetype declares the component list |

Entity off + physics/render on still means no sim/draw (effective enable). Component getters return the authored switch, not the AND. Full table: [Scenes and gameplay — Active / enable trio](/docs/systems/scenes-and-gameplay.md#active--enable-trio).

Details: [Scenes and gameplay](/docs/systems/scenes-and-gameplay.md), [First entity](/docs/tutorials/first-entity.md).

## Scripts: Zig vs Kawa vs mix

| Role | Unity | Unreal | Hikari |
|---|---|---|---|
| Primary compiled gameplay | C# assemblies | C++ modules | **Zig** game module (`hikari_game` SDK) |
| Designer / hot script | same C# (or Bolt/Visual Scripting) | Blueprints | **Kawa** (optional type annotations) |
| Mixed actor | uncommon | C++ + Blueprint child | **First-class**: Zig components + Kawa on the same archetype |

Patterns:

```text
Zig-only     defineActor(.{ .archetype = "player", .components = .{ hi.ComponentPhysics, PlayerLogic } })
Kawa-only    defineActor(.{ .archetype = "prop", .components = .{hi.ComponentRender}, .script = "asset://./scripts/prop" })
Hybrid       defineActor(.{ .archetype = "player", .components = .{…, PlayerLogic}, .script = "asset://./scripts/player" })
Component    defineComponent(.{ .name = "patrol", .script = "asset://./scripts/patrol", .data = struct { speed: f32 = 2 } })
```

Component scripts are the closest analogue to a MonoBehaviour / ActorComponent with a Blueprint body: Zig owns the typed data column, the script owns behaviour, and the same component (with its script) can sit on any archetype that lists it. Engine capability builtins and `.swarm` components cannot declare `.script`.

| Concern | Prefer |
|---|---|
| Hot path, typed APIs, CCT locomotion, HostApi surface | Zig logic component |
| Iteration on props, simple AI, cinematic props, designer edits | Kawa actor script |
| Reusable scripted behaviour with typed inspector fields, shared across archetypes | Kawa component script (`defineComponent.script`) |
| Cross-scene HUD, progress, load orchestration | Zig `GameSubsystem` and/or **session** Kawa script |
| Actor ↔ actor gameplay signals | Message bus (works Zig↔Kawa) |

Session scripts are **not** MonoBehaviours on a DontDestroyOnLoad object and not Level Blueprints glued to a map. Attach once from the subsystem; they survive replace unload. `Actor.*` is nil in session scope.

Details: [Scripting with Kawa](/docs/guides/scripting-kawa.md), [Kawa language](/docs/reference/kawa-language.md), [Session services](/docs/systems/session-services.md).

## Gameplay features

### Session vs scene (GameInstance / DontDestroyOnLoad)

| Need | Unity habit | Unreal habit | Hikari |
|---|---|---|---|
| HUD / progress / audio façade | DontDestroyOnLoad GameManager | GameInstance / subsystem | `GameSubsystem` hooks (`onLaunch`, `onTick`, scene load hooks) |
| Level content | Scene objects | Level Actors | Scene JSON entities |
| “Keep this across additive unload” | DDOL | Spawn in persistent level | `hi.world().spawn` with `SpawnDesc.layer = .loose` (host: `spawnLoose`) |

Do **not** put immortal manager entities in every scene JSON.

### Scenes and levels

| Unity | Unreal | Hikari |
|---|---|---|
| `LoadScene` (Single) | Open level / travel | `requestSceneLoad` (**replace**) |
| `LoadScene` (Additive) | Load stream level | `requestSceneLoad(ref, .{ .mode = .additive })` (**layer**, keyed by path) |
| Unload additive | Unload stream | `requestSceneUnloadLayer` |
| AsyncOperation progress | Latent load % | `sceneLoadSnapshot` + **readiness stages** (`none` → `entities` → `assets` → `gpu` → world `all`) |

Show-the-scene gates on `ready_stage == .all`, not merely “load queue idle”. Soft-missing assets do not block readiness (error cube / pink material). GPU stage also waits for deferred texture residency + map rebind push (not only mesh create queue empty).

### Physics and characters

| Unity | Unreal | Hikari |
|---|---|---|
| Rigidbody dynamic/kinematic/static | Simulate physics / move | `BodyType`: Dynamic / Kinematic / Static / Trigger |
| OnCollision / OnTrigger | Hit / Overlap events | Zig `onCollision` (and messaging from there) |
| CharacterController.Move | CMC + capsule | Physics CCT: set desired velocity; read grounded / ceiling / ground normal |
| Physics.Raycast | LineTrace | `hi.world().raycast` (plus host overlap helpers) |

Physics is a replaceable backend (Tenkai3D today); game code uses engine contracts, not middleware types.

Details: [Physics](/docs/systems/physics.md), [Input](/docs/systems/input.md).

### Messaging and actor access

| Unity | Unreal | Hikari |
|---|---|---|
| `SendMessage` / `BroadcastMessage` | Interface calls / Event Dispatcher | `hi.actors.sendEvent` / `emitEvent` (Zig); `Actors.send` / `emit` (Kawa) |
| `GameObject.Find` + `GetComponent` | `FindActor` + component access | `tryFind` + `target.component(Health).has()` / `get(.hp)` (Zig); `Actors.has` / `get_number` (Kawa) |
| ScriptableObject event channels | Gameplay Message Router | Same bus; names interned; ≤4 typed args |
| C# event on a known type | Cast + call | Messages at boundaries; same-actor Zig uses `ActorContext.get` |
| `TWeakObjectPtr` / null checks | Weak ptr / `IsValid` | Weak `ActorRef` + `isAlive` (never pins unload) |

Deferred flush after entity updates (Zig components with `onMessage`, then all Kawa `on_message` slots). Cross-language by design.

Details: [Actor communication](/docs/systems/entity-messaging.md).

## Time and routines

For straight-line gameplay use [hi.Routine](/docs/guides/concurrency.md): `try routine.wait(seconds)` suspends while preserving local variables and nested calls. `actor.startRoutine(function)` queues it; `hi.world().cancelRoutine(handle)` requests cancellation. Temporal Kit is the smaller poll-based alternative:

| Unity | Unreal | Hikari Temporal |
|---|---|---|
| `WaitForSeconds` | `Delay` latent / timer | `Timer` / `Flow.wait` |
| `yield return null` | next-tick latent | `Flow.waitFrame` |
| `WaitUntil` | wait-until latent | `Flow.waitUntil` |
| `StartCoroutine` / `StopCoroutine` | Async task handle | `flow.start()` / `flow.stop()` / `flow.goto` |
| cooldown fields | cooldown in Ability System | `Cooldown` |

Call `flow.tick(tick.dt)` once per owner update, then poll `flow.wait(step, seconds)` and the other phase helpers. The first positive wait consumes that update's time budget; subsequent waits cannot reuse it. `waitFrame` waits for another owner update, including throttled updates. Scene-load waits use readiness snapshots.

`flow.start()` only resets caller-owned state. Managed routines use the separate [Routine API](/docs/guides/concurrency.md); waits propagate cancellation through `try` so cleanup runs.

Details: [Temporal Kit](/docs/systems/temporal-kit.md). Motion/tweens: [Motion Kit](/docs/systems/motion-kit.md).

## UI

| Concern | Unity | Unreal | Hikari |
|---|---|---|---|
| Game HUD / menus | Canvas / UI Toolkit | UMG Widget | Immediate UI from entity `update` or session tick via `hi.ui()` |
| Layout | RectTransform / USS | Anchors / panels | `vStack` / `hStack` / `flow` / `zStack`, `Length` (points / percent / fill) |
| Theme | USS / theme SO | Style assets | `Theme` + density `Environment` |
| Editor tools | EditorWindow | Editor Utility / Slate | Retained `editor/ui/` — **not** the same API surface as game UI |
| Animation | Animator / DOTween on UI | UMG animations | Motion Kit drivers sampled into styles |

Game modules get the `hi.ui()` widget set (stacks, text, buttons, fields, sliders, tabs, lists, grids, modals, rich text, world anchors; not trees, split panes, popups, or drag-drop). Host/editor owns the full `UiContext`. One flattened UI draw call at end of frame.

In the editor, Play remaps game UI into the viewport; chrome stays outside. Session HUD belongs on `GameSubsystem`, not on every scene actor.

Details: [User interface](/docs/guides/user-interface.md), [UI layout](/docs/guides/ui-layout.md), [UI widgets](/docs/guides/ui-widgets.md), [UI and editor](/docs/systems/ui-and-editor.md).

## Assets and materials

| Unity | Unreal | Hikari |
|---|---|---|
| FBX import → Mesh + Materials | FBX → Static Mesh + slots | glTF/GLB → Shinra → `.shinmodel` + `.material.json` + `.model.json` |
| Multi-material renderer | Material slot array on mesh | Multi-part placeable: one actor, `parts[]`, N draws |
| Addressables / Resources | Soft object pointers / Pak | `asset://<pack>/<path>` or `asset://./<path>`; soft refs with placeholders |
| AssetDatabase refresh | Content Browser cook | Shinra watch / editor daemon; Play defers some reloads |

Details: [Assets and Shinra](/docs/systems/assets-and-shinra.md), [Asset formats](/docs/systems/asset-formats.md).

## Project, editor, and build

| Unity | Unreal | Hikari |
|---|---|---|
| `.unity` project + Packages | `.uproject` + modules | `hikari.project.json` + Zig `ProjectConfig` |
| Editor Play mode | PIE | Editor **Play** / **Edit**; Stop restores **document**, not last play scene |
| Player build | Packaged project | `kaji game … --package` |
| Editor install + open project | Editor + `.uproject` | `kaji editor` (selector) or `kaji editor --project=` |
| asmdef / plugin DLL | game module DLL | Thin SDK + dynamic `libgame` in editor; optional monolithic game product |

Game code in the editor always loads dynamically (recompile / dlopen). `--type=monolithic|dynamic` selects host/driver linkage, not “bake game into editor”.

Details: [Project file](/docs/guides/project-file.md), [Build and packaging](/docs/guides/build-and-packaging.md), [Editor project selector](/docs/systems/editor-project-selector.md), [Frontends and drivers](/docs/systems/frontends-and-drivers.md).

## Mental-model checklist

1. **Actors and components** — define reusable compositions; attachable components can also be added or removed per instance.
2. **Session ≠ scene** — cross-level state on `GameSubsystem` / session scripts.
3. **Opaque handles** — game modules call through `HostApi`; no shared `Entity` layout with the host.
4. **Zig and Kawa are peers on the bus** — prefer messages at language boundaries.
5. **Choose Temporal / Motion or Routine** — small polled values for simple timing; explicit stack budgets for suspendable functions.
6. **Soft assets** — missing content loads with placeholders; readiness still advances.
7. **Document owns Edit** — Play is a disposable world built from `SceneDocument` across ticks; Stop switches back to the retained editor world in one call.

## Where to go next

1. [Tutorials](/docs/tutorials.md) — project → entity → session → UI → messages → Play/Edit  
2. [Architecture](/docs/systems/architecture.md) — seams and ownership  
3. [Scenes and gameplay](/docs/systems/scenes-and-gameplay.md) / [Session services](/docs/systems/session-services.md)
