355 lines
12 KiB
Python
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("颜色变化脚本停止")
|