EG/scripts/ColorChangerScript.py
2026-04-21 15:57:20 +08:00

355 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
颜色变化脚本 - 让对象颜色产生循环变化
"""
from core.script_system import ScriptBase
from panda3d.core import TransparencyAttrib, Vec4
import math
class ColorChangerScript(ScriptBase):
"""颜色变化脚本类"""
def __init__(self):
super().__init__()
# 颜色参数
self.color_speed = 1.0 # 颜色变化速度 (周期/秒)
self.color_mode = "rainbow" # 颜色模式: "rainbow", "pulse", "fade", "strobe"
self.base_color = Vec4(1, 1, 1, 1) # 基础颜色
self.intensity = 1.0 # 颜色强度
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_color = None # 原始颜色
self.is_changing = True # 是否正在变化
self.strobe_state = False # 闪烁状态
# 渲染目标缓存(避免每帧重新遍历)
self._target_nodes = []
self._material_targets = []
self._property_helpers = None
def _is_node_valid(self, node):
if not node:
return False
try:
return not node.isEmpty()
except Exception:
try:
return not node.is_empty()
except Exception:
return False
def _collect_target_nodes(self):
"""收集需要同步颜色的节点(根 + GeomNode 后代)"""
targets = []
if self._is_node_valid(self.gameObject):
targets.append(self.gameObject)
try:
for geom_np in self.gameObject.findAllMatches("**/+GeomNode"):
if self._is_node_valid(geom_np):
targets.append(geom_np)
except Exception:
pass
return targets
@staticmethod
def _clone_material(material):
try:
if hasattr(material, "makeCopy"):
return material.makeCopy()
except Exception:
pass
return material
@staticmethod
def _set_material_base_color(material, color):
color_vec = Vec4(color)
try:
if hasattr(material, "set_base_color"):
material.set_base_color(color_vec)
return
if hasattr(material, "setBaseColor"):
material.setBaseColor(color_vec)
return
if hasattr(material, "setDiffuse"):
material.setDiffuse(color_vec)
except Exception:
pass
def _prepare_material_targets(self):
"""确保每个目标节点都有可编辑材质实例,避免改到共享材质。"""
helper = self._property_helpers
targets = []
seen = set()
for node in self._target_nodes:
if not self._is_node_valid(node):
continue
try:
materials = []
# 优先走编辑器现有材质通路:可覆盖 GeomState 中的材质而不仅是 NodePath 材质。
if helper:
try:
if hasattr(helper, "_ensure_unique_materials_for_node"):
helper._ensure_unique_materials_for_node(node)
except Exception:
pass
try:
if hasattr(helper, "_get_node_materials"):
materials = list(helper._get_node_materials(node) or [])
except Exception:
materials = []
if not materials:
if node.hasMaterial():
mat = node.getMaterial()
if mat is not None:
materials = [mat]
if not materials:
continue
for mat in materials:
if mat is None:
continue
material_key = id(mat)
pair_key = (id(node), material_key)
if pair_key in seen:
continue
seen.add(pair_key)
targets.append((node, mat))
except Exception:
continue
self._material_targets = targets
def _apply_color(self, color):
"""同时更新 NodePath 颜色、ColorScale、材质颜色与透明度状态。"""
if not self._is_node_valid(self.gameObject):
return
color = Vec4(color)
alpha = float(max(0.0, min(1.0, color.getW())))
rgba_tuple = (float(color.getX()), float(color.getY()), float(color.getZ()), alpha)
for node in self._target_nodes:
if not self._is_node_valid(node):
continue
try:
node.setColor(*rgba_tuple)
except Exception:
pass
try:
node.setColorScale(*rgba_tuple)
except Exception:
pass
try:
node.setShaderInput("material_base_color", rgba_tuple)
except Exception:
pass
try:
node.setShaderInput("material_opacity", alpha)
except Exception:
pass
if alpha < 0.999:
try:
node.setTransparency(TransparencyAttrib.M_alpha)
except Exception:
pass
try:
node.setAlphaScale(alpha)
except Exception:
pass
else:
try:
node.setTransparency(TransparencyAttrib.M_none)
except Exception:
pass
try:
node.setAlphaScale(1.0)
except Exception:
pass
for node, material in self._material_targets:
if not self._is_node_valid(node):
continue
try:
if self._property_helpers and hasattr(self._property_helpers, "_set_material_base_color"):
self._property_helpers._set_material_base_color(material, rgba_tuple)
else:
self._set_material_base_color(material, rgba_tuple)
except Exception:
pass
try:
if self._property_helpers and hasattr(self._property_helpers, "_sync_material_node_runtime"):
# 刷新 runtime 材质状态,但避免在每帧触发 source->runtime 全量重建。
self._property_helpers._sync_material_node_runtime(
node,
material,
refresh_ssbo_runtime=False,
)
else:
node.setMaterial(material, 1)
except Exception:
pass
try:
if self._property_helpers and hasattr(self._property_helpers, "_invalidate_material_render_cache"):
self._property_helpers._invalidate_material_render_cache()
except Exception:
pass
def start(self):
"""脚本开始时调用"""
self.log("颜色变化脚本启动!")
self.log(f"颜色参数: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
if not self._is_node_valid(self.gameObject):
self.log("警告: 挂载对象不可用,颜色变化不会生效")
return
# 记录原始颜色
try:
self.original_color = self.gameObject.getColor()
except Exception:
self.original_color = Vec4(1, 1, 1, 1)
self.log(f"原始颜色: {self.original_color}")
self._property_helpers = getattr(self.world, "property_helpers", None)
self._target_nodes = self._collect_target_nodes()
self._prepare_material_targets()
self.log(
f"颜色目标节点: {len(self._target_nodes)},材质目标: {len(self._material_targets)}"
)
def update(self, dt):
"""每帧更新"""
if not self.is_changing:
return
if not self._is_node_valid(self.gameObject):
return
# 累积时间
self.time_accumulator += dt
# 根据模式计算新颜色
if self.color_mode == "rainbow":
new_color = self._calculate_rainbow_color()
elif self.color_mode == "pulse":
new_color = self._calculate_pulse_color()
elif self.color_mode == "fade":
new_color = self._calculate_fade_color()
elif self.color_mode == "strobe":
new_color = self._calculate_strobe_color()
else:
new_color = self.base_color
# 应用颜色
self._apply_color(new_color)
def _calculate_rainbow_color(self):
"""计算彩虹颜色"""
# 使用HSV到RGB的转换创建彩虹效果
hue = (self.time_accumulator * self.color_speed) % 1.0
# 简单的HSV到RGB转换
i = int(hue * 6.0)
f = (hue * 6.0) - i
p = 0.0
q = 1.0 - f
t = f
if i % 6 == 0:
r, g, b = 1.0, t, p
elif i % 6 == 1:
r, g, b = q, 1.0, p
elif i % 6 == 2:
r, g, b = p, 1.0, t
elif i % 6 == 3:
r, g, b = p, q, 1.0
elif i % 6 == 4:
r, g, b = t, p, 1.0
else:
r, g, b = 1.0, p, q
return Vec4(r * self.intensity, g * self.intensity, b * self.intensity, 1.0)
def _calculate_pulse_color(self):
"""计算脉冲颜色"""
pulse = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
multiplier = pulse * self.intensity
return Vec4(
self.base_color.getX() * multiplier,
self.base_color.getY() * multiplier,
self.base_color.getZ() * multiplier,
self.base_color.getW(),
)
def _calculate_fade_color(self):
"""计算淡入淡出颜色"""
fade = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
alpha = fade * self.intensity
return Vec4(
self.base_color.getX(),
self.base_color.getY(),
self.base_color.getZ(),
alpha,
)
def _calculate_strobe_color(self):
"""计算闪烁颜色"""
# 根据时间间隔切换状态
speed = max(float(self.color_speed), 1e-4)
interval = 1.0 / (speed * 2.0) # 闪烁间隔
if int(self.time_accumulator / interval) % 2 == 0:
return Vec4(
self.base_color.getX() * self.intensity,
self.base_color.getY() * self.intensity,
self.base_color.getZ() * self.intensity,
self.base_color.getW(),
)
return Vec4(0.1, 0.1, 0.1, self.base_color.getW()) # 暗色状态
def set_color_parameters(self, speed=None, mode=None, base_color=None, intensity=None):
"""设置颜色参数"""
if speed is not None:
self.color_speed = speed
if mode is not None and mode in ["rainbow", "pulse", "fade", "strobe"]:
self.color_mode = mode
if base_color is not None:
self.base_color = Vec4(base_color)
if intensity is not None:
self.intensity = intensity
self.log(f"颜色参数更新: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
def toggle_color_change(self):
"""切换颜色变化状态"""
self.is_changing = not self.is_changing
status = "恢复" if self.is_changing else "暂停"
self.log(f"颜色变化{status}")
def reset_color(self):
"""重置到原始颜色"""
if self.original_color is None:
return
self._apply_color(self.original_color)
self.time_accumulator = 0.0
self.log("颜色已重置到原始值")
def set_solid_color(self, r=1.0, g=1.0, b=1.0, a=1.0):
"""设置固定颜色"""
color = Vec4(r, g, b, a)
self.base_color = color
self._apply_color(color)
self.log(f"设置固定颜色: {color}")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("颜色变化脚本停止")