""" 粒子编辑器 提供可视化的粒子效果编辑功能 """ import json from typing import Dict, Optional, Any from panda3d.core import Vec3, Point3 class ParticleEditor: """ 粒子编辑器 提供实时编辑粒子效果的功能 """ def __init__(self, world, particle_manager): """ 初始化粒子编辑器 Args: world: 3D世界对象 particle_manager: 粒子管理器 """ self.world = world self.particle_manager = particle_manager self.visible = False # 当前编辑的发射器 self.current_emitter_id = None self.current_emitter = None # 编辑器GUI元素 self.gui_elements = [] self.parameter_controls = {} self.effect_type_buttons = {} # 预览模式 self.preview_mode = True self.auto_update = True # 编辑器状态 self.editor_position = Point3(0, 0, 0) self.editor_enabled = False # 编辑参数 self.edit_parameters = { 'emitter_type': 'fire', 'emission_rate': 50, 'particle_lifetime': 2.0, 'start_color': Vec3(1.0, 0.3, 0.0), 'end_color': Vec3(1.0, 0.0, 0.0), 'start_size': 0.2, 'end_size': 0.8, 'velocity': Vec3(0, 0, 2), 'velocity_variation': 0.5, 'gravity_scale': -0.2, 'air_resistance': 0.02, 'bounce_factor': 0.5, 'emitter_shape': 'circle', 'emitter_radius': 0.3, 'rotation_enabled': False, 'rotation_speed': 0.0 } print("✓ 粒子编辑器初始化完成") def toggle_visibility(self): """切换编辑器可见性""" if self.visible: self.hide() else: self.show() def show(self): """显示编辑器""" if self.visible: return self.visible = True self._create_editor_gui() # 如果没有当前发射器,创建一个默认的 if not self.current_emitter_id: self.create_new_effect() print("✓ 粒子编辑器已显示") def hide(self): """隐藏编辑器""" if not self.visible: return self.visible = False self._cleanup_editor_gui() print("✓ 粒子编辑器已隐藏") def _create_editor_gui(self): """创建编辑器GUI""" try: if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager: print("⚠ GUI管理器不可用,无法创建编辑器界面") return gui_manager = self.world.gui_manager # 创建编辑器面板标题 panel = gui_manager.createGUIButton( pos=(0.7, 0, 0.95), text="粒子编辑器", size=0.06 ) self.gui_elements.append(panel) # 创建效果类型选择按钮 effect_types = ["火焰", "烟雾", "爆炸", "水花", "魔法", "雪花", "自定义"] for i, effect_type in enumerate(effect_types): button = gui_manager.createGUIButton( pos=(0.7, 0, 0.88 - i * 0.06), text=effect_type, size=0.04 ) self.gui_elements.append(button) self.effect_type_buttons[effect_type] = button self.world.accept(f"editor_effect_{effect_type}", lambda t=effect_type: self.set_effect_type(t)) # 创建参数控制面板 self._create_parameter_controls() # 创建操作按钮 self._create_action_buttons() except Exception as e: print(f"⚠ 编辑器GUI创建失败: {e}") def _create_parameter_controls(self): """创建参数控制界面""" if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager: return gui_manager = self.world.gui_manager # 基础参数控制 parameters = [ ("发射率", "emission_rate", 0, 500, 1), ("生命周期", "particle_lifetime", 0.1, 10.0, 0.1), ("起始尺寸", "start_size", 0.01, 5.0, 0.01), ("结束尺寸", "end_size", 0.01, 5.0, 0.01), ("速度变化", "velocity_variation", 0.0, 5.0, 0.1), ("重力缩放", "gravity_scale", -5.0, 5.0, 0.1), ("空气阻力", "air_resistance", 0.0, 1.0, 0.01), ("反弹系数", "bounce_factor", 0.0, 1.0, 0.1), ("发射器半径", "emitter_radius", 0.0, 5.0, 0.1), ("旋转速度", "rotation_speed", -10.0, 10.0, 0.1) ] for i, (name, param, min_val, max_val, step) in enumerate(parameters): y_pos = 0.8 - i * 0.055 # 参数标签 label = gui_manager.createGUIButton( pos=(0.75, 0, y_pos), text=f"{name}:", size=0.03 ) self.gui_elements.append(label) # 参数值显示 value_label = gui_manager.createGUIButton( pos=(0.88, 0, y_pos), text=f"{self.edit_parameters[param]:.2f}", size=0.03 ) self.gui_elements.append(value_label) # 减少按钮 dec_button = gui_manager.createGUIButton( pos=(0.83, 0, y_pos), text="-", size=0.03 ) self.gui_elements.append(dec_button) # 增加按钮 inc_button = gui_manager.createGUIButton( pos=(0.93, 0, y_pos), text="+", size=0.03 ) self.gui_elements.append(inc_button) # 注册事件 self.world.accept(f"dec_{param}", lambda p=param, s=step: self._decrease_parameter(p, s)) self.world.accept(f"inc_{param}", lambda p=param, s=step: self._increase_parameter(p, s)) # 存储控制信息 self.parameter_controls[param] = { 'name': name, 'min': min_val, 'max': max_val, 'step': step, 'value_label': value_label, 'dec_button': dec_button, 'inc_button': inc_button } # 布尔型参数 bool_params = [ ("启用旋转", "rotation_enabled"), ("自动更新", "auto_update"), ("预览模式", "preview_mode") ] for i, (name, param) in enumerate(bool_params): y_pos = 0.25 - i * 0.055 # 参数标签 label = gui_manager.createGUIButton( pos=(0.75, 0, y_pos), text=f"{name}:", size=0.03 ) self.gui_elements.append(label) # 开关按钮 switch_button = gui_manager.createGUIButton( pos=(0.88, 0, y_pos), text="开" if self.edit_parameters.get(param, False) else "关", size=0.03 ) self.gui_elements.append(switch_button) # 注册事件 self.world.accept(f"toggle_{param}", lambda p=param: self._toggle_boolean_parameter(p)) self.parameter_controls[param] = { 'name': name, 'value_label': switch_button, 'type': 'boolean' } def _create_action_buttons(self): """创建操作按钮""" if not hasattr(self.world, 'gui_manager') or not self.world.gui_manager: return gui_manager = self.world.gui_manager actions = [ ("应用更改", 0.15, self.apply_changes), ("重置参数", 0.09, self.reset_parameters), ("创建新效果", 0.03, self.create_new_effect), ("删除当前", -0.03, self.delete_current_effect), ("保存预设", -0.09, self.save_preset), ("加载预设", -0.15, self.load_preset) ] for name, y_offset, callback in actions: button = gui_manager.createGUIButton( pos=(0.82, 0, y_offset), text=name, size=0.035 ) self.gui_elements.append(button) self.world.accept(f"action_{name}", callback) def _cleanup_editor_gui(self): """清理编辑器GUI""" try: if hasattr(self.world, 'gui_manager') and self.world.gui_manager: gui_manager = self.world.gui_manager for element in self.gui_elements: gui_manager.deleteGUIElement(element) # 清理事件监听 for param in self.parameter_controls: self.world.ignore(f"dec_{param}") self.world.ignore(f"inc_{param}") for param in ['rotation_enabled', 'auto_update', 'preview_mode']: self.world.ignore(f"toggle_{param}") self.world.ignore("action_应用更改") self.world.ignore("action_重置参数") self.world.ignore("action_创建新效果") self.world.ignore("action_删除当前") self.world.ignore("action_保存预设") self.world.ignore("action_加载预设") self.gui_elements.clear() self.parameter_controls.clear() self.effect_type_buttons.clear() except Exception as e: print(f"⚠ 编辑器GUI清理失败: {e}") def _decrease_parameter(self, param_name: str, step: float): """减少参数值""" if param_name in self.edit_parameters: control = self.parameter_controls[param_name] current_value = self.edit_parameters[param_name] new_value = max(control['min'], current_value - step) self.edit_parameters[param_name] = new_value # 更新显示 if 'value_label' in control: control['value_label'].setText(f"{new_value:.2f}") # 自动应用更改 if self.auto_update: self.apply_changes() def _increase_parameter(self, param_name: str, step: float): """增加参数值""" if param_name in self.edit_parameters: control = self.parameter_controls[param_name] current_value = self.edit_parameters[param_name] new_value = min(control['max'], current_value + step) self.edit_parameters[param_name] = new_value # 更新显示 if 'value_label' in control: control['value_label'].setText(f"{new_value:.2f}") # 自动应用更改 if self.auto_update: self.apply_changes() def _toggle_boolean_parameter(self, param_name: str): """切换布尔参数""" if param_name in self.edit_parameters: current_value = self.edit_parameters[param_name] new_value = not current_value self.edit_parameters[param_name] = new_value # 更新显示 control = self.parameter_controls[param_name] if 'value_label' in control: control['value_label'].setText("开" if new_value else "关") # 特殊处理某些参数 if param_name == 'auto_update': self.auto_update = new_value elif param_name == 'preview_mode': self.preview_mode = new_value # 自动应用更改 if self.auto_update: self.apply_changes() def create_new_effect(self, effect_type: str = "火焰"): """创建新的粒子效果""" # 删除当前效果 if self.current_emitter_id: self.particle_manager.remove_emitter(self.current_emitter_id) # 在摄像机前方创建 camera_pos = self.world.camera.getPos() camera_forward = self.world.camera.getNetTransform().getMat().getRow3(1) position = camera_pos + camera_forward * 5 # 根据类型设置参数 self.set_effect_type(effect_type) # 创建发射器 config = self.edit_parameters.copy() config['emitter_type'] = effect_type.lower() self.current_emitter_id = self.particle_manager.create_emitter( effect_type.lower(), position, config ) self.current_emitter = self.particle_manager.get_emitter(self.current_emitter_id) self.editor_position = position print(f"✓ 创建新粒子效果: {effect_type}") def set_effect_type(self, effect_type: str): """设置效果类型""" self.edit_parameters['emitter_type'] = effect_type.lower() # 根据类型预设参数 if effect_type == "火焰": self.edit_parameters.update({ 'emission_rate': 50, 'particle_lifetime': 2.0, 'start_color': Vec3(1.0, 0.3, 0.0), 'end_color': Vec3(1.0, 0.0, 0.0), 'start_size': 0.2, 'end_size': 0.8, 'velocity': Vec3(0, 0, 2), 'velocity_variation': 0.5, 'gravity_scale': -0.2, 'emitter_shape': 'circle', 'emitter_radius': 0.3 }) elif effect_type == "烟雾": self.edit_parameters.update({ 'emission_rate': 30, 'particle_lifetime': 4.0, 'start_color': Vec3(0.8, 0.8, 0.8), 'end_color': Vec3(0.3, 0.3, 0.3), 'start_size': 0.3, 'end_size': 1.5, 'velocity': Vec3(0, 0, 1), 'velocity_variation': 0.3, 'gravity_scale': -0.1, 'emitter_shape': 'circle', 'emitter_radius': 0.5 }) elif effect_type == "爆炸": self.edit_parameters.update({ 'emission_rate': 200, 'burst_count': 100, 'particle_lifetime': 1.5, 'start_color': Vec3(1.0, 0.8, 0.0), 'end_color': Vec3(1.0, 0.2, 0.0), 'start_size': 0.1, 'end_size': 0.5, 'velocity': Vec3(0, 0, 0), 'velocity_variation': 2.0, 'gravity_scale': 0.5, 'duration': 0.2, 'emitter_shape': 'sphere', 'emitter_radius': 0.1 }) elif effect_type == "水花": self.edit_parameters.update({ 'emission_rate': 80, 'particle_lifetime': 3.0, 'start_color': Vec3(0.2, 0.6, 1.0), 'end_color': Vec3(0.1, 0.3, 0.8), 'start_size': 0.1, 'end_size': 0.05, 'velocity': Vec3(0, 0, 3), 'velocity_variation': 1.0, 'gravity_scale': 2.0, 'bounce_factor': 0.3, 'emitter_shape': 'circle', 'emitter_radius': 0.2 }) elif effect_type == "魔法": self.edit_parameters.update({ 'emission_rate': 40, 'particle_lifetime': 3.0, 'start_color': Vec3(0.5, 0.2, 1.0), 'end_color': Vec3(0.8, 0.6, 1.0), 'start_size': 0.15, 'end_size': 0.05, 'velocity': Vec3(0, 0, 0.5), 'velocity_variation': 0.8, 'gravity_scale': -0.5, 'rotation_enabled': True, 'rotation_speed': 2.0, 'emitter_shape': 'sphere', 'emitter_radius': 0.8 }) elif effect_type == "雪花": self.edit_parameters.update({ 'emission_rate': 60, 'particle_lifetime': 8.0, 'start_color': Vec3(0.9, 0.9, 1.0), 'end_color': Vec3(0.9, 0.9, 1.0), 'start_size': 0.05, 'end_size': 0.05, 'velocity': Vec3(0, 0, -1), 'velocity_variation': 0.5, 'gravity_scale': 0.8, 'air_resistance': 0.1, 'bounce_factor': 0.2, 'emitter_shape': 'box', 'emitter_dimensions': Vec3(20, 20, 1) }) # 更新GUI显示 self._update_parameter_display() def _update_parameter_display(self): """更新参数显示""" for param_name, control in self.parameter_controls.items(): if param_name in self.edit_parameters: value = self.edit_parameters[param_name] if control.get('type') == 'boolean': if 'value_label' in control: control['value_label'].setText("开" if value else "关") else: if 'value_label' in control: if isinstance(value, (int, float)): control['value_label'].setText(f"{value:.2f}") else: control['value_label'].setText(str(value)) def apply_changes(self): """应用更改到当前效果""" if not self.current_emitter_id or not self.current_emitter: print("⚠ 没有选中的效果") return # 更新发射器参数 try: # 更新发射器配置 for param_name, value in self.edit_parameters.items(): if hasattr(self.current_emitter, param_name): setattr(self.current_emitter, param_name, value) print("✓ 参数已应用") except Exception as e: print(f"⚠ 参数应用失败: {e}") def reset_parameters(self): """重置参数为默认值""" # 重新设置当前类型参数 current_type = self.edit_parameters.get('emitter_type', 'fire') self.set_effect_type(current_type.capitalize()) print("✓ 参数已重置") def delete_current_effect(self): """删除当前效果""" if self.current_emitter_id: self.particle_manager.remove_emitter(self.current_emitter_id) self.current_emitter_id = None self.current_emitter = None print("✓ 当前效果已删除") def update_parameter(self, param_name: str, value: float): """更新参数值""" if not self.current_emitter or param_name not in self.parameter_controls: return # 更新控制信息 control = self.parameter_controls[param_name] # 更新GUI显示 if 'value_label' in control: try: control['value_label'].setText(f"{value:.2f}") except: pass # 应用到发射器 self._apply_parameter_to_emitter(param_name, value) if self.auto_update: print(f"✓ 更新参数 {param_name}: {value}") def _apply_parameter_to_emitter(self, param_name: str, value: float): """将参数应用到发射器""" if not self.current_emitter: return try: if param_name == "emission_rate": self.current_emitter.emission_rate = value elif param_name == "particle_lifetime": self.current_emitter.particle_lifetime = value elif param_name == "start_size": self.current_emitter.start_size = value elif param_name == "end_size": self.current_emitter.end_size = value elif param_name == "velocity_variation": self.current_emitter.velocity_variation = value except Exception as e: print(f"⚠ 参数应用失败 {param_name}: {e}") def set_emitter_position(self, position: Point3): """设置发射器位置""" self.editor_position = position if self.current_emitter: self.current_emitter.set_position(position) def get_current_config(self) -> Dict[str, Any]: """获取当前配置""" config = self.edit_parameters.copy() config['emitter_type'] = self.edit_parameters.get('emitter_type', 'fire') config['position'] = [ self.editor_position.x, self.editor_position.y, self.editor_position.z ] return config def load_config(self, config: Dict[str, Any]): """加载配置""" try: # 设置位置 if 'position' in config: pos = config['position'] position = Point3(pos[0], pos[1], pos[2]) self.set_emitter_position(position) # 更新编辑参数 for param_name, value in config.items(): if param_name in self.edit_parameters: self.edit_parameters[param_name] = value # 更新GUI显示 self._update_parameter_display() # 应用到当前效果 if self.auto_update: self.apply_changes() print("✓ 配置加载完成") except Exception as e: print(f"⚠ 配置加载失败: {e}") def save_preset(self, name: str = "自定义预设") -> bool: """保存预设""" try: config = self.get_current_config() # 这里应该调用预设管理器保存 print(f"✓ 保存预设 '{name}':") print(json.dumps(config, indent=2, default=str)) return True except Exception as e: print(f"⚠ 预设保存失败: {e}") return False def load_preset(self, name: str = "经典火焰") -> bool: """加载预设""" try: # 这里应该调用预设管理器加载 # 简化实现:使用默认配置 default_configs = { "经典火焰": { 'emitter_type': 'fire', 'emission_rate': 50, 'particle_lifetime': 2.0, 'start_color': [1.0, 0.3, 0.0], 'end_color': [1.0, 0.0, 0.0], 'start_size': 0.2, 'end_size': 0.8, 'velocity': [0, 0, 2], 'velocity_variation': 0.5, 'gravity_scale': -0.2, 'emitter_shape': 'circle', 'emitter_radius': 0.3 }, "浓密烟雾": { 'emitter_type': 'smoke', 'emission_rate': 30, 'particle_lifetime': 4.0, 'start_color': [0.8, 0.8, 0.8], 'end_color': [0.3, 0.3, 0.3], 'start_size': 0.3, 'end_size': 1.5, 'velocity': [0, 0, 1], 'velocity_variation': 0.3, 'gravity_scale': -0.1, 'emitter_shape': 'circle', 'emitter_radius': 0.5 } } if name in default_configs: self.load_config(default_configs[name]) print(f"✓ 加载预设 '{name}'") return True else: print(f"⚠ 预设 '{name}' 不存在") return False except Exception as e: print(f"⚠ 预设加载失败: {e}") return False def reset_to_defaults(self): """重置为默认值""" if self.current_emitter: # 重新创建当前类型的发射器 effect_type = self.current_emitter.emitter_type self.create_new_effect(effect_type.capitalize()) def cleanup(self): """清理编辑器资源""" self.hide() if self.current_emitter_id: self.particle_manager.remove_emitter(self.current_emitter_id) self.current_emitter_id = None self.current_emitter = None