- 将开发过程/归档文档迁至 docs_dev/(development_plan、certification_checklist、 已废弃的 Cocos 架构草案 archived_cocos_architecture_draft),并修正全部跨引用 - 新增根 README.md(项目介绍,暂定名 Spellforge)与 docs_dev/README.md 索引 - 新增 docs_dev/doc_code_audit_2026-07-20.md:文档 vs 代码交叉审计报告(经 6 路对抗性复核,零证伪),含「代码更优 / 文档更优 / 中性」判定汇总 - 在 docs/ 各设计·技术·机制文档就地加「实现现状 (2026-07-20)」callout: 追认代码更优实现(纯 JSON 数据驱动、SpatialGrid-only 碰撞、MultiMesh 单档、 存档选最新槽等),订正陈旧/矛盾内容(.tres→JSON、Boss HP/阈值/波次、EventID、 StatusManager.apply 签名等),标记未实现功能(C# 热路径、Mana、元进展、 Boss 阶段/抗性、Tutorial、轨迹/连锁/催化等)与 latent bug(CoreFeatureTag 位运算、 pierce 空操作、MAX_OPS 不读 cpu_limit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.0 KiB
02 内容创作指南 (Content Authoring)
策划和程序共同维护的内容文件。所有数值修改无需改架构代码,只改指定位置。
⚠️ 实现现状 (2026-07-20 审计):本文多个「改硬编码」的步骤已过期作废——项目内容早已全面 JSON 数据驱动,法术/波次/敌人/共鸣/Core 均从
data/*.json加载,相关硬编码工厂函数与常量表已从代码中删除。当前唯一权威的内容创作工作流是 07 游戏设计器(编辑器内插件面板,全部经data/*.json+ 编辑器,零代码)。本文中标注 ❌OBSOLETE 的小节仅保留作历史参考,请勿据其修改代码。详见审计报告 ../../docs_dev/doc_code_audit_2026-07-20.md。
⭐ 策划/美术优先用可视化工具:07 游戏设计器 提供编辑器内一站式面板(法术/敌人/波次/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 游戏设计器 的「法术」页签(或独立的 06 法术编辑器)用表单新增/编辑法术,保存到
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 法术(当前正确流程)
- 打开游戏设计器「法术」页签,新增一条法术,填写上述字段(
data/spells.json会自动写入)。 - 在
translations/*.po四个文件中添加对应翻译键(见本地化指南)。 - 无需改动任何
.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)
配置字段说明
{"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 Boss;5=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 血量
# 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 顶部常量:
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:
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:
_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)
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 或内置回退。添加新状态的完整流程:
- 在
status_id.gd追加常量(从 9 起) - 在
StatusTypeDef对象中配置 tick_interval / stack_mode / max_stacks - 在状态数值生效的
can_catalyze字段填写对应状态 ID 列表 - 在四个
.po文件中添加STATUS_新状态_NAME等翻译键
G. 添加 Core(法杖类型)
# 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 调试器表达式):
# 查看当前法术 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)