From 346edc043210918752d4897151fb943de63902ac Mon Sep 17 00:00:00 2001 From: Sebastian Bularca Date: Thu, 18 Jun 2026 11:45:43 +0200 Subject: [PATCH] Adedd proper readme text --- README.md | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 37580a7..6c95d6b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,187 @@ -# trail-into-darkness +# Trail into Darkness (Nox) -The repo for the game prototype A Trail Into Darkness \ No newline at end of file +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.