初次提交
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# 进阶机制扩展:召唤与环境蔓延 (Advanced Mechanics: Summons & Propagation)
|
||||
|
||||
基于现有的 ECS 战斗系统,我们可以进一步拓展 **“召唤体系 (Summoning System)”** 和 **“环境传导 (Environmental Propagation)”** 维度。
|
||||
|
||||
## 1. 召唤体系 (Summoning Dimension)
|
||||
|
||||
召唤物不仅仅是“另一种子弹”,它们通常拥有独立的生命周期、AI 行为和属性继承规则。
|
||||
|
||||
### 1.1 召唤物类型 (Archetypes)
|
||||
* **Stationary (炮台型)**: 无法移动,持续对范围内敌人攻击。 (如:火舌图腾)
|
||||
* **Minion (随从型)**: 拥有碰撞体积,能阻挡敌人,有血量,会自动寻敌并攻击。 (如:骷髅兵、死灵狼)
|
||||
* **Satellite (卫星型/浮游炮)**: 围绕玩家旋转或跟随,不可被选中,辅助攻击。 (如:环绕飞剑)
|
||||
* **Clone (镜像型)**: 玩家的分身,继承完全相同的攻击行为,但伤害倍率较低。
|
||||
|
||||
### 1.2 属性继承 (Stat Inheritance)
|
||||
召唤物的数据快照(Snapshot)机制至关重要:
|
||||
* **Inheritance Ratio**: 不同的召唤物继承玩家属性的比例不同(如:继承 50% 攻速,100% 暴击)。
|
||||
* **Dynamic vs Static**:
|
||||
* **Static**: 召唤瞬间确定面板(大多数非光环类召唤)。
|
||||
* **Dynamic**: 随玩家属性实时变化(如链接玩家的灵魂锁链)。
|
||||
|
||||
### 1.3 行为指令 (Command AI)
|
||||
* **Aggressive**: 搜寻屏幕内所有敌人。
|
||||
* **Defensive**: 只攻击攻击玩家的敌人。
|
||||
* **Focus Fire**: 攻击玩家当前瞄准的目标(光标指示)。
|
||||
|
||||
## 2. 环境传播与蔓延 (Propagation & Spreading)
|
||||
|
||||
“火焰范围蔓延”属于 **Cellular Automata (元胞自动机)** 或 **传染 (Contagion)** 机制的范畴。
|
||||
|
||||
### 2.1 实体间传导 (Entity-to-Entity Propagation)
|
||||
不需要地块网格,直接基于实体距离的传播。
|
||||
* **Ignite Proliferation (点燃扩散)**:
|
||||
* 机制: 当一个敌人处于“燃烧”状态死亡时,将剩余的燃烧层数(或新的燃烧状态)复制给半径 R 内的所有敌人。
|
||||
* 扩展: “瘟疫”效果,在存活时通过 Tick 周期性传染周围。
|
||||
|
||||
### 2.2 区域场 (Field / Zone System)
|
||||
引入 `ZoneManager` 来管理地面上的持久化效果区域。
|
||||
* **Pools (地面池)**: 毒液滩、岩浆、神圣地面。
|
||||
* *实现*: 具有 Circle Collider 的 Trigger 节点。
|
||||
* *逻辑*: `OnTriggerEnter` -> Apply Status; `OnTriggerExit` -> Remove Status (optional)。
|
||||
* **Reactive Ground (反应地表)**:
|
||||
* 向“毒液滩”发射“火箭”,引爆毒池造成瞬间高伤。
|
||||
* 向“水面”发射“冰箭”,冻结水面减慢经过敌人的速度或使其滑倒。
|
||||
|
||||
### 2.3 连锁反应示例 (Chain Reaction)
|
||||
1. **步骤 A**: 玩家投掷毒瓶,生成半径 200 的 `PoisonZone`。
|
||||
2. **步骤 B**: 怪物进入区域,获得 `Poisoned` 状态。
|
||||
3. **步骤 C**: 玩家使用“尸爆术”(Tag: Fire)。
|
||||
4. **步骤 D**: 尸爆击中带毒的怪物 -> 触发 `Catalyst: Fire + Poison` -> 产生 `GasExplosion` (毒气爆炸),范围扩大至 400,并施加 `Blind` 致盲效果。
|
||||
|
||||
## 3. 开发实施建议
|
||||
|
||||
### 3.1 扩展 EnemyManager 支持友军
|
||||
* 目前的 `EnemyManager` 是专门管理敌人的。为了支持 Minion:
|
||||
* 方案 A: 改造 `EnemyManager` 为 `UnitManager`,引入 `Faction` 字段(0=Friendly, 1=Hostile),统一管理所有生物。
|
||||
* 方案 B: 新增 `MinionManager`,逻辑类似但 AI 目标相反(Target=Enemy)。**推荐方案 B**,因为逻辑差异较大。
|
||||
|
||||
### 3.2 实现传播算法 (The Spread Algorithm)
|
||||
在 `StatusManager` 中增加 `on_entity_died` 回调监听:
|
||||
```gdscript
|
||||
# 伪代码: 点燃扩散 (GDScript 风格)
|
||||
# 状态类型系统为数据驱动的整数 ID,使用 StatusID Autoload 常量(见 architecture_design.md §8 ADR-R5-N2)。
|
||||
# StatusID.BURN 等常量定义在 status_id.gd (Autoload: StatusID) 中。
|
||||
# 禁止写法:StatusManager.has_status(victim_id, StatusType.BURN) ← StatusType 枚举不存在
|
||||
func on_enemy_died(victim_id: int) -> void:
|
||||
if StatusManager.has_status(victim_id, StatusID.BURN): # ✅ 使用 StatusID 常量
|
||||
var neighbors: Array[int] = SpatialGrid.query_circle(
|
||||
EnemyManager.get_position(victim_id), 300.0)
|
||||
for n_id in neighbors:
|
||||
StatusManager.apply(n_id, StatusID.BURN, 0) # ✅ 使用 StatusID 常量
|
||||
VFXManager.play("fire_spread", EnemyManager.get_position(victim_id))
|
||||
```
|
||||
|
||||
> **StatusID Autoload 规范**:与 `CoreFeatureTag` 类比,所有状态类型必须通过
|
||||
> `StatusID` Autoload 常量引用,禁止在业务代码中直接写整数字面量或自造枚举:
|
||||
> ```gdscript
|
||||
> # status_id.gd (Autoload: StatusID)
|
||||
> ## 状态效果 ID 常量表 — 所有系统通过此处引用,禁止裸整数
|
||||
> const BURN: int = 1 # 点燃(Fire DoT)
|
||||
> const FREEZE: int = 2 # 冻结(硬控)
|
||||
> const POISON: int = 3 # 中毒(Poison DoT,可叠层)
|
||||
> const WET: int = 4 # 潮湿(增幅雷电伤害)
|
||||
> const OILY: int = 5 # 油腻(增幅火焰伤害)
|
||||
> const STUN: int = 6 # 麻痹(雷电触发)
|
||||
> const COMBO_MARK: int = 7 # 连击标记(详见 consecutive_hits_stacking.md)
|
||||
> const VULNERABILITY: int = 8 # 易伤标记(激光叠层增伤,详见 consecutive_hits_stacking.md)
|
||||
> # 新增状态 ID 从 9 开始递增,并在此处登记
|
||||
> ```
|
||||
|
||||
### 3.3 实现地面效果 (Zone Manager)
|
||||
* **ZoneManager 必须基于 `SpatialGrid` 进行空间索引(ADR-R5-N1 规范)**,
|
||||
**禁止使用 Area2D(Trigger 模式)**——Area2D 会为每个 Zone 创建 Godot 物理节点,
|
||||
1000 敌人 × 64 Zone 的碰撞检测开销完全无法接受,违反 ECS-Lite 架构原则。
|
||||
详见 `architecture_design.md §8 ADR-R5-N1`(ZoneManager 权威实现定义)。
|
||||
* 每 `_physics_process` 帧对所有 Zone 调用 `SpatialGrid.query_circle(zone_center, zone_radius)`,
|
||||
批量获取范围内敌人 ID,然后通过 `StatusManager.apply()` 施加状态效果;
|
||||
使用 `tick_interval` 速率限制防止每帧过量触发(见 architecture_design.md §8 ADR-R5-N1)。
|
||||
@@ -0,0 +1,176 @@
|
||||
# 深度战斗机制扩展设计文档 (Advanced Combat Mechanics Design)
|
||||
|
||||
为了支持 Roguelike 游戏中涌现式的玩法组合(如:闪电链、叠毒流、触发流),现有的简单"伤害+阵营"模型需要升级为多维度的**上下文感知 (Context-Aware)** 系统。
|
||||
|
||||
本文档详细定义了扩展战斗深度所需的关键维度和判断逻辑。
|
||||
|
||||
## 1. 投射物身份与溯源 (Projectile Identity & Sourcing)
|
||||
|
||||
在复杂的战斗中(例如:玩家发射的子弹击中怪物A,触发爆炸,爆炸产生碎片击中怪物B),系统必须能追踪伤害的根本来源。
|
||||
|
||||
### 1.1 来源层级 (Source Hierarchy)
|
||||
我们需要在子弹数据中维护一个"血缘关系",通常通过 ID 或 引用 实现:
|
||||
* **Root Source (根源)**: 也就是 `Owner`。通常是 PlayerEntity 或 EnemyEntity。用于判定击杀归属(触发"击杀回血"天赋)、伤害统计。
|
||||
* **Immediate Source (直接来源)**: 发射该子弹的实体。可能是玩家手中的法杖,也可能是另一个"母子弹"(在分裂/触发机制中)。
|
||||
* *应用场景*: 伤害衰减计算。如果是"母子弹"分裂出的"子弹",继承伤害时可能需要乘系数 0.5。
|
||||
|
||||
### 1.2 施法类型标记 (Cast Type Tags)
|
||||
除了 `Faction` (阵营),还需要 `Tags` (位掩码) 来区分伤害性质,以便应用特定的加成:
|
||||
* **Tag: Primary**: 玩家直接射击(享受"普攻伤害加成")。
|
||||
* **Tag: Triggered**: 由触发器产生的(享受"技能伤害加成",但可能不触发某些普攻特效)。
|
||||
* **Tag: Summon**: 召唤物造成的伤害。
|
||||
* **Tag: Reflection**: 反弹/弹反造成的伤害。
|
||||
|
||||
## 2. 连锁与弹跳逻辑 (Chain & Bounce Dimensions)
|
||||
|
||||
"闪电链"不仅仅是次数的递减,它需要一个完整的状态机来处理复杂的传递逻辑。
|
||||
|
||||
### 2.1 命中历史 (Hit History / Exclusion List)
|
||||
* **问题**: 简单的"最近距离"查找会导致闪电在两个靠近的怪物之间无限来回弹射 (Ping-Pong)。
|
||||
* **维度需求**: 每颗子弹需要维护一个已命中目标 ID 集合 `visited_targets: Array[int]`。
|
||||
* **逻辑**: `SpatialGrid.find_nearest(pos)` 过滤掉 `visited_targets` 中已包含的 `entity_id`。
|
||||
* **性能优化**: 在 ECS 架构中,可以使用 `Dictionary` (key: bullet_id int, value: Array[int]) 的 Sidecar 模式存储历史记录,仅在命中结算(低频逻辑)时查询,不影响移动更新(高频逻辑)。
|
||||
|
||||
### 2.2 衰减模型 (Decay Models)
|
||||
每次传递后的伤害变化不应只有一种模式:
|
||||
* **Linear Decay**: `Dmg = Base - (BounceCount * FixedValue)`
|
||||
* **Exponential Decay**: `Dmg = Base * (DecayFactor ^ BounceCount)` (例如每次衰减 20%)
|
||||
* **Reverse Scaling**: `Dmg = Base * (1 + BounceCount * 0.1)` (越弹伤害越高,奖励多次传递)
|
||||
|
||||
### 2.3 传递约束 (Propagation Constraints)
|
||||
* **Max Distance**: 弹射的最大搜索半径(不能跨越半个屏幕弹射)。
|
||||
* **Line of Sight**: 弹射是否需要视野(能否穿墙)。
|
||||
* **Angle Limit**: 只能向前弹射(90度扇区),不能向后弹射。
|
||||
|
||||
## 3. 状态效果与持续伤害 (Status Effects & DoT)
|
||||
|
||||
毒、火、流血等机制需要独立且统一的 **Status Manager**。
|
||||
|
||||
### 3.0 StatusType 数据驱动方案 (E4)
|
||||
|
||||
`StatusType` **不使用硬编码枚举**,而是从 `res://resources/status_types/` 目录自动加载 `.tres` Resource,实现完全数据驱动。新增状态类型无需修改引擎代码:
|
||||
|
||||
```gdscript
|
||||
# status_type_def.gd
|
||||
class_name StatusTypeDef
|
||||
extends Resource
|
||||
|
||||
@export var id: int # 全局唯一整数 ID(用于位掩码和 Dictionary key)
|
||||
@export var name: String # 显示名称
|
||||
@export var stack_mode: int # 0=Duration, 1=Intensity, 2=Independent
|
||||
@export var max_stacks: int = 99 # 最大叠加层数(0=无限)
|
||||
@export var tick_interval: float = 1.0 # DoT 跳字间隔(秒)
|
||||
@export var can_catalyze: Array[int] # 催化反应:触发时需要的另一状态 ID 列表
|
||||
@export var immune_faction_tags: int # 免疫此状态的 faction 位掩码
|
||||
```
|
||||
|
||||
`StatusManager._ready()` 启动时扫描目录注册,逻辑与 SpellRegistry 完全一致。
|
||||
|
||||
### 3.1 堆叠维度 (Stacking Dimensions)
|
||||
* **Duration Stacking (时间堆叠)**: 再次施加相同效果,刷新或延长持续时间(如:冰冻)。
|
||||
* **Intensity Stacking (强度堆叠)**: 再次施加相同效果,增加层数,伤害倍率提升(如:中毒,1层每秒10点,5层每秒50点)。
|
||||
* **Independent Instances (独立计算)**: 每次施加都是一个独立的 DoT 计时器,互不干扰(如:流血,身上可以挂10个流血Debuff,每个单独跳字)。
|
||||
|
||||
### 3.2 结算归属 (Tick Attribution)
|
||||
DoT 造成的每次伤害(Tick)都需要携带 `Root Source` 信息。
|
||||
* *场景*: 玩家给 Boss 挂了毒,然后跑开了。Boss 被毒死时,系统必须知道是"玩家"杀死的,从而触发"通关结算"或"击杀回血"。
|
||||
|
||||
### 3.2.5 StatusManager Tick 机制(P-N4 补充,P5-N1 修正)
|
||||
|
||||
`StatusManager` 绑定 `_physics_process(delta)`(60 Hz),采用**批处理 Tick**策略:
|
||||
|
||||
- **平铺数组存储(P5-N1)**:`_active_statuses: Array[StatusInstance]` 为**一维平铺数组**,每个 `StatusInstance` 携带 `entity_id` 字段。相较于原 `Dictionary{entity_id → Array}` 方案,平铺顺序遍历的缓存命中率显著更高,规避了 GDScript 哈希表迭代的无序开销。按实体查询(`get_instances_for(entity_id)`)仅在 apply/remove 等低频操作时调用,不出现在 `_physics_process` 热路径中。
|
||||
- **每帧遍历**:逆序遍历 `_active_statuses`,按各实例的 `remaining_duration` 递减 `delta`,duration ≤ 0 时原地 swap-with-last 移除(O(1)),避免遍历中途修改数组。
|
||||
- **按 tick_interval 跳字**:DoT 不在每帧触发伤害,而是维护 `tick_accumulator: float`;
|
||||
每帧 `tick_accumulator += delta`,当 `tick_accumulator >= tick_interval` 时
|
||||
触发一次 `APPLY_DAMAGE` 并扣减 `tick_interval`(允许 delta 跨多个 tick_interval 时补算多次)。
|
||||
- **分帧批处理**:若同帧活跃状态实例超过 `STATUS_BATCH_LIMIT = 500` 条,
|
||||
按 `inst.entity_id % 2` 奇偶帧分批处理,牺牲 1 帧的 tick 精度换取帧时间平摊。
|
||||
|
||||
```gdscript
|
||||
# _active_statuses: Array[StatusInstance](平铺数组,非 Dictionary)
|
||||
# StatusInstance 字段: entity_id, type_def, intensity, remaining_duration, tick_accumulator, root_owner_id
|
||||
func _physics_process(delta: float) -> void:
|
||||
var i := _active_statuses.size() - 1
|
||||
while i >= 0:
|
||||
var inst: StatusInstance = _active_statuses[i]
|
||||
inst.remaining_duration -= delta
|
||||
inst.tick_accumulator += delta
|
||||
while inst.tick_accumulator >= inst.type_def.tick_interval:
|
||||
inst.tick_accumulator -= inst.type_def.tick_interval
|
||||
_apply_dot_tick(inst.entity_id, inst)
|
||||
if inst.remaining_duration <= 0.0:
|
||||
_active_statuses[i] = _active_statuses.back()
|
||||
_active_statuses.pop_back()
|
||||
i -= 1
|
||||
```
|
||||
|
||||
### 3.3 状态交互矩阵 (Interaction Matrix)
|
||||
状态之间应有预定义的交互规则:
|
||||
* **Overwrite**: 强状态覆盖弱状态(大冰冻覆盖小减速)。
|
||||
* **Catalyze (催化)**: 状态 A 遇到 状态 B -> 消除 B 并触发 C(雷 + 湿 = 范围感电)。
|
||||
* **Immunity**: 某些怪物(如火元素)对特定状态(燃烧)免疫甚至回血。
|
||||
|
||||
## 4. 伤害计算管线 (The Damage Pipeline)
|
||||
|
||||
伤害计算遵循 `docs/design/numerical_design.md §3.1` 定义的权威公式:
|
||||
|
||||
```
|
||||
FinalDamage = ((Base + Add) × Mult) × (1 - Res) - Armor
|
||||
```
|
||||
其中:`Mult` = `SourceMultipliers × CriticalMultiplier`;`Res` = 敌人元素抗性 (0.0~1.0);`Armor` = 敌人护甲平铺扣除值。最终伤害最小为 1。
|
||||
|
||||
我们需要在伤害事件 `APPLY_DAMAGE` 中传递一个结构体 `DamageContext` 而非简单的数值:
|
||||
|
||||
```gdscript
|
||||
# damage_context.gd - 继承 RefCounted,通过对象池复用
|
||||
# 权威实现(含池化逻辑)在 DamageContextPool Autoload;本文件描述字段语义。
|
||||
class_name DamageContext
|
||||
extends RefCounted
|
||||
|
||||
var base_damage: float = 0.0 # 基础数值(含 CastStats.damage_add)
|
||||
var mult: float = 1.0 # 伤害乘算系数(暴击 × 元素弱点已合入此值)
|
||||
# 对应公式:Damage = ((Base + Add) × Mult) × (1 - Res) - Armor
|
||||
# EnemyManager.apply_damage() 读取此字段计算最终伤害
|
||||
var damage_type: int = DamageType.PHYSICAL # DamageType 枚举
|
||||
var owner_id: int = -1 # 来源实体 ID(Root Owner)
|
||||
var is_crit: bool = false # 是否暴击(影响爆字/特效触发;与 DamageContextPool 字段同步)
|
||||
var pierce_rate: float = 0.0 # 护甲穿透率(0.0~1.0;与 DamageContextPool 字段同步)
|
||||
var source_tags: int = 0 # 位掩码: PRIMARY=1, TRIGGERED=2, SUMMON=4, REFLECTED=8
|
||||
|
||||
func reset() -> void:
|
||||
base_damage = 0.0; mult = 1.0; damage_type = DamageType.PHYSICAL; owner_id = -1
|
||||
is_crit = false; pierce_rate = 0.0; source_tags = 0
|
||||
# mult 必须重置为 1.0,否则池化复用时上帧的暴击乘算残留到下次计算
|
||||
```
|
||||
|
||||
## 5. 实现建议 (ECS Adaptation)
|
||||
|
||||
针对当前的 `BulletManager` (PackedFloat32Array ECS),如何在不破坏性能的前提下引入这些维度?
|
||||
|
||||
**混合架构 (Hybrid ECS Pattern):**
|
||||
|
||||
1. **热数据 (PackedFloat32Array)**: 保持 `x, y, vx, vy, life` 在 `PackedFloat32Array` 中,确保每帧移动批处理极快。
|
||||
2. **冷数据 (Dictionary)**: 在 `BulletManager` 中维护 `_bullet_contexts: Dictionary`(key = bullet_id `int`)。
|
||||
* 值为 `BulletContext` 对象(继承 `RefCounted`,通过对象池复用),存储 `visited_targets`、`owner_id`、`proc_coefficient`、`on_hit_payload_id` 等复杂逻辑。
|
||||
* 仅在 `_on_bullet_hit()`(命中)或 `spawn()`(生成)时访问 Dictionary,这些事件频率远低于每帧移动,不会成为瓶颈。
|
||||
|
||||
```gdscript
|
||||
# bullet_context.gd - 通过 ObjectPool 复用,避免 GC 抖动
|
||||
class_name BulletContext
|
||||
extends RefCounted
|
||||
|
||||
var owner_id: int = -1 # 根来源实体 ID
|
||||
var visited_targets: Array[int] = [] # 连锁弹射已命中目标列表
|
||||
var proc_coefficient: float = 1.0 # 触发系数(加特林子弹通常设为 0.1)
|
||||
var on_hit_payload_id: int = -1 # 命中时触发的 SubPayload ID(-1=无)
|
||||
var bounce_count: int = 0 # 已弹射次数
|
||||
# BulletContext 不持有伤害计算逻辑(SRP)。
|
||||
# 弹射衰减(pow(0.9, bounce_count))集中在 EnemyManager.apply_damage() 中实现;BulletContext 仅透传 bounce_count。
|
||||
|
||||
func reset() -> void:
|
||||
owner_id = -1; visited_targets.clear()
|
||||
proc_coefficient = 1.0; on_hit_payload_id = -1; bounce_count = 0
|
||||
```
|
||||
|
||||
通过这种方式,热路径(移动积分)保持纯 Array 操作,冷路径(命中逻辑)通过 Dictionary 访问复杂上下文,兼顾了弹幕游戏的性能与 RPG 的深度。
|
||||
@@ -0,0 +1,102 @@
|
||||
# 战斗机制进阶扩展 (Advanced Combat Mechanics Extensions - Part 2)
|
||||
|
||||
为了构建一个具备极高重玩价值(Replayability)的 Roguelike 系统,除了基础的伤害和弹射,我们还可以引入物理、环境、触发链等更多维度的交互。
|
||||
|
||||
以下是可供扩展的 5 个额外维度:
|
||||
|
||||
## 1. 物理与运动学维度 (Physics & Kinematics Dimensions)【P2/P3 扩展,非当前实现目标】
|
||||
|
||||
> ⚠️ **实现状态说明**:本节所有维度均为**设计扩展提案(P2/P3)**,
|
||||
> 当前 BulletManager SoA、ProjectileDef、EnemyManager SoA **均无对应字段**。
|
||||
> 实现时需要:
|
||||
> - `ProjectileDef` 增加 `knockback_force: float`(冷数据,存入 `_bullet_contexts`)
|
||||
> - `EnemyManager` SoA 增加 `mass: float`(或冷数据中存储),`ENEMY_STRIDE` 相应扩展
|
||||
> - 否则引用本节代码片段将在运行时找不到字段
|
||||
|
||||
让投射物不仅仅是”飞行的数字”,而是具有物理实体的对象。
|
||||
|
||||
### 1.1 冲量与质量 (Impulse & Mass)【P3】
|
||||
* **Knockback Force (击退力)**: 子弹应携带“击退值”。
|
||||
* **Entity Mass (实体质量)**: 怪物应有“质量”属性。
|
||||
* **Resistance (抗性)**: 小蝙蝠被打中会飞很远,而石头人哪怕受到高伤也纹丝不动。
|
||||
* **维度**: `KnockbackVector = (BulletVelocity.normalize() * KnockbackForce) / TargetMass`
|
||||
|
||||
### 1.2 轨迹调制 (Trajectory Modulation)
|
||||
除了直线飞行,子弹的运动模式可以参数化:
|
||||
* **Acceleration (加速度)**: 子弹可以越飞越快(狙击),或越飞越慢(能量球停留)。
|
||||
* **Curvature (曲率/归航)**: `HomingStrength` (归航强度) 和 `TurnRate` (原本直飞,靠近敌人时转弯)。
|
||||
* **Oscillation (震荡)**: 正弦波飞行(Sine Wave),用于扩大打击面。
|
||||
|
||||
## 2. 环境场与交互维度 (Environment & Interaction Dimensions)
|
||||
|
||||
战斗不仅仅发生在“子弹”和“敌人”之间,还包括“环境”。
|
||||
|
||||
### 2.1 地表效果 (Surface/Ground Effects)
|
||||
* **Persistence (残留)**: 毒瓶炸开后在地面留下 `PoisonPool`,持续 5 秒。
|
||||
* **Conductivity (传导性)**:
|
||||
* 水面 + 闪电 = 全屏感电。
|
||||
* 油面 + 火焰 = 不灭之火(延长燃烧时间)。
|
||||
* 冰面 + 物理撞击 = 滑行距离增加。
|
||||
|
||||
### 2.2 障碍物交互 (Obstacle Interaction)
|
||||
* **Ricochet (跳弹)**: 击中墙壁时,根据入射角计算反射角。
|
||||
* **Phasing (相位)**: 是否能穿墙(穿墙子弹通常伤害较低或无法穿透敌人)。
|
||||
* **Embedding (嵌入)**: 箭矢击中墙壁后保留实体,可被玩家回收或作为后续法术的引雷针。
|
||||
|
||||
## 3. 触发器与事件链系统 (Trigger System / Proc Chains)
|
||||
|
||||
参考《Risk of Rain 2》或《Path of Exile》的 Proc(Process on Cast/Hit)机制。
|
||||
|
||||
### 3.1 触发钩子 (Event Hooks)
|
||||
不仅仅是“命中”,需要更细的粒度:
|
||||
* **OnHit**: 命中敌人时。
|
||||
* **OnCrit**: 暴击时(如:暴击时召唤落雷)。
|
||||
* **OnKill**: 击杀时(如:敌人产生尸爆)。
|
||||
* **OnExpire**: 子弹自然销毁/达到最大射程时(如:榴弹并在尽头爆炸)。
|
||||
* **OnHurt**: 玩家受击时(如:受击释放尖刺)。
|
||||
|
||||
### 3.2 触发系数 (Proc Coefficient)
|
||||
为了平衡高频攻击(如加特林)和低频攻击(如狙击枪):
|
||||
* **ProcRate**: 这是一个 0.0 - 1.0 的系数。
|
||||
* *例子*: "攻击有 10% 概率触发导弹"。
|
||||
* 狙击枪 (Proc: 1.0) -> 每次判定都是 10%。
|
||||
* 加特林 (Proc: 0.1) -> 每次判定实际概率是 10% * 0.1 = 1%。
|
||||
* 防止高攻速武器过于变态地触发特效。
|
||||
|
||||
## 4. 属性转换与快照机制 (Conversion & Snapshotting)
|
||||
|
||||
处理复杂的数值计算规则。
|
||||
|
||||
### 4.1 伤害转换 (Damage Conversion)
|
||||
* **Shift**: "将 50% 的物理伤害转化为火焰伤害"。
|
||||
* **Gain**: "获得等同于物理伤害 20% 的额外混沌伤害"。
|
||||
* **Priority (优先级)**: 转换通常有固定顺序(物理 -> 元素 -> 混沌),防止循环转换。
|
||||
|
||||
### 4.2 快照机制 (Snapshotting)
|
||||
* **Dynamic vs Static**:
|
||||
* 如果玩家发射了一个持续 10 秒的火龙卷,然后在第 5 秒喝了药水(增加智力)。
|
||||
* **Snapshot**: 火龙卷伤害不变(取发射瞬间的数值)。这通常更符合直觉且性能更好。
|
||||
* **Dynamic**: 火龙卷伤害随之增加。这就要求子弹必须持有 Player 引用并在每帧重新计算属性。
|
||||
|
||||
## 5. 控制与免疫维度 (Crowd Control & Diminishing Returns)
|
||||
|
||||
并不是所有的“负面效果”都是伤害(DoT)。
|
||||
|
||||
### 5.1 硬控制与软控制 (Hard/Soft CC)
|
||||
* **Soft CC**: 减速 (Slow)、致盲 (Blind - 降低命中率/索敌范围)。
|
||||
* **Hard CC**: 眩晕 (Stun - 停止行动)、冻结 (Freeze - 停止动画)、恐惧 (Fear - 反向逃跑)、魅惑 (Charm - 攻击友军)。
|
||||
|
||||
### 5.2 收益递减 (Diminishing Returns - DR)
|
||||
为了防止 Boss 被永久晕在原地:
|
||||
* **DR Meter**: 怪物有一个隐藏的抗性条。
|
||||
* **机制**: 第一次眩晕 2秒 -> 第二次 1秒 -> 第三次 免疫。
|
||||
* **Recovery**: 抗性条随时间慢慢恢复。
|
||||
|
||||
## 6. 斩杀与处决 (Culling & Execution)
|
||||
|
||||
* **Threshold Execution**: "立即消灭生命值低于 10% 的非 Boss 敌人"。
|
||||
* **Low/High Health Scaling**:
|
||||
* "对生命值 > 90% 的敌人必定暴击" (First Strike)。
|
||||
* "敌人生命值每降低 1%,造成额外 1% 伤害" (Executioner)。
|
||||
|
||||
通过引入这些维度,可以将简单的“数值比拼”转化为策略构筑游戏。例如构建一个“击退流”玩法:利用高击退把敌人推到涂满油的墙上,配合火属性子弹造成环境爆炸。
|
||||
@@ -0,0 +1,92 @@
|
||||
# 连续打击与伤害叠加设计 (Consecutive Hits & Damage Stacking)
|
||||
|
||||
## 需求分析
|
||||
用户希望实现“对同一个目标,同一种伤害在一定间隔内认定为持续多次伤害,从而可以制作伤害叠加”。
|
||||
|
||||
这通常被称为 **“连击系统 (Combo System)”** 或 **“弱点叠加 (Vulnerability Stacking)”**。
|
||||
与 DoT(中毒/燃烧)不同,这种机制侧重于 **“攻击频率”** 和 **“爆发奖励”**。
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 1. 连击窗口 (Combo Window)
|
||||
* 每个敌人对每种伤害来源(或类型)维护一个 `LastHitTime` 和 `StackCount`。
|
||||
* 如果在 `WindowDuration` (例如 2秒) 内再次受到同类伤害:`StackCount++`, 刷新 `LastHitTime`。
|
||||
* 如果超时:`StackCount` 重置为 0。
|
||||
|
||||
### 2. 叠加收益 (Stacking Benefits)
|
||||
当 `StackCount` 增加时,可以产生以下效果:
|
||||
* **Damage Scaling**: 第 N 次伤害提高 `N * 10%`。
|
||||
* **Proc Trigger**: 每第 3 次攻击触发一次爆炸 (3-Hit Passive)。
|
||||
* **State Transition**: 叠满 5 层后,施加“破甲”或“眩晕”。
|
||||
|
||||
## 数据结构设计
|
||||
|
||||
在现有的 `EnemyManager` 混合 ECS 中,我们需要拓展 `StatusContext` 或新增 `ComboContext`。
|
||||
|
||||
由于连击通常与特定的“来源”或“伤害标签”绑定,建议存放在 `StatusContext` 中的专用字段,或者复用 Status 机制。
|
||||
|
||||
### 方案 A: 专用 Combo Tracker
|
||||
在 `EnemyEntityManager` 的 Context 中增加:
|
||||
```gdscript
|
||||
# combo_tracker.gd — 通过 ObjectPool 复用,避免 GC 开销
|
||||
class_name ComboTracker
|
||||
extends RefCounted
|
||||
|
||||
var source_id: int = -1 # 来源实体 ID(支持多武器/多玩家,-1 表示无效)
|
||||
var damage_tag: int = 0 # 伤害类型位掩码(PHYSICAL=1, FIRE=2, LASER=4 ...)
|
||||
var count: int = 0 # 当前连击层数
|
||||
var timer: float = 0.0 # 剩余窗口时间(秒)
|
||||
|
||||
func reset() -> void:
|
||||
source_id = -1
|
||||
damage_tag = 0
|
||||
count = 0
|
||||
timer = 0.0
|
||||
```
|
||||
|
||||
### 方案 B: 利用现有 Status 系统
|
||||
定义一种特殊的 StatusType `COMBO_MARK`。
|
||||
* 每次攻击施加 `COMBO_MARK`。
|
||||
* `addEffect` 逻辑中,如果已存在,则 `intensity++` (层数) 并刷新 `duration`。
|
||||
* 伤害计算时,检查 `COMBO_MARK` 的 `intensity` 来加成伤害。
|
||||
|
||||
**✅ 追加选择:推荐方案 B**,因为它复用了现有的 Tick 和合并逻辑,不需要写额外的 Timer。方案 A 适合需要多来源(`source_id`)区分的场景(如多玩家共同攻击同一目标)。
|
||||
|
||||
> **R4-C3 补充(COMBO_MARK 的 StatusTypeDef 参数)**:`COMBO_MARK` 对应以下 `StatusTypeDef` 配置:
|
||||
> - `stack_mode = 1`(**Intensity 强度叠加**):每次施加 +1 层,`intensity` 字段记录当前层数。
|
||||
> - `max_stacks = 0`(**0 = 无限叠加**,不设层数上限;由高频攻击自然封顶)。
|
||||
> - `tick_interval`:⚠️ **语义说明(P6-N14 修正)**:`StatusTypeDef.tick_interval` 在 `StatusManager` 的通用实现中是"DoT 伤害跳字间隔"。但 `COMBO_MARK` **不造成 DoT 伤害**,其 `_apply_dot_tick()` 回调被设计为空操作(`pass`)。
|
||||
> 此处 `tick_interval` 被 COMBO_MARK **重载为连击窗口刷新机制**:每次施加 COMBO_MARK 时,调用方手动将 `remaining_duration` 重置为连击窗口时长(如 2.0s),利用 `StatusManager` 的 duration 计时器实现超时重置。
|
||||
> **推荐实践**:为避免混淆,`COMBO_MARK` 的 `tick_interval` 设置为一个大于连击窗口的值(如 `999.0`),确保在窗口内 `tick_accumulator` 永不达到触发阈值,彻底避免意外的 DoT 跳字。
|
||||
> **长期方案(P2)**:为 `StatusTypeDef` 增加 `is_combo_tracker: bool` 字段,从根本上区分"持续伤害状态"和"计数追踪状态",使 `StatusManager._apply_dot_tick` 在 `is_combo_tracker = true` 时直接跳过。
|
||||
> - `can_catalyze = []`(空数组):连击标记本身不触发元素反应,只作为伤害加成计算输入。
|
||||
> `StatusTypeDef.can_catalyze` 字段类型为 `Array[int]`,必须赋空数组 `[]`,
|
||||
> 禁止赋值 `false`(GDScript 4 严格类型模式下触发 bool→Array 类型错误)。
|
||||
> 详见 `combat_mechanics_depth.md §3.0`(StatusTypeDef 完整字段)。
|
||||
|
||||
## 实现逻辑 (基于 Status 系统)
|
||||
|
||||
1. **定义新状态**: `StatusID.VULNERABILITY` (易伤标记)。
|
||||
> 所有状态引用使用 `StatusID` Autoload 常量(架构为数据驱动整数 ID,不存在 StatusType 枚举):
|
||||
> - `StatusID.VULNERABILITY = 8`(在 status_id.gd 中登记)
|
||||
> - `StatusID.COMBO_MARK = 7`(已定义)
|
||||
> 禁止写法:`StatusType.VULNERABILITY`、`StatusType.COMBO_MARK`。
|
||||
2. **施加规则**:
|
||||
* 每次 `ActionLaser` (高频攻击) 命中时,施加 1 层 `StatusID.VULNERABILITY`,持续 0.5秒。
|
||||
3. **叠加规则**:
|
||||
* 在 `StatusContext.addEffect` 中,如果发现已有 `StatusID.VULNERABILITY`,则 `stack++`。
|
||||
4. **伤害修正**:
|
||||
* 在 `onApplyDamage` 中,计算最终伤害前,读取目标的 `StatusID.VULNERABILITY` 层数。
|
||||
* `FinalDamage = BaseDamage * (1 + Stack * 0.05)` (每层增伤 5%)。
|
||||
* 由于激光每秒 10 次,0.5秒内能叠 5 层,伤害会越来越高。
|
||||
|
||||
## 扩展玩法示例
|
||||
* **聚焦激光 (Focus Laser)**: 初始伤害低,但对同一目标持续照射越久伤害越高(利用 0.1s 间隔的刷新机制)。
|
||||
* **三环被动 (Three-Hit Passive)**: 类似于 LOL 的薇恩。给敌人挂一个隐藏的 `StackStatus`,当 Stack 到达 3 时,消耗所有 Stack 并造成由最大生命值决定的真实伤害。
|
||||
|
||||
## 代码落地计划
|
||||
1. 在 `status_id.gd`(StatusID Autoload)中追加 `VULNERABILITY: int = 8` 常量。
|
||||
`COMBO_MARK: int = 7` 已在 `advanced_mechanics_summons_and_environment.md §3.2` 定义。
|
||||
禁止在此文件外使用裸整数或 StatusType 枚举(见 ADR-R5-N2 规范)。
|
||||
2. 在 `EnemyManager.gd` 的 `apply_damage` 函数中,计算扎血前先检查 `StatusID.VULNERABILITY` 进行倍率修正。
|
||||
3. 在 `StatusManager.gd` 的 `apply_status` 函数中,确保此类型状态可正确无限叠加层数(或设置上限)。
|
||||
@@ -0,0 +1,112 @@
|
||||
# 武器系统扩展设计方案 (Weapon System Expansion Design)
|
||||
|
||||
本文档旨在描述基于现有的 **HMWS (Hackable Modular Weapon System)** 和 **ECS BulletManager** 架构,可以进一步扩展的玩法机制。
|
||||
|
||||
当前的系统已经支持了基础的投射物、霰弹、激光,以及修正器(多重施法、弹射、追踪、穿透、连锁、冰冻)。在此基础上,我们可以引入更高级的策略组合和“涌现式”玩法。
|
||||
|
||||
## 1. 轨迹修正 (Trajectory Modifiers)
|
||||
|
||||
通过修改子弹在 `_physics_process` 中的运动逻辑,创造独特的弹道体验。
|
||||
|
||||
### 1.1 正弦/波浪弹道 (Sine/Wave Pattern)
|
||||
* **机制**: 子弹不再直线飞行,而是沿着正弦波轨迹运动。
|
||||
* **实现**:
|
||||
* 新增 `ModifierSine`.
|
||||
* 在 `BulletManager` 的 Data Stride 中增加 `waveFrequency` 和 `waveAmplitude` (或复用 `customData`)。
|
||||
* 在 `_physics_process` 积分位置时,叠加垂直于速度方向的偏移量。
|
||||
* **玩法**: 直线命中率低,但能绕过正面护盾,或者覆盖更宽的横向区域。
|
||||
|
||||
### 1.2 回旋镖 (Boomerang)
|
||||
* **机制**: 子弹飞出一定距离后,反向加速飞回玩家。
|
||||
* **实现**:
|
||||
* 新增 `ModifierBoomerang`.
|
||||
* 在 `BulletManager` 中逻辑:当 `lifetime` 经过一半时,给予一个反向的加速度 `Acceleration` 指向玩家。
|
||||
* **玩法**: 高风险高回报,利用回程的子弹对追击的敌人造成“背刺”双倍伤害。
|
||||
|
||||
### 1.3 轨道环绕 (Orbiting/Shield)
|
||||
* **机制**: 子弹不飞远,而是围绕玩家旋转,形成护盾。
|
||||
* **实现**:
|
||||
* 轨道参数 `is_orbiting: bool`、`orbit_radius: float`、`orbit_angle: float`、`orbit_speed: float` 均存入 `_bullet_contexts: Dictionary`(**冷数据字段,P6-N9 权威定义**),BulletManager SoA 热数组不新增字段。
|
||||
* **双写优化(P6-N15 规范)**:环绕子弹的位置由圆周公式 100% 决定,不依赖速度积分。因此在 `BulletManager._physics_process` 中,**必须先判断 is_orbiting,再决定是否执行常规物理积分**:
|
||||
```gdscript
|
||||
# BulletManager._physics_process 热路径(伪代码)
|
||||
for i in _active_count:
|
||||
var ctx = _bullet_contexts.get(i, null)
|
||||
if ctx and ctx.get(“is_orbiting”, false):
|
||||
# 跳过速度积分,直接覆写位置(避免无意义的双写开销)
|
||||
var angle: float = ctx.orbit_angle + ctx.orbit_speed * delta
|
||||
ctx.orbit_angle = fmod(angle, TAU)
|
||||
var px := _player_x + cos(ctx.orbit_angle) * ctx.orbit_radius
|
||||
var py := _player_y + sin(ctx.orbit_angle) * ctx.orbit_radius
|
||||
_data[i * BULLET_STRIDE + 0] = px
|
||||
_data[i * BULLET_STRIDE + 1] = py
|
||||
# vx/vy 保持 0,lifetime 正常递减
|
||||
else:
|
||||
# 常规物理积分(速度 + 加速度)
|
||||
_data[i * BULLET_STRIDE + 0] += _data[i * BULLET_STRIDE + 2] * delta
|
||||
_data[i * BULLET_STRIDE + 1] += _data[i * BULLET_STRIDE + 3] * delta
|
||||
```
|
||||
* **性能评估**:环绕子弹数量通常不超过 20 颗(护盾构建),`is_orbiting` 冷数据查询 20 次/帧可接受;若未来出现大量环绕子弹需求,再评估是否升居热数组(SoA 增加 1 bit flag)。
|
||||
* **玩法**: 仅近战流派,配合”击退”修正器保命。
|
||||
|
||||
## 2. 触发器系统 (Trigger System) - "Noita" 风格的核心
|
||||
|
||||
这是 roguelike 构建深度玩法的关键,即“子弹生子弹”。
|
||||
|
||||
### 2.1 击中施法 (Cast On Hit)
|
||||
* **机制**: 当母弹击中敌人时,以击中点为原点,释放另一个法术。
|
||||
* **案例**: "毒箭" + "击中施法" + "爆炸" = 毒箭射中敌人后产生爆炸。
|
||||
* **架构实现**(✅ 已完成):
|
||||
`ProjectileDef.on_hit_payload_id`(`int`,-1=无触发)存储 `SubPayloadRegistry` 中预注册的子法术 ID。
|
||||
`SpellEvaluator` 预编译时将 TRIGGER 节点后续法术打包为 SubPayload 并注册到表;
|
||||
`BulletManager` 在 `_on_bullet_hit()` 回调中查表、通过 `SpellEvaluator.execute_sub()` 执行。
|
||||
弹体本身仅携带整数 ID,无需存储复杂对象引用,完全兼容 SoA PackedFloat32Array 布局。
|
||||
详见 `architecture_design.md §3.2.A`(ProjectileDef 完整字段)与 `implementation_plan.md §2.2`(SubPayloadRegistry)。
|
||||
|
||||
### 2.2 死亡/超时施法 (Cast On Death/Timer)
|
||||
* **机制**:
|
||||
* **On Timer**: 子弹飞行亦段时间后,变成另一种子弹(或释放子弹)。例如:集束手雷(飞行 -> 分裂成小炸弹)。
|
||||
* **On Death**: 子弹消失/撞墙时触发。
|
||||
* **玩法**: 延迟伤害、陷阱布局。
|
||||
|
||||
## 3. 元素交互 (Elemental Synergy)
|
||||
|
||||
扩展现有的 `Ice` (冰冻) 机制,引入状态反应。
|
||||
|
||||
### 3.1 状态定义
|
||||
* **冰 (Wet/Freeze)**: 减速。
|
||||
* **火 (Burn)**: 持续扣血 (DoT)。
|
||||
* **毒 (Poison)**: 持续扣血 + 易伤 (受到的伤害增加)。
|
||||
* **雷 (Shock)**: 间歇性打断动作。
|
||||
|
||||
### 3.2 组合反应 (Combo)
|
||||
* **热休克 (Fire + Ice)**: 解除冰冻,但在解除瞬间造成一次巨额爆发伤害 (Steam Burst)。
|
||||
* **导电 (Water/Ice + Lightning)**: 如果敌人处于冰冻/潮湿状态,被雷电击中时,触发大范围 AoE 连锁伤害。
|
||||
* **毒爆 (Poison + Fire)**: 毒气被点燃,移除中毒状态,产生一次性范围爆炸。
|
||||
|
||||
## 4. 或者是召唤物 (Summons)
|
||||
|
||||
将 `ActionNode` 的产物从子弹变为独立的实体单位。
|
||||
|
||||
### 4.1 炮台 (Turret)
|
||||
* **机制**: 在玩家位置放置一个静止单位,它拥有独立的射击逻辑(自动瞄准最近敌人)。
|
||||
* **实现**: 需要新增 `TurretManager` 或在 `EnemyManager` 扩展一个 "Friendly" 阵营。
|
||||
* **玩法**: 塔防流,玩家只负责跑位和放置。
|
||||
|
||||
### 4.2 浮游炮 (Funnel/Bit)
|
||||
* **机制**: 跟随玩家移动的无敌单位,复制玩家的射击行为(类似《沙罗曼蛇》的 Option)。
|
||||
|
||||
## 5. 开发路线图建议
|
||||
|
||||
1. **Phase 1 (Easy)**: 实现 **ModifierSize** (巨大化) 和 **ModifierSplit** (击中分裂 - 简易版 Trigger)。这两个只需要修改 `BulletManager` 的数值逻辑,无需重构 ECS。
|
||||
2. **Phase 2 (Medium)**: 实现 **状态交互 (Elemental)**。完善 `EnemyManager` 的状态槽位(目前只有 `freezeTimer`,可以用位运算扩展状态标记)。
|
||||
3. **Phase 3 (Hard)**: 实现 **Trigger System**。✅ 架构已在 `implementation_plan.md §2.2` 完成(SubPayloadRegistry + on_hit_payload_id 方案)。本阶段实施重点:编写多层 TRIGGER 嵌套的单元测试,接入 VFXManager 命中回调。
|
||||
|
||||
## 6. 数值与平衡参考
|
||||
|
||||
| 扩展类型 | 开发难度 | 趣味增益 | 性能消耗 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 正弦弹道 | 低 | 中 | 极低 |
|
||||
| 击中分裂 | 中 | 高 | 高 (子弹数指数增长) |
|
||||
| 元素反应 | 中 | 高 | 低 |
|
||||
| 召唤炮台 | 高 | 中 | 中 |
|
||||
Reference in New Issue
Block a user