Files
OGG/DEV_NOTES.md

423 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OGG — Dev Notes & Systems Guide
> Boomer shooter. Cruelty Squad vibes. Jank is a feature.
> This doc covers all current systems.
---
## Scene Hierarchy
```
Player [CharacterController, FirstPersonController, SimpleCrosshair]
│ [Player, PlayerHUD, Inventory, WeaponManager, BootsEffect]
└── Camera [Camera, AudioListener, CameraShake, WeaponBob]
```
Pickups are standalone GameObjects placed anywhere in the scene — **not** children of the player.
---
## Scripts Overview
| Script | Lives On | What it does |
|---|---|---|
| `FirstPersonController` | Player | WASD move, mouse look, jump, sprint, cursor lock |
| `Player` | Player | Health + stamina stats, drain/regen logic, sprint gating |
| `PlayerHUD` | Player | Health bar, stamina bar, speedometer (IMGUI) |
| `SimpleGun` | Weapon prefabs | Shooting, ammo, recoil, muzzle flash, bullet decals |
| `CameraShake` | Camera | Singleton shake impulses, called by SimpleGun on fire |
| `WeaponBob` | Camera | Procedural weapon sway while moving |
| `WeaponViewmodel` | Weapon prefabs | Per-weapon position/rotation/scale in the player's hands |
| `SimpleCrosshair` | Player | Draws the crosshair via OnGUI |
| `EnemyHealth` | Enemy objects | Takes damage from SimpleGun raycasts, flashes red, dies |
| `EnemySpawner` | Any object | Spawns enemies at runtime |
| `HumanoidEnemy` | Enemy objects | Enemy AI / behaviour |
| `ItemDefinition` | ScriptableObject asset | Data definition for any item: name, icon, type, weapon prefab, equippable flag |
| `PickupItem` | Pickup GameObjects | Spin/bob animation, proximity prompt, E-to-collect |
| `Inventory` | Player | Item list, [I] HUD, right-click context menu, equip logic |
| `WeaponManager` | Player | Instantiates weapon prefabs, handles slot switching |
| `BootsEffect` | Player | Watches inventory for Bunny Hop Boots equipped state, applies stamina boost |
| `StaminaBoostPickup` | Pickup GameObjects | Legacy — superseded by BootsEffect. Left in for reference. |
| `RadarHUD` | Player | Mini-map radar disc (IMGUI), top-left corner — shows enemies, NPCs, pickups |
| `DialogueLine` | (data class) | Serializable speaker + pages of text, populated in Inspector on DialogueNPC |
| `DialogueNPC` | Any interactable object | World prompt + E-to-talk trigger, references DialogueLine array |
| `DialogueManager` | Any persistent GameObject | Singleton — renders the dialogue box, handles input and cursor lock |
---
## 1. Player Stats — `Player` + `PlayerHUD`
`Player.cs` is the data container for all player stats. `PlayerHUD.cs` reads from it and draws the HUD.
**Health fields:**
- `maxHealth` / `health` — enemies subtract from `health` directly, no auto-regen
**Stamina fields:**
- `maxStamina` / `stamina` — depletes while sprinting
- `staminaDrain` — units lost per second while sprinting (default 22)
- `staminaRegen` — units gained per second when resting (default 12)
- `regenDelay` — seconds of non-sprinting before regen starts (default 1.2)
- When stamina hits 0 the player is **exhausted** — sprint locked out until stamina fully refills
**HUD Layout:**
```
[bottom-left corner]
SPEED
42.3
units / sec
HEALTH 85 / 100
[████████████░░░]
STAMINA 62 / 100
[██████░░░░░░░░░]
```
**Speedometer:**
- Sits above the health/stamina bars, bottom-left
- Speed calculated from actual position delta per frame (horizontal only)
- Colour shifts: green → yellow → red as speed increases
- Thresholds configurable in Inspector (`speedoFastThreshold`, `speedoCritThreshold`)
- Can be toggled off via `showSpeedometer` bool
---
## 2. Movement — `FirstPersonController`
Standard FPS controller.
**Inspector fields:**
- `walkSpeed` / `runSpeed` — intentionally high (50/80) due to CharacterController collision tuning
- `mouseSensitivity` — linear, no acceleration
- `playerCamera` — auto-found if empty, best to assign explicitly
**Controls:**
| Key | Action |
|---|---|
| W A S D / Arrows | Move |
| Left Shift | Sprint (drains stamina) |
| Space | Jump |
| Mouse | Look |
| Escape | Unlock cursor |
| Left Click (unlocked) | Re-lock cursor |
---
## 3. Shooting — `SimpleGun`
Raycast-based hitscan. No projectiles.
**Key fields:**
- `damage` — per shot
- `range` — raycast distance in units
- `fireRate` — shots per second
- `isAutomatic` — hold vs click
- `recoilKickback` / `recoilKickUp` — feel tuning
- `fpsCam` — auto-found if null
**Internals:**
- Muzzle flash: child Quad + Point Light, shown ~50ms per shot
- Bullet decals: pooled (30 max), oldest reused when full
- Spark particles: spawned and self-destroyed on impact
- Calls `CameraShake.Instance.Shake()` on every shot
- Shooting blocked when inventory is open
- Position driven by `WeaponManager.weaponPositionOffset` each frame so recoil applies on top correctly
**Ammo:** `currentAmmo` / `maxAmmo`. Press **R** to reload (instant).
---
## 4. Item Definitions — `ItemDefinition` (ScriptableObject)
**Create:** Right-click in Project → **Create → OGG → Item Definition**
| Field | Notes |
|---|---|
| `itemName` | Display name everywhere — must be unique |
| `icon` | Sprite shown in inventory slots |
| `type` | `Misc`, `Consumable`, or `Weapon` |
| `isEquippable` | Tick for non-weapon items that can be equipped (e.g. boots) — shows equip button in inventory |
| `weaponPrefab` | Weapon type only — prefab spawned in weapon holder when equipped |
| `description` | Shown in inventory detail panel |
---
## 5. Pickups — `PickupItem`
Attach to any GameObject to make it a spinning, bobbing pickup.
**Setup:**
1. Place a GameObject in the scene with any mesh
2. Add `PickupItem` component
3. Drag an `ItemDefinition` into the `Definition` slot
**Inspector fields:**
| Field | Notes |
|---|---|
| `definition` | The ItemDefinition asset |
| `spinSpeed` | Degrees/sec Y rotation (default 120) |
| `bobHeight` | Vertical travel in units (default 0.18) |
| `bobSpeed` | Bob cycles per second (default 2.2) |
| `pickupRadius` | Proximity trigger distance |
| `pickupKey` | Default E |
| `pickupParticlesPrefab` | Optional — spawned on collect |
| `pickupSound` | Optional — played on collect |
**On pickup:** Calls `Inventory.AddItem(definition, autoEquip: true)` — picking up a weapon automatically equips it.
---
## 6. Inventory — `Inventory`
Lives on the Player. Press **I** to open/close. Cursor unlocks when open.
**HUD Layout:**
```
┌──────────────────────────────┐
│ [ INVENTORY ] │ ← header, shows active weapon top-right
├──────────────────────────────┤
│ [ ] [ ] [ ] [ ] │
│ [ ] [ ] [ ] [ ] │ ← item grid (4×5 by default)
├──────────────────────────────┤
│ SELECTED: Item Name │ ← detail panel
│ Description │
│ ▶ EQUIPPED [ F ] UNEQUIP│ ← shown for weapons AND equippable items
└──────────────────────────────┘
```
**Right-click context menu** on any slot:
- **Equip** / **Unequip** — shown for weapons and items with `isEquippable = true`
- **Inspect** — selects the item in the detail panel
- **Drop** — removes from inventory
**Controls:**
| Input | Action |
|---|---|
| I | Toggle inventory open/close |
| Click slot | Select item |
| Double-click weapon slot | Equip it |
| Right-click slot | Open context menu |
| F | Equip selected item |
| Scroll Wheel (closed) | Cycle weapons |
| 19 (closed) | Equip weapon by slot number |
**Slot colours:**
- Dark grey = empty
- Dark green = has item
- Bright green = selected
- Orange = equipped
**Useful methods:**
```csharp
inventory.HasItem("Medkit") // bool
inventory.CountOf("Grenade") // int
inventory.RemoveItem("Medkit", 1) // bool
inventory.AddItem(definition) // add by ItemDefinition
inventory.AddItem("Raw Name", sprite) // add by name
inventory.IsOpen // bool — checked by gun + WeaponManager
```
---
## 7. Weapon Manager — `WeaponManager`
Lives on the Player. Manages weapon instances and which is active.
**Setup:**
- Add `WeaponManager` to Player
- Set `Weapon Holder` to the Camera transform
- Any `SimpleGun` child of the holder is auto-registered but starts **inactive** — player must pick up and equip from inventory
**Weapon Position fields:**
- `weaponPositionOffset` / `weaponRotationOffset` / `weaponScale` — global fallback if the weapon has no `WeaponViewmodel` component
- Per-weapon positioning is preferred — add `WeaponViewmodel` to the weapon prefab instead
**Live positioning workflow (in Editor):**
1. Hit Play, equip the gun
2. On WeaponManager Inspector → click **"Select Active Weapon in Scene"**
3. Use gizmos in Scene view to position it
4. Click **"Sync Transform → Offset Fields"** — saves to the `WeaponViewmodel` on the prefab automatically
5. Stop Play — values persist
**Useful methods:**
```csharp
weaponManager.EquipByIndex(0)
weaponManager.EquipByName("Gun Splat")
weaponManager.CycleWeapon(1) // next
weaponManager.CycleWeapon(-1) // previous
weaponManager.ActiveWeaponName // string
weaponManager.Unequip() // holster all
```
---
## 8. WeaponViewmodel — `WeaponViewmodel`
Add to a **weapon prefab** to define how it sits in the player's hands. WeaponManager reads from it on equip instead of using the global offset.
**Fields:**
- `positionOffset` — local position relative to weapon holder
- `rotationOffset` — euler angles
- `scale` — local scale
Each weapon prefab gets its own values, so Gun Splat can sit differently from any future weapon.
---
## 9. Radar HUD — `RadarHUD`
Attach to the **Player** alongside `PlayerHUD`. Draws a circular mini-map in the top-left corner. Rotates so "up" always faces the player's forward direction.
**Blip legend:**
| Blip | Colour | Shape | Source component |
|---|---|---|---|
| Player | White | Round | (always centre) |
| Enemy | Red (pulsing) | Round | `HumanoidEnemy` |
| NPC | Green | Round | `DialogueNPC` |
| Pickup | Yellow | Square | `PickupItem` |
Points beyond `worldRange` are clamped to the disc edge so off-screen threats always show a direction.
**Key Inspector fields:**
| Field | Notes |
|---|---|
| `radarRadius` | Visual size of the disc in pixels (default 60) |
| `worldRange` | World-space units covered (default 40) |
| `scanInterval` | How often the scene is re-scanned for objects (default 0.25s) |
No tags or layers required — blips are found by component type.
---
## 10. Dialogue — `DialogueNPC` + `DialogueManager`
### Setup
1. Add `DialogueManager` to any persistent GameObject in the scene (e.g. a "Managers" empty). One instance required per scene.
2. Add `DialogueNPC` to any world object you want to be talkable.
3. In the Inspector on `DialogueNPC`, expand the `Lines` array and fill in speaker names and pages.
### `DialogueNPC` Inspector fields
| Field | Notes |
|---|---|
| `lines[]` | Array of `DialogueLine` entries — each has a speaker name + pages of text |
| `interactRange` | Max distance for the E prompt to appear (default 3.5 units) |
| `interactAngle` | Max degrees off-centre the player can be looking (default 45°) |
| `occlusionMask` | Layer mask for the line-of-sight raycast |
| `promptText` | Label shown in the world-space prompt bubble (default `[E] Talk`) |
### Dialogue box controls
| Key | Action |
|---|---|
| E / Space / Enter | Advance page (or skip typewriter) |
| Escape | Close immediately |
### `DialogueManager` Inspector fields
| Field | Notes |
|---|---|
| `useTypewriter` | Enable/disable letter-by-letter reveal (default on) |
| `charsPerSecond` | Typewriter speed (default 40) |
| `boxHeightFraction` | Box height as fraction of screen height (default 0.22) |
Rich text tags (`<b>`, `<i>`, `<color=red>`) work inside page text — good for glitchy/stylised dialogue.
While the dialogue box is open, mouse look and movement are frozen and the cursor is unlocked automatically.
### NPCs on the radar
Any GameObject with a `DialogueNPC` component automatically appears as a **green dot** on the `RadarHUD`. No extra tagging needed.
---
## 11. Equippable Items — `BootsEffect`
**Add `BootsEffect` to the Player** for the Bunny Hop Boots to work.
Lives on the Player. Watches `Inventory.items` each frame for the boots' `isEquipped` state and adjusts `Player.maxStamina` accordingly.
| State | Effect |
|---|---|
| Boots equipped | `maxStamina` → 200, stamina topped up |
| Boots unequipped | `maxStamina` → 100, stamina clamped back down |
The stamina bar stretches/shrinks live to reflect the new max.
**Inspector fields:**
- `bootsItemName` — must match the ItemDefinition `itemName` exactly (default `"Bunny Hop Boots"`)
- `boostedMaxStamina` — default 200
---
## Setup Scripts (Editor Menu: OGG → Setup)
### Create Gun Splat Weapon
Creates:
- `Assets/Prefabs/Weapons/GunSplat.prefab` — held weapon with `SimpleGun`
- `Assets/Prefabs/Pickups/GunSplat_Pickup.prefab` — world pickup with spin/bob
- `Assets/Items/GunSplat_Item.asset``ItemDefinition` (type = Weapon)
### Create Bunny Hop Boots
Creates:
- `Assets/Items/BhopBoots_Item.asset``ItemDefinition` (type = Misc, isEquippable = true)
- `Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab` — world pickup
**After running either script:** drag the pickup prefab into your scene.
---
## Adding a New Weapon — Step by Step
1. Create an `ItemDefinition` asset — set `type = Weapon`, assign your gun prefab to `weaponPrefab`
2. Add `SimpleGun` to the gun prefab root
3. Add `WeaponViewmodel` to the gun prefab — set position/rotation/scale or dial it in with the live Editor workflow
4. Create a pickup: any mesh + `PickupItem` component + drag in the ItemDefinition
5. Place the pickup in the scene
---
## Adding a New Equippable Item
1. Create an `ItemDefinition` — set `type = Misc`, tick `isEquippable = true`
2. Write a component that lives on the Player, watches `inventory.items` for `entry.isEquipped`, and applies/removes the effect
3. Place a pickup in the scene referencing the definition
---
## Adding a Consumable
```csharp
Inventory inv = GetComponent<Inventory>();
if (inv.HasItem("Medkit")) {
health += 50f;
inv.RemoveItem("Medkit");
}
```
---
## Known Rough Edges
- **No weapon switch animation** — instant swap. Hook into `WeaponManager.EquipByIndex()` when ready.
- **No save system** — inventory is runtime only, nothing persists between sessions.
- **Starting gun** — any `SimpleGun` that's a child of the Camera at Start gets auto-registered but starts inactive. Player needs to find it as a pickup in the scene.
- **Inventory is IMGUI** — not Canvas. Performance fine at this scale. All data logic is decoupled from drawing so a Canvas swap later would be straightforward.
- **`SimpleGun` assumes it's always active** — non-SimpleGun weapons will slot-switch fine but need their own input handling to shoot.
- **Dialogue LOS raycast** — uses `occlusionMask` defaulting to `~0` (all layers). If geometry is blocking the prompt unexpectedly, trim the mask on the `DialogueNPC` component.
---
## Controls Reference
| Key | Action |
|---|---|
| W A S D | Move |
| Left Shift | Sprint |
| Space | Jump |
| Left Click | Shoot |
| Right Click | Context menu (inventory open) |
| R | Reload |
| E | Pick up nearby item / advance dialogue |
| I | Open / close inventory |
| F | Equip selected item |
| Scroll Wheel | Cycle weapons |
| 1 9 | Equip weapon by slot |
| Escape | Unlock cursor |