init
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# ADR-001: Three-Layer Architecture & Code Isolation
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2026-04-27
|
||||
**Engine**: Unity 2022.3.62f3c1
|
||||
**GDD Requirements Addressed**: All L0/L1/L2 system boundaries from systems-index.md
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
夜裔的架构要求在 L0(纯 C# 核心逻辑)、L1(Unity 适配层)、L2(Unity 表现层)之间建立严格的代码隔离。L0 必须零 UnityEngine 依赖以支持独立测试和未来复用。外部开发者需要清晰的物理边界来理解什么代码属于哪一层。
|
||||
|
||||
## Decision
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
Assets/Scripts/
|
||||
Core/ # L0 — Zero UnityEngine dependency
|
||||
Core.asmdef
|
||||
Combat/ # BloodEnergyEconomy, CombatLogic, FormSwitchSM, AttackStyle
|
||||
Enemy/ # EnemyAILogic, BossAILogic, WaveManagerLogic
|
||||
Player/ # DeathRespawnRules
|
||||
Progression/ # SkillTreeRules
|
||||
Persistence/ # SaveLoadLogic
|
||||
Meta/ # ScoreCalculator
|
||||
Geometry/ # Vector3, Shape, MathUtil (pure C#)
|
||||
Adapters/ # L1 — Thin MonoBehaviour wrappers
|
||||
Adapters.asmdef
|
||||
InputAdapter.cs, CameraController.cs, LevelLoader.cs,
|
||||
AudioPlayer.cs, VFXSpawner.cs
|
||||
Presentation/ # L2 — UI Rendering
|
||||
Presentation.asmdef
|
||||
HUDManager.cs, HUDState.cs, MenuSystem.cs
|
||||
```
|
||||
|
||||
### asmdef Reference Chain
|
||||
|
||||
| Assembly | References | Prohibited |
|
||||
|----------|-----------|------------|
|
||||
| Core | (none) | All UnityEngine packages |
|
||||
| Adapters | Core | Presentation |
|
||||
| Presentation | Core, Adapters | — |
|
||||
| Core.Tests | Core | Unity (NUnit standalone) |
|
||||
| Adapter.Tests | Core, Adapters | — |
|
||||
|
||||
### Enforcement Rules
|
||||
|
||||
1. `using UnityEngine;` in any L0 file → violation
|
||||
2. `MonoBehaviour` inheritance in L0 → violation
|
||||
3. L1 classes may NOT contain game rule logic — forwarding and subscription only
|
||||
4. asmdef reference chain is strictly one-way: Core ← Adapters ← Presentation
|
||||
|
||||
## Consequences
|
||||
|
||||
- L0 compiles and tests via `dotnet test` + NUnit without Unity, enabling fast feedback
|
||||
- External developers cannot accidentally put game logic in MonoBehaviour — asmdef enforces it
|
||||
- If the project migrates engines or adds server-side, L0 is directly reusable
|
||||
- Tradeoff: small systems (e.g., ScoreCalculator) pay the adapter abstraction cost
|
||||
@@ -0,0 +1,42 @@
|
||||
# ADR-002: Event Bus & Cross-Layer Communication Pattern
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2026-04-27
|
||||
**Engine**: Unity 2022.3.62f3c1
|
||||
**GDD Requirements Addressed**: All L0→L1 communication from systems-index.md
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Architecture requires L0 to never call L1 or L2. All upward communication must go through an event mechanism. Need to choose: C# built-in `event` vs. a dedicated EventBus class. This decision affects every L0 module's interface design and memory safety.
|
||||
|
||||
## Decision
|
||||
|
||||
**Use C# built-in `event` + `Action<T>`. No dedicated EventBus class.**
|
||||
|
||||
Rationale:
|
||||
- L0 has a fixed set of 10 modules — direct `event` declarations are manageable without a central registry
|
||||
- C# `event` provides compile-time type safety; parameter mismatches are caught at build time, not runtime
|
||||
- Zero external dependencies — no third-party messaging library
|
||||
- Each L0 module's events are part of its public API contract, visible and self-documenting for external developers
|
||||
|
||||
**Subscription lifecycle**:
|
||||
```
|
||||
L1/L2 subscribe to L0 events in OnEnable()
|
||||
L1/L2 unsubscribe in OnDisable()
|
||||
(NOT OnDestroy — avoids lost subscriptions on disable/re-enable cycles)
|
||||
```
|
||||
|
||||
**Naming convention**: `On[What]` — e.g., `OnFormChanged`, `OnEnergyChanged`, `OnWaveStart`
|
||||
|
||||
**Parameter convention**: Use lightweight structs or primitive types. Never pass mutable L0 internal object references to subscribers (prevents L1 from mutating L0 state).
|
||||
|
||||
**Hot path exception**: Attack resolution and damage calculation use per-frame method calls (`CombatLogic.ResolveAttacks()`), not events. Events are reserved for low-frequency state transition notifications.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Each L0 module's event surface is explicitly visible, making inter-system communication self-documenting
|
||||
- No centralized message registry reduces coupling
|
||||
- L1 developers MUST follow OnEnable/OnDisable subscription discipline — leaks or null refs otherwise
|
||||
- If system count grows beyond ~20, a refactor to a dedicated EventBus may be warranted
|
||||
@@ -0,0 +1,43 @@
|
||||
# ADR-003: Pure C# Geometry & Math Library
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2026-04-27
|
||||
**Engine**: Unity 2022.3.62f3c1
|
||||
**GDD Requirements Addressed**: Combat Logic (hit detection), Form Switch SM (attack style shapes), Enemy AI (distance/range checks)
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
L0 must not reference `UnityEngine`, but Combat Logic requires geometric intersection tests (circle vs rect vs sector), Form Switch SM needs distance calculations, and all systems need basic math utilities. A self-contained geometry/math library is required.
|
||||
|
||||
## Decision
|
||||
|
||||
**Build a minimal geometry library implementing only what Nightborn's combat systems actually need.**
|
||||
|
||||
### Type Inventory
|
||||
|
||||
| Type | Fields | Methods | Consumer |
|
||||
|------|--------|---------|----------|
|
||||
| `Vector3` | `float X, Y, Z` | `Distance(Vector3)`, `Normalize()`, `Length`, `Dot`, `Cross`, `+`, `-`, `*` | All modules |
|
||||
| `Circle` | `Vector3 Center`, `float Radius` | `Intersects(Circle)`, `Intersects(Rect)`, `Contains(Vector3)` | Combat (Mist form AOE) |
|
||||
| `Rect` | `Vector3 Center`, `float Width`, `float Height`, `float Rotation` | `Intersects(Circle)`, `Intersects(Rect)`, `Contains(Vector3)` | Combat (Wolf form dash) |
|
||||
| `Sector` | `Vector3 Origin`, `float Radius`, `float Angle`, `float Direction` | `Intersects(Circle)`, `Contains(Vector3)` | Combat (Human form parry fan) |
|
||||
| `MathUtil` | — | `Clamp`, `Lerp`, `InverseLerp`, `Remap`, `Approximately` | All modules |
|
||||
|
||||
### Explicitly Out of Scope
|
||||
|
||||
- Full 3D geometry library — Nightborn is top-down/isometric, needs XZ plane only
|
||||
- Matrix operations — use `System.Numerics.Matrix4x4` (.NET Standard 2.1 built-in)
|
||||
- Physics collision engine — only static intersection tests between geometric shapes
|
||||
|
||||
### Why Not System.Numerics.Vector3
|
||||
|
||||
`System.Numerics.Vector3` is available but its field names and method surface overlap with Unity's Vector3. Self-building avoids confusion for external developers juggling two Vector3 types. If SIMD performance becomes needed later, switching to System.Numerics is a drop-in replacement.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Combat hit detection is fully self-contained and testable without Unity
|
||||
- Geometric types precisely match game needs (Sector directly addresses Human form's parry shape detection)
|
||||
- Tradeoff: maintaining a micro math library (~500 lines maximum)
|
||||
- If complex physics is needed later, re-evaluate against third-party libraries
|
||||
@@ -0,0 +1,90 @@
|
||||
# ADR-004: Form Switch State Machine Design
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2026-04-27
|
||||
**Engine**: Unity 2022.3.62f3c1
|
||||
**GDD Requirements Addressed**: Form Switch State Machine (systems-index.md #3)
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Form switching is Nightborn's core operation — every switch is a risky commitment action. The state machine must balance "responsive feel" against "decisions have weight." It must also support future switch-speed upgrades from the Skill Tree.
|
||||
|
||||
## Decision
|
||||
|
||||
### Four-Phase Linear State Machine
|
||||
|
||||
```
|
||||
RequestSwitch(Wolf)
|
||||
Idle ─────────────────────────────→ Windup
|
||||
↑ │
|
||||
│ timer >= recovery │ timer >= windup
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ Hit during Windup → │←──│
|
||||
│ │ OnSwitchInterrupted │ │
|
||||
│ └─────────────────────────────┘ ↓
|
||||
│ Switching (i-frames)
|
||||
Recovery ←───────────────────────────┘
|
||||
timer >= switch
|
||||
```
|
||||
|
||||
### Phase Parameters (baseline, modifiable by Skill Tree)
|
||||
|
||||
| Phase | Duration | Can Act | Interruptible | Can Re-Switch |
|
||||
|-------|----------|---------|---------------|---------------|
|
||||
| **Idle** | — | Full | — | — |
|
||||
| **Windup** | 0.25s | Cannot attack | Yes (hit → lose Blood Energy) | No |
|
||||
| **Switching** | 0.10s | Cannot act | No (i-frames) | No |
|
||||
| **Recovery** | 0.15s | Can attack/move | No | No |
|
||||
|
||||
**Total switch time**: 0.50s. Danger window: 0.25s (Windup).
|
||||
|
||||
### Interrupt Rules
|
||||
|
||||
- Hit during Windup → `OnSwitchInterrupted` → Blood Energy already spent, switch fails → return to Idle
|
||||
- This IS the "switched at the wrong time" punishment — core to the Switch is Commitment pillar
|
||||
|
||||
### Post-Switch Cooldown
|
||||
|
||||
After Recovery ends, an additional 0.3s cooldown before another switch is allowed (minimum 0.8s between switches) — prevents switch spam and preserves rhythmic feel.
|
||||
|
||||
### Implementation
|
||||
|
||||
```csharp
|
||||
// L0 — Pure C#, zero Unity
|
||||
public class FormSwitchStateMachine
|
||||
{
|
||||
public FormType CurrentForm { get; private set; } = FormType.Human;
|
||||
public SwitchPhase Phase { get; private set; } = SwitchPhase.Idle;
|
||||
public float PhaseTimer { get; private set; }
|
||||
|
||||
// Tunable (Skill Tree can modify)
|
||||
public float WindupDuration = 0.25f;
|
||||
public float SwitchDuration = 0.10f;
|
||||
public float RecoveryDuration = 0.15f;
|
||||
public float CooldownDuration = 0.3f;
|
||||
|
||||
public SwitchResult RequestSwitch(FormType target, BloodEnergyEconomy energy);
|
||||
public void Update(float deltaTime);
|
||||
public void Interrupt();
|
||||
|
||||
public event Action<FormType, FormType> OnFormChanged;
|
||||
public event Action<SwitchPhase> OnPhaseChanged;
|
||||
public event Action<FormType> OnSwitchInterrupted;
|
||||
}
|
||||
```
|
||||
|
||||
### Why a Simple Linear FSM
|
||||
|
||||
- 4 phases progress sequentially — no Hierarchical FSM or Behavior Tree needed
|
||||
- Only Idle accepts input — logic is straightforward for external developers
|
||||
- If a future form needs a different flow, it can subclass with its own state machine
|
||||
|
||||
## Consequences
|
||||
|
||||
- Switch feel is controlled by exactly 4 parameters (WindupDuration, SwitchDuration, RecoveryDuration, CooldownDuration) — tuning is centralized
|
||||
- Interrupt during Windup creates clear "wrong time to switch" punishment, reinforcing Switch is Commitment
|
||||
- Skill Tree can directly modify these 4 parameters for switch-speed upgrades
|
||||
- Windup duration (0.25s) is the critical feel parameter — MUST be validated through prototype, likely iterating between 0.15s–0.35s
|
||||
@@ -0,0 +1,84 @@
|
||||
# ADR-005: Enemy AI Decision Model
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2026-04-27
|
||||
**Engine**: Unity 2022.3.62f3c1
|
||||
**GDD Requirements Addressed**: Enemy AI Logic, Boss AI Logic (systems-index.md #4, #5)
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Nightborn's enemy AI doesn't need complex tactical reasoning, but has a special requirement: each enemy type must strongly favor a specific form, creating "I need to switch" pressure. MVP scope is 3-5 enemy types. The AI model must be simple to implement, produce predictable behavior, and be easily understood and tested by external developers.
|
||||
|
||||
## Decision
|
||||
|
||||
**Use lightweight per-type Finite State Machines. Reject Behavior Trees and Utility AI.**
|
||||
|
||||
### Base FSM Structure (shared by all enemy types)
|
||||
|
||||
```
|
||||
Idle ──→ Chase ──→ Attack ──→ Cooldown
|
||||
↑ │ │
|
||||
└────────────────────┴──────────┘
|
||||
If too far, return to Chase
|
||||
```
|
||||
|
||||
### Per-Type Differentiation in the Attack State
|
||||
|
||||
| Enemy Type | Attack Behavior | Encouraged Form | Why |
|
||||
|------------|----------------|-----------------|-----|
|
||||
| **Swarm** | Many simultaneous small-area hits | Mist | Mist's circular AOE clears groups at once |
|
||||
| **Brute** | Slow, high-damage single strike | Human | Human's parry window is clear and precise against it |
|
||||
| **Stalker** | Fast rush + point-blank flurry | Wolf | Wolf's rectangular dash collides for a stagger counter |
|
||||
| **Shooter** | Ranged projectile barrage from periphery | Mist/Wolf | Mist phases through to close, or Wolf bursts to eliminate |
|
||||
| **Boss** | Multi-phase mixed patterns | All forms | Phase transitions require form adaptation |
|
||||
|
||||
### Why Not Behavior Trees
|
||||
|
||||
BTs excel at complex decision chains (e.g., FPS cover-shooter AI). Nightborn enemies need only 4 states. The tree's maintenance cost exceeds its value.
|
||||
|
||||
### Why Not Utility AI
|
||||
|
||||
Scoring systems produce "optimal" behavior but aren't predictable enough — players can't learn enemy patterns, violating the "Battlefield is Information" pillar.
|
||||
|
||||
### Implementation
|
||||
|
||||
```csharp
|
||||
// L0 — Pure C#, returns one AICommand per frame
|
||||
public enum EnemyStateType { Idle, Chase, Attack, Cooldown }
|
||||
|
||||
public class EnemyAILogic
|
||||
{
|
||||
public EnemyTypeConfig Config;
|
||||
|
||||
public AICommand ComputeAction(EnemyState self, PlayerState player)
|
||||
{
|
||||
TransitionState(self, player);
|
||||
return CurrentState switch
|
||||
{
|
||||
Idle => ScanForPlayer(player),
|
||||
Chase => MoveToward(player),
|
||||
Attack => ExecuteAttack(self.AttackPattern, player),
|
||||
Cooldown => WaitAndReset(self.CooldownTimer),
|
||||
};
|
||||
}
|
||||
|
||||
// Per-type override for unique transition logic
|
||||
protected virtual void TransitionState(EnemyState self, PlayerState player) { }
|
||||
}
|
||||
```
|
||||
|
||||
### Boss Extension
|
||||
|
||||
Boss AI extends the base FSM with:
|
||||
- `BossPhase` enum (additional state dimension)
|
||||
- Phase transition conditions (health thresholds, timer triggers)
|
||||
- `OnPhaseChanged` event for L1 VFX/Audio synchronization
|
||||
|
||||
## Consequences
|
||||
|
||||
- External developers can understand an enemy's behavior from `EnemyTypeConfig` alone — no need to parse behavior trees
|
||||
- Enemy behavior is predictable and learnable — satisfies "Battlefield is Information" pillar
|
||||
- Each enemy type's Attack pattern naturally points to one form counter — reinforces "Form is Tactics"
|
||||
- If future enemies need more intelligence (cover, coordination), the FSM base can be extended without replacement
|
||||
@@ -0,0 +1,405 @@
|
||||
# 夜裔 (Nightborn) — Master Architecture
|
||||
|
||||
## Document Status
|
||||
|
||||
- **Version**: 1.0
|
||||
- **Last Updated**: 2026-04-27
|
||||
- **Engine**: Unity 2022.3.62f3c1 (URP 14.0.12)
|
||||
- **Language**: C# (.NET Standard 2.1)
|
||||
- **GDDs Covered**: game-concept.md, systems-index.md
|
||||
- **ADRs Referenced**: None yet — see Required ADRs section
|
||||
- **Review Mode**: Lean (no director sign-off)
|
||||
- **Architecture**: L0 Pure C# / L1 Unity Adapter / L2 Unity Presentation
|
||||
|
||||
---
|
||||
|
||||
## Engine Knowledge Gap Summary
|
||||
|
||||
| Risk Level | Domains | Implication |
|
||||
|------------|---------|-------------|
|
||||
| **LOW** | All domains | Unity 2022.3 LTS is within LLM training data (cutoff May 2025). No engine knowledge gaps. |
|
||||
| **Note** | New Input System | Project uses Input System package (not legacy Input Manager), consistent with best practices |
|
||||
| **Note** | URP 14.x | Render pipeline is stable and well-documented |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Principles
|
||||
|
||||
These are non-negotiable technical rules derived from the game pillars and design concept.
|
||||
|
||||
1. **L0 is pure** — No `UnityEngine` reference in any L0 source file. L0 compiles as a standalone .NET library.
|
||||
2. **L0 doesn't know it's in Unity** — L0 communicates only via C# primitives and events. It never calls up.
|
||||
3. **L1 is thin** — MonoBehaviour classes in L1 contain only Unity lifecycle wiring and event forwarding. No game logic.
|
||||
4. **Events go up, methods go down** — L0 emits events (L1/L2 subscribe). L1 calls methods on L0. Never the reverse.
|
||||
5. **Data owned by L0, rendered by L1/L2** — All game state lives in L0. L1 and L2 are stateless regarding game rules.
|
||||
|
||||
---
|
||||
|
||||
## System Layer Map
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ L2: Unity 表现层 │
|
||||
│ ┌─────────────┐ ┌──────────────┐ │
|
||||
│ │ UI/HUD │ │ Menu System │ │
|
||||
│ │ Canvas │ │ Canvas │ │
|
||||
│ └──────┬──────┘ └──────┬───────┘ │
|
||||
│ │ │ │
|
||||
│ └───────┬────────┘ │
|
||||
│ │ 只读L0状态 │
|
||||
├─────────────────┼────────────────────────────────────────┤
|
||||
│ L1: Unity 适配层 │
|
||||
│ ┌──────────────┐ ┌──────────┐ ┌───────────┐ │
|
||||
│ │Input Adapter │ │ Camera │ │Level │ │
|
||||
│ │InputSystem │ │ Ctrl │ │Loader │ │
|
||||
│ └──────┬───────┘ └────┬─────┘ └─────┬─────┘ │
|
||||
│ ┌──────┴───────┐ ┌────┴─────┐ │
|
||||
│ │ VFX Spawner │ │ Audio │ │
|
||||
│ │ ParticleSys │ │ Player │ │
|
||||
│ └──────┬───────┘ └────┬─────┘ │
|
||||
│ │ │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ 方法调用↓ 事件订阅↑ │
|
||||
├────────────────┼────────────────────────────────────────────┤
|
||||
│ L0: 纯C# 核心逻辑层 (零Unity依赖) │
|
||||
│ ┌──────────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │Blood Energy │ │ Combat │ │Form Switch │ │
|
||||
│ │Economy │ │ Logic │ │State Machine │ │
|
||||
│ └──────┬───────┘ └────┬─────┘ └──────┬───────┘ │
|
||||
│ ┌──────┴───────┐ ┌────┴─────┐ ┌──────┴───────┐ │
|
||||
│ │Enemy AI │ │Boss AI │ │Wave Manager │ │
|
||||
│ │Logic │ │Logic │ │Logic │ │
|
||||
│ └──────────────┘ └──────────┘ └──────────────┘ │
|
||||
│ ┌──────────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │Death/Respawn │ │Skill Tree│ │Save/Load │ │
|
||||
│ │Rules │ │Rules │ │Logic │ │
|
||||
│ └──────────────┘ └──────────┘ └──────────────┘ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │Score Calc │ │
|
||||
│ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Communication Rules
|
||||
|
||||
| Direction | Allowed | Mechanism |
|
||||
|-----------|---------|-----------|
|
||||
| L0 → L0 | ✓ | Direct method call, C# event |
|
||||
| L0 → L1 | ✗ | L0 does NOT call L1. L0 emits C# events; L1 subscribes. |
|
||||
| L0 → L2 | ✗ | L2 reads L0 state through L1 or direct POCO access |
|
||||
| L1 → L0 | ✓ | Method calls (e.g., `combat.Attack()`), property reads |
|
||||
| L1 → L2 | ✓ | Method calls (e.g., `hud.Refresh(state)`) |
|
||||
| L2 → L0 | ✓ | Read-only access to L0 public properties and state aggregates |
|
||||
|
||||
---
|
||||
|
||||
## Module Ownership
|
||||
|
||||
### L0 — Pure C# Logic Layer
|
||||
|
||||
| Module | Owns | Exposes | Consumes | Unity Deps |
|
||||
|--------|------|---------|----------|-----------|
|
||||
| **Blood Energy Economy** | Current/max energy, gain/spend rules, decay formula | `float Current`, `float Max`, `void Add(float)`, `bool Spend(float)`, `event OnEnergyChanged` | — | Zero |
|
||||
| **Combat Logic** | Hit detection (geometry intersection), damage formula, crit rules | `DamageResult Attack(AttackData)`, `bool HitTest(Shape, Shape)` | — (uses own geometry lib) | Zero |
|
||||
| **Form Switch SM** | Current form, switch state machine, windup timer, switch conditions | `FormType CurrentForm`, `SwitchResult RequestSwitch(FormType)`, `AttackStyle GetAttackStyle(FormType)`, `event OnFormChanged` | Blood Energy (check/spend) | Zero |
|
||||
| **Enemy AI Logic** | Per-type behavior rules, target selection, attack decisions | `AICommand Update(EnemyState, PlayerState)` | Combat Logic (damage calc) | Zero |
|
||||
| **Boss AI Logic** | Phase logic, phase transition conditions, phase-specific behaviors | `BossCommand Update(BossState, PlayerState)`, `event OnPhaseChanged` | Enemy AI, Combat Logic | Zero |
|
||||
| **Wave Manager Logic** | Wave composition data, spawn timers, wave state | `WaveComposition GetNextWave()`, `bool IsWaveComplete()`, `event OnWaveStart`, `event OnWaveBreak` | Enemy AI (types) | Zero |
|
||||
| **Death/Respawn Rules** | Death trigger, checkpoint state, respawn logic | `bool IsDead(float)`, `Vector3 RespawnPoint`, `event OnDeath`, `event OnRespawn` | Combat Logic (health) | Zero |
|
||||
| **Skill Tree Rules** | Unlock conditions, upgrade formulas, form synergy effects | `bool CanUnlock(SkillNode)`, `void Unlock(SkillNode)` | Form Switch SM | Zero |
|
||||
| **Save/Load Logic** | Serialization format, file paths, version migration | `void Save(GameState)`, `GameState Load()`, `bool SaveExists(int)` | Skill Tree Rules | Zero |
|
||||
| **Score Calculator** | Score formula, combo tracking, per-wave stats | `int Score`, `float StyleRank`, `void OnKill(KillData)` | Combat Logic, Wave Manager | Zero |
|
||||
|
||||
### L1 — Unity Adapter Layer
|
||||
|
||||
| Module | Owns | Exposes | Consumes | Key Unity APIs |
|
||||
|--------|------|---------|----------|---------------|
|
||||
| **Input Adapter** | Input Action bindings, action map config | `event OnAttack`, `event OnSwitch(FormType)`, `Vector2 MoveInput` | — | `InputAction`, `PlayerInput` |
|
||||
| **Camera Controller** | Camera component, follow logic, shake | `void SetTarget(Vector3)`, `void Shake(float)` | L0 player position (read) | `Camera`, `Transform` |
|
||||
| **Level Loader** | Scene refs, load progress | `void LoadLevel(string)`, `event OnLevelLoaded` | — | `SceneManager`, `AsyncOperation` |
|
||||
| **Audio Player** | AudioSource pool, clip mappings | `void Play(SfxType)`, `void SetMusic(MusicTrack)` | L0 events (subscribes) | `AudioSource`, `AudioMixer` |
|
||||
| **VFX Spawner** | ParticleSystem pool, geometric preset library | `void Spawn(VfxType, Vector3)`, `void SetFormColor(FormType)` | L0 events (subscribes) | `ParticleSystem`, `ObjectPool<T>` |
|
||||
|
||||
### L2 — Unity Presentation Layer
|
||||
|
||||
| Module | Owns | Exposes | Consumes | Key Unity APIs |
|
||||
|--------|------|---------|----------|---------------|
|
||||
| **UI/HUD** | Canvas, form indicator, blood energy bar, wave info | `void Refresh(HUDState)` | L0 state (read only) | `Canvas`, `UI.Image`, `TMP_Text` |
|
||||
| **Menu System** | Main menu, pause panel, skill tree UI, settings | `void ShowMainMenu()`, `void ShowPause()` | Input Adapter, Level Loader, Save/Load | `Canvas`, `UI.Button` |
|
||||
|
||||
---
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Combat Frame Path
|
||||
```
|
||||
Unity Update (L1)
|
||||
→ Input Adapter reads Input System → fires C# events
|
||||
→ L1 calls L0: CombatSystem.Attack(), FormSwitchSM.Update()
|
||||
→ L0 resolves: hit tests, damage, AI decisions, state machine ticks
|
||||
→ L0 fires events: OnHit, OnFormChanged, OnEnergyChanged
|
||||
→ L1 subscribers: VFX.Spawn(), Audio.Play()
|
||||
→ L1 reads L0 state: Camera.SetTarget(), HUD.Refresh(state)
|
||||
→ Unity renders to screen
|
||||
```
|
||||
|
||||
### Form Switch Event Chain
|
||||
```
|
||||
Input → OnSwitch(Wolf) event
|
||||
→ FormSwitchSM.RequestSwitch(Wolf)
|
||||
→ BloodEnergy.Spend(cost)
|
||||
→ Windup timer starts
|
||||
→ Windup completes → OnFormChanged(Human→Wolf)
|
||||
→ VFX: switch particle burst
|
||||
→ Audio: switch SFX
|
||||
→ HUD: color update
|
||||
→ Enemy AI: re-evaluate threat priority
|
||||
```
|
||||
|
||||
### Initialization Order
|
||||
```
|
||||
1. Unity Awake: Level Loader → Input Adapter → Camera → Audio → VFX
|
||||
2. L0 bootstrap (triggered by L1):
|
||||
Blood Energy → Combat Logic → Form Switch SM → Enemy AI → Wave Manager
|
||||
3. L2 HUD subscribes to L0 events
|
||||
4. Game start signal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Boundaries
|
||||
|
||||
### Core L0 Interfaces
|
||||
|
||||
```csharp
|
||||
// Blood Energy Economy
|
||||
public class BloodEnergyEconomy {
|
||||
public float Current { get; }
|
||||
public float Max { get; }
|
||||
public float GainRate { get; set; }
|
||||
public void Add(float amount);
|
||||
public bool CanSpend(float amount);
|
||||
public bool Spend(float amount);
|
||||
public void Reset();
|
||||
public event Action<float,float> OnEnergyChanged;
|
||||
// Invariant: 0 ≤ Current ≤ Max
|
||||
}
|
||||
|
||||
// Combat Logic
|
||||
public struct AttackData {
|
||||
public Vector3 Origin;
|
||||
public Shape HitShape;
|
||||
public float Damage;
|
||||
public float KnockbackForce;
|
||||
public FormType SourceForm;
|
||||
}
|
||||
public struct DamageResult {
|
||||
public int TargetId;
|
||||
public float FinalDamage;
|
||||
public bool IsCritical;
|
||||
public Vector3 HitPoint;
|
||||
public bool IsKillingBlow;
|
||||
}
|
||||
public class CombatLogic {
|
||||
public static bool TestHit(Shape a, Shape b);
|
||||
public DamageResult CalculateDamage(AttackData atk, EnemyDefenseData def);
|
||||
public List<DamageResult> ResolveAttacks(List<AttackData> atks, List<EnemyState> enemies);
|
||||
}
|
||||
|
||||
// Form Switch State Machine
|
||||
public enum FormType { Human, Wolf, Mist }
|
||||
public enum SwitchPhase { Idle, Windup, Switching, Recovery }
|
||||
public enum SwitchResult { Success, InsufficientEnergy, OnCooldown, InvalidTarget }
|
||||
public struct AttackStyle {
|
||||
public float Range, BaseDamage, AttackSpeed, AreaSize;
|
||||
public Shape AttackShape;
|
||||
public int MaxTargets;
|
||||
}
|
||||
public class FormSwitchStateMachine {
|
||||
public FormType CurrentForm { get; }
|
||||
public SwitchPhase Phase { get; }
|
||||
public SwitchResult RequestSwitch(FormType target);
|
||||
public AttackStyle GetAttackStyle(FormType form);
|
||||
public void Update(float deltaTime);
|
||||
public event Action<FormType,FormType> OnFormChanged;
|
||||
public event Action<SwitchPhase> OnPhaseChanged;
|
||||
}
|
||||
|
||||
// Enemy AI Logic
|
||||
public struct AICommand {
|
||||
public CommandType Type; // Move, Attack, Ability, Flee
|
||||
public Vector3 TargetPosition;
|
||||
public int TargetId, AbilityId;
|
||||
}
|
||||
public class EnemyAILogic {
|
||||
public AICommand ComputeAction(EnemyState self, PlayerState player);
|
||||
}
|
||||
|
||||
// Wave Manager Logic
|
||||
public struct WaveComposition {
|
||||
public List<EnemySpawnEntry> Enemies;
|
||||
public float DelayBetweenGroups;
|
||||
public string[] SpecialConditions;
|
||||
}
|
||||
public class WaveManagerLogic {
|
||||
public WaveState CurrentState { get; }
|
||||
public int CurrentWave { get; }
|
||||
public WaveComposition GetNextWave();
|
||||
public void OnEnemyKilled(int enemyId);
|
||||
public void Update(float deltaTime);
|
||||
public event Action<WaveComposition> OnWaveStart;
|
||||
public event Action<float> OnWaveBreak;
|
||||
public event Action OnAllWavesComplete;
|
||||
}
|
||||
```
|
||||
|
||||
### L1 Adapter Interfaces
|
||||
|
||||
```csharp
|
||||
// Input Adapter — sole owner of Unity Input System interaction
|
||||
public class InputAdapter : MonoBehaviour {
|
||||
public event Action OnAttackPressed, OnAttackReleased, OnDodgePressed, OnPausePressed;
|
||||
public event Action<FormType> OnSwitchPressed;
|
||||
public Vector2 MoveInput { get; }
|
||||
}
|
||||
|
||||
// VFX Spawner — subscribes to L0 events, drives Unity ParticleSystem
|
||||
public class VFXSpawner : MonoBehaviour {
|
||||
public void PlayFormSwitchEffect(FormType from, FormType to, Vector3 pos);
|
||||
public void PlayHitEffect(Vector3 pos, float damage);
|
||||
public void PlayKillEffect(Vector3 pos);
|
||||
public void PlayBossPhaseEffect(Vector3 pos);
|
||||
}
|
||||
```
|
||||
|
||||
### L2 HUD Interface
|
||||
|
||||
```csharp
|
||||
// HUDState — pure data aggregate of L0 state for rendering
|
||||
public struct HUDState {
|
||||
public float BloodEnergyCurrent, BloodEnergyMax;
|
||||
public FormType CurrentForm;
|
||||
public float PlayerHealth;
|
||||
public int CurrentWave, TotalWaves;
|
||||
public float WaveProgress;
|
||||
public int StyleRank;
|
||||
public float BossHealth;
|
||||
}
|
||||
|
||||
public class HUDManager : MonoBehaviour {
|
||||
public void Refresh(HUDState state); // called every frame by L1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ADR Audit
|
||||
|
||||
| Status | Detail |
|
||||
|--------|--------|
|
||||
| Existing ADRs found | 0 |
|
||||
| Traceable TRs from GDDs | 0 (no per-system GDDs authored) |
|
||||
| Architecture decisions in this doc | All API boundaries, layer map, data flow, communication rules |
|
||||
|
||||
No existing ADRs to audit. All architectural decisions in this document require formal ADR records.
|
||||
|
||||
---
|
||||
|
||||
## Required ADRs
|
||||
|
||||
### Must create before coding (Foundation)
|
||||
|
||||
| ADR ID | Title | Covers | Priority |
|
||||
|--------|-------|--------|----------|
|
||||
| ADR-001 | Three-Layer Architecture & Code Isolation | L0/L1/L2 directory layout, asmdef configuration, cross-layer reference prohibition | BLOCKING |
|
||||
| ADR-002 | Event Bus & Communication Pattern | C# event as sole L0→L1 mechanism, subscription lifecycle, no direct L0→Unity calls | BLOCKING |
|
||||
| ADR-003 | Pure C# Geometry & Math Library | Custom Vector3, Shape hierarchy, Math utilities — zero UnityEngine dependency | BLOCKING |
|
||||
|
||||
### Should create before relevant system (Core)
|
||||
|
||||
| ADR ID | Title | Covers | Priority |
|
||||
|--------|-------|--------|----------|
|
||||
| ADR-004 | Form Switch State Machine Design | State transitions, timing parameters, windup/invuln/recovery phases, interrupt behavior | HIGH |
|
||||
| ADR-005 | Enemy AI Decision Model | Behavior tree vs state machine vs utility AI — choice and rationale | HIGH |
|
||||
| ADR-006 | Wave Composition Data Format | WaveConfig structure, ScriptableObject vs JSON, editor tooling | MEDIUM |
|
||||
| ADR-007 | Serialization Format & Save Strategy | JSON vs binary, version migration policy, save slot management | MEDIUM |
|
||||
|
||||
### Can defer to implementation (Feature)
|
||||
|
||||
| ADR ID | Title | Covers | Priority |
|
||||
|--------|-------|--------|----------|
|
||||
| ADR-008 | Skill Tree Data Structure | Node graph representation, unlock dependencies, synergy trigger conditions | LOW |
|
||||
| ADR-009 | Score & Style Formula | Kill weighting, combo decay, wave speed bonus, rank thresholds | LOW |
|
||||
|
||||
---
|
||||
|
||||
## Project Directory Structure
|
||||
|
||||
```
|
||||
Assets/
|
||||
├── Scripts/
|
||||
│ ├── Core/ # L0 — Pure C# (no UnityEngine)
|
||||
│ │ ├── Core.asmdef # asmdef: no Unity refs
|
||||
│ │ ├── Combat/
|
||||
│ │ │ ├── BloodEnergyEconomy.cs
|
||||
│ │ │ ├── CombatLogic.cs
|
||||
│ │ │ ├── FormSwitchStateMachine.cs
|
||||
│ │ │ └── AttackStyle.cs
|
||||
│ │ ├── Enemy/
|
||||
│ │ │ ├── EnemyAILogic.cs
|
||||
│ │ │ ├── BossAILogic.cs
|
||||
│ │ │ └── WaveManagerLogic.cs
|
||||
│ │ ├── Player/
|
||||
│ │ │ └── DeathRespawnRules.cs
|
||||
│ │ ├── Progression/
|
||||
│ │ │ └── SkillTreeRules.cs
|
||||
│ │ ├── Persistence/
|
||||
│ │ │ └── SaveLoadLogic.cs
|
||||
│ │ ├── Meta/
|
||||
│ │ │ └── ScoreCalculator.cs
|
||||
│ │ └── Geometry/ # ADR-003 — Pure C# math types
|
||||
│ │ ├── Vector3.cs
|
||||
│ │ ├── Shape.cs
|
||||
│ │ └── MathUtil.cs
|
||||
│ ├── Adapters/ # L1 — MonoBehaviour wrappers
|
||||
│ │ ├── Adapters.asmdef # asmdef: refs Core + Unity
|
||||
│ │ ├── InputAdapter.cs
|
||||
│ │ ├── CameraController.cs
|
||||
│ │ ├── LevelLoader.cs
|
||||
│ │ ├── AudioPlayer.cs
|
||||
│ │ └── VFXSpawner.cs
|
||||
│ └── Presentation/ # L2 — UI & Rendering
|
||||
│ ├── Presentation.asmdef # asmdef: refs Core + Adapters + Unity
|
||||
│ ├── HUDManager.cs
|
||||
│ ├── HUDState.cs
|
||||
│ └── MenuSystem.cs
|
||||
├── Scenes/ # Unity场景文件
|
||||
├── Settings/ # URP质量配置
|
||||
└── Tests/
|
||||
├── Core.Tests/ # L0 单元测试 (NUnit, 无Unity)
|
||||
│ ├── Core.Tests.asmdef
|
||||
│ ├── BloodEnergyEconomyTests.cs
|
||||
│ ├── CombatLogicTests.cs
|
||||
│ └── FormSwitchSMTests.cs
|
||||
└── Adapter.Tests/ # L1 集成测试 (Unity Test Framework)
|
||||
└── Adapter.Tests.asmdef
|
||||
```
|
||||
|
||||
### asmdef 配置规则
|
||||
|
||||
| 程序集 | 引用 | 禁止引用 |
|
||||
|--------|------|----------|
|
||||
| `Core.asmdef` | (none) | 不可引用任何 Unity 包 |
|
||||
| `Adapters.asmdef` | Core | 不可引用 Presentation |
|
||||
| `Presentation.asmdef` | Core, Adapters | — |
|
||||
| `Core.Tests.asmdef` | Core | 不可引用 Unity (NUnit standalone) |
|
||||
| `Adapter.Tests.asmdef` | Core, Adapters | — |
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 是否需要专用的事件总线类管理 L0 事件的订阅/取消,还是直接使用 C# `event` + `Action`?—— ADR-002 中决定
|
||||
- 敌人的 WaveConfig 用 ScriptableObject (方便策划编辑) 还是纯 JSON (更便携)?—— ADR-006 中决定
|
||||
- 是否需要支持中途存档(战斗中途保存并恢复)还是仅关卡间存档?—— 在 Save/Load GDD 中决定
|
||||
- L0 的三个 asmdef 子模块(Combat / Enemy / Player)是否需要独立 asmdef 而非一个 Core.asmdef?—— 当前一个 Core.asmdef 即可,复杂度不足以拆分
|
||||
Reference in New Issue
Block a user