423 lines
16 KiB
Markdown
423 lines
16 KiB
Markdown
# 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 |
|
||
| 1–9 (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 |
|