# Trail into Darkness (Nox) A party-based exploration RPG demo built in **Unity 6 (6000.3.7f1)** with URP. The player travels a world map, manages supplies and time, and resolves randomized encounters. The codebase is the focus here — the gameplay is a vertical slice meant to exercise the architecture. > This is a portfolio demo. It showcases a hand-rolled application framework, clean separation of concerns, and a set of reusable in-house packages — not a finished game. --- ## At a Glance - **Engine:** Unity 6, Universal Render Pipeline - **Input:** new Input System - **Asset loading:** Addressables (everything content-facing is loaded by address/reference) - **Config:** data-driven via `ScriptableObject` settings assets - **Language:** C# 9+ (nullable enabled in core gameplay) - **Localization, Test Framework, Memory Profiler** wired in --- ## Goals The architecture is built around a few deliberate goals, aimed at production workflows for small-to-medium teams: - **Scene-independent.** Logic does not depend on a particular scene being set up by hand. Scenes are data; the framework boots and wires itself regardless of where you press Play. - **Core abstracted from gameplay.** Gameplay coders work inside relatively isolated, modular "feature cocoons" against clean interfaces, without needing to understand boot, platform, or state-management internals. - **Built to extend.** Adding or modifying features — new play modes, systems, or DLC-style content — should be additive and low-risk, not a rewrite. - **Testable by design.** POCO (Plain Old CLR Object — a plain C# class with no `MonoBehaviour`/engine dependency) logic and interface boundaries keep systems coverable by both editor tests and play-mode tests, decoupled from the engine. - **Team-friendly.** Code lives in C# and small assets rather than large monolithic scenes/prefabs, reducing the asset-locking and merge conflicts that stall multi-person teams. - **Swappable systems.** Concrete implementations sit behind interfaces (save, platform, input, camera, transitions, …) so any one system can be replaced without touching its callers. --- ## Architecture The project runs on a custom state machine with a **single Unity update driver**. Almost all gameplay logic lives in plain C# classes (POCOs) that are ticked manually — `MonoBehaviour` is used only where Unity forces it. ### Boot flow ``` Boot (RuntimeInitializeOnLoadMethod) ├─ platform initialization (early, pre-Initializer) └─ instantiates "Initializer" Addressable └─ EntryPoint (MonoBehaviour) ├─ loads InitializerSettingsFile + BootstrapReferences ├─ selects + initializes IPlatform (Desktop / UnityEditor) ├─ builds GameDataState + the IGameState lookup └─ spawns GameStateRunner ── drives everything ``` > **Platform initialization runs in both `Boot` and `EntryPoint`.** `Boot` establishes the platform context early so that platform-specific data is available before the Initializer loads, and `EntryPoint` then selects and fully initializes the concrete `IPlatform` for the rest of the runtime. This two-step setup guarantees the correct platform-specific data is provided at every stage of boot. ### Layers - **`Boot` / `EntryPoint`** — entry point and dependency wiring. Loads settings, picks the platform, constructs the game states, hands off to the runner. - **`GameStateRunner`** — the *only* class that uses Unity's `Update`/`FixedUpdate`/`LateUpdate`. Ticks the active state and owns screen-fade transitions between states. - **Game States (`IGameState`)** — top-level application modes: `BootState` (splash), `MainMenu`, `GameMode`. Each implements `EnterGameState / Tick / LateTick / ExitGameState / Dispose`. - **Play Modes (`IPlayMode`)** — gameplay sub-modes living inside `GameMode`: `Adventure`, `Town`, `Rest`, `Combat`, `PauseMenu`. `GameModeGameState` handles switching, caching, and pause suspend/resume. ### How the state machines run There are two nested state machines, and at any moment **exactly one branch of each is live**: - `GameStateRunner` keeps a single *active* `IGameState` and ticks only that one. The others simply exist in a lookup and consume nothing. - Inside `GameMode`, `GameModeGameState` keeps a single *active* `IPlayMode` and ticks only that one. Inactive modes are kept in a cache (or suspended), dormant, until switched back in. This keeps each part **isolated and self-contained** — a state or play mode owns its own setup, update, and teardown, and never reaches into another's lifecycle. It also means **no resources are spent on ticks that don't matter**: - Nothing ticks until it reports ready (`IsGameStateInitialized` / `IsGameModeInitialized`); half-loaded systems stay idle instead of running against missing data. - Ticks are frozen entirely during fade transitions, so nothing runs mid-swap. - Pausing **suspends** the underlying play mode rather than tearing it down — it stops ticking but keeps its state, so resume is instant and allocation-free. - Switching play modes reuses the cached instance when possible, avoiding reload/re-alloc churn. The result is one predictable update per frame: one state, one play mode, with everything else parked. ### Design principles - **One update loop.** All logic is driven from `GameStateRunner` via manual ticks — predictable order, no scattered `Update()` methods. - **Constructor injection.** States and play modes receive their dependencies explicitly; no service locators or singletons in gameplay. - **Data-driven.** Behavior is configured through `ScriptableObject` settings (`*Settings`, `*References`) loaded as Addressables, not hardcoded. - **Interface-first.** `IGameState`, `IPlayMode`, `IPlatform`, `ICameraController`, `ISceneTransition`, save-system interfaces — concrete types are swappable. - **Platform abstraction.** `IPlatform` + `PlatformSelector` isolate input/platform differences (Desktop vs. Editor). - **Allocation-aware.** Uses `ZLinq` for allocation-free LINQ on hot paths. --- ## UI architecture The UI follows the same philosophy as the rest of the codebase: **logic lives in plain C#, `MonoBehaviour` is only a thin bridge to the scene.** Each screen is split into two pieces: - **`*References` (MonoBehaviour)** — a dumb scene-authoring component that just holds serialized links to the actual widgets (buttons, images, TMP texts, containers). No logic. Examples: `GuiReferences`, `PauseMenuReferences`, `MainMenuReference`. - **`*View` (POCO, implements `IMenuView`)** — the view logic. It receives its references and the data it renders via constructor injection, and exposes `Initialize / Show / Hide / Tick`. Examples: `AdventureView`, `PauseMenuView`, `PartyGuiView`. Key points: - **Manually ticked.** Views are driven by their owning play mode (e.g. `AdventurePlayMode.Tick()` calls `adventureView.Tick()`) — consistent with the single-update-loop model, no hidden `Update()` methods. - **One-way coupling.** UI never reaches into game logic directly; it requests changes through `GameDataState` (e.g. a button calls `ChangePlayMode(PlayMode.PauseMenu)`). State drives UI, UI signals intent. - **Lazy, Addressable-loaded.** UI prefabs are loaded by address and instantiated on demand (`PauseMenuView` pulls `PauseMenuPrefabs`); references already present in the scene are reused via `FindFirstObjectByType`. - **Allocation-aware updates.** Views update only what changed — `AdventureView` rewrites text only when day/time changes; `PartyGuiView` uses a fixed-size **slot pool** (activate/deactivate, never instantiate per frame) and rebuilds only when party composition changes. - **Data-driven popups.** Rich tooltips/popups are composed through a builder (`PopupContentBuilder`) backed by the `Jovian.PopupSystem` package, so content is declared, not hand-wired per widget. - Built on **uGUI + TextMeshPro**. --- ## In-house packages (`Packages/com.jovian.*`) Reusable systems are split into local packages, each with Runtime/Editor (and some Tests) assembly definitions: | Package | Purpose | |---|---| | `save-system` | Save/load with JSON or obfuscated binary, slots, file-system storage | | `encounter-system` | Encounter tables, registries, quest progress/log | | `zone-system` | Spatial zones used to trigger encounters during travel | | `calendar` | World clock, day phases, in-game time | | `popup-system` | Categorized UI popups | | `ingame-logging` / `logger` | In-game event log + general logging | | `tag-system` | Lightweight tagging | | `inspector-tools` / `assets-history` / `unitypackagesync` | Editor tooling | | `utilities` | Shared helpers | Third-party: `ZLinq` (zero-alloc LINQ), `ayellowpaper.serialized-dictionary`. --- ## Project layout ``` Assets/ Code/ Core/ Boot, EntryPoint, GameStateRunner, IGameState, settings GameState/ Camera/ camera controller + settings Entities/ characters, parties, stats, perks, modifiers (factories + registries) PlayModes/ Adventure, Town, Rest, Combat, Pause + encounters, map, time UI/ views and GUI references Input/ IInput, desktop/editor input + settings Platform/ IPlatform, desktop/editor platforms + settings SplashMainMenuUI/ splash + main menu views Util/ BootMode (editor), SceneReference editor Scenes/ Startup, MainMenu, Adventure, Combat Art/ map, UI, animations Packages/ in-house com.jovian.* packages + third-party ``` --- ## Running the project 1. Open with **Unity 6000.3.7f1**. 2. Press Play — boot behavior depends on the boot mode (below). Default, play from any scene. ### Boot modes — start from any scene The project can launch from any scene. This is controlled by the **`Nox/Boot`** editor menu, which sets a `BootType`: - **Full Boot** — loads the `Startup` scene first, then the normal boot chain. Closest to a real build. - **Scene Boot** — instantiates the `Initializer` directly into the *current* scene, so you can press Play on `Adventure`, `MainMenu`, etc. and boot in place. - **Unity Default** — no boot injection; plays the scene as-is (raw Unity behavior). > In a player build, the full boot chain always runs. ### Scene authoring (`SceneReference`) Each standalone-playable scene carries a `SceneReference` component declaring its entry point: - `gameState` — which `IGameState` the scene belongs to (e.g. `GameMode`) - `playMode` — which `IPlayMode` to start in (e.g. `Adventure`) `EntryPoint` reads this on boot to start in the right state. If a scene has no `SceneReference`, it defaults to `BootState`. --- ## Notes for reviewers - Start with `EntryPoint.cs` → `GameStateRunner.cs` → `GameModeGameState.cs` to follow the control flow top-down. - `AdventurePlayMode.cs` is the most representative gameplay file — it shows how a play mode composes handlers (movement, time, encounters, inventory, UI) and integrates the in-house packages. - Save/load round-trips through `NoxSaveData` + `Jovian.SaveSystem`. --- ## Status This is a **vertical slice**. The application framework, boot/state flow, save system, and adventure loop are in place, but: - The **combat system is not implemented** — `Combat` exists as a play mode hook only. - **Design data is still minimal** — zones, encounters, encounter tables, and related content are placeholders rather than authored, balanced data. The intent is to demonstrate the architecture and systems, with content authoring and combat as the next steps. --- ## License **Copyright © 2026 Sebastian Bularca. All Rights Reserved.** This repository is proprietary. It is shared for **evaluation purposes only** (e.g. portfolio or hiring review): you may view and clone it, but you may **not** reproduce, modify, distribute, or reuse any part of it. See [LICENSE](LICENSE) for full terms.