refactor(spell-editor): 并入 game_designer tab,移除独立 spell_editor 插件
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ func _init() -> void:
|
|||||||
add_child(tabs)
|
add_child(tabs)
|
||||||
|
|
||||||
# 法术(复用现有法术编辑器停靠脚本)
|
# 法术(复用现有法术编辑器停靠脚本)
|
||||||
_add_tab(tabs, "✨ 法术", preload("res://addons/spell_editor/spell_dock.gd").new())
|
_add_tab(tabs, "✨ 法术", preload("res://addons/game_designer/spell_tab.gd").new())
|
||||||
_add_tab(tabs, "👾 敌人", preload("res://addons/game_designer/enemy_tab.gd").new())
|
_add_tab(tabs, "👾 敌人", preload("res://addons/game_designer/enemy_tab.gd").new())
|
||||||
_add_tab(tabs, "🌊 波次", preload("res://addons/game_designer/wave_tab.gd").new())
|
_add_tab(tabs, "🌊 波次", preload("res://addons/game_designer/wave_tab.gd").new())
|
||||||
_add_tab(tabs, "🪄 Core", preload("res://addons/game_designer/core_tab.gd").new())
|
_add_tab(tabs, "🪄 Core", preload("res://addons/game_designer/core_tab.gd").new())
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
[plugin]
|
|
||||||
name="Spell Editor (法术编辑器)"
|
|
||||||
description="可视化法术编辑器:策划/开发无需改代码即可新增、编辑、删除法术,保存为 res://data/spells.json,游戏运行时自动加载。"
|
|
||||||
author="Base Games"
|
|
||||||
version="1.0.0"
|
|
||||||
script="spell_editor.gd"
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
## 法术编辑器停靠面板(@tool)— 策划/开发可视化新增·编辑·删除法术
|
|
||||||
## 数据持久化到 res://data/spells.json,游戏运行时由 SpellRegistry 加载
|
|
||||||
@tool
|
|
||||||
extends VBoxContainer
|
|
||||||
|
|
||||||
const DATA_PATH: String = "res://data/spells.json"
|
|
||||||
const TYPE_NAMES: Array = ["ACTION", "MODIFIER", "TRIGGER", "LOGIC"]
|
|
||||||
|
|
||||||
## meta 模板(按类型“插入模板”用)
|
|
||||||
const META_TEMPLATES: Dictionary = {
|
|
||||||
0: '{ "base_damage": 5.0, "speed": 350.0, "lifetime": 4.0, "radius": 6.0, "damage_type": 0, "mana_cost": 5, "shop_cost": 20 }',
|
|
||||||
1: '{ "damage_add": 10.0, "mana_cost": 10, "shop_cost": 20 }',
|
|
||||||
2: '{ "base_damage": 3.0, "speed": 250.0, "lifetime": 4.0, "radius": 6.0, "damage_type": 0, "mana_cost": 10, "shop_cost": 30 }',
|
|
||||||
3: '{ "logic_op": "every_n_shots", "n": 3, "reg": 0, "mana_cost": 0, "shop_cost": 24 }',
|
|
||||||
}
|
|
||||||
|
|
||||||
var _spells: Dictionary = {} # { id: {type, display_name, description, element_tags, meta} }
|
|
||||||
var _cur_id: String = ""
|
|
||||||
|
|
||||||
# UI 引用
|
|
||||||
var _list: ItemList
|
|
||||||
var _id_edit: LineEdit
|
|
||||||
var _type_opt: OptionButton
|
|
||||||
var _name_edit: LineEdit
|
|
||||||
var _desc_edit: LineEdit
|
|
||||||
var _tags_edit: LineEdit
|
|
||||||
var _meta_edit: TextEdit
|
|
||||||
var _status: Label
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
_build_ui()
|
|
||||||
_load_data()
|
|
||||||
_refresh_list()
|
|
||||||
|
|
||||||
func _build_ui() -> void:
|
|
||||||
custom_minimum_size = Vector2(300, 400)
|
|
||||||
add_theme_constant_override("separation", 4)
|
|
||||||
|
|
||||||
var title := Label.new()
|
|
||||||
title.text = "✨ 法术编辑器"
|
|
||||||
title.add_theme_font_size_override("font_size", 16)
|
|
||||||
add_child(title)
|
|
||||||
|
|
||||||
# 法术列表
|
|
||||||
_list = ItemList.new()
|
|
||||||
_list.custom_minimum_size = Vector2(0, 120)
|
|
||||||
_list.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
||||||
_list.item_selected.connect(_on_select)
|
|
||||||
add_child(_list)
|
|
||||||
|
|
||||||
var row := HBoxContainer.new()
|
|
||||||
add_child(row)
|
|
||||||
row.add_child(_btn("➕ 新增", _on_new))
|
|
||||||
row.add_child(_btn("⧉ 复制", _on_dup))
|
|
||||||
row.add_child(_btn("🗑 删除", _on_delete))
|
|
||||||
|
|
||||||
add_child(HSeparator.new())
|
|
||||||
|
|
||||||
# 表单
|
|
||||||
_id_edit = _field("ID(唯一)")
|
|
||||||
_type_opt = OptionButton.new()
|
|
||||||
for n in TYPE_NAMES:
|
|
||||||
_type_opt.add_item(n)
|
|
||||||
_labeled("类型", _type_opt)
|
|
||||||
_name_edit = _field("显示名(tr 键或文本)")
|
|
||||||
_desc_edit = _field("描述(tr 键或文本)")
|
|
||||||
_tags_edit = _field("元素标签(逗号分隔,如 tag:water)")
|
|
||||||
|
|
||||||
var meta_lbl := Label.new()
|
|
||||||
meta_lbl.text = "meta(JSON,因类型而异)"
|
|
||||||
add_child(meta_lbl)
|
|
||||||
_meta_edit = TextEdit.new()
|
|
||||||
_meta_edit.custom_minimum_size = Vector2(0, 90)
|
|
||||||
_meta_edit.placeholder_text = '{ "base_damage": 5.0, ... }'
|
|
||||||
add_child(_meta_edit)
|
|
||||||
add_child(_btn("插入该类型 meta 模板", _on_template))
|
|
||||||
|
|
||||||
add_child(HSeparator.new())
|
|
||||||
|
|
||||||
var save_row := HBoxContainer.new()
|
|
||||||
add_child(save_row)
|
|
||||||
save_row.add_child(_btn("✓ 应用到列表", _on_apply))
|
|
||||||
save_row.add_child(_btn("💾 保存文件", _on_save_file))
|
|
||||||
save_row.add_child(_btn("↻ 重载", func(): _load_data(); _refresh_list(); _set_status("已重载")))
|
|
||||||
|
|
||||||
_status = Label.new()
|
|
||||||
_status.add_theme_font_size_override("font_size", 11)
|
|
||||||
_status.modulate = Color(0.6, 0.9, 0.6)
|
|
||||||
add_child(_status)
|
|
||||||
|
|
||||||
func _btn(text: String, cb: Callable) -> Button:
|
|
||||||
var b := Button.new()
|
|
||||||
b.text = text
|
|
||||||
b.pressed.connect(cb)
|
|
||||||
return b
|
|
||||||
|
|
||||||
func _field(placeholder: String) -> LineEdit:
|
|
||||||
var le := LineEdit.new()
|
|
||||||
le.placeholder_text = placeholder
|
|
||||||
add_child(le)
|
|
||||||
return le
|
|
||||||
|
|
||||||
func _labeled(label: String, ctrl: Control) -> void:
|
|
||||||
var l := Label.new()
|
|
||||||
l.text = label
|
|
||||||
add_child(l)
|
|
||||||
add_child(ctrl)
|
|
||||||
|
|
||||||
# ── 数据 ────────────────────────────────────────────────────
|
|
||||||
func _load_data() -> void:
|
|
||||||
_spells = {}
|
|
||||||
if FileAccess.file_exists(DATA_PATH):
|
|
||||||
var parsed = JSON.parse_string(FileAccess.get_file_as_string(DATA_PATH))
|
|
||||||
if parsed is Dictionary:
|
|
||||||
_spells = parsed
|
|
||||||
|
|
||||||
func _refresh_list() -> void:
|
|
||||||
_list.clear()
|
|
||||||
var ids: Array = _spells.keys()
|
|
||||||
ids.sort()
|
|
||||||
for sid in ids:
|
|
||||||
var t: int = int(_spells[sid].get("type", 0))
|
|
||||||
_list.add_item("[%s] %s" % [TYPE_NAMES[clampi(t, 0, 3)], sid])
|
|
||||||
_list.set_item_metadata(_list.item_count - 1, sid)
|
|
||||||
|
|
||||||
func _on_select(idx: int) -> void:
|
|
||||||
_cur_id = String(_list.get_item_metadata(idx))
|
|
||||||
var d: Dictionary = _spells.get(_cur_id, {})
|
|
||||||
_id_edit.text = _cur_id
|
|
||||||
_type_opt.selected = clampi(int(d.get("type", 0)), 0, 3)
|
|
||||||
_name_edit.text = String(d.get("display_name", ""))
|
|
||||||
_desc_edit.text = String(d.get("description", ""))
|
|
||||||
_tags_edit.text = ",".join(d.get("element_tags", []))
|
|
||||||
_meta_edit.text = JSON.stringify(d.get("meta", {}), " ")
|
|
||||||
|
|
||||||
func _on_apply() -> void:
|
|
||||||
var sid: String = _id_edit.text.strip_edges()
|
|
||||||
if sid == "":
|
|
||||||
_set_status("✗ ID 不能为空", true); return
|
|
||||||
var meta = JSON.parse_string(_meta_edit.text)
|
|
||||||
if not (meta is Dictionary):
|
|
||||||
_set_status("✗ meta 不是合法 JSON 对象", true); return
|
|
||||||
var tags: Array = []
|
|
||||||
for t in _tags_edit.text.split(",", false):
|
|
||||||
var s: String = t.strip_edges()
|
|
||||||
if s != "":
|
|
||||||
tags.append(s)
|
|
||||||
# 改了 ID:移除旧键
|
|
||||||
if _cur_id != "" and _cur_id != sid and _spells.has(_cur_id):
|
|
||||||
_spells.erase(_cur_id)
|
|
||||||
_spells[sid] = {
|
|
||||||
"type": _type_opt.selected,
|
|
||||||
"display_name": _name_edit.text,
|
|
||||||
"description": _desc_edit.text,
|
|
||||||
"element_tags": tags,
|
|
||||||
"meta": meta,
|
|
||||||
}
|
|
||||||
_cur_id = sid
|
|
||||||
_refresh_list()
|
|
||||||
_set_status("✓ 已应用『%s』到列表(记得保存文件)" % sid)
|
|
||||||
|
|
||||||
func _on_save_file() -> void:
|
|
||||||
DirAccess.make_dir_recursive_absolute("res://data")
|
|
||||||
var f := FileAccess.open(DATA_PATH, FileAccess.WRITE)
|
|
||||||
if f:
|
|
||||||
f.store_string(JSON.stringify(_spells, " "))
|
|
||||||
f.close()
|
|
||||||
_set_status("💾 已保存 %d 条法术 → %s" % [_spells.size(), DATA_PATH])
|
|
||||||
else:
|
|
||||||
_set_status("✗ 写入失败", true)
|
|
||||||
|
|
||||||
func _on_new() -> void:
|
|
||||||
var base := "new_spell"
|
|
||||||
var sid := base
|
|
||||||
var i := 1
|
|
||||||
while _spells.has(sid):
|
|
||||||
sid = "%s_%d" % [base, i]; i += 1
|
|
||||||
_spells[sid] = {"type": 0, "display_name": sid, "description": "",
|
|
||||||
"element_tags": [], "meta": JSON.parse_string(META_TEMPLATES[0])}
|
|
||||||
_refresh_list()
|
|
||||||
_select_id(sid)
|
|
||||||
|
|
||||||
func _on_dup() -> void:
|
|
||||||
if _cur_id == "" or not _spells.has(_cur_id):
|
|
||||||
return
|
|
||||||
var sid := _cur_id + "_copy"
|
|
||||||
var i := 1
|
|
||||||
while _spells.has(sid):
|
|
||||||
sid = "%s_copy%d" % [_cur_id, i]; i += 1
|
|
||||||
_spells[sid] = _spells[_cur_id].duplicate(true)
|
|
||||||
_refresh_list()
|
|
||||||
_select_id(sid)
|
|
||||||
|
|
||||||
func _on_delete() -> void:
|
|
||||||
if _cur_id != "" and _spells.has(_cur_id):
|
|
||||||
_spells.erase(_cur_id)
|
|
||||||
_cur_id = ""
|
|
||||||
_refresh_list()
|
|
||||||
_set_status("已删除(记得保存文件)")
|
|
||||||
|
|
||||||
func _on_template() -> void:
|
|
||||||
_meta_edit.text = String(META_TEMPLATES.get(_type_opt.selected, "{}"))
|
|
||||||
|
|
||||||
func _select_id(sid: String) -> void:
|
|
||||||
for i in _list.item_count:
|
|
||||||
if String(_list.get_item_metadata(i)) == sid:
|
|
||||||
_list.select(i)
|
|
||||||
_on_select(i)
|
|
||||||
return
|
|
||||||
|
|
||||||
func _set_status(msg: String, is_err: bool = false) -> void:
|
|
||||||
if _status:
|
|
||||||
_status.text = msg
|
|
||||||
_status.modulate = Color(1.0, 0.5, 0.5) if is_err else Color(0.6, 0.9, 0.6)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://dkk2bspn83h8s
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
## 法术编辑器插件入口(EditorPlugin)
|
|
||||||
## 在编辑器左下停靠区添加「法术编辑器」面板。
|
|
||||||
@tool
|
|
||||||
extends EditorPlugin
|
|
||||||
|
|
||||||
var _dock: Control = null
|
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
|
||||||
_dock = preload("res://addons/spell_editor/spell_dock.gd").new()
|
|
||||||
_dock.name = "法术编辑器"
|
|
||||||
add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_BR, _dock)
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
if _dock:
|
|
||||||
remove_control_from_docks(_dock)
|
|
||||||
_dock.free()
|
|
||||||
_dock = null
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://qmds48f8kkm0
|
|
||||||
+1
-1
@@ -65,7 +65,7 @@ project/assembly_name="Rogue"
|
|||||||
|
|
||||||
[editor_plugins]
|
[editor_plugins]
|
||||||
|
|
||||||
enabled=PackedStringArray("res://addons/game_designer/plugin.cfg", "res://addons/godot_mcp/plugin.cfg", "res://addons/spell_editor/plugin.cfg")
|
enabled=PackedStringArray("res://addons/game_designer/plugin.cfg", "res://addons/godot_mcp/plugin.cfg")
|
||||||
|
|
||||||
[gui]
|
[gui]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user