toon_refresh

This commit is contained in:
2026-10-14 16:36:34 +08:00
parent 877d3bbad0
commit ecebf76f47
4 changed files with 152 additions and 1 deletions
+7
View File
@@ -0,0 +1,7 @@
[plugin]
name="Toon 材质自动刷新"
description="在编辑器里修改 shader/toon.tres(含内联的描边 next_pass 子材质)参数时,自动重刷当前编辑场景中所有 toon_apply 节点的材质,不必再手动勾 refresh_now。也可从 项目 > 工具 菜单手动触发。"
author="agame"
version="1.0"
script="plugin.gd"
+143
View File
@@ -0,0 +1,143 @@
@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
+1
View File
@@ -0,0 +1 @@
uid://dvoc5463koalp
+1 -1
View File
@@ -36,7 +36,7 @@ project/assembly_name="agame"
[editor_plugins]
enabled=PackedStringArray("res://addons/godot_ai/plugin.cfg", "res://addons/godotsteam/plugin.cfg", "res://addons/sky_3d/plugin.cfg")
enabled=PackedStringArray("res://addons/godot_ai/plugin.cfg", "res://addons/godotsteam/plugin.cfg", "res://addons/sky_3d/plugin.cfg", "res://addons/toon_refresh/plugin.cfg")
[input]