Compare commits

...
Author SHA1 Message Date
lennartandClaude Opus 5 6bf22f2d6e fix two wrong expectations in the locomotion math tests
Both were test bugs; MotionInputMath itself is correct.

WorldToCameraRelativeInput_Yaw90 expected +1 for the camera-relative x. A camera
yawed +90 degrees faces +X, so world +Z is to its left and the value is -1. The
conversion applies Quaternion.Euler(0, -cameraYaw, 0) and Unity's Y rotation is
clockwise viewed from above, so forward maps to -right. The round-trip test
already pinned this convention and passes precisely because of it.

ScreenDeltaToWorldPan compared Vector3 with Assert.AreEqual, which is exact.
-10f * 0.02f lands one ulp from the -0.2f literal, so a correct result failed a
comparison that printed as identical at two decimals. Now compared per component
with a tolerance.

EditMode suite is 39/39.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 19:50:25 +02:00
lennartandClaude Opus 5 5da782d21b add agent reference and restructure plan
CLAUDE.md documents repository layout, the Unity command line, the dialogue
content pipeline, validation, conventions, and known traps. AGENTS.md points at
it for non-Claude agents.

docs/restructure-plan.md covers both halves of the restructure: moving character
and voice prose out of JSON string fields into a writer-facing writing/ tree, and
migrating off the single-scene DialogueTest scaffold into Bootstrap/Systems/Level
scenes. Phase 0 is recorded as executed; everything from Phase 1 on is proposal.

Two findings in it were corrected after first being got wrong: the Unity CLI is
installed (just not on PATH for non-interactive shells) and does have build and
test commands, and the binary scene is not a stale save but a standing Unity
behaviour where Force Text converts only when the setting changes.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 19:12:49 +02:00
lennartandClaude Opus 5 4f97fc704c check in the DialogueTest scene and add it to the build settings
This is the scene the camera rig, UI layer, player control, and dialogue work in
the preceding commits were built against: 14 roots covering Room_101, the
Dialogue and Skill systems, the three UI canvases, the Cinemachine brain and
camera, the reveal volume, navigation, and the camera anchor.

Committed as-is, in binary, deliberately. The file is a valid Unity scene and
opens intact, but it is serialized as binary while the project is set to Force
Text, so it is not diffable or mergeable. Converting it needs the Asset
Serialization setting to be re-applied from the Editor UI, which cannot be done
from a batch-mode run — the setter does not trigger a reserialize pass. Checking
it in binary is strictly better than leaving days of scene wiring untracked;
the conversion is a follow-up, tracked in docs/restructure-plan.md.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 19:09:52 +02:00
lennartandClaude Opus 5 5b0827072b suppress world interaction while a menu panel is open
DialogueInteractable, DialogueTransitionController, and DialogueUIVisibility are
reworked against the new UI layer: visibility is driven by UIStateController
rather than each component toggling canvases itself, which removes the
duplicated show/hide logic the two had accumulated.

NPCStandIn now ignores the interact button while UIStateController reports a
menu open, so pressing interact through an open panel no longer starts a
conversation behind it.

The yarnproject change is graph-editor node positions only.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 19:04:16 +02:00
lennartandClaude Opus 5 ccabd3e7ac wire player control setup and tune the third-person controller
Editor/PlayerControlSetup.cs is the one-shot menu that assembles the playable
player: the armature, the Cinemachine rig, the NavMesh agent, and the pointer
input wiring, so a scene can be brought up to a controllable state repeatably
rather than by hand.

StarterAssets input actions gain the pointer and interaction bindings the new
locomotion and interaction layers read, and PlayerArmature is updated to match.

ThirdPersonController blends the animator toward target speed scaled by input
magnitude. Previously a partial stick deflection or a click-to-move arrival
played a slowed run clip via MotionSpeed alone; scaling the blend makes those
use the walk cycle instead.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 19:04:01 +02:00
lennartandClaude Opus 5 cba87dcb31 add UI layer: dialogue stream, HUD, player menu, and skills panel
Runtime UI under Scripts/UI: a UIStateController owning menu/dialogue state, a
PlayerControlLock so opening a panel suppresses world input, panel visibility
helpers, and the menu input binding.

Views: the dialogue stream presenter and its options presenter and message row,
the portrait HUD, and the player menu with character, journal, and tab-bar
views. Skills gains SkillsPanelView and SkillRowView, which is why the Skills
asmdef now references UnityEngine.UI.

Prefabs/UI holds the authored canvases (dialogue stream, HUD, player menu) and
their rows. UILayerBootstrap remains a hand-added runtime fallback for scenes
where those prefabs are not instantiated; it deliberately does not auto-spawn.

Editor/UILayerSetup.cs is the one-shot menu that builds and wires those prefabs
into a scene, with UILayerSetupAutoRun reminding after a script reload.

Art/UI/Placeholder carries the placeholder portrait the HUD falls back to.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 19:03:42 +02:00
lennartandClaude Opus 5 932cd0582f add camera rig, click-to-move locomotion, and click interaction
Camera: Cinemachine-based rig with an anchor, a state director, point-of-
interest framing, pan input, and dialogue framing volumes.

Player: click-to-move over the NavMesh, a pointer input layer, and a motion
arbiter that reconciles direct stick input with navigation-driven movement.
The pure math is isolated in NightclubArcadia.Locomotion.Math with an EditMode
suite, so the arbitration rules are testable without a scene.

Interaction: IClickInteractable plus a bridge that routes pointer clicks to it.

Supporting project settings: adds the Cinemachine package, the Interactable and
Ground tags the raycasts depend on, and NavMesh agent dimensions matched to the
player capsule (radius 0.3, height 1.8, climb 0.25).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 19:03:18 +02:00
lennart 1ef1720bae fixed detour stacking and improper option gating 2026-08-16 14:50:38 +02:00
lennart bea880a999 added a path to SC102 from SC101 2026-08-16 13:52:48 +02:00
lennart 6f2225f911 fleshed out the party guests 2026-08-15 21:51:53 +02:00
lennart fea0a29d10 added the table and an interactable chair 2026-08-15 19:09:20 +02:00
lennart f89781d78b implemented NPC system and working bartender conversation 2026-08-15 19:02:01 +02:00
lennart be63c21e15 The player character movement is disabled during dialogue 2026-08-15 16:14:02 +02:00
lennart 86614f3104 fixed player character in room, test successful 2026-08-15 15:37:56 +02:00
lennart 38ba2b0eff Added player character and step in/out of dialogue in scene 101 2026-08-15 15:22:47 +02:00
lennart a42f25d5fa updated gitignore in preperation of adding new packages 2026-08-15 15:22:19 +02:00
lennart 14f542c8a1 downgrade aborted, staying 6.5 2026-08-15 14:38:32 +02:00
lennart af6bc82c67 pre 6.3-downgrade checkpoint 2026-08-15 14:21:51 +02:00
lennart e5f41bde07 implemented SC-101 2026-08-15 13:45:07 +02:00
lennart 57bc10ba79 updated Candidate System with Voice System 2026-08-15 13:25:04 +02:00
lennart 22b8c7a438 integrated candidate system 2026-08-15 13:01:04 +02:00
2189 changed files with 195671 additions and 388 deletions
+7
View File
@@ -108,6 +108,13 @@ yarn-error.log*
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
# ---------------------------------------------------------------------------
# .NET tooling under tools/ (Unity's own Library/ ignores live in the Unity
# project's .gitignore and don't reach this far up)
# ---------------------------------------------------------------------------
tools/**/bin/
tools/**/obj/
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# AI / agent tooling # AI / agent tooling
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
+64
View File
@@ -0,0 +1,64 @@
{
"cSpell.enableFiletypes": [
"yarnspinner"
],
"cSpell.languageSettings": [
{
"languageId": "yarnspinner",
"patterns": [
{
"name": "yarn-commands",
"pattern": "/<<[^>]*>>/g"
},
{
"name": "yarn-expressions",
"pattern": "/\\{[^}]*\\}/g"
},
{
"name": "yarn-variables",
"pattern": "/\\$[a-zA-Z_][a-zA-Z0-9_]*/g"
},
{
"name": "yarn-markup",
"pattern": "/\\[[^\\]]*\\]/g"
},
{
"name": "yarn-hashtags",
"pattern": "/#[a-zA-Z_][a-zA-Z0-9_:-]*/g"
},
{
"name": "yarn-node-markers",
"pattern": "/^(---|===)$/gm"
},
{
"name": "yarn-headers",
"pattern": "/^(title|tags|position|color|colorID|tracking|custom|style):.*/gm"
},
{
"name": "yarn-character-names",
"pattern": "/^\\s*[A-Za-z_][A-Za-z0-9_]*(?=:)/gm"
},
{
"name": "yarn-comments",
"pattern": "/\\/\\/.*$/gm"
}
],
"ignoreRegExpList": [
"yarn-commands",
"yarn-expressions",
"yarn-variables",
"yarn-markup",
"yarn-hashtags",
"yarn-node-markers",
"yarn-headers",
"yarn-character-names",
"yarn-comments"
]
}
],
"cSpell.words": [
"Cassian",
"Thal",
"Vaselian"
]
}
+10
View File
@@ -0,0 +1,10 @@
# AGENTS.md
This repository's agent instructions live in [`CLAUDE.md`](./CLAUDE.md). It is
tool-agnostic — repository layout, the Unity command line, the dialogue content
pipeline, validation, conventions, and known traps.
Read `CLAUDE.md` before making changes, regardless of which agent you are.
Design and migration plans are in [`docs/`](./docs/), most relevantly
[`docs/restructure-plan.md`](./docs/restructure-plan.md).
+361
View File
@@ -0,0 +1,361 @@
# Nightclub Arcadia — agent reference
Reference for coding agents (Claude Code and others) working in this repository.
Human-facing design docs live in `docs/`. This file is the operational one: where
things are, what is generated, and how to build/test/validate from the terminal.
Companion: `AGENTS.md` (pointer file, same content applies).
---
## 1. Repository layout
Repo root is `nightlcub-arcadia` (the typo is in the directory name, not a mistake
to fix — it is the checkout path). The Unity project is a **subdirectory**:
```
/Users/lennart/Dev/nightlcub-arcadia/ # git root
├── CLAUDE.md, AGENTS.md # this reference
├── docs/ # design + plan docs (markdown)
├── tools/YarnCheck/ # .NET 9 Yarn compiler/player, no Unity needed
├── .gitignore # macOS / IDE / toolchain rules
└── NightclubArcadia/ # ← the Unity project (-projectPath)
├── .gitignore # Unity rules (anchored, must stay here)
├── Assets/
├── Packages/ # incl. embedded dev.yarnspinner.unity
└── ProjectSettings/
```
**Two `.gitignore` files, deliberately.** The Unity one anchors every rule with a
leading `/`, so it only works at the root of the Unity project folder. Do not merge them.
Unity **6000.5.8f1**, URP 17.5.0, Cinemachine 3.1.7, Input System 1.20.0,
AI Navigation 2.0.14, ProBuilder 6.1.2, Test Framework 1.7.0. Yarn Spinner is an
**embedded package** at `NightclubArcadia/Packages/dev.yarnspinner.unity` (plus the
`textanimator` addon) — it is committed, not resolved from a registry.
---
## 2. Ground rules
1. **Never hand-edit generated `.asset` files.** `Assets/Skills/Definitions/*.asset`,
`Assets/Skills/SkillDatabase.asset`, `Assets/Candidates/Definitions/*.asset` and
`Assets/Candidates/CandidateDatabase.asset` are *build output*. Their sources are
`Assets/Skills/skill_bible.json` and `Assets/Candidates/candidates.json`. Edits are
overwritten by the Editor setup scripts. Edit the JSON.
2. **Never hand-edit scene or prefab YAML unless you have read the surrounding block.**
GUID/fileID surgery in `.unity` / `.prefab` files silently detaches components.
3. **One Unity instance per project path.** Unity refuses to open a project in batch
mode while the Editor has it open — a single instance at a time. Always check before
any `-batchmode` invocation (§3.1).
4. **Prefer YarnCheck over Unity for dialogue validation.** It is seconds, not minutes,
and needs no Editor lock (§5.3).
5. **Do not commit on the user's behalf** unless asked. Unity writes to tracked files
(`ProjectSettings/*`, `.meta`, scenes) as a side effect of simply being open, so
`git status` noise is expected and is not always yours.
---
## 3. Unity from the command line
### 3.0 Two different tools, both present
| | What it is | Status |
|---|---|---|
| `/Users/lennart/.unity/bin/unity` | The **Unity CLI** (v1.0.0-beta.6). Wraps the Editor for CI-shaped work: `build`, `test`, `run`, `open`, plus editor/license/cloud management. | Installed. **Prefer this.** |
| `/Applications/Unity/Hub/Editor/6000.5.8f1/Unity.app/Contents/MacOS/Unity` | The **Editor executable**. Raw `-batchmode` flags. | Installed. Fallback, for flags the CLI does not expose. |
The CLI is **not on `PATH` for non-interactive shells** — `which unity` fails even though the
binary exists, and `unity doctor` reports `check.binary-on-path warn`. Always invoke it by
absolute path, or export it:
```bash
export UNITY_CLI="/Users/lennart/.unity/bin/unity"
export UNITY="/Applications/Unity/Hub/Editor/6000.5.8f1/Unity.app/Contents/MacOS/Unity"
export UNITY_PROJECT="/Users/lennart/Dev/nightlcub-arcadia/NightclubArcadia"
```
`unity doctor --no-banner` confirms the environment: signed in via OAuth keyring, editor
`6000.5.8f1` arm64 resolved from `ProjectVersion.txt`. Add `--json` to any command for
machine-readable output and `--no-banner` to keep logs clean.
### 3.1 Always check the lock first
Only one Unity instance per project path. Check before any headless run:
```bash
pgrep -fl "Unity.app/Contents/MacOS/Unity" | grep -v AssetImportWorker
```
`AssetImportWorker*` processes are children of the main Editor — filter them out or you will
misread the check. `Temp/UnityLockfile` and `unity status` are the other two signals.
### 3.2 Run tests
```bash
"$UNITY_CLI" test "$UNITY_PROJECT" --no-banner --mode EditMode \
--output /tmp/editmode-results.xml \
--report-format both --junit-output /tmp/editmode-junit.xml
```
Useful flags: `--filter <pattern>`, `--retries <0-10>` (reports tests that pass on retry as
flaky), `--rerun-failed`, `--shard N/M` for parallel CI, `--coverage`, `--timeout <seconds>`.
`--mode PlayMode` runs the PlayMode suite. **There are none yet** — `Assets/Tests/` is EditMode
only, so that command reports zero tests. A PlayMode suite needs a new
`Assets/Tests/PlayMode/*.asmdef` referencing `UnityEngine.TestRunner` with `includePlatforms`
left empty.
Raw-Editor equivalent, if you need a flag the CLI does not expose:
```bash
"$UNITY" -batchmode -nographics -projectPath "$UNITY_PROJECT" \
-runTests -testPlatform EditMode \
-testResults /tmp/editmode-results.xml -logFile /tmp/unity-editmode.log
```
**Do not add `-quit` to a `-runTests` run** — the runner terminates the Editor itself, and
`-quit` on top of async work can hang it.
**Parse the results XML; do not trust the exit code.** Unity's own docs state there is no common
exit-code definition across the components under test. Read `total`/`passed`/`failed` off the
root element and list any `test-case` whose `result` is not `Passed`.
### 3.3 Build a player
```bash
"$UNITY_CLI" build "$UNITY_PROJECT" --no-banner \
--target StandaloneOSX -o /tmp/NightclubArcadia.app
```
Without `--execute-method` the CLI uses Unity's built-in build — a Build Profile on Unity 6+, or
the legacy desktop player flags — and builds whatever scenes `EditorBuildSettings` enables.
`--profile <name>` selects a profile from `Assets/Settings/Build Profiles` (the profile defines
the target). `-l/--log-file` overrides the log path; the log streams to console unless `--no-tail`.
**No build script exists yet.** When one is added, put it at `Assets/Editor/BuildPipeline.cs` and
call `--execute-method NightclubArcadia.Editor.BuildPipeline.BuildMacOS`. The CLI forwards the
output path as `-buildOutput` and **your method is responsible for honouring it**. A build method
must call `EditorApplication.Exit(1)` on failure, or Unity exits 0 and CI reports a green build
that produced nothing.
### 3.4 Run an arbitrary Editor method
```bash
"$UNITY_CLI" run "$UNITY_PROJECT" --no-banner --timeout 900 -- \
-executeMethod SomeClass.SomeStaticMethod
```
**`unity run` owns `-batchmode`, `-nographics`, `-quit` and `-logFile`.** Passing any of them
after `--` fails with a reserved-flag error. Pass only your own arguments.
Output does **not** stream to stdout. The log lands at `$UNITY_PROJECT/Logs/Editor.log` (the
previous run rotates to `Editor-prev.log`), so tag your `Debug.Log` calls and grep for them:
```bash
grep -n "\[YourTag\]" "$UNITY_PROJECT/Logs/Editor.log" | tail -20
```
This is the mechanism for the JSON → ScriptableObject regeneration in
`Assets/Editor/SkillSystemSetup.cs` and `CandidateSystemSetup.cs`. Read the actual class and
method names out of those files before invoking — do not guess them.
## 4. The dialogue content pipeline
### 4.1 Where content lives
```
NightclubArcadia/Assets/Dialogue/
├── NightclubArcadia.yarnproject # globs **/*.yarn — new files need no registration
├── Common.yarn # every <<declare>>, project-wide. Never played.
├── Commentary.yarn # passive skill barks, one node per (context, skill)
├── Scenes/ SC101, SC102, Debug
├── Characters/ Bartender, Bradford Kane, Chevalier Cassian Thal
└── Objects/ Chair
```
**Dialogue is already plain text in the repo.** It is authored in `.yarn` files, not inside
the Unity Editor, and any new `.yarn` file dropped anywhere under `Assets/Dialogue/` is picked
up automatically by the `**/*.yarn` glob in the `.yarnproject`. Adding a scene or a character
requires **no C#, no Unity, and no `.yarnproject` edit**.
The one caveat: the `.yarnproject` also stores `editorOptions.yarnScriptEditor.projectGraphPositions`
— per-file x/y coordinates for the Yarn graph editor. These churn whenever anyone opens the
graph view. Treat that block as editor state; do not fight over it in review.
### 4.2 The variable contract
Every variable used anywhere must be `<<declare>>`d in `Common.yarn`. This is the whole point
of that file: the compiler then catches `$reputaton` instead of silently creating a second
variable at runtime. Adding a scene flag means adding a `<<declare>>` line — that is a writer
action, not a code action.
Naming families that are load-bearing (enforced or lint-checked):
| Family | Meaning | Checked by |
|---|---|---|
| `$self_lean_<candidate_id>` | identity-lean counter | `CandidateYarnLintTests` — id must exist in `candidates.json` |
| `$found_<clue_id>` | clue coverage | `CandidateYarnLintTests` — id must exist in `candidates.json` |
| `$visited_<location_id>` | first arrival | convention; `unlockFlag` in `candidates.json` may only name these |
| `$errand_<errand_id>` | errand state | same |
| `$seen_<scene_id>` | scene witnessed | convention |
| `$check_*` | written by `<<check>>` | **never `<<set>>` by hand** |
### 4.3 Writer-facing Yarn vocabulary
Commands and functions registered by game code, i.e. the full set a writer may use:
| Syntax | Defined in | Notes |
|---|---|---|
| `<<check <skill> <Band>>>` | `Skills/Yarn/YarnSkillCommands.cs` | writes `$check_result`, `$check_roll`, `$check_total`, `$check_dc`, `$check_modifier`, `$check_degree`, `$check_skill` |
| `<<skill_mod <source> <skill> <n>>>` | same | `source` = bucket key, e.g. `scene`, `drunk` |
| `<<clear_skill_mods <source>>>` | same | |
| `skill_rank(<skill>)` | same | function |
| `top_skill(a, b, …)` | `Dialogue/DialougueSkillComparison.cs` | **returns its FIRST argument on a tie** — argument order is a design decision, not incidental |
| `alias_name(<name>)` | `Dialogue/AliasNameGenerator.cs` | deterministic; `$player_alias` is a smart variable derived from it |
| `<<enter_environment>>` | `Dialogue/DialogueTransitionController.cs` | hands control back to the world |
(The filename `DialougueSkillComparison.cs` is misspelled in the repo. Left alone deliberately —
renaming it changes a `.meta` GUID. Fold it into a rename batch, not a drive-by fix.)
### 4.4 Flow control discipline
`<<detour>>` returns to the caller; `<<jump>>` does not and clears the return stack.
The rule the existing files follow, and which new content must follow:
- A node started directly by `DialogueInteractable` has nothing waiting on it → its branches
`<<jump>>`, and each destination calls `<<enter_environment>>` itself.
- A sub-conversation that must resume its caller → `<<detour>>`, and **never** `<<jump>>` inside it.
Nodes sharing a `title:` form a **node group**; Yarn runs the most specific variation whose
`when:` conditions pass. Prefer adding a variation over growing an `<<if>>` chain.
### 4.5 How content reaches Unity
There is **no manual sync step** for dialogue. The chain is:
```
.yarn file saved → Unity's YarnProjectImporter recompiles the .yarnproject on focus
→ DialogueRunner plays the new nodes on next Play
```
The only things that need a human/Editor step are the *data* files, not the dialogue:
```
skill_bible.json → Tools ▸ Nightclub Arcadia ▸ (SkillSystemSetup) → Skills/Definitions/*.asset + SkillDatabase.asset
candidates.json → Tools ▸ Nightclub Arcadia ▸ (CandidateSystemSetup) → Candidates/Definitions/*.asset + CandidateDatabase.asset
```
Those setup scripts also register a `[DidReloadScripts]` callback, so in practice the assets
regenerate on script reload too. The JSON is the source of truth either way.
### 4.6 Code-owned vs writer-owned
| Writer-owned (edit freely, no Unity) | Code-owned (needs an engineer) |
|---|---|
| `Assets/Dialogue/**/*.yarn` | `Assets/Scripts/**/*.cs` |
| `<<declare>>` lines in `Common.yarn` | `Assets/Editor/**/*.cs` |
| `Assets/Skills/skill_bible.json` (prose fields) | Yarn command/function *implementations* |
| `Assets/Candidates/candidates.json` (`status`, `sceneId`, `notes`, `unlockFlag`) | `.asset`, `.prefab`, `.unity`, `.meta` |
| `docs/` writing docs | `ProjectSettings/**` |
**A writer must never need to open Unity or touch C# to write or revise a scene, a character,
or a branch.** If a writing task requires either, that is a pipeline bug — file it.
---
## 5. Validation
### 5.1 EditMode test suite
`NightclubArcadia/Assets/Tests/EditMode/` — 7 files, ~870 LOC, two assemblies:
- `NightclubArcadia.Skills.Tests` — `SkillRosterTests`, `SkillCheckSystemTests`, `DcBandTests`,
`CommentaryPickerTests`, `CandidateRegistryTests`, `CandidateYarnLintTests`
- `NightclubArcadia.Locomotion.Math.Tests` — `MotionInputMathTests`
`CandidateYarnLintTests` is a **regex lint over Yarn source**: it asserts every
`$self_lean_*` / `$found_*` in any `.yarn` file resolves to a real id in `CandidateDatabase`.
It is a tripwire, not a proof.
### 5.2 One-click in-Editor run
`Tools ▸ Nightclub Arcadia ▸ Run Skill EditMode Tests` (`Assets/Editor/SkillSystemTestRunner.cs`).
### 5.3 YarnCheck — validate dialogue without Unity ⭐
`tools/YarnCheck` is a .NET 9 console app that compiles **and plays** the Yarn scripts using
the exact Yarn Spinner DLLs shipped in `Packages/dev.yarnspinner.unity/Runtime/DLLs`, so it
can never drift from the compiler the game uses. This is the fastest correctness signal in the
repo and it needs no Editor lock. Use it on every dialogue change.
```bash
cd /Users/lennart/Dev/nightlcub-arcadia/tools/YarnCheck
# compile everything — exit 1 + diagnostics on failure
dotnet run -- ../../NightclubArcadia/Assets/Dialogue
# compile, then play a node, choosing option 2, 2, 0, 0
dotnet run -- ../../NightclubArcadia/Assets/Dialogue Bartender_Talk 2 2 0 0
```
Leftover picks restart the node — that deliberately models the player walking away and coming
back, which is how these scenes are actually played.
**What it does not model** (read traces with this in mind): Unity commands are printed, not
executed, so `<<check>>` / `<<enter_environment>>` / `<<skill_mod>>` are stepped over and
`$check_result` stays `false` — every check-gated branch shows its *failure* side. Yarn
functions are stubbed: `top_skill` mirrors the real first-argument-wins tie behaviour,
`alias_name` returns a placeholder, `skill_rank` returns 0. Variables reset to their
`<<declare>>` defaults each run; there is no save state.
Requires the .NET 9 SDK. `tools/YarnCheck/bin/` and `obj/` are currently **committed** and
should not be — see `docs/restructure-plan.md`.
---
## 6. Code conventions
- Namespaces: `NightclubArcadia.<Area>` — `Skills`, `Dialogue`, `UI`, `UI.Dialogue`,
`UI.HUD`, `UI.Menu`, `Cinematics`, `Interaction`, `Player`, `Player.Math`.
**Exception:** `NPCStandIn.cs` sits in the global namespace. That is a scaffold tell, not a
convention to copy — new runtime types always get a namespace.
- Assembly definitions exist for `NightclubArcadia.Skills` and `NightclubArcadia.Locomotion.Math`
only. Camera, Player, UI, Dialogue, NPCs and Interaction all currently compile into the
default `Assembly-CSharp`. Widening asmdef coverage is planned, not done.
- Comments in this codebase carry *decision history* — why a flag is written at the bottom of a
node rather than the top, why four voices in SC-101 breaks the three-voice cap on purpose.
Match that density. Do not strip such comments as "noise"; they are the design record.
- Editor-only code lives under `Assets/Editor/`. Anything there is stripped from builds and may
reference `UnityEditor`.
---
## 7. Traps
1. **`Assets/Scenes/DialogueTest.unity` is serialized as binary**, and Unity will not convert it
from a headless run. The project is set to Force Text (`m_SerializationMode: 2`) and the API
agrees — `EditorSettings.serializationMode` reads `ForceText` — but Force Text governs
conversion **at the moment the setting changes**, not on every save. A file that entered the
project as binary stays binary. Four approaches were tried and all failed silently:
`AssetDatabase.ForceReserializeAssets` does not rewrite scene files; `OpenScene` +
`MarkSceneDirty` + `SaveScene` to the same path returns `true` and writes nothing, because
`MarkSceneDirty` does not take in batch mode; `SaveScene` to a *new* path writes a fresh file
that is also binary; and re-assigning `serializationMode` (Mixed → ForceText) from script
does not trigger a reserialize pass. **The fix is Editor-UI only:** Project Settings ▸ Editor
▸ Asset Serialization, switch the mode and switch it back, which reserializes on apply.
Until then the scene is not diffable or mergeable. It is committed and intact (14 roots).
2. **`unity run` reserves `-batchmode`, `-nographics`, `-quit`, `-logFile`.** Passing any of them
after `--` is a hard error, and its output does not reach stdout — read `Logs/Editor.log` (§3.4).
3. **`-quit` with `-runTests`** hangs or truncates. Omit it (§3.2).
4. **Exit code alone is not a test verdict.** Parse the results XML (§3.2).
5. **`which unity` fails** in non-interactive shells even though the CLI is installed. Use the
absolute path `/Users/lennart/.unity/bin/unity` (§3.0).
6. **Editor lock** — check before every headless run, filtering out `AssetImportWorker` children (§3.1).
7. **Regenerated assets** — hand edits to `Skills/Definitions/*`, `Candidates/Definitions/*`
and the two `*Database.asset` files are silently discarded (§2.1).
8. **`top_skill` ties** resolve to the first argument. Reordering arguments changes the game.
9. **No `.gitattributes`** — no Unity YAML merge driver, no LFS rule for `Assets/Art/`.
Scene and prefab merges will conflict destructively under branching.
10. **Template leftovers still present**: `Assets/Scenes/SampleScene.unity`, `Assets/Readme.asset`,
`Assets/TutorialInfo/`. `SampleScene` is in the build scene list. Not load-bearing.
+37
View File
@@ -180,3 +180,40 @@ InitTestScene*.unity*
# Source-generator output for attributed commands/functions (regenerated). # Source-generator output for attributed commands/functions (regenerated).
YarnActionRegistration.cs YarnActionRegistration.cs
YarnActionRegistration.cs.meta YarnActionRegistration.cs.meta
# ---------------------------------------------------------------------------
# ProBuilder (com.unity.probuilder, embedded as source under /Packages instead
# of resolving through the registry into Library/PackageCache)
# ---------------------------------------------------------------------------
# Folders ending in "~" are already invisible to the Unity Editor/AssetDatabase
# (no .meta files are ever generated for them), so committing them only bloats
# the repo for content nobody can reference from the project:
# Documentation~/ ~16 MB of package docs
# Samples~/ ~1 MB of optional sample scenes/scripts
/[Pp]ackages/com.unity.probuilder/Documentation~/
/[Pp]ackages/com.unity.probuilder/Samples~/
# ---------------------------------------------------------------------------
# StarterAssets (Unity's ThirdPerson Starter Assets package, imported under
# /Assets/StarterAssets)
# ---------------------------------------------------------------------------
# Vendor onboarding readme + its screenshot, shown once in the Editor's
# "Readme" inspector window. Not referenced by any scene or script.
/[Aa]ssets/StarterAssets/Readme.asset
/[Aa]ssets/StarterAssets/Readme.asset.meta
/[Aa]ssets/StarterAssets/TutorialInfo/
# Vendor PDF manual, not used at runtime.
/[Aa]ssets/StarterAssets/StarterAssets_Documentation_v1.1.pdf
/[Aa]ssets/StarterAssets/StarterAssets_Documentation_v1.1.pdf.meta
# "Playground" demo scene the package ships to showcase the controller.
# Not in EditorBuildSettings and not referenced by any of the project's own
# scenes — includes baked LightingData + reflection probe .exr captures that
# only apply to that demo scene's lighting setup.
/[Aa]ssets/StarterAssets/ThirdPersonController/Scenes/Playground.unity
/[Aa]ssets/StarterAssets/ThirdPersonController/Scenes/Playground.unity.meta
/[Aa]ssets/StarterAssets/ThirdPersonController/Scenes/Playground/
/[Aa]ssets/StarterAssets/ThirdPersonController/Scenes/Playground.meta
/[Aa]ssets/StarterAssets/ThirdPersonController/Scenes/PlaygroundSettings.lighting
/[Aa]ssets/StarterAssets/ThirdPersonController/Scenes/PlaygroundSettings.lighting.meta
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 98c69c750f094ae79e2f9362c4422647
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6270e22570864d7c862434241018328f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bffd4617fb74418f9eb0d0d6378cf79a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ff84645ddce3641b0a7e9c68db56c8db
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1,84 @@
fileFormatVersion: 2
guid: a1b2c3d4e5f60718293a4b5c6d7e8f90
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings: []
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: db0c69a0766e477bb76a94393518cf60
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c4dc6e246e9a462dbae29cd42b16317e, type: 3}
m_Name: CandidateDatabase
m_EditorClassIdentifier:
candidates:
- {fileID: 11400000, guid: af55e04701494a71a0b292d910db3d13, type: 2}
- {fileID: 11400000, guid: 7282093aca2e4d408d4972354ebcd634, type: 2}
- {fileID: 11400000, guid: b6184c723bab4bef8f8fbaaf462a324d, type: 2}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e2fa27d75ae14f498235dd461791765d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 75c54c58c29b4b738e7c0f55e960540b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1f0a63ccd8da4e5ab64dea83c64eeb6a, type: 3}
m_Name: inheritor
m_EditorClassIdentifier:
id: inheritor
displayName: The Inheritor
reading: This was never the PC's own story; a parent or elder relative had standing
or a debt with a faction, which passed without full consent.
intuitionAxis: 3
clues:
- id: inheritor_object
type: 0
sceneId:
unlockFlag: $visited_private_box
status: 0
skillId:
notes: "Object clue \u2014 Planned. Not yet authored."
- id: inheritor_testimony
type: 1
sceneId:
unlockFlag: $errand_ask_about_name
status: 0
skillId:
notes: "Testimony clue \u2014 Planned. Not yet authored."
- id: inheritor_document
type: 2
sceneId:
unlockFlag: $visited_membership_ledger
status: 0
skillId:
notes: "Document clue \u2014 Planned. Not yet authored."
- id: inheritor_intuition
type: 3
sceneId: SC101
unlockFlag: $visited_desk
status: 1
skillId: standing_order
notes: 'SC-101 Self intuition: correcting an etiquette rule never consciously
learned.'
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b6184c723bab4bef8f8fbaaf462a324d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1f0a63ccd8da4e5ab64dea83c64eeb6a, type: 3}
m_Name: journalist
m_EditorClassIdentifier:
id: journalist
displayName: The Journalist
reading: The PC came for a specific person, not a story; 'journalist' is a cover
identity.
intuitionAxis: 2
clues:
- id: journalist_object
type: 0
sceneId:
unlockFlag: $visited_coat_check
status: 0
skillId:
notes: "Object clue \u2014 Planned. Not yet authored."
- id: journalist_testimony
type: 1
sceneId:
unlockFlag: $errand_find_contact
status: 0
skillId:
notes: "Testimony clue \u2014 Planned. Not yet authored."
- id: journalist_document
type: 2
sceneId:
unlockFlag: $visited_press_room
status: 0
skillId:
notes: "Document clue \u2014 Planned. Not yet authored."
- id: journalist_intuition
type: 3
sceneId: SC101
unlockFlag: $visited_desk
status: 1
skillId: facework
notes: 'SC-101 Social intuition: a reaction that lands wrong and can''t yet be
explained.'
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7282093aca2e4d408d4972354ebcd634
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1f0a63ccd8da4e5ab64dea83c64eeb6a, type: 3}
m_Name: made_asset
m_EditorClassIdentifier:
id: made_asset
displayName: The Made Asset
reading: The PC was trained, conditioned, or activated by one of the factions for
a role near the central conference.
intuitionAxis: 0
clues:
- id: made_asset_object
type: 0
sceneId:
unlockFlag: $visited_back_office
status: 0
skillId:
notes: "Object clue \u2014 Planned. Not yet authored."
- id: made_asset_testimony
type: 1
sceneId:
unlockFlag: $errand_door_list
status: 0
skillId:
notes: "Testimony clue \u2014 Planned. Not yet authored."
- id: made_asset_document
type: 2
sceneId:
unlockFlag: $visited_archives
status: 0
skillId:
notes: "Document clue \u2014 Planned. Not yet authored."
- id: made_asset_intuition
type: 3
sceneId: SC101
unlockFlag: $visited_desk
status: 1
skillId: confluence
notes: 'SC-101 Reason intuition: parsing something too fluently, before consciously
trying to.'
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: af55e04701494a71a0b292d910db3d13
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,136 @@
{
"candidates": [
{
"id": "made_asset",
"displayName": "The Made Asset",
"reading": "The PC was trained, conditioned, or activated by one of the factions for a role near the central conference.",
"intuitionAxis": "Reason",
"clues": [
{
"id": "made_asset_object",
"type": "Object",
"sceneId": "",
"unlockFlag": "$visited_back_office",
"status": "Planned",
"skillId": "",
"notes": "Object clue — Planned. Not yet authored."
},
{
"id": "made_asset_testimony",
"type": "Testimony",
"sceneId": "",
"unlockFlag": "$errand_door_list",
"status": "Planned",
"skillId": "",
"notes": "Testimony clue — Planned. Not yet authored."
},
{
"id": "made_asset_document",
"type": "Document",
"sceneId": "",
"unlockFlag": "$visited_archives",
"status": "Planned",
"skillId": "",
"notes": "Document clue — Planned. Not yet authored."
},
{
"id": "made_asset_intuition",
"type": "Intuition",
"sceneId": "SC101",
"unlockFlag": "$visited_desk",
"status": "Authored",
"skillId": "confluence",
"notes": "SC-101 Reason intuition: parsing something too fluently, before consciously trying to."
}
]
},
{
"id": "journalist",
"displayName": "The Journalist",
"reading": "The PC came for a specific person, not a story; 'journalist' is a cover identity.",
"intuitionAxis": "Social",
"clues": [
{
"id": "journalist_object",
"type": "Object",
"sceneId": "",
"unlockFlag": "$visited_coat_check",
"status": "Planned",
"skillId": "",
"notes": "Object clue — Planned. Not yet authored."
},
{
"id": "journalist_testimony",
"type": "Testimony",
"sceneId": "",
"unlockFlag": "$errand_find_contact",
"status": "Planned",
"skillId": "",
"notes": "Testimony clue — Planned. Not yet authored."
},
{
"id": "journalist_document",
"type": "Document",
"sceneId": "",
"unlockFlag": "$visited_press_room",
"status": "Planned",
"skillId": "",
"notes": "Document clue — Planned. Not yet authored."
},
{
"id": "journalist_intuition",
"type": "Intuition",
"sceneId": "SC101",
"unlockFlag": "$visited_desk",
"status": "Authored",
"skillId": "facework",
"notes": "SC-101 Social intuition: a reaction that lands wrong and can't yet be explained."
}
]
},
{
"id": "inheritor",
"displayName": "The Inheritor",
"reading": "This was never the PC's own story; a parent or elder relative had standing or a debt with a faction, which passed without full consent.",
"intuitionAxis": "Self",
"clues": [
{
"id": "inheritor_object",
"type": "Object",
"sceneId": "",
"unlockFlag": "$visited_private_box",
"status": "Planned",
"skillId": "",
"notes": "Object clue — Planned. Not yet authored."
},
{
"id": "inheritor_testimony",
"type": "Testimony",
"sceneId": "",
"unlockFlag": "$errand_ask_about_name",
"status": "Planned",
"skillId": "",
"notes": "Testimony clue — Planned. Not yet authored."
},
{
"id": "inheritor_document",
"type": "Document",
"sceneId": "",
"unlockFlag": "$visited_membership_ledger",
"status": "Planned",
"skillId": "",
"notes": "Document clue — Planned. Not yet authored."
},
{
"id": "inheritor_intuition",
"type": "Intuition",
"sceneId": "SC101",
"unlockFlag": "$visited_desk",
"status": "Authored",
"skillId": "standing_order",
"notes": "SC-101 Self intuition: correcting an etiquette rule never consciously learned."
}
]
}
]
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8255e20527a84dbabb918a5f5ad590d7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,107 @@
// Everything the bartender ever says.
//
// Stack discipline (the player comes back to the bar, so it matters):
// `Bartender_Talk` is the node DialogueInteractable starts, so nothing is
// waiting for it to return. Its branches therefore <<jump>> — they are the last
// thing the node does — and each destination hands control back with
// <<enter_environment>> itself. <<detour>> is only for sub-conversations that
// have to resume the caller; a <<jump>> inside one would clear the return stack.
title: Bartender_Talk
position: -121,-60
---
<<if $spoke_to_bartender>>
<<if $revealed_fugue_to_bartender>>
Bartender: Ah, still hazy? A drink?
<<else>>
Bartender: Ah, {$player_alias}. A drink?
<<endif>>
-> Yes, please.
<<jump SC101_Hook_Drink_Bartender_Talk>>
-> No, I have questions.
<<jump SC101_Hook_Questions_Bartender_Talk>>
<<else>>
<<set $spoke_to_bartender = true>>
Bartender: Ah, {$player_alias}, back again?
Narrator: What does he mean? "Back again?"
-> Back? I have never been here.
<<set $revealed_fugue_to_bartender = true>>
Narrator: The bartender squints with a critical look.
FACEWORK: He will tell on you. This is the face of a man who is not only here to tend the bar, but to watch, to observe. And to report back to his masters.
Bartender: I could have sworn you are {$player_alias}, weren't you with Mr. Doss a few days ago?
<<if $looking_for_doss eq false>>
<<set $looking_for_doss = true>>
Narrator: Mr. Doss?
PROVENANCE: Mr. Doss. We need to find him, appraise him, document our findings, file them, cross-reference them ...
<<else>>
CONFLUENCE: There he is again: Mr. Doss.
<<endif>>
<<set $self_lean_made_asset += 1>>
Bartender: What can I get ya?
-> What can you recommend?
Bartender: Last time you tooke the Estate.
Narrator: You ignore the jab.
<<jump SC101_Hook_Drink_Bartender_Talk>>
-> Whatever.
<<jump SC101_Hook_Drink_Bartender_Talk>>
-> Yes. I'll take the usual.
Narrator: The bartender doesn't even flinch, while taking ingredients from the bar.
PROVENANCE: We cannot remember these substances.
AMNESTY: Is this really our favorite drink?
<<set $self_lean_inheritor += 1>>
<<jump SC101_Hook_Drink_Bartender_Talk>>
-> I am, and I have a few questions.
<<jump SC101_Hook_Questions_Bartender_Talk>>
<<endif>>
===
title: SC101_Hook_Drink_Bartender_Talk
position: 116,222
---
// $had_the_estate_drink is written at the BOTTOM of this node, not here. The whole
// node reads first-glass state — the pour, the taste, and the lean write under the
// answer — and the glass is only recorded once all of it has run. Setting the flag
// up here (as this node used to) leaves the tests below reading a flag this same
// run already flipped, and the lean write can never fire.
Bartender: One Estate coming right up.
<<if $had_the_estate_drink>>
Narrator: He puts another amber drink before you.
<<else>>
Narrator: He puts a deep amber drink before you.
HOUSE POUR: It smells like old wood, oak, which went sour with age, like a library shelf that's been rained on once and dried wrong.
Narrator: You take a sip.
HOUSE POUR: Deceptively soft. Brown sugar, a little burnt. Vanilla that's actually oak-tannin doing an impression of vanilla.
<<endif>>
AMNESTY: Is that the taste of our favorite drink? I cannot remember ...
-> Yes, it is!
<<if $had_the_estate_drink eq false>>
<<set $self_lean_inheritor += 1>>
HOUSE POUR: Long, dry, faintly bitter - gentian root or scorched citrus pith - and it doesn't fade so much as recede, like it's waiting to be poured again.
<<else>>
HOUSE POUR: Long and dry, it recedes again.
<<endif>>
-> I don't think so.
<<if $had_the_estate_drink eq false>>
<<set $self_lean_inheritor -= 1>>
HOUSE POUR: Bitter, at the end. Like it's waiting to be poured again.
<<else>>
HOUSE POUR: Still bitter.
<<endif>>
<<set $had_the_estate_drink = true>>
<<enter_environment>>
===
title: SC101_Hook_Questions_Bartender_Talk
position: -139,224
---
Bartender: Sure, what do you want to know?
-> Question 1
Bartender: Answer 1
-> Question 2
Bartender: Answer 2
-> Nothing.
Bartender: Alright.
<<enter_environment>>
<<stop>>
<<jump SC101_Hook_Questions_Bartender_Talk>>
===
@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 582cd3b2ab9274b8ca0737660a0d6b1a guid: 6b8ac538a619b45b8a197ad048a94505
ScriptedImporter: ScriptedImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}
@@ -1,65 +0,0 @@
// Everything the bouncer ever says.
//
// These nodes share a title, which makes them a node group. Callers just
// `<<detour Bouncer_Talk>>` and Yarn runs the most specific variation whose
// `when:` conditions pass. Adding a new variation later means adding a node,
// not editing a growing <<if>> chain.
//
// Each variation sets $went_inside so the calling scene knows the verdict.
title: Bouncer_Talk
when: once
---
Bouncer: Evening. Name on the list?
You: Depends who's asking.
-> I'm on the list.
<<set $claimed_to_be_on_list = true>>
<<check placement routine>>
<<if $check_result>>
Bouncer: ...you're not on the list.
Bouncer: But it's slow tonight. Go on.
<<set $went_inside = true>>
<<else>>
Bouncer: Nobody's on the list tonight. Especially not you.
<<set $went_inside = false>>
<<endif>>
-> Let me through. <<if skill_rank("undisclosed") >= 2>>
<<check undisclosed hard -1>>
<<if $check_degree == "critical_success">>
Bouncer: ...yeah. Yeah, okay.
<<set $reputation += 2>>
<<set $went_inside = true>>
<<elseif $check_result>>
Bouncer: Fine. Don't make it a thing.
<<set $went_inside = true>>
<<else>>
Bouncer: Try that again and you're talking to the pavement.
<<set $reputation -= 1>>
<<set $went_inside = false>>
<<endif>>
-> Actually, I'll head home.
Bouncer: Smart choice.
<<set $went_inside = false>>
===
title: Bouncer_Talk
when: $is_vip
---
Bouncer: Didn't even see you there. Go on in.
<<set $went_inside = true>>
===
title: Bouncer_Talk
when: $claimed_to_be_on_list
---
Bouncer: Still not on the list, I assume.
Bouncer: Go on, before I change my mind.
<<set $went_inside = true>>
===
title: Bouncer_Talk
when: always
---
Bouncer: Back again.
<<set $went_inside = true>>
===
@@ -0,0 +1,13 @@
// Everything Bradford Kane ever says.
//
// These nodes share a title, which makes them a node group. Callers just
// `<<detour Bradford_Kane_Talk>>` and Yarn runs the most specific variation whose
// `when:` conditions pass. Adding a new variation later means adding a node,
// not editing a growing <<if>> chain.
//
// Each variation sets $went_inside so the calling scene knows the verdict.
title: Bradford_Kane_Talk
---
<<set $spoke_to_bradford_kane = true>>
===
@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: cb0faaa47fe2440d78e49de30f921218 guid: a1ee2125b1dbf4068868b09cbb685aa8
ScriptedImporter: ScriptedImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}
@@ -0,0 +1,150 @@
// Everything Ser Cassian Thal ever says.
//
// These nodes share a title, which makes them a node group. Callers just
// `<<detour Chevalier_Cassian_Thal_Talk>>` and Yarn runs the most specific variation whose
// `when:` conditions pass. Adding a new variation later means adding a node,
// not editing a growing <<if>> chain.
//
// Each variation sets $went_inside so the calling scene knows the verdict.
title: Chevalier_Cassian_Thal_Talk
position: 44,-117
---
<<if $spoke_to_chevalier_cassian_thal>>
Chevalier Cassian Thal: Well met good sir.
<<else>>
<<set $spoke_to_chevalier_cassian_thal = true>>
Narrator: You introduce yourself to the man. He takes measure before bowing his head and tipping his forehead with a half-open fist.
Chevalier Cassian Thal: {$player_name_full}, nice to meet you. I'm Chevalier Cassian Thal of Basel and Cyprus.
THE FLOAT: His name reeks of money. Old money.
<<check provenance Hard>>
<<if $check_result>>
PROVENANCE: Basel? Makes total sense. But which connections does this old codger have to Cyprus?
-> Inquire further about Cyprus.
<<detour SC101_Inquire_Cypurs_Chevalier_Cassian_Thal_Talk>>
-> Leave it be.
<<endif>>
<<endif>>
<<if $saw_knights_atlantean_cross_on_place_card eq true and $had_the_estate_drink eq true>>
<<detour SC101_Hook_Asks_Candidate_Chevalier_Cassian_Thal_Talk>>
<<endif>>
===
title: SC101_Inquire_Cypurs_Chevalier_Cassian_Thal_Talk
position: -175,95
---
<<set $inquired_about_cyprus = true>>
Chevalier Cassian Thal: Oh, a sharp question, {$player_name_full}.
Narrator: The knight laughs.
Chevalier Cassian Thal: I assume you should ask your friend Mr. Doss, I am of no right to reveal this connection.
<<if $looking_for_doss eq false>>
<<set $looking_for_doss = true>>
Narrator: Mr. Doss?
PROVENANCE: Mr. Doss. We need to find him, appraise him, document our findings, file them, cross-reference them ...
<<else>>
CONFLUENCE: There he is again: Mr. Doss.
<<endif>>
===
title: SC101_Hook_Asks_Candidate_Chevalier_Cassian_Thal_Talk
position: 146,85
---
<<if $inquired_about_cyprus>>
Chevalier Cassian Thal: This were a lot of questions. Now tell me something about yourself, old chap.
<<endif>>
<<detour SC101_Asks_Candidate_Chevalier_Cassian_Thal_Talk>>
<<if $declared_candidate_to_chevalier_cassian_thal>>
Narrator: Chevalier Cassian Thal turns towards the head of the table again, just, when another man stands up.
<<jump SC102_Table>>
<<endif>>
===
title: SC101_Asks_Candidate_Chevalier_Cassian_Thal_Talk
position: 157,261
---
<<set $winner = top_skill($self_lean_made_asset, "asset", $self_lean_journalist, "journalist", $self_lean_inheritor, "inheritor")>>
<<set $previous_winner = $winner>>
<<if $winner eq "asset">>
Narrator: He leans in and whispers:
Chevalier Cassian Thal: I assume you are here to observe the gathering in the interest of ... our friends of the Vaselian Concordat?
-> Yes, I am.
<<set $self_lean_made_asset += 1>>
<<detour SC101_Declares_Asset_Chevalier_Cassian_Thal_Talk>>
-> You must be mistaken.
<<set $self_lean_journalist += 1>>
<<set $self_lean_inheritor += 1>>
<<detour SC101_Deny_Candidate_Chevalier_Cassian_Thal_Talk>>
<<elseif $winner eq "journalist">>
Chevalier Cassian Thal: I got the feeling I may read my answers in tomorrow's newspaper.
Narrator: He says it with a boyish grin, as if he is sure he hasn't disclosed anything contentious.
-> You actually may.
<<set $self_lean_journalist += 1>>
<<detour SC101_Declares_Journalist_Chevalier_Cassian_Thal_Talk>>
-> No, I got nothing to do with newspapers and such!
<<set $self_lean_made_asset += 1>>
<<set $self_lean_inheritor += 1>>
<<detour SC101_Deny_Candidate_Chevalier_Cassian_Thal_Talk>>
<<else>>
Chevalier Cassian Thal: You probably look to settle your parent's debts?
FACEWORK: He doesn't seem emotional about it, he is just interested.
-> I actually am.
<<set $self_lean_inheritor += 1>>
<<detour SC101_Declares_Inheritor_Chevalier_Cassian_Thal_Talk>>
-> Oh, I don't know about any debt, I hope they didn't borrow money from you?
<<set $self_lean_journalist += 1>>
<<set $self_lean_made_asset +=1>>
Chevalier Cassian Thal: Oh god, no, they didn't borrow anything from us.
CONFLUENCE: Us?
<<detour SC101_Deny_Candidate_Chevalier_Cassian_Thal_Talk>>
<<endif>>
===
title: SC101_Declares_Asset_Chevalier_Cassian_Thal_Talk
position: -9,670
---
<<set $declared_candidate_to_chevalier_cassian_thal = true>>
Narrator: Chevalier Cassian Thal looks knowingly and cannot hide a little smirk.
Chevalier Cassian Thal: Well, then let us hope the ... others follow the Knight's compromise.
Narrator: He winks at you.
FACEWORK: He seems to know more about us, than we know about ourselves. Though if we back out now he will become suspicious.
===
title: SC101_Declares_Journalist_Chevalier_Cassian_Thal_Talk
position: -222,667
---
<<set $declared_candidate_to_chevalier_cassian_thal = true>>
Narrator: Chevalier Cassian Thal laughs.
Chevalier Cassian Thal: Well, then let me give you something to write about: You and your readers will be surprised to hear, what you will witness in a second.
CONFLUENCE: He can hardly hide his smirk.
===
title: SC101_Declares_Inheritor_Chevalier_Cassian_Thal_Talk
position: 207,664
---
<<set $declared_candidate_to_chevalier_cassian_thal = true>>
Chevalier Cassian Thal: You may very well get your chance this week, my boy.
FACEWORK: His look is benevolent, nearly fatherly.
CONFLUENCE: What should our parents have done, that requires repayment?
===
title: SC101_Deny_Candidate_Chevalier_Cassian_Thal_Talk
position: 436,260
---
Chevalier Cassian Thal: Oh, I am rarely wrong in matters like these. Though enlighten me: What else could have been your wish coming here?
FACEWORK: He looks genuinely interested.
<<set $winner = top_skill($self_lean_made_asset, "asset", $self_lean_journalist, "journalist", $self_lean_inheritor, "inheritor")>>
<<if $previous_winner == $winner>>
-> No, you are totally right, indeed.
<<if $winner == "asset">>
<<detour SC101_Declares_Asset_Chevalier_Cassian_Thal_Talk>>
<<elseif $winner == "journalist">>
<<detour SC101_Declares_Journalist_Chevalier_Cassian_Thal_Talk>>
<<else>>
<<detour SC101_Declares_Inheritor_Chevalier_Cassian_Thal_Talk>>
<<endif>>
-> Actually ...
<<detour SC101_Asks_Candidate_Chevalier_Cassian_Thal_Talk>>
<<else>>
<<detour SC101_Asks_Candidate_Chevalier_Cassian_Thal_Talk>>
<<endif>>
===
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a9f603a6134614e0fa576727d10d487a
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 94073015eacc34c1d8fc6786e43d60ca, type: 3}
@@ -5,6 +5,14 @@
// Writing rules (code does not enforce these): // Writing rules (code does not enforce these):
// - Wrong Truth failures: no failure signal in the prose; the UI banner is the only tell. // - Wrong Truth failures: no failure signal in the prose; the UI banner is the only tell.
// - Documentary routes are gated on Act and errand state, never on a voice. // - Documentary routes are gated on Act and errand state, never on a voice.
//
// TODO: ambient fallback. Every voice in the Skill Bible has one context-free
// PASSIVE VOICE line, and right now those lines have nowhere to live — a voice
// with no Commentary_{context}_{id} node simply stays silent in that context.
// Proposal: when no context node matches an eligible skill, CommentarySystem
// falls back to Commentary_Ambient_{id}. Deferred from the refactor as optional;
// only worth doing if it doesn't complicate the candidate loop.
// See docs/skill-system-refactor-plan.md §5.6.
title: Commentary_Entrance_Queue_facework title: Commentary_Entrance_Queue_facework
--- ---
+88 -10
View File
@@ -18,16 +18,6 @@ title: Declarations
// How much the door staff likes you. // How much the door staff likes you.
<<declare $reputation = 0 as number>> <<declare $reputation = 0 as number>>
// Set when the player has tried the "I'm on the list" line at least once.
<<declare $claimed_to_be_on_list = false as bool>>
// The bouncer's verdict, read by the scene that called him.
<<declare $went_inside = false as bool>>
// Smart variable: derived, not stored. Can never drift out of sync with
// $reputation, so there's nothing to remember to update.
<<declare $is_vip = $reputation > 10>>
// Written by <<check>> (YarnSkillCommands). Never <<set>> these by hand. // Written by <<check>> (YarnSkillCommands). Never <<set>> these by hand.
<<declare $check_result = false as bool>> <<declare $check_result = false as bool>>
<<declare $check_roll = 0 as number>> <<declare $check_roll = 0 as number>>
@@ -36,4 +26,92 @@ title: Declarations
<<declare $check_modifier = 0 as number>> <<declare $check_modifier = 0 as number>>
<<declare $check_degree = "" as string>> <<declare $check_degree = "" as string>>
<<declare $check_skill = "" as string>> <<declare $check_skill = "" as string>>
// Used by top_skill
<<declare $winner = "" as string>>
<<declare $previous_winner = "" as string>>
// Candidate lean. Three permanently-live readings of who the PC is; they are
// not mutually exclusive and never resolve into a canonical identity.
//
// DESIGN DECISION 2026-08-16 — amends the "no code ever reads lean" guardrail in
// docs/candidate-system-implementation-plan.md §6.1: scenes are meant to tilt
// toward one reading and NPCs are meant to act on the current tilt, so that a
// player who thinks they have settled on a path gets challenged on it in the
// next scene. Reading lean through top_skill(...) is therefore sanctioned, as in
// SC101_Asks_Candidate_Chevalier_Cassian_Thal_Talk. top_skill returns its FIRST
// argument on a tie, which is why made_asset is passed first — the Made Asset
// bias on an even score is deliberate, not an accident of argument order.
//
// What the amendment does NOT license: a stored winner. There is still no
// $dominant_candidate, no ground-truth identity field, and nothing in save data
// beyond these three integers. $winner above is scratch, recomputed per use.
<<declare $self_lean_made_asset = 0 as number>>
<<declare $self_lean_journalist = 0 as number>>
<<declare $self_lean_inheritor = 0 as number>>
// Scene witnessed. Convention: $seen_<scene_id>.
<<declare $seen_sc_101 = false as bool>>
// Clue coverage flags. One per registry clue id ($found_<clue_id>). Writers
// <<set>> these at the discovery beat alongside the lean write. Lean tracks
// belief (and can move on a Wrong Truth failure); $found_* tracks coverage.
// No code reads these.
<<declare $found_made_asset_object = false as bool>>
<<declare $found_made_asset_testimony = false as bool>>
<<declare $found_made_asset_document = false as bool>>
<<declare $found_made_asset_intuition = false as bool>>
<<declare $found_journalist_object = false as bool>>
<<declare $found_journalist_testimony = false as bool>>
<<declare $found_journalist_document = false as bool>>
<<declare $found_journalist_intuition = false as bool>>
<<declare $found_inheritor_object = false as bool>>
<<declare $found_inheritor_testimony = false as bool>>
<<declare $looking_for_doss = false as bool>>
<<declare $had_the_estate_drink = false as bool>>
<<declare $found_inheritor_document = false as bool>>
<<declare $found_inheritor_intuition = false as bool>>
// Exploration gating. Clue availability is exploration-gated, never skill-gated.
// Convention: $visited_<location_id> on first arrival; $errand_<errand_id> when
// an errand reaches the state that matters. Writers <<set>> these from scene
// content. unlockFlag fields in candidates.json may only name these families.
<<declare $visited_desk = false as bool>>
<<declare $visited_back_office = false as bool>>
<<declare $visited_archives = false as bool>>
<<declare $visited_coat_check = false as bool>>
<<declare $visited_press_room = false as bool>>
<<declare $visited_private_box = false as bool>>
<<declare $visited_membership_ledger = false as bool>>
<<declare $errand_door_list = false as bool>>
<<declare $errand_find_contact = false as bool>>
<<declare $errand_ask_about_name = false as bool>>
<<declare $spoke_to_bartender = false as bool>>
<<declare $revealed_fugue_to_bartender = false as bool>>
// Player name substitution. Uppercase [PC] in the Skill Bible means the full,
// formal form (STANDING ORDER, PROVENANCE); lowercase [pc] means the familiar
// short form (CONFLUENCE, FACEWORK). Two variables, not one — collapsing them
// loses a characterisation rule the bible states for four separate voices.
// Defaults are placeholders; naming the PC is design's call.
<<declare $player_name = "Vesper" as string>>
<<declare $player_name_full = "Vesper Adaire" as string>>
// Club alias. Smart variable: deterministically derived from $player_name via
// AliasNameGenerator.alias_name (hash-seeded pick from two word pools), so it
// is never in sync-drift and never <<set>> by hand. Same real name always
// yields the same alias; the alias itself bears no resemblance to the input.
<<declare $player_alias = alias_name($player_name) as string>>
// Information about history and symbols
<<declare $sat_down_at_table = false as bool>>
<<declare $saw_knights_atlantean_cross_on_place_card = false as bool>>
// Character dialogue gates
<<declare $spoke_to_chevalier_cassian_thal = false as bool>>
<<declare $spoke_to_bradford_kane = false as bool>>
<<declare $inquired_about_cyprus = false as bool>>
<<declare $declared_candidate_to_chevalier_cassian_thal = false as bool>>
=== ===
@@ -11,5 +11,51 @@
"baseLanguage": "en", "baseLanguage": "en",
"compilerOptions": { "compilerOptions": {
"diagnosticsSeverity": {} "diagnosticsSeverity": {}
},
"projectName": "NightclubArcade",
"authorName": [
"LennartGrabowski"
],
"editorOptions": {
"yarnScriptEditor": {
"projectGraphPositions": {
"Bartender.yarn": {
"x": 282,
"y": 12
},
"Bradford Kane.yarn": {
"x": 1320,
"y": 2287
},
"Chevalier Cassian Thal.yarn": {
"x": 1621,
"y": 2260
},
"Commentary.yarn": {
"x": 12,
"y": 231
},
"Common.yarn": {
"x": 12,
"y": 12
},
"Chair.yarn": {
"x": 866,
"y": 1104
},
"Debug.yarn": {
"x": 14,
"y": 448
},
"SC101.yarn": {
"x": 820,
"y": -3
},
"SC102.yarn": {
"x": 2595,
"y": 2682
}
}
}
} }
} }
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: beedfd387b0d049ffb7534a81d2df45f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,121 @@
// Everything that can be interacted with the chair.
//
// Stack discipline (the player re-enters all of this, so it matters):
// `Chair_Interaction` is the node DialogueInteractable starts, so nothing above
// it is waiting for a return. The menu therefore LOOPS with <<jump>>, which
// replaces the current node and pushes nothing, and only sub-conversations that
// have to come back use <<detour>>. Never <<jump>> out of a detoured node —
// that clears the return stack and the caller never resumes.
//
// The sit-down line lives in its own node so the menu loop can't replay it,
// while a player who stands up and sits down again still gets it.
title: Chair_Interaction
position: -334,-601
---
Narrator: An empty chair with a place card: {$player_name}.
-> Sit down.
<<set $sat_down_at_table = true>>
<<jump SC101_Hook_Sitting_Chair_Interaction>>
-> Not yet.
<<enter_environment>>
===
title: SC101_Hook_Sitting_Chair_Interaction
position: -328,-360
---
Narrator: You sit down between two very distinguished gentlemen.
<<jump SC101_Hook_Menu_Chair_Interaction>>
===
// The loop target. Every menu variation jumps back here when its sub-conversation
// returns, so which variation shows is re-evaluated after every interaction.
title: SC101_Hook_Menu_Chair_Interaction
position: -328,-205
---
<<if $spoke_to_chevalier_cassian_thal eq true and $spoke_to_bradford_kane eq true>>
<<jump SC101_Hook_Both_Chair_Interaction>>
<<elseif $spoke_to_chevalier_cassian_thal eq true and $spoke_to_bradford_kane eq false>>
<<jump SC101_Hook_Left_Not_Right_Chair_Interaction>>
<<elseif $spoke_to_chevalier_cassian_thal eq false and $spoke_to_bradford_kane eq true>>
<<jump SC101_Hook_Right_Not_Left_Chair_Interaction>>
<<else>>
<<jump SC101_Hook_First_Sitting_Chair_Interaction>>
<<endif>>
===
title: SC101_Hook_First_Sitting_Chair_Interaction
position: -320,-51
---
-> Look at the details of the place card.
<<detour SC101_Hook_Place_Card_Chair_Interaction>>
-> Introduce yourself to the gentleman on your left, sitting closer to the head of the table.
<<detour Chevalier_Cassian_Thal_Talk>>
-> Introduce yourself to the gentleman on your right.
<<detour Bradford_Kane_Talk>>
-> Stand up.
<<enter_environment>>
<<stop>>
<<jump SC101_Hook_Menu_Chair_Interaction>>
===
title: SC101_Hook_Left_Not_Right_Chair_Interaction
position: -95,-48
---
-> Look at the details of the place card.
<<detour SC101_Hook_Place_Card_Chair_Interaction>>
-> Talk to Chevalier Cassian Thal.
<<detour Chevalier_Cassian_Thal_Talk>>
-> Introduce yourself to the gentleman on your right.
<<detour Bradford_Kane_Talk>>
-> Stand up.
<<enter_environment>>
<<stop>>
<<jump SC101_Hook_Menu_Chair_Interaction>>
===
title: SC101_Hook_Right_Not_Left_Chair_Interaction
position: 134,-32
---
-> Look at the details of the place card.
<<detour SC101_Hook_Place_Card_Chair_Interaction>>
-> Introduce yourself to the gentleman on your left, sitting closer to the head of the table.
<<detour Chevalier_Cassian_Thal_Talk>>
-> Talk to Bradford Kane.
<<detour Bradford_Kane_Talk>>
-> Stand up.
<<enter_environment>>
<<stop>>
<<jump SC101_Hook_Menu_Chair_Interaction>>
===
title: SC101_Hook_Both_Chair_Interaction
position: 363,-36
---
-> Look at the details of the place card.
<<detour SC101_Hook_Place_Card_Chair_Interaction>>
-> Talk to Chevalier Cassian Thal.
<<detour Chevalier_Cassian_Thal_Talk>>
-> Talk to Bradford Kane.
<<detour Bradford_Kane_Talk>>
-> Stand up.
<<enter_environment>>
<<stop>>
<<jump SC101_Hook_Menu_Chair_Interaction>>
===
title: SC101_Hook_Place_Card_Chair_Interaction
position: -296,331
---
Narrator: You take the place card and take a look.
<<check provenance Hard>>
<<if $check_result>>
<<set $saw_knights_atlantean_cross_on_place_card = true>>
PROVENANCE: What is this?
Narrator: You see a plain equal-armed cross inside a circle embossed in the upper right corner.
PROVENANCE: We are in Switzerland, after all. Though this symbol is not known to be associated with Switzerland. Most peculiar.
<<else>>
Narrator: You see a cross inside a circle embossed in the upper right corner.
PROVENANCE: This is probably related to Switzerland.
<<endif>>
===
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6f6437b77328240e590f205a6605cfcb
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 94073015eacc34c1d8fc6786e43d60ca, type: 3}
@@ -1,6 +1,12 @@
// Scratch node for verifying the skill-check plumbing. Not part of the story. // Scratch nodes for verifying mechanics in isolation. Not part of the story.
// Point DialogueRunner.startNode at "Debug_SkillCheck" to run it, then set it // Point DialogueRunner.startNode at the node you want to run, then set it
// back to "Start" when you're done. // back to "Start" when you're done.
//
// Candidate active-check pattern (docs/candidate-system-implementation-plan.md §5 Phase 3):
// <<check skillId band>>
// <<if $check_result>> success branch writes $found_* + $self_lean_*
// <<else>> failure is a Wrong Truth — equally confident, unmarked in prose,
// and still writes lean (belief, not truth). Banner is the only tell.
title: Debug_SkillCheck title: Debug_SkillCheck
--- ---
@@ -20,3 +26,34 @@ Narrator: Confluence came out {$check_degree}.
<<check nosuchskill routine>> <<check nosuchskill routine>>
Narrator: Survived the bad skill id. Narrator: Survived the bad skill id.
=== ===
// Candidate lean accumulator round-trip. Expect made_asset 2 / journalist 1 /
// inheritor 0, and the threshold branch to fire.
title: Debug_CandidateLean
---
<<set $self_lean_made_asset += 1>>
<<set $self_lean_made_asset += 1>>
<<set $self_lean_journalist += 1>>
Narrator: made_asset {$self_lean_made_asset} / journalist {$self_lean_journalist} / inheritor {$self_lean_inheritor}
<<if $self_lean_made_asset >= 2>>
Narrator: threshold branch reached.
<<endif>>
===
// Candidate active-check pattern. Set SkillRuntime.rollSeed non-zero for a
// SeededRollSource so both branches are reachable deterministically across runs.
// Success feeds made_asset; failure feeds inheritor (Wrong Truth lean — §4.4).
title: Debug_CandidateCheck
---
Narrator: Candidate check harness. Banner is the only pass/fail tell.
<<check confluence hard>>
<<if $check_result>>
CONFLUENCE: One: the card's stock matches the Concordat watermark. Two: the date is a week before the invitation went out. Three — and three is the one — somebody left this for you to find. Which means you were expected.
<<set $found_made_asset_document = true>>
<<set $self_lean_made_asset += 1>>
<<else>>
CONFLUENCE: One: the card's stock matches a wedding invitation from the thirties. Two: the date is your mother's. Three — and three is the one — you already knew the seating. Which means this was never yours to choose.
<<set $self_lean_inheritor += 1>>
<<endif>>
Narrator: lean made_asset {$self_lean_made_asset} / inheritor {$self_lean_inheritor}. Degree {$check_degree}.
===
@@ -1,26 +0,0 @@
// The door. Scene files stay thin: they set the stage, detour into character
// nodes for the actual talking, then branch on the outcome.
//
// `Start` is the DialogueRunner's entry node (set on the Dialogue System
// prefab), so this is where the game begins.
title: Start
---
Narrator: Neon bleeds across wet asphalt. The queue hasn't moved in ten minutes.
<<detour Bouncer_Talk>>
<<if $went_inside>>
<<jump Entrance_Inside>>
<<else>>
<<jump Entrance_Outside>>
<<endif>>
===
title: Entrance_Inside
---
Narrator: The bass hits you like a wall.
===
title: Entrance_Outside
---
Narrator: The night swallows you back up.
===
@@ -0,0 +1,76 @@
// SC-101 — The Card at the Desk. Opening candidate hooks.
//
// Scene files stay thin: set the stage, <<detour>> into hook nodes, return.
//
// Evidence rule: each candidate gets exactly one of each evidence type —
// object, testimony, document, intuition. Preserve that balance; do not
// stack types so trust becomes an artifact of imbalance.
// See Assets/Candidates/candidates.json.
//
// Beat order: hooks fire in sequence from SC101_Desk (STANDING ORDER →
// HOUSE POUR → FACEWORK → CONFLUENCE). Each remains an independently authored
// node. Never <<if>>-guard a hook on another hook having fired, on any
// $self_lean_*, or on skill_rank(...).
//
// Passive pattern (clue-bearing):
// one voice per hook, from the candidate's attached pair;
// the line is that character's argument, never a report;
// exactly one $self_lean_* write; set $found_<clue_id> alongside it.
//
// Four-voice exception: this opening uses four voice introductions (one per
// pair) against the writing handbook's "cap introductions at three voices"
// rule. Deliberate and for SC-101 only — do not "correct" it down to three,
// and do not copy it as a new precedent elsewhere.
//
// Body stays neutral on purpose: no lean, no candidate attachment.
title: Start
position: -17,-276
---
<<jump SC101_Desk>>
===
title: SC101_Desk
position: -19,-36
---
<<set $seen_sc_101 = true>>
<<set $visited_desk = true>>
Narrator: The mahogany doors are too heavy for the hinges someone recently, badly, tightened — you feel the extra half-second of resistance before either side gives. Beyond them: a hall built for four hundred people politely disagreeing about tariffs, hosting perhaps sixty, none of whom look like they've read the programme.
Narrator: Someone left a card with your name on it at the registration desk three hours before you decided to come. You don't remember deciding.
<<detour SC101_Hook_Inheritor>>
<<detour SC101_Hook_Neutral>>
<<detour SC101_Hook_Journalist>>
<<detour SC101_Hook_MadeAsset>>
Narrator: You're drawn toward a seat — toward a waiting conversation you didn't agree to have yet.
<<enter_environment>>
===
title: SC101_Hook_MadeAsset
position: -40,567
---
CONFLUENCE: One: the seating chart's alphabetical. Two: one gap in it, held open past the start time. Three, and three is the one, {$player_name}: whoever printed this knew you'd be late to your own arrival. Which means somebody in this room has already met you, {$player_name}, and you're the only one who doesn't know it.
<<set $found_made_asset_intuition = true>>
<<set $self_lean_made_asset += 1>>
===
title: SC101_Hook_Journalist
position: -299,452
---
FACEWORK: His hands gave it away before his face did — a flinch toward you, checked, turned into reaching for a drink that wasn't there. That's not a stranger's hands. What did you do to earn that flinch?
<<set $found_journalist_intuition = true>>
<<set $self_lean_journalist += 1>>
===
title: SC101_Hook_Inheritor
position: 335,366
---
STANDING ORDER: You are standing in a doorway, {$player_name_full}. That's the whole of what's certain. Build outward from there — a person who starts anywhere else is a person coming apart in public.
<<set $found_inheritor_intuition = true>>
<<set $self_lean_inheritor += 1>>
===
title: SC101_Hook_Neutral
position: 87,310
---
HOUSE POUR: Cigar smoke, spilled aperitif, and under it — cold, sharp — the ammonia bite of a floor mopped in a hurry, love. Someone's trying to make this room smell like money. It smells like panic with good cologne on top.
===
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ef4adc1687e744cd0afd7041ab38c377
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 94073015eacc34c1d8fc6786e43d60ca, type: 3}
@@ -0,0 +1,6 @@
// SC-102 — The figure at the head of the table just stood up.
title: SC102_Table
---
Figure at the head of the table: "Welcome."
===
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 717dc562b961f41c49f5615fb8415f67
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 94073015eacc34c1d8fc6786e43d60ca, type: 3}
@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.IO;
using NightclubArcadia.Skills;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
/// <summary>
/// Regenerates CandidateDefinition assets + CandidateDatabase from candidates.json.
///
/// The JSON is the source of truth; .asset files are build output and hand-edits are lost
/// on script reload by EnsureCandidateAssetsOnly.
/// </summary>
public static class CandidateSystemSetup
{
const string CandidatesFolder = "Assets/Candidates";
const string DefinitionsFolder = "Assets/Candidates/Definitions";
const string JsonPath = "Assets/Candidates/candidates.json";
const string DatabasePath = "Assets/Candidates/CandidateDatabase.asset";
const string AutoSetupSessionKey = "NightclubArcadia.CandidateSystem.AutoSetupDone";
[DidReloadScripts]
static void AutoSetupAfterReload()
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
}
if (SessionState.GetBool(AutoSetupSessionKey, false))
{
return;
}
EditorApplication.delayCall += () =>
{
if (SessionState.GetBool(AutoSetupSessionKey, false))
{
return;
}
SessionState.SetBool(AutoSetupSessionKey, true);
EnsureCandidateAssetsOnly();
};
}
[MenuItem("Tools/Nightclub Arcadia/Set Up Candidate System")]
public static void SetUpCandidateSystem()
{
EnsureCandidateAssetsOnly();
}
/// <summary>
/// Creates/updates ScriptableObject assets without opening a scene.
/// Safe to run on domain reload.
/// </summary>
public static void EnsureCandidateAssetsOnly()
{
if (!TryLoadRoster(out var roster))
{
return;
}
EnsureFolder("Assets", "Candidates");
EnsureFolder("Assets/Candidates", "Definitions");
var definitions = new List<CandidateDefinition>();
var rosterIds = new HashSet<string>(StringComparer.Ordinal);
foreach (var entry in roster)
{
rosterIds.Add(entry.id);
definitions.Add(CreateOrUpdateDefinition(entry));
}
DeleteOrphanDefinitions(rosterIds);
var database = AssetDatabase.LoadAssetAtPath<CandidateDatabase>(DatabasePath);
if (database == null)
{
database = ScriptableObject.CreateInstance<CandidateDatabase>();
AssetDatabase.CreateAsset(database, DatabasePath);
}
database.EditorSetCandidates(definitions);
EditorUtility.SetDirty(database);
AssetDatabase.SaveAssets();
Debug.Log($"[CandidateSystemSetup] Candidate assets ready at {DatabasePath}.");
}
static bool TryLoadRoster(out CandidateEntry[] roster)
{
roster = null;
var text = AssetDatabase.LoadAssetAtPath<TextAsset>(JsonPath);
if (text == null)
{
// JSON may exist on disk before Unity has imported it as a TextAsset.
if (!File.Exists(JsonPath))
{
Debug.LogError($"[CandidateSystemSetup] Missing registry at {JsonPath}.");
return false;
}
text = new TextAsset(File.ReadAllText(JsonPath));
}
CandidateRoot root;
try
{
root = JsonUtility.FromJson<CandidateRoot>(text.text);
}
catch (Exception ex)
{
Debug.LogError($"[CandidateSystemSetup] Failed to parse {JsonPath}: {ex.Message}");
return false;
}
if (root?.candidates == null || root.candidates.Length == 0)
{
Debug.LogError($"[CandidateSystemSetup] {JsonPath} has no candidates.");
return false;
}
foreach (var entry in root.candidates)
{
if (entry == null || string.IsNullOrEmpty(entry.id))
{
Debug.LogError($"[CandidateSystemSetup] {JsonPath} contains a candidate with empty id.");
return false;
}
if (!Enum.TryParse<SkillAxis>(entry.intuitionAxis, ignoreCase: true, out _))
{
Debug.LogError(
$"[CandidateSystemSetup] Candidate '{entry.id}': invalid intuitionAxis '{entry.intuitionAxis}'.");
return false;
}
if (entry.clues == null)
{
continue;
}
foreach (var clue in entry.clues)
{
if (clue == null || string.IsNullOrEmpty(clue.id))
{
Debug.LogError($"[CandidateSystemSetup] Candidate '{entry.id}' has a clue with empty id.");
return false;
}
if (!Enum.TryParse<CandidateEvidenceType>(clue.type, ignoreCase: true, out _))
{
Debug.LogError(
$"[CandidateSystemSetup] Clue '{clue.id}': invalid type '{clue.type}'.");
return false;
}
var status = string.IsNullOrEmpty(clue.status) ? "Planned" : clue.status;
if (!Enum.TryParse<CandidateClueStatus>(status, ignoreCase: true, out _))
{
Debug.LogError(
$"[CandidateSystemSetup] Clue '{clue.id}': invalid status '{clue.status}'.");
return false;
}
}
}
roster = root.candidates;
return true;
}
static CandidateDefinition CreateOrUpdateDefinition(CandidateEntry entry)
{
var path = $"{DefinitionsFolder}/{entry.id}.asset";
var asset = AssetDatabase.LoadAssetAtPath<CandidateDefinition>(path);
if (asset == null)
{
asset = ScriptableObject.CreateInstance<CandidateDefinition>();
AssetDatabase.CreateAsset(asset, path);
}
asset.EditorSet(entry);
EditorUtility.SetDirty(asset);
return asset;
}
static void DeleteOrphanDefinitions(HashSet<string> rosterIds)
{
if (!AssetDatabase.IsValidFolder(DefinitionsFolder))
{
return;
}
var guids = AssetDatabase.FindAssets("t:CandidateDefinition", new[] { DefinitionsFolder });
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var fileName = Path.GetFileNameWithoutExtension(path);
if (rosterIds.Contains(fileName))
{
continue;
}
Debug.Log($"[CandidateSystemSetup] Deleting orphan definition {path}");
AssetDatabase.DeleteAsset(path);
}
}
static void EnsureFolder(string parent, string child)
{
var path = $"{parent}/{child}";
if (!AssetDatabase.IsValidFolder(path))
{
AssetDatabase.CreateFolder(parent, child);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 871be622b3a34b8fb46ce81bb87272d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,316 @@
using NightclubArcadia.Cinematics;
using NightclubArcadia.Interaction;
using NightclubArcadia.Player;
using StarterAssets;
using Unity.AI.Navigation;
using Unity.Cinemachine;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.AI;
/// <summary>
/// Wires player control, navigation, and camera systems into DialogueTest.unity.
/// Run via Tools → Nightclub Arcadia → Set Up Player Control &amp; Camera.
/// </summary>
public static class PlayerControlSetup
{
const string ScenePath = "Assets/Scenes/DialogueTest.unity";
const string PlayerPrefabPath = "Assets/StarterAssets/ThirdPersonController/Prefabs/PlayerArmature.prefab";
const string SettingsFolder = "Assets/Settings";
[MenuItem("Tools/Nightclub Arcadia/Set Up Player Control & Camera")]
public static void SetUp()
{
EnsurePlayerPrefab();
WireScene();
AssetDatabase.SaveAssets();
Debug.Log("[PlayerControlSetup] Player control and camera setup complete.");
}
static void EnsurePlayerPrefab()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
if (prefab == null)
{
Debug.LogError($"[PlayerControlSetup] Missing prefab at {PlayerPrefabPath}");
return;
}
var root = PrefabUtility.LoadPrefabContents(PlayerPrefabPath);
EnsureComponent<PlayerPointerInput>(root);
EnsureComponent<PlayerMotionArbiter>(root);
EnsureComponent<ClickToMoveController>(root);
EnsureComponent<PointerNavigationInput>(root);
EnsureComponent<NavMeshAgent>(root);
var inputs = root.GetComponent<StarterAssetsInputs>();
if (inputs != null)
{
inputs.analogMovement = true;
inputs.cursorLocked = false;
}
var controller = root.GetComponent<ThirdPersonController>();
if (controller != null)
{
controller.LockCameraPosition = true;
}
var clickToMove = root.GetComponent<ClickToMoveController>();
var pointerNav = root.GetComponent<PointerNavigationInput>();
var pointerInput = root.GetComponent<PlayerPointerInput>();
var arbiter = root.GetComponent<PlayerMotionArbiter>();
WireSerializedReference(clickToMove, "brainCamera", null);
WireSerializedReference(pointerNav, "pointerInput", pointerInput);
WireSerializedReference(pointerNav, "clickToMove", clickToMove);
WireSerializedReference(pointerNav, "motionArbiter", arbiter);
PrefabUtility.SaveAsPrefabAsset(root, PlayerPrefabPath);
PrefabUtility.UnloadPrefabContents(root);
}
static void WireScene()
{
var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
var player = GameObject.FindGameObjectWithTag("Player");
if (player == null)
{
Debug.LogError("[PlayerControlSetup] No Player-tagged object found in scene.");
return;
}
EnsureComponent<PlayerPointerInput>(player);
EnsureComponent<PlayerMotionArbiter>(player);
EnsureComponent<ClickToMoveController>(player);
EnsureComponent<PointerNavigationInput>(player);
EnsureComponent<NavMeshAgent>(player);
var inputs = player.GetComponent<StarterAssetsInputs>();
if (inputs != null)
{
inputs.analogMovement = true;
inputs.cursorLocked = false;
}
var controller = player.GetComponent<ThirdPersonController>();
if (controller != null)
{
controller.LockCameraPosition = true;
}
var brainCamera = Camera.main;
var pointerInput = player.GetComponent<PlayerPointerInput>();
var clickToMove = player.GetComponent<ClickToMoveController>();
var pointerNav = player.GetComponent<PointerNavigationInput>();
var arbiter = player.GetComponent<PlayerMotionArbiter>();
WireSerializedReference(clickToMove, "brainCamera", brainCamera);
WireSerializedReference(pointerNav, "brainCamera", brainCamera);
WireSerializedReference(pointerNav, "pointerInput", pointerInput);
WireSerializedReference(pointerNav, "clickToMove", clickToMove);
WireSerializedReference(pointerNav, "motionArbiter", arbiter);
var groundLayer = LayerMask.NameToLayer("Ground");
var interactableLayer = LayerMask.NameToLayer("Interactable");
SetLayerIfExists("Floor", groundLayer);
SetLayerIfExists("Room_101", groundLayer);
SetLayerIfExists("Wall_Back", groundLayer);
SetLayerIfExists("Wall_Front", groundLayer);
SetLayerIfExists("Table", groundLayer);
WireBartender("Bartender", interactableLayer);
WireBartender("Bartender (1)", interactableLayer);
EnsureNavigation(player.transform.position);
EnsureCameraAnchor(player.transform, brainCamera);
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene);
}
static void WireBartender(string objectName, int interactableLayer)
{
var bartender = GameObject.Find(objectName);
if (bartender == null)
{
return;
}
if (interactableLayer >= 0)
{
bartender.layer = interactableLayer;
}
EnsureComponent<ClickInteractableBridge>(bartender);
EnsureComponent<CameraPointOfInterest>(bartender);
}
static void EnsureNavigation(Vector3 sampleOrigin)
{
var navigation = GameObject.Find("Navigation");
if (navigation == null)
{
navigation = new GameObject("Navigation");
}
var surface = EnsureComponent<NavMeshSurface>(navigation);
surface.agentTypeID = 0;
surface.collectObjects = CollectObjects.All;
surface.useGeometry = NavMeshCollectGeometry.RenderMeshes;
var included = 0;
var defaultLayer = LayerMask.NameToLayer("Default");
var groundLayer = LayerMask.NameToLayer("Ground");
if (defaultLayer >= 0)
{
included |= 1 << defaultLayer;
}
if (groundLayer >= 0)
{
included |= 1 << groundLayer;
}
surface.layerMask = included;
var table = GameObject.Find("Table");
if (table != null)
{
var modifier = EnsureComponent<NavMeshModifier>(table);
modifier.overrideArea = true;
modifier.area = NavMesh.GetAreaFromName("Not Walkable");
}
surface.BuildNavMesh();
if (!NavMesh.SamplePosition(sampleOrigin, out _, 2f, NavMesh.AllAreas))
{
Debug.LogWarning("[PlayerControlSetup] NavMesh bake did not cover the player start position.");
}
}
static void EnsureCameraAnchor(Transform followTarget, Camera brainCamera)
{
var vcamObject = GameObject.Find("CinemachineCamera");
if (vcamObject == null)
{
Debug.LogError("[PlayerControlSetup] CinemachineCamera not found.");
return;
}
var vcam = vcamObject.GetComponent<CinemachineCamera>();
var composer = vcamObject.GetComponent<CinemachinePositionComposer>();
var anchorObject = GameObject.Find("CameraAnchor");
if (anchorObject == null)
{
anchorObject = new GameObject("CameraAnchor");
}
anchorObject.transform.position = followTarget.position;
var anchor = EnsureComponent<CameraRigAnchor>(anchorObject);
EnsureComponent<CameraPanInput>(anchorObject);
EnsureComponent<CameraPointOfInterestDirector>(anchorObject);
EnsureComponent<CameraStateDirector>(anchorObject);
WireSerializedReference(anchor, "followTarget", followTarget);
WireSerializedReference(anchor, "composer", composer);
if (vcam != null)
{
vcam.Target.TrackingTarget = anchorObject.transform;
vcam.Priority.Value = 10;
}
var pan = anchorObject.GetComponent<CameraPanInput>();
WireSerializedReference(pan, "brainCamera", brainCamera);
WireSerializedReference(pan, "anchor", anchor);
WireSerializedReference(pan, "stateDirector", anchorObject.GetComponent<CameraStateDirector>());
var poiDirector = anchorObject.GetComponent<CameraPointOfInterestDirector>();
WireSerializedReference(poiDirector, "anchor", anchor);
WireSerializedReference(poiDirector, "stateDirector", anchorObject.GetComponent<CameraStateDirector>());
var stateDirector = anchorObject.GetComponent<CameraStateDirector>();
WireSerializedReference(stateDirector, "anchor", anchor);
EnsureRevealCamera(vcamObject, followTarget);
}
static void EnsureRevealCamera(GameObject followVcam, Transform followTarget)
{
var revealName = "CM_Reveal_Room101";
var revealObject = GameObject.Find(revealName);
if (revealObject == null)
{
revealObject = new GameObject(revealName);
revealObject.transform.SetPositionAndRotation(
followVcam.transform.position + new Vector3(-4f, 2f, -2f),
followVcam.transform.rotation);
}
var revealCam = EnsureComponent<CinemachineCamera>(revealObject);
revealCam.Target.TrackingTarget = followTarget;
revealCam.Priority.Value = 0;
revealCam.Lens.FieldOfView = followVcam.GetComponent<CinemachineCamera>().Lens.FieldOfView;
var volumeObject = GameObject.Find("RevealVolume_Room101");
if (volumeObject == null)
{
volumeObject = new GameObject("RevealVolume_Room101");
volumeObject.transform.position = followTarget.position + new Vector3(0f, 1f, 2f);
}
var box = EnsureComponent<BoxCollider>(volumeObject);
box.isTrigger = true;
box.size = new Vector3(6f, 3f, 4f);
var volume = EnsureComponent<CameraFramingVolume>(volumeObject);
WireSerializedReference(volume, "revealCamera", revealCam);
}
static T EnsureComponent<T>(GameObject target) where T : Component
{
var component = target.GetComponent<T>();
if (component == null)
{
component = target.AddComponent<T>();
}
return component;
}
static void SetLayerIfExists(string objectName, int layer)
{
if (layer < 0)
{
return;
}
var obj = GameObject.Find(objectName);
if (obj != null)
{
obj.layer = layer;
}
}
static void WireSerializedReference(Object target, string propertyName, Object value)
{
if (target == null)
{
return;
}
var so = new SerializedObject(target);
var property = so.FindProperty(propertyName);
if (property != null)
{
property.objectReferenceValue = value;
so.ApplyModifiedPropertiesWithoutUndo();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: eedfabcda58c94099b2ad3e19f141b09
@@ -0,0 +1,818 @@
using System.IO;
using NightclubArcadia.Cinematics;
using NightclubArcadia.Dialogue;
using NightclubArcadia.Skills;
using NightclubArcadia.UI;
using NightclubArcadia.UI.Dialogue;
using NightclubArcadia.UI.HUD;
using NightclubArcadia.UI.Menu;
using StarterAssets;
using TMPro;
using Unity.Cinemachine;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;
using Yarn.Unity;
/// <summary>
/// Builds UI prefabs and wires them into DialogueTest.unity.
/// Run via Tools → Nightclub Arcadia → Set Up UI Layer.
/// </summary>
public static class UILayerSetup
{
const string ScenePath = "Assets/Scenes/DialogueTest.unity";
const string PrefabFolder = "Assets/Prefabs/UI";
const string ArtFolder = "Assets/Art/UI/Placeholder";
// Nested Resources folder: UILayerBootstrap builds the HUD at runtime and
// can only reach the sprite through Resources.Load. Keeping it under
// Art/UI/Placeholder preserves the organisation while making it
// build-safe — an AssetDatabase-only path is null outside the editor.
const string PortraitFolder = ArtFolder + "/Resources";
const string PortraitPath = PortraitFolder + "/portrait_placeholder.png";
const string MessageRowPrefabPath = PrefabFolder + "/DialogueMessageRow.prefab";
const string StreamCanvasPrefabPath = PrefabFolder + "/DialogueStreamCanvas.prefab";
const string HudCanvasPrefabPath = PrefabFolder + "/HudCanvas.prefab";
const string SkillRowPrefabPath = PrefabFolder + "/SkillRow.prefab";
const string MenuCanvasPrefabPath = PrefabFolder + "/PlayerMenuCanvas.prefab";
[MenuItem("Tools/Nightclub Arcadia/Set Up UI Layer")]
public static void SetUp()
{
EnsureFolders();
CreatePortraitPlaceholder();
var messageRow = CreateMessageRowPrefab();
var skillRow = CreateSkillRowPrefab();
var streamCanvas = CreateDialogueStreamCanvasPrefab(messageRow);
var hudCanvas = CreateHudCanvasPrefab();
var menuCanvas = CreatePlayerMenuCanvasPrefab(skillRow);
WireScene(streamCanvas, hudCanvas, menuCanvas);
AssetDatabase.SaveAssets();
Debug.Log("[UILayerSetup] UI layer setup complete.");
}
static void EnsureFolders()
{
EnsureFolder("Assets/Prefabs");
EnsureFolder(PrefabFolder);
EnsureFolder("Assets/Art");
EnsureFolder("Assets/Art/UI");
EnsureFolder(ArtFolder);
EnsureFolder(PortraitFolder);
}
static void EnsureFolder(string path)
{
if (AssetDatabase.IsValidFolder(path))
{
return;
}
var parent = Path.GetDirectoryName(path)?.Replace('\\', '/');
var name = Path.GetFileName(path);
if (!string.IsNullOrEmpty(parent) && !string.IsNullOrEmpty(name))
{
AssetDatabase.CreateFolder(parent, name);
}
}
static void CreatePortraitPlaceholder()
{
if (File.Exists(PortraitPath))
{
return;
}
var tex = new Texture2D(512, 512, TextureFormat.RGBA32, false);
var dark = new Color(0.12f, 0.12f, 0.14f, 1f);
var mid = new Color(0.35f, 0.35f, 0.4f, 1f);
for (var y = 0; y < 512; y++)
{
for (var x = 0; x < 512; x++)
{
var dx = (x - 256) / 256f;
var dy = (y - 280) / 256f;
var inHead = dx * dx + dy * dy < 0.12f;
var inBody = Mathf.Abs(dx) < 0.22f && y < 220 && y > 40;
tex.SetPixel(x, y, inHead || inBody ? mid : dark);
}
}
tex.Apply();
File.WriteAllBytes(PortraitPath, tex.EncodeToPNG());
Object.DestroyImmediate(tex);
AssetDatabase.Refresh();
var importer = AssetImporter.GetAtPath(PortraitPath) as TextureImporter;
if (importer != null)
{
importer.textureType = TextureImporterType.Sprite;
importer.spritePixelsPerUnit = 100;
importer.SaveAndReimport();
}
}
static GameObject CreateMessageRowPrefab()
{
var root = new GameObject("DialogueMessageRow", typeof(RectTransform), typeof(DialogueMessageRow));
var rootRt = root.GetComponent<RectTransform>();
rootRt.sizeDelta = new Vector2(0f, 80f);
var layout = root.AddComponent<VerticalLayoutGroup>();
layout.padding = new RectOffset(12, 12, 8, 8);
layout.spacing = 4f;
layout.childControlHeight = true;
layout.childControlWidth = true;
layout.childForceExpandHeight = false;
layout.childForceExpandWidth = true;
var fitter = root.AddComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
var speakerGo = CreateTmp("Speaker", root.transform, 22f, FontStyles.Bold, TextAlignmentOptions.Left);
var bodyGo = CreateTmp("Body", root.transform, 28f, FontStyles.Normal, TextAlignmentOptions.TopLeft);
var row = root.GetComponent<DialogueMessageRow>();
var so = new SerializedObject(row);
so.FindProperty("speakerText").objectReferenceValue = speakerGo.GetComponent<TMP_Text>();
so.FindProperty("bodyText").objectReferenceValue = bodyGo.GetComponent<TMP_Text>();
so.FindProperty("speakerLabel").objectReferenceValue = speakerGo;
so.ApplyModifiedPropertiesWithoutUndo();
var prefab = PrefabUtility.SaveAsPrefabAsset(root, MessageRowPrefabPath);
Object.DestroyImmediate(root);
return prefab;
}
static GameObject CreateSkillRowPrefab()
{
var root = new GameObject("SkillRow", typeof(RectTransform), typeof(SkillRowView));
var rootRt = root.GetComponent<RectTransform>();
rootRt.sizeDelta = new Vector2(0f, 48f);
var hlg = root.AddComponent<HorizontalLayoutGroup>();
hlg.padding = new RectOffset(8, 8, 4, 4);
hlg.spacing = 12f;
hlg.childAlignment = TextAnchor.MiddleLeft;
hlg.childControlHeight = true;
hlg.childControlWidth = true;
hlg.childForceExpandHeight = true;
hlg.childForceExpandWidth = false;
var stripe = CreateUiImage("Accent", root.transform, new Color(1f, 1f, 1f, 1f));
stripe.GetComponent<LayoutElement>().preferredWidth = 6f;
stripe.GetComponent<LayoutElement>().flexibleWidth = 0f;
var nameGo = CreateTmp("Name", root.transform, 24f, FontStyles.Bold, TextAlignmentOptions.Left);
nameGo.GetComponent<LayoutElement>().flexibleWidth = 1f;
var barGo = new GameObject("Bar", typeof(RectTransform), typeof(Image), typeof(LayoutElement));
barGo.transform.SetParent(root.transform, false);
barGo.GetComponent<Image>().color = new Color(0.2f, 0.2f, 0.22f, 1f);
barGo.GetComponent<LayoutElement>().preferredWidth = 160f;
barGo.GetComponent<LayoutElement>().flexibleWidth = 0f;
barGo.GetComponent<LayoutElement>().preferredHeight = 16f;
var fillGo = CreateUiImage("Fill", barGo.transform, Color.white);
var fillRt = fillGo.GetComponent<RectTransform>();
fillRt.anchorMin = Vector2.zero;
fillRt.anchorMax = Vector2.one;
fillRt.offsetMin = Vector2.zero;
fillRt.offsetMax = Vector2.zero;
var fillImage = fillGo.GetComponent<Image>();
fillImage.type = Image.Type.Filled;
fillImage.fillMethod = Image.FillMethod.Horizontal;
fillImage.fillAmount = 0.2f;
Object.DestroyImmediate(fillGo.GetComponent<LayoutElement>());
var rankGo = CreateTmp("Rank", root.transform, 24f, FontStyles.Normal, TextAlignmentOptions.Right);
rankGo.GetComponent<LayoutElement>().preferredWidth = 40f;
rankGo.GetComponent<LayoutElement>().flexibleWidth = 0f;
var view = root.GetComponent<SkillRowView>();
var so = new SerializedObject(view);
so.FindProperty("nameLabel").objectReferenceValue = nameGo.GetComponent<TMP_Text>();
so.FindProperty("rankLabel").objectReferenceValue = rankGo.GetComponent<TMP_Text>();
so.FindProperty("barFill").objectReferenceValue = fillImage;
so.FindProperty("accentStripe").objectReferenceValue = stripe.GetComponent<Image>();
so.ApplyModifiedPropertiesWithoutUndo();
var prefab = PrefabUtility.SaveAsPrefabAsset(root, SkillRowPrefabPath);
Object.DestroyImmediate(root);
return prefab;
}
static GameObject CreateDialogueStreamCanvasPrefab(GameObject messageRowPrefab)
{
var canvasGo = CreateCanvas("DialogueStreamCanvas", 5);
var canvasGroup = canvasGo.AddComponent<CanvasGroup>();
var panel = new GameObject("Panel", typeof(RectTransform), typeof(Image));
panel.transform.SetParent(canvasGo.transform, false);
var panelRt = panel.GetComponent<RectTransform>();
panelRt.anchorMin = new Vector2(0.66f, 0f);
panelRt.anchorMax = new Vector2(1f, 1f);
panelRt.offsetMin = Vector2.zero;
panelRt.offsetMax = Vector2.zero;
panel.GetComponent<Image>().color = new Color(0.05f, 0.05f, 0.08f, 0.92f);
var scrollGo = new GameObject("Scroll", typeof(RectTransform), typeof(ScrollRect), typeof(Image));
scrollGo.transform.SetParent(panel.transform, false);
Stretch(scrollGo.GetComponent<RectTransform>(), 8f);
scrollGo.GetComponent<Image>().color = new Color(0f, 0f, 0f, 0.01f);
var viewport = new GameObject("Viewport", typeof(RectTransform), typeof(RectMask2D), typeof(Image));
viewport.transform.SetParent(scrollGo.transform, false);
Stretch(viewport.GetComponent<RectTransform>());
viewport.GetComponent<Image>().color = new Color(1f, 1f, 1f, 0.01f);
var content = new GameObject("Content", typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter));
content.transform.SetParent(viewport.transform, false);
var contentRt = content.GetComponent<RectTransform>();
contentRt.anchorMin = new Vector2(0f, 0f);
contentRt.anchorMax = new Vector2(1f, 0f);
contentRt.pivot = new Vector2(0.5f, 0f);
contentRt.sizeDelta = new Vector2(0f, 0f);
var vlg = content.GetComponent<VerticalLayoutGroup>();
vlg.childAlignment = TextAnchor.LowerCenter;
vlg.childControlHeight = true;
vlg.childControlWidth = true;
vlg.childForceExpandHeight = false;
vlg.childForceExpandWidth = true;
vlg.spacing = 6f;
vlg.padding = new RectOffset(4, 4, 8, 8);
var csf = content.GetComponent<ContentSizeFitter>();
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
var messages = new GameObject("Messages", typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter));
messages.transform.SetParent(content.transform, false);
ConfigureVerticalContent(messages);
var options = new GameObject("Options", typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter), typeof(CanvasGroup));
options.transform.SetParent(content.transform, false);
ConfigureVerticalContent(options);
var optionsGroup = options.GetComponent<CanvasGroup>();
optionsGroup.alpha = 0f;
optionsGroup.interactable = false;
optionsGroup.blocksRaycasts = false;
var scroll = scrollGo.GetComponent<ScrollRect>();
scroll.content = contentRt;
scroll.viewport = viewport.GetComponent<RectTransform>();
scroll.horizontal = false;
scroll.vertical = true;
scroll.movementType = ScrollRect.MovementType.Clamped;
var streamPresenter = canvasGo.AddComponent<DialogueStreamPresenter>();
var streamSo = new SerializedObject(streamPresenter);
streamSo.FindProperty("messageRowPrefab").objectReferenceValue = messageRowPrefab.GetComponent<DialogueMessageRow>();
streamSo.FindProperty("messageContainer").objectReferenceValue = messages.GetComponent<RectTransform>();
streamSo.FindProperty("scrollRect").objectReferenceValue = scroll;
streamSo.ApplyModifiedPropertiesWithoutUndo();
var optionPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(
"Packages/dev.yarnspinner.unity/Prefabs/Option Item.prefab");
var optionsPresenter = canvasGo.AddComponent<DialogueStreamOptionsPresenter>();
var optSo = new SerializedObject(optionsPresenter);
if (optionPrefab != null)
{
optSo.FindProperty("optionViewPrefab").objectReferenceValue = optionPrefab.GetComponent<OptionItem>();
}
optSo.FindProperty("optionsContainer").objectReferenceValue = options.transform;
optSo.FindProperty("canvasGroup").objectReferenceValue = optionsGroup;
optSo.ApplyModifiedPropertiesWithoutUndo();
// Start hidden — DialogueTransitionController shows it.
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
var prefab = PrefabUtility.SaveAsPrefabAsset(canvasGo, StreamCanvasPrefabPath);
Object.DestroyImmediate(canvasGo);
return prefab;
}
static GameObject CreateHudCanvasPrefab()
{
var canvasGo = CreateCanvas("HudCanvas", 10);
var portraitSprite = AssetDatabase.LoadAssetAtPath<Sprite>(PortraitPath);
var panel = new GameObject("PortraitPanel", typeof(RectTransform), typeof(CanvasGroup), typeof(Image), typeof(Button));
panel.transform.SetParent(canvasGo.transform, false);
var panelRt = panel.GetComponent<RectTransform>();
panelRt.anchorMin = new Vector2(1f, 0f);
panelRt.anchorMax = new Vector2(1f, 0f);
panelRt.pivot = new Vector2(1f, 0f);
panelRt.sizeDelta = new Vector2(220f, 280f);
panelRt.anchoredPosition = new Vector2(-24f, 24f);
panel.GetComponent<Image>().color = new Color(0.15f, 0.15f, 0.18f, 0.95f);
if (portraitSprite != null)
{
panel.GetComponent<Image>().sprite = portraitSprite;
panel.GetComponent<Image>().preserveAspect = true;
}
var hint = CreateTmp("TabHint", panel.transform, 18f, FontStyles.Normal, TextAlignmentOptions.Center);
var hintRt = hint.GetComponent<RectTransform>();
hintRt.anchorMin = new Vector2(0f, 0f);
hintRt.anchorMax = new Vector2(1f, 0f);
hintRt.pivot = new Vector2(0.5f, 0f);
hintRt.sizeDelta = new Vector2(0f, 28f);
hintRt.anchoredPosition = new Vector2(0f, 4f);
hint.GetComponent<TMP_Text>().text = "Tab";
Object.DestroyImmediate(hint.GetComponent<LayoutElement>());
var view = canvasGo.AddComponent<PortraitHudView>();
var so = new SerializedObject(view);
so.FindProperty("panelRoot").objectReferenceValue = panel;
so.ApplyModifiedPropertiesWithoutUndo();
var prefab = PrefabUtility.SaveAsPrefabAsset(canvasGo, HudCanvasPrefabPath);
Object.DestroyImmediate(canvasGo);
return prefab;
}
static GameObject CreatePlayerMenuCanvasPrefab(GameObject skillRowPrefab)
{
var canvasGo = CreateCanvas("PlayerMenuCanvas", 20);
var rootGroup = canvasGo.AddComponent<CanvasGroup>();
var dimmer = CreateUiImage("Dimmer", canvasGo.transform, new Color(0f, 0f, 0f, 0.6f));
Stretch(dimmer.GetComponent<RectTransform>());
Object.DestroyImmediate(dimmer.GetComponent<LayoutElement>());
var body = new GameObject("MenuBody", typeof(RectTransform), typeof(Image));
body.transform.SetParent(canvasGo.transform, false);
var bodyRt = body.GetComponent<RectTransform>();
bodyRt.anchorMin = new Vector2(0.15f, 0.1f);
bodyRt.anchorMax = new Vector2(0.85f, 0.9f);
bodyRt.offsetMin = Vector2.zero;
bodyRt.offsetMax = Vector2.zero;
body.GetComponent<Image>().color = new Color(0.08f, 0.08f, 0.1f, 0.98f);
var tabBarGo = new GameObject("TabBar", typeof(RectTransform), typeof(HorizontalLayoutGroup), typeof(MenuTabBar));
tabBarGo.transform.SetParent(body.transform, false);
var tabBarRt = tabBarGo.GetComponent<RectTransform>();
tabBarRt.anchorMin = new Vector2(0f, 1f);
tabBarRt.anchorMax = new Vector2(1f, 1f);
tabBarRt.pivot = new Vector2(0.5f, 1f);
tabBarRt.sizeDelta = new Vector2(0f, 56f);
tabBarRt.anchoredPosition = Vector2.zero;
var tabHlg = tabBarGo.GetComponent<HorizontalLayoutGroup>();
tabHlg.spacing = 8f;
tabHlg.padding = new RectOffset(12, 12, 8, 8);
// childControl* default to FALSE on a script-added layout group. Left
// false, each tab button keeps its own 100px sizeDelta ("Character"
// clips to "Charact") and childForceExpandWidth dumps all the leftover
// room between them as a gap instead of widening them.
tabHlg.childControlWidth = true;
tabHlg.childControlHeight = true;
tabHlg.childForceExpandWidth = true;
tabHlg.childForceExpandHeight = true;
var contentArea = new GameObject("ContentArea", typeof(RectTransform));
contentArea.transform.SetParent(body.transform, false);
var contentRt = contentArea.GetComponent<RectTransform>();
contentRt.anchorMin = new Vector2(0f, 0f);
contentRt.anchorMax = new Vector2(1f, 1f);
contentRt.offsetMin = new Vector2(16f, 16f);
contentRt.offsetMax = new Vector2(-16f, -64f);
var characterPanel = CreateTabPanel("CharacterPanel", contentArea.transform);
var skillsPanel = CreateTabPanel("SkillsPanel", contentArea.transform);
var journalPanel = CreateTabPanel("JournalPanel", contentArea.transform);
// Character content
var charView = characterPanel.AddComponent<CharacterPanelView>();
var nameLabel = CreateTmp("Name", characterPanel.transform, 36f, FontStyles.Bold, TextAlignmentOptions.Left);
var aliasLabel = CreateTmp("Alias", characterPanel.transform, 28f, FontStyles.Italic, TextAlignmentOptions.Left);
var invLabel = CreateTmp("Inventory", characterPanel.transform, 24f, FontStyles.Normal, TextAlignmentOptions.Left);
var charVlg = characterPanel.AddComponent<VerticalLayoutGroup>();
charVlg.spacing = 12f;
charVlg.padding = new RectOffset(8, 8, 8, 8);
charVlg.childControlWidth = true;
charVlg.childControlHeight = true;
charVlg.childForceExpandWidth = true;
charVlg.childForceExpandHeight = false;
var charSo = new SerializedObject(charView);
charSo.FindProperty("nameLabel").objectReferenceValue = nameLabel.GetComponent<TMP_Text>();
charSo.FindProperty("aliasLabel").objectReferenceValue = aliasLabel.GetComponent<TMP_Text>();
charSo.FindProperty("inventoryPlaceholder").objectReferenceValue = invLabel.GetComponent<TMP_Text>();
charSo.ApplyModifiedPropertiesWithoutUndo();
// Skills content
var skillsScroll = new GameObject("SkillsScroll", typeof(RectTransform), typeof(ScrollRect));
skillsScroll.transform.SetParent(skillsPanel.transform, false);
Stretch(skillsScroll.GetComponent<RectTransform>());
var skillsViewport = new GameObject("Viewport", typeof(RectTransform), typeof(RectMask2D), typeof(Image));
skillsViewport.transform.SetParent(skillsScroll.transform, false);
Stretch(skillsViewport.GetComponent<RectTransform>());
skillsViewport.GetComponent<Image>().color = new Color(1f, 1f, 1f, 0.01f);
var skillsContent = new GameObject("Content", typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter));
skillsContent.transform.SetParent(skillsViewport.transform, false);
ConfigureVerticalContent(skillsContent);
var skillsContentRt = skillsContent.GetComponent<RectTransform>();
skillsContentRt.anchorMin = new Vector2(0f, 1f);
skillsContentRt.anchorMax = new Vector2(1f, 1f);
skillsContentRt.pivot = new Vector2(0.5f, 1f);
var skillsScrollRect = skillsScroll.GetComponent<ScrollRect>();
skillsScrollRect.content = skillsContentRt;
skillsScrollRect.viewport = skillsViewport.GetComponent<RectTransform>();
skillsScrollRect.horizontal = false;
var axisHeaderTemplate = CreateTmp("AxisHeaderTemplate", skillsContent.transform, 20f, FontStyles.Bold, TextAlignmentOptions.Left);
axisHeaderTemplate.SetActive(false);
var skillsView = skillsPanel.AddComponent<SkillsPanelView>();
var skillsSo = new SerializedObject(skillsView);
skillsSo.FindProperty("contentRoot").objectReferenceValue = skillsContentRt;
skillsSo.FindProperty("rowPrefab").objectReferenceValue = skillRowPrefab.GetComponent<SkillRowView>();
skillsSo.FindProperty("axisHeaderPrefab").objectReferenceValue = axisHeaderTemplate.GetComponent<TMP_Text>();
skillsSo.FindProperty("displayMaxRank").intValue = 6;
skillsSo.ApplyModifiedPropertiesWithoutUndo();
// Journal content
var journalView = journalPanel.AddComponent<JournalPanelView>();
var journalLabel = CreateTmp("Placeholder", journalPanel.transform, 28f, FontStyles.Italic, TextAlignmentOptions.Center);
Stretch(journalLabel.GetComponent<RectTransform>());
Object.DestroyImmediate(journalLabel.GetComponent<LayoutElement>());
var journalSo = new SerializedObject(journalView);
journalSo.FindProperty("placeholderLabel").objectReferenceValue = journalLabel.GetComponent<TMP_Text>();
journalSo.ApplyModifiedPropertiesWithoutUndo();
var charBtn = CreateTabButton("Character", tabBarGo.transform);
var skillsBtn = CreateTabButton("Skills", tabBarGo.transform);
var journalBtn = CreateTabButton("Journal", tabBarGo.transform);
var tabBar = tabBarGo.GetComponent<MenuTabBar>();
var tabSo = new SerializedObject(tabBar);
var tabsProp = tabSo.FindProperty("tabs");
tabsProp.arraySize = 3;
// Character tab must refresh on show for the same reason as Skills:
// panels toggle via CanvasGroup, so OnEnable never fires again.
SetTabEntry(tabsProp.GetArrayElementAtIndex(0), charBtn.GetComponent<Button>(), characterPanel.GetComponent<CanvasGroup>(), charView, "Refresh");
SetTabEntry(tabsProp.GetArrayElementAtIndex(1), skillsBtn.GetComponent<Button>(), skillsPanel.GetComponent<CanvasGroup>(), skillsView, "Refresh");
SetTabEntry(tabsProp.GetArrayElementAtIndex(2), journalBtn.GetComponent<Button>(), journalPanel.GetComponent<CanvasGroup>(), null);
tabSo.ApplyModifiedPropertiesWithoutUndo();
var menuController = canvasGo.AddComponent<PlayerMenuController>();
var menuSo = new SerializedObject(menuController);
menuSo.FindProperty("menuRoot").objectReferenceValue = canvasGo;
menuSo.FindProperty("tabBar").objectReferenceValue = tabBar;
menuSo.ApplyModifiedPropertiesWithoutUndo();
rootGroup.alpha = 0f;
rootGroup.interactable = false;
rootGroup.blocksRaycasts = false;
var prefab = PrefabUtility.SaveAsPrefabAsset(canvasGo, MenuCanvasPrefabPath);
Object.DestroyImmediate(canvasGo);
return prefab;
}
static void SetTabEntry(SerializedProperty entry, Button button, CanvasGroup panel, Object target, string method = null)
{
entry.FindPropertyRelative("button").objectReferenceValue = button;
entry.FindPropertyRelative("panel").objectReferenceValue = panel;
if (target != null && method != null)
{
var onShown = entry.FindPropertyRelative("onShown");
onShown.FindPropertyRelative("m_PersistentCalls.m_Calls").arraySize = 1;
var call = onShown.FindPropertyRelative("m_PersistentCalls.m_Calls").GetArrayElementAtIndex(0);
call.FindPropertyRelative("m_Target").objectReferenceValue = target;
call.FindPropertyRelative("m_MethodName").stringValue = method;
call.FindPropertyRelative("m_Mode").enumValueIndex = 1; // Void
call.FindPropertyRelative("m_CallState").enumValueIndex = 2; // RuntimeOnly
}
}
static void WireScene(GameObject streamCanvasPrefab, GameObject hudCanvasPrefab, GameObject menuCanvasPrefab)
{
var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
// UI System root
var uiSystem = GameObject.Find("UI System");
if (uiSystem == null)
{
uiSystem = new GameObject("UI System");
}
if (uiSystem.GetComponent<UIStateController>() == null)
{
uiSystem.AddComponent<UIStateController>();
}
// Player components
var player = GameObject.Find("PlayerArmature");
if (player == null)
{
Debug.LogError("[UILayerSetup] PlayerArmature not found in scene.");
return;
}
var controlLock = player.GetComponent<PlayerControlLock>();
if (controlLock == null)
{
controlLock = player.AddComponent<PlayerControlLock>();
}
var menuInput = player.GetComponent<PlayerMenuInput>();
if (menuInput == null)
{
menuInput = player.AddComponent<PlayerMenuInput>();
}
var inputs = player.GetComponent<StarterAssetsInputs>();
if (inputs != null)
{
inputs.cursorLocked = false;
EditorUtility.SetDirty(inputs);
}
// DialogueTransitionController
var transition = Object.FindFirstObjectByType<DialogueTransitionController>();
if (transition != null)
{
var tSo = new SerializedObject(transition);
tSo.FindProperty("controlLock").objectReferenceValue = controlLock;
tSo.ApplyModifiedPropertiesWithoutUndo();
}
// Instantiate / refresh UI prefab instances
var streamInstance = EnsurePrefabInstance(streamCanvasPrefab, "DialogueStreamCanvas");
var hudInstance = EnsurePrefabInstance(hudCanvasPrefab, "HudCanvas");
var menuInstance = EnsurePrefabInstance(menuCanvasPrefab, "PlayerMenuCanvas");
// Wire menu controller refs
var menuController = menuInstance.GetComponent<PlayerMenuController>();
if (menuController != null)
{
var mSo = new SerializedObject(menuController);
mSo.FindProperty("menuInput").objectReferenceValue = menuInput;
mSo.FindProperty("controlLock").objectReferenceValue = controlLock;
mSo.ApplyModifiedPropertiesWithoutUndo();
}
// Portrait → Open menu
var portraitButton = hudInstance.GetComponentInChildren<Button>(true);
if (portraitButton != null && menuController != null)
{
while (portraitButton.onClick.GetPersistentEventCount() > 0)
{
UnityEditor.Events.UnityEventTools.RemovePersistentListener(portraitButton.onClick, 0);
}
UnityEditor.Events.UnityEventTools.AddPersistentListener(portraitButton.onClick, menuController.Open);
EditorUtility.SetDirty(portraitButton);
}
// Camera framing
var vcam = GameObject.Find("CinemachineCamera");
if (vcam != null)
{
var framing = vcam.GetComponent<DialogueCameraFraming>();
if (framing == null)
{
framing = vcam.AddComponent<DialogueCameraFraming>();
}
var composer = vcam.GetComponent<CinemachinePositionComposer>();
var fSo = new SerializedObject(framing);
fSo.FindProperty("composer").objectReferenceValue = composer;
fSo.FindProperty("dialogueScreenX").floatValue = -0.17f;
fSo.ApplyModifiedPropertiesWithoutUndo();
}
// Re-point DialogueRunner presenters
var runner = Object.FindFirstObjectByType<DialogueRunner>();
var streamPresenter = streamInstance.GetComponent<DialogueStreamPresenter>();
var optionsPresenter = streamInstance.GetComponent<DialogueStreamOptionsPresenter>();
var lineAdvancer = Object.FindFirstObjectByType<LineAdvancer>();
if (runner != null && streamPresenter != null && optionsPresenter != null)
{
var rSo = new SerializedObject(runner);
var presenters = rSo.FindProperty("dialoguePresenters");
if (presenters != null)
{
presenters.arraySize = lineAdvancer != null ? 3 : 2;
presenters.GetArrayElementAtIndex(0).objectReferenceValue = streamPresenter;
presenters.GetArrayElementAtIndex(1).objectReferenceValue = optionsPresenter;
if (lineAdvancer != null)
{
presenters.GetArrayElementAtIndex(2).objectReferenceValue = lineAdvancer;
}
rSo.ApplyModifiedPropertiesWithoutUndo();
}
}
if (lineAdvancer != null && streamPresenter != null)
{
var aSo = new SerializedObject(lineAdvancer);
aSo.FindProperty("presenter").objectReferenceValue = streamPresenter;
aSo.ApplyModifiedPropertiesWithoutUndo();
}
// Point DialogueTransitionController.dialogueUI at the new canvas
if (transition != null && streamInstance != null)
{
var tSo = new SerializedObject(transition);
tSo.FindProperty("dialogueUI").objectReferenceValue = streamInstance;
tSo.FindProperty("controlLock").objectReferenceValue = controlLock;
tSo.ApplyModifiedPropertiesWithoutUndo();
}
// Deactivate the package Dialogue System Canvas (keep EventSystem alive)
var dialogueSystem = GameObject.Find("Dialogue System");
if (dialogueSystem != null)
{
var packageCanvas = dialogueSystem.transform.Find("Canvas");
if (packageCanvas != null)
{
// Rescue the Skill Check Banner onto the scene-owned Skill Check
// Canvas — NOT into streamInstance. EnsurePrefabInstance
// DestroyImmediates the stream canvas on every re-run, so
// anything parented under it dies with it; that is what left
// SkillRuntime.resultView null after the second setup run.
// Skill Check Canvas is scene-owned, survives re-runs, and its
// sortingOrder 100 already draws above the dialogue canvas.
var banner = packageCanvas.Find("Skill Check Banner");
if (banner != null)
{
var bannerHost = GameObject.Find("Skill Check Canvas");
if (bannerHost != null)
{
banner.SetParent(bannerHost.transform, false);
}
}
packageCanvas.gameObject.SetActive(false);
}
}
// Repair SkillRuntime.resultView. It can be left dangling by an earlier
// run of this menu item, and a null here silently disables every roll
// banner ("SkillCheckResultView is not assigned; skipping roll banner").
// Prefer an active view if more than one survives.
SkillCheckResultView resultView = null;
foreach (var candidate in Object.FindObjectsByType<SkillCheckResultView>(
FindObjectsInactive.Include, FindObjectsSortMode.None))
{
if (resultView == null
|| (!resultView.gameObject.activeInHierarchy && candidate.gameObject.activeInHierarchy))
{
resultView = candidate;
}
}
var skillRuntime = Object.FindFirstObjectByType<SkillRuntime>();
if (skillRuntime != null && resultView != null)
{
var skillSo = new SerializedObject(skillRuntime);
skillSo.FindProperty("resultView").objectReferenceValue = resultView;
skillSo.ApplyModifiedPropertiesWithoutUndo();
}
else if (skillRuntime != null)
{
Debug.LogWarning("[UILayerSetup] No SkillCheckResultView found in the scene; roll banners will not display.", skillRuntime);
}
// Update NPCStandIn dialogueUI refs to the new canvas
foreach (var npc in Object.FindObjectsByType<NPCStandIn>(FindObjectsInactive.Include, FindObjectsSortMode.None))
{
var so = new SerializedObject(npc);
var uiProp = so.FindProperty("dialogueUI");
if (uiProp != null)
{
uiProp.objectReferenceValue = streamInstance;
so.ApplyModifiedPropertiesWithoutUndo();
}
}
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene);
}
static GameObject EnsurePrefabInstance(GameObject prefab, string name)
{
var existing = GameObject.Find(name);
if (existing != null)
{
Object.DestroyImmediate(existing);
}
var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
instance.name = name;
return instance;
}
static GameObject CreateCanvas(string name, int sortingOrder)
{
var go = new GameObject(name, typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
var canvas = go.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = sortingOrder;
var scaler = go.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.matchWidthOrHeight = 0.5f;
return go;
}
static GameObject CreateTabPanel(string name, Transform parent)
{
var go = new GameObject(name, typeof(RectTransform), typeof(CanvasGroup));
go.transform.SetParent(parent, false);
Stretch(go.GetComponent<RectTransform>());
var group = go.GetComponent<CanvasGroup>();
group.alpha = 0f;
group.interactable = false;
group.blocksRaycasts = false;
return go;
}
static GameObject CreateTabButton(string label, Transform parent)
{
var go = new GameObject(label, typeof(RectTransform), typeof(Image), typeof(Button), typeof(LayoutElement));
go.transform.SetParent(parent, false);
go.GetComponent<Image>().color = new Color(0.2f, 0.2f, 0.25f, 1f);
var text = CreateTmp("Label", go.transform, 22f, FontStyles.Bold, TextAlignmentOptions.Center);
Stretch(text.GetComponent<RectTransform>());
Object.DestroyImmediate(text.GetComponent<LayoutElement>());
text.GetComponent<TMP_Text>().text = label;
return go;
}
static GameObject CreateTmp(string name, Transform parent, float size, FontStyles style, TextAlignmentOptions align)
{
var go = new GameObject(name, typeof(RectTransform), typeof(TextMeshProUGUI), typeof(LayoutElement));
go.transform.SetParent(parent, false);
var tmp = go.GetComponent<TextMeshProUGUI>();
tmp.fontSize = size;
tmp.fontStyle = style;
tmp.alignment = align;
tmp.color = Color.white;
tmp.text = name;
tmp.textWrappingMode = TextWrappingModes.Normal;
// Deliberately no LayoutElement.preferredHeight. A fixed height here is
// one line's worth, and LayoutElement outranks TMP's own ILayoutElement
// report — so a wrapped 4-line body would be allocated ~40px while
// rendering ~150px, and consecutive rows draw on top of each other.
// Leaving it at the -1 default lets TMP measure the wrapped text.
// The component stays so callers can still set preferredWidth /
// flexibleWidth (see the skill row).
return go;
}
static GameObject CreateUiImage(string name, Transform parent, Color color)
{
var go = new GameObject(name, typeof(RectTransform), typeof(Image), typeof(LayoutElement));
go.transform.SetParent(parent, false);
go.GetComponent<Image>().color = color;
return go;
}
static void Stretch(RectTransform rt, float pad = 0f)
{
rt.anchorMin = Vector2.zero;
rt.anchorMax = Vector2.one;
rt.offsetMin = new Vector2(pad, pad);
rt.offsetMax = new Vector2(-pad, -pad);
}
static void ConfigureVerticalContent(GameObject go)
{
var rt = go.GetComponent<RectTransform>();
rt.anchorMin = new Vector2(0f, 0f);
rt.anchorMax = new Vector2(1f, 0f);
rt.pivot = new Vector2(0.5f, 0f);
// A RectTransform added via `new GameObject(..., typeof(RectTransform))`
// starts at sizeDelta (100,100). With horizontal-stretch anchors that
// makes the rect parentWidth + 100 wide, centred — overhanging 50px off
// each side, and the RectMask2D shears the left edge off every label.
// ContentSizeFitter only governs the vertical axis, so it never
// corrects this. Zero the horizontal size explicitly.
rt.sizeDelta = new Vector2(0f, rt.sizeDelta.y);
rt.anchoredPosition = Vector2.zero;
var vlg = go.GetComponent<VerticalLayoutGroup>();
vlg.childControlHeight = true;
vlg.childControlWidth = true;
vlg.childForceExpandHeight = false;
vlg.childForceExpandWidth = true;
vlg.spacing = 4f;
var csf = go.GetComponent<ContentSizeFitter>();
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5bdf044f1ab04312b76895dac1740b37
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
using UnityEditor;
using UnityEngine;
/// <summary>
/// Reminds the user to run the one-shot prefab/scene wiring menu after scripts
/// compile. Runtime play works without it via UILayerBootstrap.
/// </summary>
[InitializeOnLoad]
static class UILayerSetupAutoRun
{
const string PrefKey = "NightclubArcadia.UILayerSetup.Reminded";
const string MarkerPrefab = "Assets/Prefabs/UI/PlayerMenuCanvas.prefab";
static UILayerSetupAutoRun()
{
EditorApplication.delayCall += RemindIfNeeded;
}
static void RemindIfNeeded()
{
if (EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling)
{
return;
}
if (AssetDatabase.LoadAssetAtPath<GameObject>(MarkerPrefab) != null)
{
return;
}
if (EditorPrefs.GetBool(PrefKey, false))
{
return;
}
EditorPrefs.SetBool(PrefKey, true);
Debug.Log(
"[UILayer] Prefabs not built yet. Play Mode works via UILayerBootstrap. " +
"To author prefabs into the scene, run Tools → Nightclub Arcadia → Set Up UI Layer.");
}
[MenuItem("Tools/Nightclub Arcadia/Reset UI Layer Auto-Setup Flag")]
static void ResetFlag()
{
EditorPrefs.DeleteKey(PrefKey);
Debug.Log("[UILayer] Reminder flag cleared.");
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6c0e3c886ff143d3b26b7f55d7f22410
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fe41419ba50842189534933e2b64e958
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 11e75200bdad4ff48299a7d24bb8df99
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,413 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3506879458838080580
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1649868454372858534}
- component: {fileID: 5353601244371665078}
- component: {fileID: 3249155410833833935}
- component: {fileID: 3956878563407369997}
m_Layer: 0
m_Name: DialogueMessageRow
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1649868454372858534
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3506879458838080580}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 5063401541721017946}
- {fileID: 7857316982467238224}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 80}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &5353601244371665078
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3506879458838080580}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a7773ea9a6f44798aa98356903037d16, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::NightclubArcadia.UI.Dialogue.DialogueMessageRow
speakerText: {fileID: 5900251945930220314}
bodyText: {fileID: 1670956017877202442}
speakerLabel: {fileID: 5822252844234204748}
--- !u!114 &3249155410833833935
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3506879458838080580}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup
m_Padding:
m_Left: 12
m_Right: 12
m_Top: 8
m_Bottom: 8
m_ChildAlignment: 0
m_Spacing: 4
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!114 &3956878563407369997
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3506879458838080580}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!1 &5450003319614969337
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7857316982467238224}
- component: {fileID: 3605186933310922639}
- component: {fileID: 1670956017877202442}
- component: {fileID: 6572944391823112727}
m_Layer: 0
m_Name: Body
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7857316982467238224
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5450003319614969337}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1649868454372858534}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 200, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3605186933310922639
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5450003319614969337}
m_CullTransparentMesh: 1
--- !u!114 &1670956017877202442
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5450003319614969337}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Body
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 28
m_fontSizeBase: 28
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 256
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &6572944391823112727
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5450003319614969337}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &5822252844234204748
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5063401541721017946}
- component: {fileID: 4569120956328637162}
- component: {fileID: 5900251945930220314}
- component: {fileID: 3733932380812113541}
m_Layer: 0
m_Name: Speaker
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5063401541721017946
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5822252844234204748}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1649868454372858534}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 200, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4569120956328637162
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5822252844234204748}
m_CullTransparentMesh: 1
--- !u!114 &5900251945930220314
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5822252844234204748}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Speaker
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 22
m_fontSizeBase: 22
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 1
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &3733932380812113541
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5822252844234204748}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 90818ef7dc74247ceb348994755cea86
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,673 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1431520567503728820
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4838388606347767229}
- component: {fileID: 848167695161913818}
- component: {fileID: 872887529246740654}
m_Layer: 0
m_Name: Messages
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4838388606347767229
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1431520567503728820}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6371610150170887506}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 100}
m_Pivot: {x: 0.5, y: 0}
--- !u!114 &848167695161913818
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1431520567503728820}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup
m_Padding:
m_Left: 0
m_Right: 0
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 0
m_Spacing: 4
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!114 &872887529246740654
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1431520567503728820}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!1 &2888832924926796905
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8553742246756266605}
- component: {fileID: 7475958128581596541}
- component: {fileID: 5788080976967704390}
m_Layer: 0
m_Name: Panel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8553742246756266605
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2888832924926796905}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6287368187237088396}
m_Father: {fileID: 1243836425688988300}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.66, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7475958128581596541
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2888832924926796905}
m_CullTransparentMesh: 1
--- !u!114 &5788080976967704390
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2888832924926796905}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 0.05, g: 0.05, b: 0.08, a: 0.92}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &4923635399097880277
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3746324764683222304}
- component: {fileID: 3035847077530824546}
- component: {fileID: 5149667566061008557}
- component: {fileID: 2566128074157561357}
m_Layer: 0
m_Name: Viewport
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3746324764683222304
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4923635399097880277}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6371610150170887506}
m_Father: {fileID: 6287368187237088396}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &3035847077530824546
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4923635399097880277}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3312d7739989d2b4e91e6319e9a96d76, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.RectMask2D
m_Padding: {x: 0, y: 0, z: 0, w: 0}
m_Softness: {x: 0, y: 0}
--- !u!222 &5149667566061008557
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4923635399097880277}
m_CullTransparentMesh: 1
--- !u!114 &2566128074157561357
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4923635399097880277}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0.01}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &5260492858234310193
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6371610150170887506}
- component: {fileID: 2397794206583674815}
- component: {fileID: 5419335647702174254}
m_Layer: 0
m_Name: Content
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6371610150170887506
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5260492858234310193}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4838388606347767229}
- {fileID: 6329947636030823459}
m_Father: {fileID: 3746324764683222304}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0}
--- !u!114 &2397794206583674815
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5260492858234310193}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup
m_Padding:
m_Left: 4
m_Right: 4
m_Top: 8
m_Bottom: 8
m_ChildAlignment: 7
m_Spacing: 6
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!114 &5419335647702174254
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5260492858234310193}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!1 &7249108092969100431
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6329947636030823459}
- component: {fileID: 5824694681547497274}
- component: {fileID: 6817391513969849154}
- component: {fileID: 5117926241503925785}
m_Layer: 0
m_Name: Options
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6329947636030823459
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7249108092969100431}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6371610150170887506}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 100}
m_Pivot: {x: 0.5, y: 0}
--- !u!114 &5824694681547497274
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7249108092969100431}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup
m_Padding:
m_Left: 0
m_Right: 0
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 0
m_Spacing: 4
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!114 &6817391513969849154
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7249108092969100431}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!225 &5117926241503925785
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7249108092969100431}
m_Enabled: 1
m_Alpha: 0
m_Interactable: 0
m_BlocksRaycasts: 0
m_IgnoreParentGroups: 0
--- !u!1 &8691714907102969075
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1243836425688988300}
- component: {fileID: 1592513625297910240}
- component: {fileID: 7055069541643637941}
- component: {fileID: 4390775391658295920}
- component: {fileID: 3476584232491665726}
- component: {fileID: 6097567599041115085}
- component: {fileID: 307489608854241188}
m_Layer: 0
m_Name: DialogueStreamCanvas
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1243836425688988300
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8691714907102969075}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8553742246756266605}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!223 &1592513625297910240
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8691714907102969075}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_VertexColorAlwaysGammaSpace: 0
m_UseReflectionProbes: 0
m_AdditionalShaderChannelsFlag: 0
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 5
m_TargetDisplay: 0
--- !u!114 &7055069541643637941
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8691714907102969075}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
m_UiScaleMode: 1
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 1920, y: 1080}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0.5
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!114 &4390775391658295920
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8691714907102969075}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GraphicRaycaster
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!225 &3476584232491665726
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8691714907102969075}
m_Enabled: 1
m_Alpha: 0
m_Interactable: 0
m_BlocksRaycasts: 0
m_IgnoreParentGroups: 0
--- !u!114 &6097567599041115085
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8691714907102969075}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 88cb6495795a4e13ac7fb72343c132af, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::NightclubArcadia.UI.Dialogue.DialogueStreamPresenter
messageRowPrefab: {fileID: 5353601244371665078, guid: 90818ef7dc74247ceb348994755cea86, type: 3}
messageContainer: {fileID: 4838388606347767229}
scrollRect: {fileID: 7105587415705135446}
maxRows: 100
lettersPerSecond: 60
--- !u!114 &307489608854241188
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8691714907102969075}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8d86cc99d5b545c4a3fad9e1482031a2, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::NightclubArcadia.UI.Dialogue.DialogueStreamOptionsPresenter
optionViewPrefab: {fileID: 2576261519635394347, guid: e605972137b0b4c1399ffb07beae9a9b, type: 3}
optionsContainer: {fileID: 6329947636030823459}
canvasGroup: {fileID: 5117926241503925785}
showUnavailableOptions: 0
--- !u!1 &8898733438161318853
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6287368187237088396}
- component: {fileID: 7105587415705135446}
- component: {fileID: 6289533314517078144}
- component: {fileID: 7217060756955950799}
m_Layer: 0
m_Name: Scroll
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6287368187237088396
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8898733438161318853}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3746324764683222304}
m_Father: {fileID: 8553742246756266605}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: -16, y: -16}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &7105587415705135446
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8898733438161318853}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ScrollRect
m_Content: {fileID: 6371610150170887506}
m_Horizontal: 0
m_Vertical: 1
m_MovementType: 2
m_Elasticity: 0.1
m_Inertia: 1
m_DecelerationRate: 0.135
m_ScrollSensitivity: 1
m_Viewport: {fileID: 3746324764683222304}
m_HorizontalScrollbar: {fileID: 0}
m_VerticalScrollbar: {fileID: 0}
m_HorizontalScrollbarVisibility: 0
m_VerticalScrollbarVisibility: 0
m_HorizontalScrollbarSpacing: 0
m_VerticalScrollbarSpacing: 0
m_OnValueChanged:
m_PersistentCalls:
m_Calls: []
--- !u!222 &6289533314517078144
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8898733438161318853}
m_CullTransparentMesh: 1
--- !u!114 &7217060756955950799
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8898733438161318853}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.01}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 55fc979a64b174ce796ffa39b13b7259
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,390 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3845905829654890729
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4691680407920056117}
- component: {fileID: 9194898352646057999}
- component: {fileID: 7450534218736675587}
m_Layer: 0
m_Name: TabHint
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4691680407920056117
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3845905829654890729}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 252585930844827291}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: 0, y: 4}
m_SizeDelta: {x: 0, y: 28}
m_Pivot: {x: 0.5, y: 0}
--- !u!222 &9194898352646057999
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3845905829654890729}
m_CullTransparentMesh: 1
--- !u!114 &7450534218736675587
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3845905829654890729}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Tab
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 18
m_fontSizeBase: 18
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &4914891251226459434
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8125896019114993279}
- component: {fileID: 637496887729740374}
- component: {fileID: 7333171482869056901}
- component: {fileID: 3439018567020279905}
- component: {fileID: 4573334312575796821}
m_Layer: 0
m_Name: HudCanvas
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8125896019114993279
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4914891251226459434}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 252585930844827291}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!223 &637496887729740374
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4914891251226459434}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_VertexColorAlwaysGammaSpace: 0
m_UseReflectionProbes: 0
m_AdditionalShaderChannelsFlag: 0
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 10
m_TargetDisplay: 0
--- !u!114 &7333171482869056901
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4914891251226459434}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler
m_UiScaleMode: 1
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 1920, y: 1080}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0.5
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!114 &3439018567020279905
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4914891251226459434}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GraphicRaycaster
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!114 &4573334312575796821
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4914891251226459434}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fd1354ade14b4b1d80da9acac4aa9e12, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::NightclubArcadia.UI.HUD.PortraitHudView
panelRoot: {fileID: 4989543578856257477}
--- !u!1 &4989543578856257477
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 252585930844827291}
- component: {fileID: 7861001760273437131}
- component: {fileID: 1348039723167098503}
- component: {fileID: 4045319608296230342}
- component: {fileID: 1473442362196520338}
m_Layer: 0
m_Name: PortraitPanel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &252585930844827291
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4989543578856257477}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4691680407920056117}
m_Father: {fileID: 8125896019114993279}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: -24, y: 24}
m_SizeDelta: {x: 220, y: 280}
m_Pivot: {x: 1, y: 0}
--- !u!225 &7861001760273437131
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4989543578856257477}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!222 &1348039723167098503
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4989543578856257477}
m_CullTransparentMesh: 1
--- !u!114 &4045319608296230342
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4989543578856257477}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 0.15, g: 0.15, b: 0.18, a: 0.95}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: a1b2c3d4e5f60718293a4b5c6d7e8f90, type: 3}
m_Type: 0
m_PreserveAspect: 1
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &1473442362196520338
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4989543578856257477}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 4045319608296230342}
m_OnClick:
m_PersistentCalls:
m_Calls: []
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 792e1d4c4cde942e8a71947301a3868c
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: bb4c52aaa2c9e41aabc4bb11fa6372ba
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,669 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &738428826500747930
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7033684844244958404}
- component: {fileID: 8854647563054178895}
- component: {fileID: 7773152306665733045}
- component: {fileID: 3269936702826226914}
m_Layer: 0
m_Name: Accent
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7033684844244958404
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 738428826500747930}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6014094232834212140}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8854647563054178895
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 738428826500747930}
m_CullTransparentMesh: 1
--- !u!114 &7773152306665733045
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 738428826500747930}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &3269936702826226914
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 738428826500747930}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 6
m_PreferredHeight: -1
m_FlexibleWidth: 0
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &1043270585848180102
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6266953780690568889}
- component: {fileID: 6662307575930959716}
- component: {fileID: 874797289038649292}
- component: {fileID: 3005905926608676038}
m_Layer: 0
m_Name: Bar
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6266953780690568889
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1043270585848180102}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 437950331768363985}
m_Father: {fileID: 6014094232834212140}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6662307575930959716
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1043270585848180102}
m_CullTransparentMesh: 1
--- !u!114 &874797289038649292
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1043270585848180102}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 0.2, g: 0.2, b: 0.22, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &3005905926608676038
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1043270585848180102}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 160
m_PreferredHeight: 16
m_FlexibleWidth: 0
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &1311471292218943158
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4484942689062487776}
- component: {fileID: 285052103366591294}
- component: {fileID: 6986784551111800944}
- component: {fileID: 4453305505185908855}
m_Layer: 0
m_Name: Rank
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4484942689062487776
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1311471292218943158}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6014094232834212140}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 200, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &285052103366591294
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1311471292218943158}
m_CullTransparentMesh: 1
--- !u!114 &6986784551111800944
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1311471292218943158}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Rank
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 24
m_fontSizeBase: 24
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 4
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &4453305505185908855
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1311471292218943158}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 40
m_PreferredHeight: -1
m_FlexibleWidth: 0
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &3239427835033733195
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4775921688302885094}
- component: {fileID: 3498508026980942921}
- component: {fileID: 4172720891822695416}
- component: {fileID: 6787294827931527212}
m_Layer: 0
m_Name: Name
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4775921688302885094
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3239427835033733195}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6014094232834212140}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 200, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3498508026980942921
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3239427835033733195}
m_CullTransparentMesh: 1
--- !u!114 &4172720891822695416
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3239427835033733195}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Name
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 24
m_fontSizeBase: 24
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 1
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &6787294827931527212
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3239427835033733195}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: -1
m_FlexibleWidth: 1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &3768236759226886124
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 437950331768363985}
- component: {fileID: 5606763019278455248}
- component: {fileID: 1019288287942172810}
m_Layer: 0
m_Name: Fill
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &437950331768363985
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3768236759226886124}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6266953780690568889}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5606763019278455248
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3768236759226886124}
m_CullTransparentMesh: 1
--- !u!114 &1019288287942172810
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3768236759226886124}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 3
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 0
m_FillAmount: 0.2
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &7442810364304304402
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6014094232834212140}
- component: {fileID: 1141587833647839303}
- component: {fileID: 8736413170534072599}
m_Layer: 0
m_Name: SkillRow
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6014094232834212140
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7442810364304304402}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7033684844244958404}
- {fileID: 4775921688302885094}
- {fileID: 6266953780690568889}
- {fileID: 4484942689062487776}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 48}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1141587833647839303
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7442810364304304402}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6ccda37aee7e42b09a0628f8a5729060, type: 3}
m_Name:
m_EditorClassIdentifier: NightclubArcadia.Skills::NightclubArcadia.Skills.SkillRowView
nameLabel: {fileID: 4172720891822695416}
rankLabel: {fileID: 6986784551111800944}
barFill: {fileID: 1019288287942172810}
accentStripe: {fileID: 7773152306665733045}
--- !u!114 &8736413170534072599
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7442810364304304402}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.HorizontalLayoutGroup
m_Padding:
m_Left: 8
m_Right: 8
m_Top: 4
m_Bottom: 4
m_ChildAlignment: 3
m_Spacing: 12
m_ChildForceExpandWidth: 0
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e14f68d6a57ea4945bd93c44202db910
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1647a101742545fe8af6187e3a743858
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,81 @@
using Unity.Cinemachine;
using UnityEngine;
namespace NightclubArcadia.Cinematics
{
/// <summary>
/// Raises a reveal camera priority while the player is inside the volume.
/// </summary>
[RequireComponent(typeof(BoxCollider))]
public sealed class CameraFramingVolume : MonoBehaviour
{
[SerializeField] CinemachineCamera revealCamera;
[SerializeField] int activePriority = 20;
[SerializeField] float minDwell = 0.5f;
CameraStateDirector stateDirector;
int defaultPriority;
int occupants;
float exitTimer;
void Awake()
{
var box = GetComponent<BoxCollider>();
box.isTrigger = true;
if (revealCamera != null)
{
defaultPriority = revealCamera.Priority.Value;
}
stateDirector = FindFirstObjectByType<CameraStateDirector>();
}
void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Player") || revealCamera == null)
{
return;
}
occupants++;
exitTimer = 0f;
revealCamera.Priority.Value = activePriority;
stateDirector?.RegisterReveal();
}
void OnTriggerExit(Collider other)
{
if (!other.CompareTag("Player") || revealCamera == null)
{
return;
}
occupants = Mathf.Max(0, occupants - 1);
if (occupants == 0)
{
exitTimer = minDwell;
}
}
void Update()
{
if (occupants > 0 || revealCamera == null)
{
return;
}
if (exitTimer > 0f)
{
exitTimer -= Time.deltaTime;
return;
}
if (revealCamera.Priority.Value != defaultPriority)
{
revealCamera.Priority.Value = defaultPriority;
stateDirector?.UnregisterReveal();
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7cccc93b78eb84019b4524d65305612a
@@ -0,0 +1,162 @@
using NightclubArcadia.Player;
using NightclubArcadia.Player.Math;
using UnityEngine;
namespace NightclubArcadia.Cinematics
{
/// <summary>
/// Converts mouse drag-pan into CameraRigAnchor manual offset with recenter rules.
/// </summary>
public sealed class CameraPanInput : MonoBehaviour
{
[SerializeField] Camera brainCamera;
[SerializeField] CameraRigAnchor anchor;
[SerializeField] CameraStateDirector stateDirector;
[SerializeField] PlayerPointerInput pointerInput;
[SerializeField] float panMetresPerPixel = 0.02f;
[SerializeField] float maxPanDistance = 12f;
[SerializeField] float recenterDelay = 2.5f;
[SerializeField] float recenterSmoothTime = 0.8f;
[SerializeField] float movementRecenterDelay = 0.4f;
float releaseTimer = float.MaxValue;
Vector3 recenterVelocity;
Vector3 camRight;
Vector3 camForward;
bool basisInitialized;
public bool IsManualPanActive => pointerInput != null && pointerInput.IsPanning || releaseTimer < recenterDelay;
void Awake()
{
if (anchor == null)
{
anchor = GetComponent<CameraRigAnchor>();
}
if (stateDirector == null)
{
stateDirector = GetComponent<CameraStateDirector>();
}
if (brainCamera == null)
{
brainCamera = Camera.main;
}
}
void Start()
{
if (pointerInput == null)
{
var player = GameObject.FindGameObjectWithTag("Player");
if (player != null)
{
pointerInput = player.GetComponent<PlayerPointerInput>();
}
}
if (pointerInput != null)
{
pointerInput.PanDelta += OnPanDelta;
pointerInput.PanEnded += OnPanEnded;
}
}
void OnDestroy()
{
if (pointerInput != null)
{
pointerInput.PanDelta -= OnPanDelta;
pointerInput.PanEnded -= OnPanEnded;
}
}
void Update()
{
if (anchor == null || stateDirector == null)
{
return;
}
if (stateDirector.AllowsManualPan && pointerInput != null && pointerInput.IsPanning)
{
releaseTimer = 0f;
}
else if (releaseTimer < recenterDelay)
{
releaseTimer += Time.deltaTime;
}
if (ShouldShortenRecenter())
{
releaseTimer = Mathf.Max(releaseTimer, recenterDelay - movementRecenterDelay);
}
if (!stateDirector.AllowsManualPan)
{
return;
}
if (releaseTimer >= recenterDelay && (pointerInput == null || !pointerInput.IsPanning))
{
anchor.ManualPanOffset = Vector3.SmoothDamp(
anchor.ManualPanOffset,
Vector3.zero,
ref recenterVelocity,
recenterSmoothTime);
}
anchor.ClampManualPan(maxPanDistance);
}
void OnPanDelta(Vector2 delta)
{
if (stateDirector == null || !stateDirector.AllowsManualPan || anchor == null)
{
return;
}
EnsureCameraBasis();
anchor.Nudge(MotionInputMath.ScreenDeltaToWorldPan(delta, camRight, camForward, panMetresPerPixel));
anchor.ClampManualPan(maxPanDistance);
}
void OnPanEnded()
{
releaseTimer = 0f;
}
bool ShouldShortenRecenter()
{
var player = GameObject.FindGameObjectWithTag("Player");
if (player == null)
{
return false;
}
var arbiter = player.GetComponent<PlayerMotionArbiter>();
return arbiter != null && arbiter.ActiveSource != MotionSource.None;
}
void EnsureCameraBasis()
{
if (brainCamera == null)
{
brainCamera = Camera.main;
}
if (brainCamera == null)
{
return;
}
if (!basisInitialized)
{
camRight = brainCamera.transform.right;
camForward = brainCamera.transform.forward;
basisInitialized = true;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f9b06ade88f2643a3b9f52a80e06081d
@@ -0,0 +1,31 @@
using UnityEngine;
namespace NightclubArcadia.Cinematics
{
/// <summary>
/// Marker on NPCs and props that the POI director may bias toward.
/// </summary>
public sealed class CameraPointOfInterest : MonoBehaviour
{
[SerializeField] float weight = 1f;
[SerializeField] float triggerRadius = 8f;
[SerializeField] float maxBias = 2.5f;
[SerializeField] float extraDistance = 1.5f;
public float Weight => weight;
public float TriggerRadius => triggerRadius;
public float MaxBias => maxBias;
public float ExtraDistance => extraDistance;
public Transform Target => transform;
void OnEnable()
{
CameraPointOfInterestDirector.Register(this);
}
void OnDisable()
{
CameraPointOfInterestDirector.Unregister(this);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7862f80331f384d28b27600075971a77
@@ -0,0 +1,161 @@
using System.Collections.Generic;
using UnityEngine;
namespace NightclubArcadia.Cinematics
{
/// <summary>
/// Scores registered POIs and writes bias onto the camera anchor.
/// </summary>
public sealed class CameraPointOfInterestDirector : MonoBehaviour
{
static readonly List<CameraPointOfInterest> Registered = new List<CameraPointOfInterest>();
[SerializeField] CameraRigAnchor anchor;
[SerializeField] CameraStateDirector stateDirector;
[SerializeField] float poiSmoothTime = 1f;
[SerializeField] float poiReleaseTime = 1.4f;
[SerializeField] float maxHeadingAngle = 100f;
Vector3 poiVelocity;
float distanceVelocity;
CameraPointOfInterest activePoi;
public bool HasActivePoi => activePoi != null;
public static void Register(CameraPointOfInterest poi)
{
if (poi != null && !Registered.Contains(poi))
{
Registered.Add(poi);
}
}
public static void Unregister(CameraPointOfInterest poi)
{
Registered.Remove(poi);
}
void Awake()
{
if (anchor == null)
{
anchor = GetComponent<CameraRigAnchor>();
}
if (stateDirector == null)
{
stateDirector = GetComponent<CameraStateDirector>();
}
}
void LateUpdate()
{
if (anchor == null || stateDirector == null)
{
return;
}
var follow = anchor.FollowTarget;
if (follow == null)
{
return;
}
if (!stateDirector.AllowsPointOfInterest)
{
return;
}
if (stateDirector.Current == CameraState.ManualPan)
{
return;
}
activePoi = FindBestPoi(follow);
if (activePoi == null)
{
ReleasePoi();
return;
}
var toPoi = activePoi.Target.position - follow.position;
toPoi.y = 0f;
var targetBias = Vector3.ClampMagnitude(toPoi, activePoi.MaxBias);
anchor.PoiBias = Vector3.SmoothDamp(
anchor.PoiBias,
targetBias,
ref poiVelocity,
poiSmoothTime);
anchor.DistanceBias = Mathf.SmoothDamp(
anchor.DistanceBias,
activePoi.ExtraDistance,
ref distanceVelocity,
poiSmoothTime);
}
CameraPointOfInterest FindBestPoi(Transform follow)
{
CameraPointOfInterest best = null;
var bestScore = 0f;
var forward = follow.forward;
foreach (var poi in Registered)
{
if (poi == null || !poi.isActiveAndEnabled)
{
continue;
}
var offset = poi.Target.position - follow.position;
offset.y = 0f;
var distance = offset.magnitude;
if (distance > poi.TriggerRadius || distance <= 0.01f)
{
continue;
}
var angle = Vector3.Angle(forward, offset);
if (angle > maxHeadingAngle)
{
continue;
}
var score = poi.Weight * (1f - distance / poi.TriggerRadius);
if (score > bestScore)
{
bestScore = score;
best = poi;
}
}
return best;
}
void ReleasePoi()
{
if (anchor == null)
{
return;
}
anchor.PoiBias = Vector3.SmoothDamp(
anchor.PoiBias,
Vector3.zero,
ref poiVelocity,
poiReleaseTime);
anchor.DistanceBias = Mathf.SmoothDamp(
anchor.DistanceBias,
0f,
ref distanceVelocity,
poiReleaseTime);
if (anchor.PoiBias.sqrMagnitude < 0.0001f && Mathf.Abs(anchor.DistanceBias) < 0.01f)
{
activePoi = null;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e3343417e1de440f095b73e55c6977b6
@@ -0,0 +1,85 @@
using Unity.Cinemachine;
using UnityEngine;
namespace NightclubArcadia.Cinematics
{
/// <summary>
/// Proxy transform the follow camera tracks. Combines player follow,
/// POI bias, manual pan offset, and distance bias.
/// </summary>
public sealed class CameraRigAnchor : MonoBehaviour
{
[SerializeField] Transform followTarget;
[SerializeField] CinemachinePositionComposer composer;
[SerializeField] float followSmoothTime = 0.12f;
[SerializeField] float distanceSmoothTime = 0.8f;
float defaultCameraDistance = 10f;
float distanceVelocity;
Vector3 followVelocity;
public Vector3 PoiBias { get; set; }
public Vector3 ManualPanOffset { get; set; }
public float DistanceBias { get; set; }
public Transform FollowTarget => followTarget;
void Start()
{
if (composer == null)
{
composer = FindFirstObjectByType<CinemachinePositionComposer>();
}
if (composer != null)
{
defaultCameraDistance = composer.CameraDistance;
}
}
void LateUpdate()
{
if (followTarget == null)
{
return;
}
var targetPosition = followTarget.position + PoiBias + ManualPanOffset;
transform.position = Vector3.SmoothDamp(
transform.position,
targetPosition,
ref followVelocity,
followSmoothTime);
if (composer != null)
{
var targetDistance = defaultCameraDistance + DistanceBias;
composer.CameraDistance = Mathf.SmoothDamp(
composer.CameraDistance,
targetDistance,
ref distanceVelocity,
distanceSmoothTime);
}
}
public void Nudge(Vector3 worldDelta)
{
ManualPanOffset += worldDelta;
}
public void ClampManualPan(float maxDistance)
{
if (followTarget == null)
{
return;
}
var offset = ManualPanOffset;
offset.y = 0f;
if (offset.magnitude > maxDistance)
{
ManualPanOffset = offset.normalized * maxDistance;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 16d6717a3703a4bb3a8c3bb0bb0392de
@@ -0,0 +1,135 @@
using NightclubArcadia.UI;
using UnityEngine;
namespace NightclubArcadia.Cinematics
{
public enum CameraState
{
Follow,
ManualPan,
PointOfInterest,
Reveal,
Dialogue,
Menu
}
/// <summary>
/// Resolves which camera subsystem may write each frame.
/// </summary>
public sealed class CameraStateDirector : MonoBehaviour
{
[SerializeField] CameraRigAnchor anchor;
[SerializeField] float dialogueRecenterTime = 0.5f;
int activeRevealCount;
float dialogueRecenterTimer;
bool wasDialogueActive;
bool wasMenuOpen;
public CameraState Current { get; private set; } = CameraState.Follow;
public bool AllowsManualPan =>
Current != CameraState.Dialogue
&& Current != CameraState.Menu
&& Current != CameraState.Reveal;
public bool AllowsPointOfInterest =>
Current == CameraState.Follow || Current == CameraState.PointOfInterest;
void Awake()
{
if (anchor == null)
{
anchor = GetComponent<CameraRigAnchor>();
}
}
void Start()
{
if (UIStateController.Instance != null)
{
UIStateController.Instance.StateChanged += OnUiStateChanged;
}
ResolveState(false);
}
void OnDestroy()
{
if (UIStateController.Instance != null)
{
UIStateController.Instance.StateChanged -= OnUiStateChanged;
}
}
void Update()
{
ResolveState(true);
if (Current == CameraState.Dialogue && anchor != null)
{
dialogueRecenterTimer += Time.deltaTime;
var t = Mathf.Clamp01(dialogueRecenterTimer / dialogueRecenterTime);
anchor.ManualPanOffset = Vector3.Lerp(anchor.ManualPanOffset, Vector3.zero, t);
}
}
void OnUiStateChanged() => ResolveState(true);
public void RegisterReveal() => activeRevealCount++;
public void UnregisterReveal() => activeRevealCount = Mathf.Max(0, activeRevealCount - 1);
void ResolveState(bool allowManualPanPromotion)
{
var ui = UIStateController.Instance;
var menuOpen = ui != null && ui.IsMenuOpen;
var dialogueActive = ui != null && ui.IsDialogueActive;
if (menuOpen)
{
Current = CameraState.Menu;
}
else if (dialogueActive)
{
if (!wasDialogueActive)
{
dialogueRecenterTimer = 0f;
}
Current = CameraState.Dialogue;
}
else if (activeRevealCount > 0)
{
Current = CameraState.Reveal;
}
else if (allowManualPanPromotion && IsManualPanActive())
{
Current = CameraState.ManualPan;
}
else if (HasActivePointOfInterest())
{
Current = CameraState.PointOfInterest;
}
else
{
Current = CameraState.Follow;
}
wasDialogueActive = dialogueActive;
wasMenuOpen = menuOpen;
}
bool IsManualPanActive()
{
var pan = GetComponent<CameraPanInput>();
return pan != null && pan.IsManualPanActive;
}
bool HasActivePointOfInterest()
{
var director = GetComponent<CameraPointOfInterestDirector>();
return director != null && director.HasActivePoi;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ca0160e4d996040238dab18725a2e844
@@ -0,0 +1,72 @@
using NightclubArcadia.UI;
using Unity.Cinemachine;
using UnityEngine;
namespace NightclubArcadia.Cinematics
{
/// <summary>
/// Shifts CinemachinePositionComposer.Composition.ScreenPosition so the
/// player is centred in the screen space left of the dialogue panel.
/// </summary>
public sealed class DialogueCameraFraming : MonoBehaviour
{
[SerializeField] CinemachinePositionComposer composer;
[SerializeField] float dialogueScreenX = -0.17f;
[SerializeField] bool suppressDeadZoneXInDialogue = true;
float defaultDeadZoneX = 0.2f;
void Start()
{
if (composer == null)
{
composer = FindFirstObjectByType<CinemachinePositionComposer>();
}
if (composer != null)
{
defaultDeadZoneX = composer.Composition.DeadZone.Size.x;
}
if (UIStateController.Instance != null)
{
UIStateController.Instance.StateChanged += OnStateChanged;
}
ApplyFraming();
}
void OnDestroy()
{
if (UIStateController.Instance != null)
{
UIStateController.Instance.StateChanged -= OnStateChanged;
}
}
void OnStateChanged() => ApplyFraming();
void ApplyFraming()
{
if (composer == null)
{
return;
}
var active = UIStateController.Instance != null && UIStateController.Instance.IsDialogueActive;
var target = active ? dialogueScreenX : 0f;
var composition = composer.Composition;
composition.ScreenPosition = new Vector2(target, composition.ScreenPosition.y);
if (suppressDeadZoneXInDialogue)
{
var size = composition.DeadZone.Size;
size.x = active ? 0f : defaultDeadZoneX;
composition.DeadZone.Size = size;
}
composer.Composition = composition;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 56142a5f8795401498df8e01dc74b258
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 16d046b9603964894b61827ac87b5020
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,61 @@
using Yarn.Unity;
namespace NightclubArcadia.Dialogue
{
/// <summary>
/// Deterministically turns the player's real name into an unrelated club
/// alias. Same input name always yields the same alias (hash-seeded pick
/// from two word pools), but the alias carries no resemblance to the
/// input — it's a stage name, not a nickname derived from it.
///
/// Used from Yarn as a smart variable (see $player_alias in Common.yarn)
/// or directly: {alias_name($player_name)}
/// </summary>
public static class AliasNameGenerator
{
static readonly string[] FirstWords =
{
"Midnight", "Velvet", "Neon", "Crimson", "Static", "Amber",
"Obsidian", "Electric", "Hollow", "Silver", "Violet", "Iron",
"Phantom", "Copper", "Glass", "Ashen",
};
static readonly string[] SecondWords =
{
"Vex", "Wilde", "Sable", "Rook", "Marlowe", "Cross",
"Vane", "Thorn", "Rye", "Blaze", "Faye", "Quinn",
"Storm", "Reyes", "Lark", "Doyle",
};
[YarnFunction("alias_name")]
public static string AliasName(string realName)
{
if (string.IsNullOrEmpty(realName))
{
realName = "unknown";
}
var hash = Fnv1aHash(realName);
var first = FirstWords[hash % (uint)FirstWords.Length];
var second = SecondWords[(hash / (uint)FirstWords.Length) % (uint)SecondWords.Length];
return $"{first} {second}";
}
// FNV-1a: simple, stable across platforms and .NET versions (unlike
// string.GetHashCode, which is not guaranteed stable run-to-run).
static uint Fnv1aHash(string value)
{
const uint offsetBasis = 2166136261;
const uint prime = 16777619;
var hash = offsetBasis;
foreach (var c in value)
{
hash ^= c;
hash *= prime;
}
return hash;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ee985664120c84b41921806fadeccb34
@@ -0,0 +1,69 @@
using UnityEngine;
using UnityEngine.InputSystem;
using Yarn.Unity;
namespace NightclubArcadia.Dialogue
{
/// <summary>
/// Trigger-zone interactable: player walks in range, presses E, and this
/// starts the given Yarn node. The reverse of DialogueTransitionController's
/// <<enter_environment>> — this is the "step into dialogue" half of the loop.
/// </summary>
public class DialogueInteractable : MonoBehaviour
{
[SerializeField] private DialogueRunner dialogueRunner;
[SerializeField] private GameObject dialogueUI;
[SerializeField] private string yarnNodeName = "Bartender_Chat";
private bool playerInRange;
private InputAction interactAction;
void Awake()
{
interactAction = new InputAction("Interact");
interactAction.AddBinding("<Keyboard>/e");
interactAction.AddBinding("<Gamepad>/buttonNorth");
interactAction.performed += _ =>
{
if (playerInRange)
{
StartConversation();
}
};
}
void OnEnable() => interactAction?.Enable();
void OnDisable() => interactAction?.Disable();
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
playerInRange = true;
}
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player"))
{
playerInRange = false;
}
}
async void StartConversation()
{
// Player controls are locked by DialogueTransitionController off the
// runner's onDialogueStart event — nothing to do for that here.
if (DialogueUIVisibility.Show(dialogueUI))
{
// Only reached if the UI was found deactivated; give TMP a
// frame to re-cache its Canvas in OnEnable before any text is
// generated.
await Awaitable.NextFrameAsync();
}
await dialogueRunner.StartDialogue(yarnNodeName);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 99afbbd28c8054d59a7a0dec94f5ec8f
@@ -0,0 +1,85 @@
using NightclubArcadia.UI;
using UnityEngine;
using Yarn.Unity;
namespace NightclubArcadia.Dialogue
{
/// <summary>
/// Owns the hand-off between dialogue and the 3D environment: locks the
/// player's controls for as long as a conversation is running, and registers
/// the &lt;&lt;enter_environment&gt;&gt; Yarn command that hands control back.
///
/// The lock is driven by the DialogueRunner's own lifecycle events rather
/// than by each interactable, so every entry point — auto-started nodes,
/// NPCStandIn, DialogueInteractable — gets the same behaviour without
/// having to remember to do it. Control locking is delegated to
/// <see cref="PlayerControlLock"/> so the player menu can hold a second
/// independent lock without re-enabling movement mid-conversation.
/// </summary>
public class DialogueTransitionController : MonoBehaviour
{
[SerializeField] private DialogueRunner dialogueRunner;
[SerializeField] private GameObject dialogueUI;
[SerializeField] private GameObject playerArmature;
[SerializeField] private PlayerControlLock controlLock;
void Awake()
{
if (controlLock == null && playerArmature != null)
{
controlLock = playerArmature.GetComponent<PlayerControlLock>();
}
// Subscribed in Awake rather than Start: the runner auto-starts its
// first node from its own Start(), and script execution order
// between the two is not guaranteed.
dialogueRunner.onDialogueStart?.AddListener(OnDialogueStart);
dialogueRunner.onDialogueComplete?.AddListener(OnDialogueComplete);
}
void Start()
{
if (controlLock == null && playerArmature != null)
{
controlLock = playerArmature.GetComponent<PlayerControlLock>();
}
// Dialogue is in progress as soon as this controller is active
// (including auto-started nodes), so the player stays locked out
// until <<enter_environment>>. The armature itself stays
// active/visible — only its controls are disabled.
controlLock?.Acquire(this);
dialogueRunner.AddCommandHandler("enter_environment", EnterEnvironment);
}
void OnDestroy()
{
dialogueRunner.onDialogueStart?.RemoveListener(OnDialogueStart);
dialogueRunner.onDialogueComplete?.RemoveListener(OnDialogueComplete);
}
void OnDialogueStart()
{
DialogueUIVisibility.Show(dialogueUI);
UIStateController.Instance?.SetDialogueActive(true);
controlLock?.Acquire(this);
}
// Safety net: a node that ends without <<enter_environment>> must still
// give the player their controls back.
void OnDialogueComplete() => EnterEnvironment();
void EnterEnvironment()
{
// Hide, don't deactivate: deactivating the Canvas breaks TMP's
// cached Canvas reference and makes the next conversation throw.
// See DialogueUIVisibility / PanelVisibility.
DialogueUIVisibility.Hide(dialogueUI);
UIStateController.Instance?.SetDialogueActive(false);
// Release is idempotent on a HashSet — safe when both
// <<enter_environment>> and onDialogueComplete fire.
controlLock?.Release(this);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dc43c99ef338a41ea8f7a18c283fe6df
@@ -0,0 +1,28 @@
using NightclubArcadia.UI;
using UnityEngine;
namespace NightclubArcadia.Dialogue
{
/// <summary>
/// Shows and hides the dialogue UI *without* deactivating its GameObject.
/// Forwards to <see cref="PanelVisibility"/> — kept as a thin wrapper so
/// existing call sites (NPCStandIn, DialogueInteractable,
/// DialogueTransitionController) continue to compile unchanged.
/// </summary>
public static class DialogueUIVisibility
{
/// <summary>
/// Makes the dialogue UI visible and interactive. Returns true if the
/// GameObject had to be re-activated (legacy scene state), in which
/// case the caller must wait one frame before rendering text so that
/// TMP's OnEnable can re-cache its Canvas.
/// </summary>
public static bool Show(GameObject dialogueUI) => PanelVisibility.Show(dialogueUI);
/// <summary>
/// Hides the dialogue UI, leaving the hierarchy active. See
/// <see cref="PanelVisibility"/>.
/// </summary>
public static void Hide(GameObject dialogueUI) => PanelVisibility.Hide(dialogueUI);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ad5dcf93105bd49b287dbd19bc42cef7
@@ -0,0 +1,13 @@
using UnityEngine;
using Yarn.Unity;
public class SkillFunctions : MonoBehaviour
{
[YarnFunction("top_skill")]
public static string TopSkill(float a, string aName, float b, string bName, float c, string cName)
{
if (a >= b && a >= c) return aName;
if (b >= a && b >= c) return bName;
return cName;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f7e92eaa5873f456281db864a1a7871b
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 97642050992484049b6c021481f2baf2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,29 @@
using UnityEngine;
namespace NightclubArcadia.Interaction
{
/// <summary>
/// Adapts NPCStandIn to the click-to-interact pipeline without modifying
/// the existing proximity + keypress path.
/// </summary>
public sealed class ClickInteractableBridge : MonoBehaviour, IClickInteractable
{
[SerializeField] NPCStandIn npc;
public Transform ApproachPoint => transform;
public float ApproachRadius => npc != null ? Mathf.Max(0.15f, npc.interactionRange - 0.5f) : 0.15f;
void Awake()
{
if (npc == null)
{
npc = GetComponent<NPCStandIn>();
}
}
public void OnApproached()
{
npc?.Interact();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5257f07d188de44eb9a04ba4d3a65fec
@@ -0,0 +1,11 @@
using UnityEngine;
namespace NightclubArcadia.Interaction
{
public interface IClickInteractable
{
Transform ApproachPoint { get; }
float ApproachRadius { get; }
void OnApproached();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4812c7fb9cf2d41c28966640827dd0c8
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2216657f3a0224501bcfa09add14f689
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
/// <summary>
/// Tracks which input device was most recently active, globally, across the whole game.
/// NPCStandIn (and any other UI that shows button prompts) reads InputDeviceTracker.Current
/// to decide whether to show "[F]", "(X)", "(✕)", etc.
/// Requires the Input System package (Window > Package Manager > Input System),
/// and Project Settings > Player > Active Input Handling set to
/// "Input System Package (New)" or "Both".
/// </summary>
public static class InputDeviceTracker
{
public enum DeviceType { Keyboard, Xbox, PlayStation, Generic }
public static DeviceType Current { get; private set; } = DeviceType.Keyboard;
[RuntimeInitializeOnLoadMethod]
static void Init()
{
InputSystem.onEvent += OnEvent;
}
static void OnEvent(InputEventPtr eventPtr, InputDevice device)
{
// Only care about actual state changes (button/stick/key presses), not noise.
if (!eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>()) return;
switch (device)
{
case Keyboard:
case Mouse:
Current = DeviceType.Keyboard;
break;
case Gamepad gp:
string n = gp.displayName.ToLowerInvariant();
if (n.Contains("dualshock") || n.Contains("dualsense") || n.Contains("playstation"))
Current = DeviceType.PlayStation;
else if (n.Contains("xbox") || n.Contains("xinput"))
Current = DeviceType.Xbox;
else
Current = DeviceType.Generic;
break;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 941f7ff9039984f729ceddebd57d4159
@@ -0,0 +1,205 @@
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
using Yarn.Unity;
using NightclubArcadia.Dialogue;
using NightclubArcadia.UI;
/// <summary>
/// Drop this on a sphere primitive to use it as a placeholder NPC:
/// - Sets sphere color per-instance
/// - Spawns a floating TextMeshPro name label that billboards to face the camera
/// - Shows a proximity interaction prompt ("[F]", "(X)", "(✕)") that swaps based on
/// the last input device used, via InputDeviceTracker
/// - Press the interact button while in range to start a Yarn Spinner dialogue node
/// Requires: TextMeshPro Essentials imported, Input System package installed,
/// InputDeviceTracker.cs in the project, and a "Player" tag on your player object
/// (or assign the Player field manually in the Inspector).
/// </summary>
[RequireComponent(typeof(SphereCollider))]
public class NPCStandIn : MonoBehaviour
{
[Header("Identity")]
public string npcName = "Unnamed NPC";
public Color npcColor = Color.white;
[Header("Dialogue")]
public DialogueRunner dialogueRunner;
public string yarnStartNode = "Start";
[Tooltip("Optional. Left empty, the Canvas under the DialogueRunner is used.")]
public GameObject dialogueUI;
[Header("Label")]
public float labelHeight = 1.2f;
public float labelFontSize = 4f;
public Color labelColor = Color.white;
[Header("Interaction Prompt")]
public Transform player; // auto-found by "Player" tag if left empty
public float interactionRange = 3f;
public float promptHeight = 0.7f;
public float promptFontSize = 3f;
private TextMeshPro labelText;
private TextMeshPro promptText;
private Renderer rend;
private Camera mainCam;
private InputAction interactAction;
private bool playerInRange;
void Awake()
{
rend = GetComponent<Renderer>();
rend.material.color = npcColor;
mainCam = Camera.main;
if (player == null)
{
GameObject found = GameObject.FindGameObjectWithTag("Player");
if (found != null) player = found.transform;
}
CreateLabel();
CreatePrompt();
interactAction = new InputAction("Interact");
interactAction.AddBinding("<Keyboard>/f");
interactAction.AddBinding("<Gamepad>/buttonSouth");
interactAction.performed += ctx =>
{
if (playerInRange) Interact();
};
}
void OnEnable() => interactAction?.Enable();
void OnDisable() => interactAction?.Disable();
void CreateLabel()
{
GameObject labelObj = new GameObject($"{npcName}_Label");
labelObj.transform.SetParent(transform);
labelObj.transform.localPosition = Vector3.up * labelHeight;
labelText = labelObj.AddComponent<TextMeshPro>();
labelText.text = npcName;
labelText.fontSize = labelFontSize;
labelText.color = labelColor;
labelText.alignment = TextAlignmentOptions.Center;
labelText.rectTransform.sizeDelta = new Vector2(2f, 0.5f);
}
void CreatePrompt()
{
GameObject promptObj = new GameObject($"{npcName}_Prompt");
promptObj.transform.SetParent(transform);
promptObj.transform.localPosition = Vector3.up * promptHeight;
promptText = promptObj.AddComponent<TextMeshPro>();
promptText.fontSize = promptFontSize;
promptText.color = Color.yellow;
promptText.alignment = TextAlignmentOptions.Center;
promptText.rectTransform.sizeDelta = new Vector2(1.5f, 0.4f);
promptObj.SetActive(false); // hidden until player is in range
}
void Update()
{
if (player == null) return;
float dist = Vector3.Distance(transform.position, player.position);
bool inRangeNow = dist <= interactionRange;
if (inRangeNow != playerInRange)
{
playerInRange = inRangeNow;
promptText.gameObject.SetActive(playerInRange);
}
if (playerInRange)
{
promptText.text = GlyphFor(InputDeviceTracker.Current);
}
}
void LateUpdate()
{
if (mainCam == null) return;
if (labelText != null)
labelText.transform.rotation = Quaternion.LookRotation(
labelText.transform.position - mainCam.transform.position);
if (promptText != null)
promptText.transform.rotation = Quaternion.LookRotation(
promptText.transform.position - mainCam.transform.position);
}
static string GlyphFor(InputDeviceTracker.DeviceType type)
{
switch (type)
{
case InputDeviceTracker.DeviceType.Xbox: return "(X)";
case InputDeviceTracker.DeviceType.PlayStation: return "(\u2715)"; // Cross button
default: return "[F]";
}
}
public void Interact()
{
if (dialogueRunner == null)
{
Debug.LogWarning($"[NPCStandIn] No DialogueRunner assigned on {npcName}");
return;
}
if (UIStateController.Instance != null && UIStateController.Instance.IsMenuOpen)
{
return;
}
if (!dialogueRunner.IsDialogueRunning)
{
StartDialogueAsync();
}
}
async void StartDialogueAsync()
{
// The dialogue UI is hidden when the player leaves a conversation via
// <<enter_environment>>. Make it visible again before the runner
// renders its first line.
if (DialogueUIVisibility.Show(ResolveDialogueUI()))
{
// Only reached if the UI was found deactivated; give TMP a frame
// to re-cache its Canvas in OnEnable before any text is generated.
await Awaitable.NextFrameAsync();
}
await dialogueRunner.StartDialogue(yarnStartNode);
}
/// <summary>
/// The Canvas under the DialogueRunner, unless one was assigned explicitly.
/// Resolved at call time (including inactive children) so this works
/// without any per-NPC Inspector wiring.
/// </summary>
GameObject ResolveDialogueUI()
{
if (dialogueUI != null)
{
return dialogueUI;
}
var canvas = dialogueRunner.GetComponentInChildren<Canvas>(true);
if (canvas == null)
{
Debug.LogWarning($"[NPCStandIn] No Canvas found under {dialogueRunner.name} for {npcName}");
return null;
}
dialogueUI = canvas.gameObject;
return dialogueUI;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 23d0ac85377c640a1900cfd13e89c389
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 381786de30b3e49e7a693028cc2127f5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More