Files
onboarding-project/docs/progression.md
T
Oleksandr Vlasiuk 1d38990d7f some changes
2026-08-17 12:36:56 +03:00

77 lines
3.8 KiB
Markdown

# Progression
Two independent weapon progressions run at the same time, each fed by its own
resource and its own fixed Pay Zone. They are not related and must not be
merged into one counter.
## Bat level — `BatLevelC`
* Currency: wood, dropped by crates (`ResourceC`, spawned by `BreakablePropC`)
and delivered into the bat's own Pay Zone.
* Displayed by `WeaponHudC` (the bottom-left octagon).
* Added to the bat's damage per crate hit (`PlayerC.updateCombat` ->
`BreakablePropC.hit`), on top of the debug panel's own base `hit_damage`.
* Visual payoff: that Pay Zone's reward pedestal shows a floating bat
(`PlayerC.createBatDisplay`).
## Pistol level — `PistolLevelC`
* Currency: green zombie-part chunks, dropped by dying zombies
(`ZombiePartC`, spawned by `ZombieC`) and delivered into the pistol's own
Pay Zone.
* Displayed by `PistolHudC`, stacked directly below `WeaponHudC`'s octagon.
* Added to the pistol's base damage per shot (see `PlayerC.fireAt`).
* Visual payoff: `WeaponAuraC`, an additive `Sprite` glow on the drawn pistol
that grows with the level and flattens out at its own `MAX_EFFECT_LEVEL`.
A `Sprite` specifically because `Character_Pistol` is a `SkinnedMesh` (a
same-geometry overlay would need re-binding to the skeleton) and because a
light would do nothing against unlit materials.
* That Pay Zone's reward pedestal shows a floating pistol
(`PlayerC.createPistolDisplay`).
Both `createBatDisplay`/`createPistolDisplay` share one private helper in
`PlayerC` (`createWeaponDisplay`) that rebuilds a static, unskinned copy of
whichever prop's geometry — never a `.clone()` of the live mesh, since
`Character_Pistol` is skinned and would otherwise stay bound to the
character's own skeleton and follow the player's hand around the map.
## Pay Zones
Both Pay Zones are `PayZoneC` instances, constructed in `TestSceneC` with the
resource/level pair they each belong to (`getCollectedCount`,
`createDepositVisual`, `onFilled`, `buildRewardIcon`). Filling one raises only
its own level, then it shrinks away, pauses, and grows back **in the same
spot** — there is no relocation and no randomness.
* The map ships one interactive-zone marker (`UI_Interactive_Zone_02`). It is
scaled down by `PAY_ZONE_SCALE` and used as-is for the pistol zone; the bat
zone is a plain clone of it, offset alongside by its own width plus
`PAY_ZONE_GAP` (see `TestSceneC.createPayZoneNodes`).
* `PayZoneC.deposited` is a **lifetime** total per instance and must never be
reset — each HUD's displayed balance is `collected - deposited`, so
zeroing it would make the counter jump. Per-round progress is
`deposited - zoneBaseline`.
* The marker nodes are nested in the map's own hierarchy, so the bat zone's
clone is positioned via `parent.worldToLocal()` rather than a world delta
written straight onto `position` — same reasoning as the crate respawn
path below.
* Each zone's centre also carries a reward preview: a static copy of the
matching weapon (see above) plus a projected DOM `+1 LVL` label.
## Crate respawn
Destroyed crates come back after `RESPAWN_DELAY_MS` (30 s) at their original
spot — without it a long run strips the map of wood and the bat progression
dies with it.
The same collider **nodes** are reused across the whole break/respawn cycle;
only the `PhysicsBody` is rebuilt. `BreakablePropC.init` therefore takes both an
`onDestroyed` and an `onRespawned` callback, and `PlayerC.removeObstacle` /
`PlayerC.addObstacle` are mirrors of each other.
`respawn()` calls `root.updateWorldMatrix(true, true)` after restoring the
resting transform. `Box3.setFromObject` only refreshes the node it is handed,
using its parent's *existing* `matrixWorld` — without the explicit update, the
obstacle box would be sampled at the sunk, toppled transform the crate was left
in when it broke.