Vampire-Act-Base/design/gdd/combat-logic.md

11 KiB
Raw Blame History

Combat Logic

Status: In Design Author: SepComet + Claude Last Updated: 2026-04-27 Implements Pillar: 操作优于数值 (Skill Over Stats), 战场即信息 (Battlefield is Information)

Overview

Combat Logic 是夜裔的即时战斗判定核心——负责"打中了没有""伤害是多少""是否暴击"。它接收攻击数据(形状、伤害值、来源形态),对目标进行几何相交检测,产出 DamageResult。系统完全纯C#,使用 ADR-003 定义的 Circle/Rect/Sector 进行形状判定。它不产生任何视觉或音频效果——而是通过 C# event 将结果传递给 L1/L2。设计原则判定必须确定、反馈必须清晰、计算必须快速(每帧数百次判定在 1ms 内完成)。

Player Fantasy

夜裔的战斗幻想不是"数字在涨",而是每一击都有形状、有重量、有来由。当你用人形精准弹反——扇形判定区恰好覆盖精英的攻击前摇——一个金色暴击数字弹出,你感觉自己不是"按了攻击键",而是用几何体"接住了"对方的攻击。当你切到狼躯冲刺——矩形判定贯穿一排敌人——敌人被击退的轨迹本身就是你所画的一条线。当雾形圆形AOE笼罩整个屏幕的杂兵那种"我画了一个圈,圈里全死"的掌控感,就是 Combat Logic 要传达的幻想。参考 RE4 弹反的"接住"反馈 + DMC 的判定可视化 + 割草的密度感。

Detailed Design

Attack Shape Definitions

Each form maps to one attack geometry type (via ADR-003):

Form Shape Parameters Visual Read
Human Sector Angle=90°, Radius=3.0 Forward fan — parry judgment zone
Wolf Rect Width=1.5, Length=5.0 Forward rectangle — dash trajectory
Mist Circle Radius=5.0 Self-centered circle — full AOE

Data Structures

AttackData {
    Vector3 Origin          // Attack origin (player position)
    float Direction         // Facing angle
    Shape HitShape          // Circle / Rect / Sector
    FormType SourceForm     // Form that launched the attack
    float BaseDamage        // From Form Switch SM's AttackStyle
    float CritChance        // Base 0.05, upgradable
    float CritMultiplier    // Default 1.5x
    float KnockbackForce
}

DamageResult {
    int TargetId
    float FinalDamage
    bool IsCritical
    Vector3 HitPoint        // For VFX positioning
    bool IsKillingBlow
    FormType SourceForm
}

Hit Resolution Pipeline (called once per frame)

CombatLogic.ResolveAttacks(attacks, enemies, deltaTime)

1. COLLECT: Receive List<AttackData> + List<EnemyState>
2. CULL: Remove dead enemies, skip enemies outside attack range (coarse filter)
3. INTERSECT: For each attack × potential target, call ADR-003 Shape.Intersects()
4. DAMAGE: For each hit pair, execute damage formula (Section D)
5. SORT: Descending by damage — high damage resolves first
6. DEDUPLICATE: Same attack can hit multiple enemies; same enemy can be hit by multiple attacks — but each attack hits each enemy at most once per frame
7. EMIT: OnHit(DamageResult), OnKill(DamageResult), OnCrit(DamageResult)
8. RETURN: List<DamageResult> grouped by TargetId

Target Filtering Rules

  • Enemy state = Dead → skip
  • Attack shape has no intersection with enemy hit shape → skip
  • Same attack already hit this enemy this frame → skip (dedup)

Events

Event Trigger Payload
OnHit Every successful hit DamageResult
OnKill Hit reduces enemy health to 0 DamageResult
OnCrit Hit triggers critical DamageResult
OnAttackResolved Attack resolution complete (fires even if 0 hits) AttackData, int hitCount

Interactions with Other Systems

System Direction Interface
Form Switch SM Upstream Reads AttackStyle for shape + base damage per form
Enemy AI Logic Downstream Enemy receives damage, updates health, may trigger behavior change
VFX Spawner (L1) Downstream Subscribes to OnHit/OnKill/OnCrit for visual feedback
Death/Respawn Downstream OnKill with IsKillingBlow triggers death check

Formulas

Damage Formula

finalDamage = baseDamage × critFactor

where critFactor = critMultiplier if random(0,1) < critChance, else 1.0
Variable Symbol Type Range Description
Base damage baseDamage float 550 From AttackStyle, determined by form and upgrades
Crit chance critChance float 0.00.30 Baseline 0.05, grows via Skill Tree
Crit multiplier critMultiplier float 1.23.0 Baseline 1.5
Crit factor critFactor float 1.0 or critMultiplier Dice roll per hit

Output Range: 5150 (extreme: baseDamage=50 × critMultiplier=3.0 = 150) Design Intent: Combat Logic applies only base damage. Enemy resistance modifiers and form affinity bonuses are defined in Enemy AI GDD. Skill damage bonuses are defined in Skill Tree GDD. This keeps Combat Logic single-responsibility.

Knockback Formula

knockbackDistance = knockbackForce / enemyWeight × (IsCritical ? 1.5 : 1.0)
Variable Symbol Type Range Description
Knockback force knockbackForce float 010 From AttackStyle
Enemy weight enemyWeight float 110 Heavier enemies resist knockback

Output: 015 units | Critical hits add 1.5× knockback

Edge Cases

# Condition Resolution Rationale
1 Attack shape has zero area (radius=0, width=0) Skip, return empty list Degenerate shape — no valid hit possible
2 Enemy has no hit shape defined Skip that enemy Incomplete data — should not happen at runtime
3 Same attack hits same enemy twice (edge overlap) Dedup: count only first hit One attack = one hit per target per frame
4 critChance exceeds 1.0 via buffs Clamp to 1.0 Guaranteed crit is the ceiling
5 baseDamage = 0 (status effect attack) Still perform hit test, emit OnHit with 0 damage Knockback may still apply
6 Multiple attacks in same frame against same enemy Resolve all, sort by damage desc Independent attacks stack
7 Enemy killed mid-frame by attack #1 Attacks #2-N skip this enemy (dead filter) Dead enemies don't accumulate damage
8 knockbackForce = 0 Return 0, skip calculation No force applied
9 deltaTime = 0 (pause) Return empty list, no events Paused game = no combat resolution
10 Empty enemies list Return empty list, no events No targets to hit
11 Shape intersection at exact tangent point Count as miss (requires overlap > epsilon) Prevents degenerate edge-touch = hit

Dependencies

System Relationship Interface
Form Switch SM Upstream Reads AttackStyle.BaseDamage, AttackStyle.AttackShape per form
Enemy AI Logic Downstream Enemy receives DamageResult for health subtraction and behavior response
VFX Spawner (L1) Downstream Subscribes to OnHit, OnKill, OnCrit for visual feedback
Death/Respawn Downstream OnKill(IsKillingBlow=true) triggers death check
ADR-003 Geometry Foundation Uses Circle, Rect, Sector for all intersection tests

No upstream GDD dependencies — this is a Foundation layer system. Form Switch SM GDD is undesigned; the expected AttackStyle contract is defined here as provisional until that GDD is written.

Tuning Knobs

Parameter Default Safe Range Too Low Too High
baseDamage (Human) 15 825 Parry feels unrewarding Parry out-damages other forms
baseDamage (Wolf) 25 1540 Burst doesn't feel bursty Wolf becomes the only viable form
baseDamage (Mist) 8 415 AOE feels useless Mist clears everything, no switching needed
critChance 0.05 0.020.10 Crits never seen Crits too frequent, lose impact
critMultiplier 1.5 1.22.5 Crits not noticeable Damage spikes too wild
Human Sector Angle 90° 60120° Too narrow, parry frustrating Too wide, parry trivial
Wolf Rect Length 5.0 3.08.0 Dash feels short Range competes with Mist
Mist Circle Radius 5.0 3.08.0 AOE feels cramped Covers whole screen, no positioning

Visual/Audio Requirements

Combat is a visual system — Visual/Audio is REQUIRED.

VFX (via L1 VFX Spawner):

  • OnHit: Geometric particle burst at DamageResult.HitPoint — color matches SourceForm
  • OnCrit: Larger burst + screen shake micro — gold/amber tint
  • OnKill: Enemy shatter into geometric fragments — fragment color = enemy type
  • Debug overlay: Semi-transparent fill of Sector/Rect/Circle during attack frames

Audio (via L1 Audio Player):

  • OnHit: Impact SFX — varies by form (Human=sharp slash, Wolf=heavy thud, Mist=ethereal whoosh)
  • OnCrit: Distinct "clink" + bass emphasis
  • OnKill: Shatter sound — pitch varies by enemy size

Art Bible Alignment: Principle 1 (Color is Identity — hit VFX color = form color), Principle 3 (Particles are Feedback — geometric fragments = information).

UI Requirements

Element Position Content
Damage Numbers Floating at hit point Numeric value, color = form color, crit = larger + gold
Hit Indicator Screen edge Directional flash when player takes damage
Kill Counter HUD Combo/kill streak (fed by OnKill event)

Acceptance Criteria

# GIVEN WHEN THEN
1 Player in Human form, enemy in sector zone ResolveAttacks called Hit detected, OnHit fires with SourceForm=Human
2 Player in Wolf form, enemy 6 units away ResolveAttacks (Rect length=5) No hit (beyond range), 0 results
3 Player in Mist form, 5 enemies in 4-unit radius ResolveAttacks (Circle radius=5) All 5 enemies hit, 5 results returned
4 Enemy at exact edge of shape boundary Intersection test Miss (epsilon threshold > tangent)
5 critChance=0.3, 100 attacks ResolveAttacks × 100 ~30 hits critical (±5 tolerance)
6 Enemy health=10, baseDamage=15 Hit resolves OnKill fires, IsKillingBlow=true
7 Enemy health=10, baseDamage=5 Hit resolves Health → 5, OnHit fires, no OnKill
8 knockbackForce=10, enemyWeight=2 Hit resolves knockbackDistance = 5.0
9 Dead enemy in list ResolveAttacks Dead enemy skipped
10 critChance buffed to 1.2 Crit roll Clamped 1.0, all hits crit
11 3 attacks vs same enemy in one frame ResolveAttacks 3 independent hits, damage summed
12 Attack misses everything ResolveAttacks OnAttackResolved fires with hitCount=0
13 deltaTime=0 (pause) ResolveAttacks Returns empty, no events

Open Questions

  • Should enemies have different hit shape sizes (e.g., Brute larger than Swarm) or uniform? — Defer to Enemy AI GDD
  • Should there be a "perfect timing" bonus for Human parry (hitting within narrow enemy attack window)? — Candidate for Skill Tree / Form Switch SM GDD
  • Attack shape size scaling: should Skill Tree upgrades increase shape dimensions, or only damage? — Defer to Skill Tree GDD