Files
spellforge/docs/handbook/02_content_authoring.md
T

219 lines
9.0 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.
# 02 内容创作指南 (Content Authoring)
> 策划和程序共同维护的内容文件。所有数值修改无需改架构代码,只改指定位置。
> **⚠️ 实现现状 (2026-07-20 审计)**:本文多个「改硬编码」的步骤已**过期作废**——项目内容早已全面 JSON 数据驱动,法术/波次/敌人/共鸣/Core 均从 `data/*.json` 加载,相关硬编码工厂函数与常量表已从代码中删除。**当前唯一权威的内容创作工作流是 [07 游戏设计器](07_game_designer.md)**(编辑器内插件面板,全部经 `data/*.json` + 编辑器,零代码)。本文中标注 ❌**OBSOLETE** 的小节仅保留作历史参考,请勿据其修改代码。详见审计报告 ../../docs_dev/doc_code_audit_2026-07-20.md。
> ⭐ **策划/美术优先用可视化工具**:[07 游戏设计器](07_game_designer.md) 提供编辑器内一站式面板(法术/敌人/波次/Core/平衡),表单填数值即可,**无需写代码**。本文的代码方式已大部分作废(见上方现状说明)。
---
## A. 添加/修改法术
> **⚠️ 实现现状 (2026-07-20 审计)**:法术定义唯一来源是 `res://data/spells.json`,由 `SpellRegistry` 启动时加载。`wand_preset.gd` **已不再定义任何法术**(文件头注:「法术已迁至 SpellRegistry」),下文 `make_frost_bolt` / `make_spell_by_id` / `get_all_spell_ids` 等工厂函数**在代码中已不存在**,请勿添加。详见审计报告 ../../docs_dev/doc_code_audit_2026-07-20.md。
> 💡 **策划优先用可视化工具**:在 [07 游戏设计器](07_game_designer.md) 的「法术」页签(字段化表单,用法详见 [06 法术编辑器](06_spell_editor.md))新增/编辑法术,保存到 `data/spells.json`**无需写代码**。
### 法术定义位置
- **唯一来源**`res://data/spells.json`(由游戏设计器/法术编辑器维护,启动时由 `SpellRegistry` 加载)。
- 手工编辑时,每条法术是一个对象,键与旧工厂函数字段对应:`id` / `type` / `display_name`tr() 键)/ `description` / `element_tags`(如 `["tag:ice"]`/ `meta``base_damage` / `speed` / `lifetime` / `radius` / `damage_type` / `apply_status_id` / `shop_cost` 等)。推荐用编辑器面板生成,避免手写 JSON 出错。
### 步骤:添加一个新 ACTION 法术(当前正确流程)
1. 打开游戏设计器「法术」页签,新增一条法术,填写上述字段(`data/spells.json` 会自动写入)。
2.`translations/*.po` 四个文件中添加对应翻译键(见本地化指南)。
3. 无需改动任何 `.gd` 代码;`SpellRegistry` 启动即加载,商店会自动抽取新法术。
> ❌ **OBSOLETE(历史参考,代码已删除)**:早期版本需在 `wand_preset.gd` 添加 `make_frost_bolt()` 工厂函数并注册到 `make_spell_by_id` / `get_all_spell_ids`。这些函数已随 SpellRegistry 迁移删除,请勿参照。
---
### 支持的法术类型
| `SpellNode.SpellType` | 说明 | meta 必填键 |
|:---|:---|:---|
| `ACTION` | 发射弹道 | `base_damage`, `speed`, `lifetime`, `radius` |
| `ACTION`(zone) | 地面效果 | `action_kind:"zone"`, `radius`, `status_id`, `duration`, `tick_interval` |
| `ACTION`(summon) | 召唤物 | `action_kind:"summon"`, `lifetime`, `range`, `fire_interval`, `base_damage` |
| `MODIFIER` | 修改施法参数 | 至少一个:`damage_add`, `damage_mult`, `spread_add`, `speed_mult`, `multicast` |
| `TRIGGER` | 命中触发子弹 | `base_damage`, `speed`, `lifetime`, `radius`(触发物自身弹道参数) |
| `LOGIC` | 条件/循环 | `logic_op`(见下表)|
### LOGIC meta 参数
| `logic_op` | 额外 meta | 效果 |
|:---|:---|:---|
| `"every_n_shots"` | `n:int`, `reg:0-3` | 每 N 次施法触发 |
| `"if_hp_below"` | `threshold:float(0-1)` | HP < 阈值时触发 |
| `"if_enemy_nearby"` | `range:float` | 范围内有敌时触发 |
| `"loop"` | `count:int(1-16)` | 后续法术重复 N 次 |
---
## B. 修改波次配置
### 波次配置位置
`scripts/autoloads/wave_manager.gd``WAVE_CONFIG: Array`(数组索引 0 = Wave 1
### 配置字段说明
```gdscript
{"count": 20, "hp": 15.0, "type": 0, "elite": 0, "gold": 10, "xp": 20, "boss": -1}
# count : 本波生成的杂兵数量
# hp : 杂兵基础血量(受难度乘子影响)
# type : 敌人原型(见下方原型表)
# elite : W15+ 带 NavigationAgent2D 寻路的精英数量
# gold : 波次结算奖励金币
# xp : 波次结算奖励 XP
# boss : -1=无;4=Mini Boss5=Final Boss
```
### 敌人原型(`EnemyManager.Type`
| 值 | 名称 | 速度 | 护甲 | 颜色 | 大小 |
|:---|:---|:---|:---|:---|:---|
| 0 | BASIC | 80 | 0 | 红 | 14px |
| 1 | FAST | 140 | 0 | 黄 | 11px |
| 2 | ARMORED | 60 | 5 | 灰 | 16px |
| 3 | ELITE | 90 | 3 | 橙 | 22px |
| 4 | MINIBOSS | 55 | 8 | 品红 | 48px |
| 5 | BOSS | 45 | 14 | 深红 | 76px |
### 示例:修改 W8 Mini Boss 血量
```gdscript
# wave_manager.gd line ~50
{"count": 12, "hp": 30.0, "type": 0, "elite": 0, "gold": 80, "xp": 120, "boss": 4},
# ↑ boss:4 → _BOSS_HP[4] = 450.0 控制 Boss 血量
# 修改 Boss 血量,找到下面这行:
const _BOSS_HP: Dictionary = {4: 450.0, 5: 1800.0}
# 改成你想要的数值
```
---
## C. 修改敌人原型数值
`scripts/autoloads/enemy_manager.gd` 顶部常量:
```gdscript
const _SPEED: Dictionary = {0: 80.0, 1: 140.0, 2: 60.0, 3: 90.0, 4: 55.0, 5: 45.0}
const _ARMOR: Dictionary = {0: 0.0, 1: 0.0, 2: 5.0, 3: 3.0, 4: 8.0, 5: 14.0}
```
直接修改对应值即可,无需改其他代码。
---
## D. 修改难度乘子
`scripts/autoloads/settings_manager.gd`
```gdscript
func enemy_hp_mult() -> float: return [0.7, 1.0, 1.0][difficulty]
func player_dmg_taken_mult()-> float: return [0.7, 1.0, 1.0][difficulty]
func wave_count_mult() -> float: return [1.0, 1.0, 1.2][difficulty]
func boss_hp_mult() -> float: return [1.0, 1.0, 1.3][difficulty]
# 数组格式:[初学者, 标准, 挑战]
```
---
## E. 添加共鸣配方
`scripts/domain/spell_system/spell_evaluator.gd``_ready()``_resonance_recipes`
```gdscript
_resonance_recipes = [
{
"id": "plasma_storm", # 配方 ID
"pattern": ["tag:water", "tag:lightning"], # 元素标签组合
"match": "adjacent", # "adjacent"=相邻;"anywhere_in_deck"=任意位置
"result_spell_id":"action_plasma_storm", # 产物法术 id(须在 WandPreset 中注册)
"consume_inputs": true, # true=消耗原始法术插槽
},
# 新增配方直接追加到这里
{
"id": "ice_nova",
"pattern": ["tag:ice", "tag:water"],
"match": "adjacent",
"result_spell_id":"action_ice_nova",
"consume_inputs": false, # false=原法术保留,额外注入产物
},
]
```
> **注意**:产物法术必须同时在 `WandPreset.make_spell_by_id()` 中注册,才能被 `SpellRegistry` 识别。
---
## F. 添加/修改状态效果
### 现有状态 ID`scripts/autoloads/status_id.gd`
```gdscript
const BURN = 1 # 燃烧 DoT(火系)
const FREEZE = 2 # 冻结(硬控)
const POISON = 3 # 中毒(可叠层 DoT
const WET = 4 # 潮湿
const OILY = 5 # 油腻
const STUN = 6 # 麻痹
const COMBO_MARK = 7
const VULNERABILITY = 8
# 新增从 9 开始递增
```
### 状态数值定义(`.tres` 资源,目前占位代码生成)
当前 `StatusRegistry``_ready()` 扫描 `res://resources/status_types/` 目录。若不存在资源文件,会尝试从 `WandPreset` 或内置回退。添加新状态的完整流程:
1.`status_id.gd` 追加常量(从 9 起)
2.`StatusTypeDef` 对象中配置 tick_interval / stack_mode / max_stacks
3. 在状态数值生效的 `can_catalyze` 字段填写对应状态 ID 列表
4. 在四个 `.po` 文件中添加 `STATUS_新状态_NAME` 等翻译键
---
## G. 添加 Core(法杖类型)
```gdscript
# wand_preset.gd 中添加:
func make_staff_long() -> CoreDefinition:
var c := CoreDefinition.new()
c.id = "staff_long"
c.display_name = "CORE_STAFF_LONG_NAME" # tr() 键
c.slot_count = 10 # 插槽数
c.topology = CoreDefinition.Topology.LINEAR
c.cpu_limit = 10
c.cast_interval = 0.7
return c
```
然后在 `CombatManager._CORE_ROSTER` 中追加 `"staff_long"`,以及在 `_make_core_by_id()``_default_deck_for()` 中添加对应分支。
---
## H. 调试技巧
### 在运行中检查任意系统状态
利用 MCP 游戏脚本执行器(或 Godot 调试器表达式):
```gdscript
# 查看当前法术 VM 编译结果
var ids = []
for nd in SpellEvaluator._equipped_compiled.nodes: ids.append(nd.id)
print(ids)
# 立即杀死所有敌人(测试波次结算)
for i in range(EnemyManager.get_active_count()-1, -1, -1):
var eid = EnemyManager._slot_entity_map.get(i, -1)
if eid >= 0: EnemyManager.apply_damage(eid, 99999.0, -1, false)
# 给玩家大量金币
PlayerStats.gain_gold(9999)
```