Files
spellforge/docs/handbook/02_content_authoring.md
T
2026-07-20 10:56:52 +08:00

8.5 KiB
Raw Blame History

02 内容创作指南 (Content Authoring)

策划和程序共同维护的内容文件。所有数值修改无需改架构代码,只改指定位置。

策划/美术优先用可视化工具07 游戏设计器 提供编辑器内一站式面板(法术/敌人/波次/Core/平衡),表单填数值即可,无需写代码。本文的代码方式适合需要复杂自定义逻辑的程序员,或了解底层数据结构。


A. 添加/修改法术

💡 策划优先用可视化工具06 法术编辑器 提供编辑器内表单,无需写代码即可新增/编辑法术(保存到 data/spells.json)。下面的代码方式适合需要复杂逻辑的程序员。

法术定义位置

  • 数据驱动(推荐)res://data/spells.json(由法术编辑器维护,启动时由 SpellRegistry 加载,优先级最高)
  • 硬编码(程序)scripts/autoloads/wand_preset.gd(向后兼容回退)

步骤:添加一个新 ACTION 法术

1. 在 wand_preset.gd 添加工厂函数

func make_frost_bolt() -> SpellNode:
    var n := SpellNode.new()
    n.id           = "action_frost_bolt"          # 全局唯一 ID
    n.type         = SpellNode.SpellType.ACTION
    n.display_name = "SPELL_FROST_BOLT_NAME"       # tr() 键(见本地化指南)
    n.description  = "SPELL_FROST_BOLT_DESC"
    n.element_tags = ["tag:ice"]                   # 共鸣系统用的元素标签
    n.meta = {
        "base_damage":  5.0,    # 基础伤害
        "speed":        380.0,  # 弹速 (px/s)
        "lifetime":     4.0,    # 存活时间 (s)
        "radius":       7.0,    # 弹体半径 (px)
        "damage_type":  0,      # 0=通用 (DamageType.NORMAL)
        "apply_status_id": StatusID.FREEZE,  # 命中施加状态(可选)
        "shop_cost":    22,     # 商店价格
    }
    return n

2. 注册到 make_spell_by_idget_all_spell_ids

func make_spell_by_id(spell_id: String) -> SpellNode:
    match spell_id:
        # ... 已有法术 ...
        "action_frost_bolt": return make_frost_bolt()   # 新增
        _: push_warning(...)

func get_all_spell_ids() -> Array:
    return [
        # ... 已有 ...
        "action_frost_bolt",   # 新增到列表末尾(商店会自动抽取)
    ]

3. 在 translations/*.po 四个文件中添加翻译键(见本地化指南)


支持的法术类型

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.gdWAVE_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 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 血量

# 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. 添加/修改状态效果

现有状态 IDscripts/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 或内置回退。添加新状态的完整流程:

  1. status_id.gd 追加常量(从 9 起)
  2. StatusTypeDef 对象中配置 tick_interval / stack_mode / max_stacks
  3. 在状态数值生效的 can_catalyze 字段填写对应状态 ID 列表
  4. 在四个 .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)