#!/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("颜色变化脚本停止")