Cozy Wather + buld systeme

This commit is contained in:
2026-07-07 16:43:51 +02:00
parent 031ac42e5d
commit 6b26ae3376
2140 changed files with 3482825 additions and 2252 deletions
+51 -4
View File
@@ -51,9 +51,20 @@ Context for current work:
per-player networked state, which belongs on a `NetworkBehaviour` (SyncVar/RPC), not the
bus (see §3).
**Each script owns one concern.** A script reads its own inputs/state from the bus,
does its job, and raises events for others. Do not let a script mutate another system's
internal data directly. If two systems need to coordinate, route it through `PlayerEvents`.
**Each script owns one concern — but "concern" means one reason to change, not one tiny
action.** A script reads its own inputs/state from the bus, does its job, and raises events for
others. Do not let a script mutate another system's internal data directly. If two systems need
to coordinate, route it through `PlayerEvents`.
Group by **cohesion, not by a per-action counter.** Splitting a feature into many micro-scripts
is its own anti-pattern (scattered logic, high coupling, inspector-wiring noise) just as much as a
god-object is. Keep together what changes together and is used together; split only at a **real
seam** — a genuinely different reason to change, a different collaborator set, or a file growing
unwieldy. The seam that always matters: **local player UX vs shared networked state** (see §3) —
never fuse across it. Reference: the build system splits into `BuildManager` (all the *local*
build UX — catalog, menu, placement) and `BuildRegistry` (the *networked* side — committed
structures + the ghost NetworkObject), one clean local/network seam, not a script per
sub-responsibility.
Data that is authored in the editor lives in **ScriptableObjects** (`ItemData`,
`CraftingRecipe`, `SurfaceSoundProfile`, `ScatterProfile`, `MusicPlaylist`,
@@ -63,6 +74,20 @@ Singletons exist where a single instance is genuinely global (`PlayerInventory.I
`PlayerStats.Instance`, …); guard every access for `null` — they may not exist yet or in
every scene.
**UI is a dumb view — a manager owns the data and populates it.** A UI component (a panel,
a card, a list row, …) must **never** reach for domain data itself: no catalog, no
`ScriptableObject` list, no scanning a database/model to decide *what* to show. It exposes a
`Populate(...)` / `Set(...)` method that takes a lightweight **view payload** (plain
icon / label / number data — never the authored `ScriptableObject`) plus a selection
callback, and renders exactly what it is handed. A dedicated **manager** owns the real data
(the catalog, the model), builds the view payload, pushes it into the UI, and handles the
callback — i.e. it decides what a click *does*. The view reports interactions back by
**index or token**, never by resolving the underlying data. Data flows one way (manager →
view), interactions flow back one way (view → manager via callback), and views stay
swappable and data-agnostic. Reference implementation: `BuildManager` (owns the catalog,
populates + handles picks) + `BuildMenuUI` / `BuildMenuCardUI` (pure views) +
`BuildCardView` (the view payload).
---
## 3. Multiplayer — the second golden rule
@@ -90,6 +115,21 @@ player — get it from `PlayerInventory.Instance` / `PlayerStats.Instance` (owne
singletons), **never** `FindObjectOfType<PlayerX>()`, which also matches disabled remote
puppets (their root GameObject stays active) and targets the wrong player.
### Keep systems OFF the player — it holds only its own intrinsic state
**Default to a scene object/registry, not the player prefab.** The player carries *only what is
intrinsically the local player itself*: its owner-only local simulation (input, locomotion, camera,
tools, animation driver) and its own per-player networked state (health/stats, inventory, the body).
**Everything that touches the world or shared state — and anything that does not need per-player
networking — lives OUTSIDE the player.** This is logical: a build, a placed structure, a shared
station, even a *per-player preview of a world action* (the build ghost), is world-facing, so it
belongs to a scene `NetworkBehaviour` (a `SyncList<…>` registry, or a NetworkObject it spawns),
**never bolted onto the player**. Do not reflexively put per-player networked state on the player
just because it is "per player" — ask first *"is this the player, or is it the world?"*. If it is
world/shared, it is a scene object. Reference implementations: `WorldObjectRegistry`, `BuildRegistry`
(committed structures + the spawned ghost preview), `CookingStation`. The player-hosted pattern
below is only for state that genuinely *is* the player.
### Visible body — first-person arms (owner) + third-person body (remotes)
Every player is drawn twice, split by viewpoint (see §1):
@@ -131,7 +171,14 @@ World objects (pickables, harvestables) are **not** NetworkObjects. They use the
owns a synced set of inactive ids + server-side authority (health, loot, drops) via RPC.
Add new harvestable/pickable-like world state here, not as per-object NetworkObjects.
`ItemData` cannot cross the wire — map items to ids through `ItemDatabase`
(**Tools ▸ Ashwild ▸ Rebuild Item Database** after adding items).
(**Tools ▸ Ashwild ▸ Rebuild Item Database** after adding items). `BuildRegistry` +
`BuildableDatabase` mirror this for placed structures (which also accumulate to high counts).
This "no NetworkObjects" rule targets **high-count** world state (thousands of scatter objects,
hundreds of builds) — making those NetworkObjects would flood the network. It is **not** absolute:
a **transient, low-count** object that moves and needs smooth replication — the build ghost, one
per player — is the case where a real **NetworkObject + client-authoritative NetworkTransform**
is the better tool, and `BuildRegistry` spawns it as such. Pick by count and lifetime, not dogma.
### Shared interactables (cooking stations, future chests/benches)