Files
agame/addons/toon_refresh/plugin.gd
T
2026-10-14 16:36:34 +08:00

144 lines
4.2 KiB
GDScript
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.
@tool
extends EditorPlugin
## 让 toon_apply 的材质在编辑器里"改模板即时生效"。
##
## 背景:toon_apply.gd 给每个 MeshInstance3D 都 duplicate() 了一份独立材质
## (因为每个模型有自己的贴图,必须独立),所以之后再改 shader/toon.tres 上的
## 材质级参数(color / specular / fresnel / uv_scale / uv_offset / normal_scale …)
## 不会传到那些副本上,必须重新跑一次 _apply_materials()。
##
## ShaderMaterial 的 Resource.changed 信号在改 shader 参数时不会发出(官方出于性能
## 考虑只在 resource_name 变化时发),所以没法在 toon_apply.gd 内部自己监听。
## 这里改从编辑器侧挂:监听 Inspector 的 property_edited,命中 toon.tres 时重刷。
##
## 注意:本插件只影响"当前正在编辑的场景"里的 toon_apply 节点。
const TARGET_MATERIAL := "res://shader/toon.tres"
const TARGET_SCRIPT_FILE := "toon_apply.gd"
## 防抖间隔:拖颜色滑块时每一步都会触发 property_edited,等停手再重刷一次。
const DEBOUNCE_SEC := 0.2
const MENU_ITEM := "刷新 toon 材质(当前场景)"
var _inspector: EditorInspector
var _dirty := false
var _elapsed := 0.0
func _enter_tree() -> void:
_inspector = EditorInterface.get_inspector()
if _inspector != null and not _inspector.property_edited.is_connected(_on_property_edited):
_inspector.property_edited.connect(_on_property_edited)
add_tool_menu_item(MENU_ITEM, refresh_edited_scene)
set_process(false)
print("[toon_refresh] 已启用:改 %s 会自动重刷当前场景的 toon_apply 材质" % TARGET_MATERIAL)
func _exit_tree() -> void:
remove_tool_menu_item(MENU_ITEM)
if _inspector != null and _inspector.property_edited.is_connected(_on_property_edited):
_inspector.property_edited.disconnect(_on_property_edited)
_inspector = null
func _process(delta: float) -> void:
# 即便 set_process(false) 被引擎重开也安全:这里第一件事就是挡掉
if not _dirty:
return
_elapsed += delta
if _elapsed < DEBOUNCE_SEC:
return
_dirty = false
_elapsed = 0.0
set_process(false)
refresh_edited_scene()
# --- 触发源 ---------------------------------------------------------------
func _on_property_edited(_property: String) -> void:
if _inspector == null:
return
if not _is_target_material(_inspector.get_edited_object()):
return
_dirty = true
_elapsed = 0.0
set_process(true)
## 判断被编辑的资源是不是 toon.tres,或者是它内联的 next_pass 子材质(描边)。
func _is_target_material(obj: Object) -> bool:
if not (obj is ShaderMaterial):
return false
var mat: ShaderMaterial = obj
if mat.resource_path == TARGET_MATERIAL:
return true
# 内联子资源(描边材质)的路径形如
# res://shader/toon.tres::ShaderMaterial_m5gih
if mat.resource_path.begins_with(TARGET_MATERIAL + "::"):
return true
# 兜底:按实例比对 next_pass 链
var template := load(TARGET_MATERIAL) as ShaderMaterial
if template == null:
return false
var pass_mat: Material = template.next_pass
while pass_mat != null:
if pass_mat == mat:
return true
pass_mat = pass_mat.next_pass
return false
# --- 刷新 -----------------------------------------------------------------
## 重刷当前编辑场景里所有挂着 toon_apply.gd 的节点。返回刷新的节点数。
func refresh_edited_scene() -> int:
var root := EditorInterface.get_edited_scene_root()
if root == null:
return 0
var count := _refresh_branch(root)
if count > 0:
print("[toon_refresh] 已重刷 %d 个 toon_apply 节点的材质" % count)
return count
func _refresh_branch(root: Node) -> int:
var count := 0
var stack: Array[Node] = [root]
while not stack.is_empty():
var node: Node = stack.pop_back()
if _is_toon_apply(node):
node.call("_apply_materials")
count += 1
for child in node.get_children():
stack.append(child)
return count
## 按脚本文件名匹配,避免依赖 class_name;同时要求确实有 _apply_materials,
## 防止脚本编译失败时调用出错。
func _is_toon_apply(node: Node) -> bool:
if not node.has_method("_apply_materials"):
return false
var scr := node.get_script()
if not (scr is Script):
return false
return (scr as Script).resource_path.get_file() == TARGET_SCRIPT_FILE