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 (scenes/*.json) |
| 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 |
| 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 | Temporal Kit (Timer, Flow, …) — poll edges, no yield |
| 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 gamedefineComponentmay carry its own.scripttoo — one script slot per component instance,dataseeded from that component's fields. - Engine capabilities are
hi.ComponentRender,hi.ComponentPhysics,hi.ComponentCamera,hi.ComponentLight, … — not bool flags. - Game logic is
defineComponenttypes listed next to capabilities..behaviouris rejected at compile time. - Every entity has a transform. Scenes are
com.hikari.sceneversion1documents; 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 optionaldeinit). - Game modules never hold
Entity*/World*layout. Handles are opaqueEntityIds; calls go throughHostApi(hi.world(),hi.render(),hi.ui(), …).
| Unity | Unreal | Hikari |
|---|---|---|
AddComponent<T>() at runtime | CreateDefaultSubobject / add component | Capabilities + logic on the archetype; runtime spawn uses SpawnDesc + host APIs; instance add for columns via editor/mutation path |
GetComponent<T>() | FindComponentByClass | Typed access via messages / user_data / host; no game-side Entity* |
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 | Component metadata.properties → inspector + JSON user_data |
| 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.
Details: Scenes and gameplay, First entity.
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 (typed VM language) |
| Mixed actor | uncommon | C++ + Blueprint child | First-class: Zig components + Kawa on the same archetype |
Patterns:
Zig-only defineActor(.{ .archetype = "player", .components = .{ hi.ComponentPhysics, PlayerLogic } })
Kawa-only defineActor(.{ .archetype = "prop", .components = .{hi.ComponentRender}, .script = "asset://./scripts/….kawa" })
Hybrid defineActor(.{ .archetype = "player", .components = .{…, PlayerLogic}, .script = "asset://./scripts/player.kawa" })
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, Kawa language, Session services.
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.
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.
Time and routines
Zig has no yield / IEnumerator. Temporal Kit is poll-based state on the entity or subsystem:
| 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 |
Tick with TickContext.dt / total_time each frame. Scene-load waits use readiness snapshots, not coroutines parked on the load op.
Details: Temporal Kit. Motion/tweens: Motion Kit.
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, UI layout, UI widgets, UI and editor.
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, Asset formats.
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, Build and packaging, Editor project selector, Frontends and drivers.
Mental-model checklist
- Archetypes, not free ECS — declare capabilities once; place instances in scene JSON.
- Session ≠ scene — cross-level state on
GameSubsystem/ session scripts. - Opaque handles — game modules call through
HostApi; no sharedEntitylayout with the host. - Zig and Kawa are peers on the bus — prefer messages at language boundaries.
- Poll Temporal / Motion — no hidden coroutine stacks on the hot path.
- Soft assets — missing content loads with placeholders; readiness still advances.
- Document owns Edit — Play is a disposable world built from
SceneDocumentacross ticks; Stop switches back to the retained editor world in one call.
Where to go next
- Tutorials — project → entity → session → UI → messages → Play/Edit
- Architecture — seams and ownership
- Scenes and gameplay / Session services