EG/plugins/user/rigid_body_physics/particles/particle_system.py
2025-12-12 16:16:15 +08:00

1158 lines
42 KiB
Python

"""
粒子系统模块
负责物理粒子的创建和管理
"""
from panda3d.core import Vec3, Point3, LVector3f, ClockObject
from panda3d.bullet import BulletRigidBodyNode, BulletSphereShape, BulletBoxShape
import random
import math
import time
from collections import deque
class ParticleSystem:
"""
粒子系统类
管理大量物理粒子的创建、更新和销毁
"""
# 粒子类型常量
TYPE_DEFAULT = 'default'
TYPE_SMOKE = 'smoke'
TYPE_FIRE = 'fire'
TYPE_WATER = 'water'
TYPE_DEBRIS = 'debris'
TYPE_EXPLOSION = 'explosion'
TYPE_SPARK = 'spark'
TYPE_BUBBLE = 'bubble'
TYPE_LEAF = 'leaf'
TYPE_SNOW = 'snow'
TYPE_RAIN = 'rain'
def __init__(self, physics_world, max_particles=1000, particle_type=TYPE_DEFAULT):
"""
初始化粒子系统
Args:
physics_world (PhysicsWorld): 物理世界对象
max_particles (int): 最大粒子数
particle_type (str): 粒子类型
"""
self.physics_world = physics_world
self.max_particles = max_particles
self.particle_type = particle_type
# 粒子容器
self.particles = []
self.active_particles = []
self.inactive_particles = deque() # 对象池
# 粒子发射器参数
self.emission_rate = 100 # 每秒发射粒子数
self.emission_accumulator = 0.0
self.emission_burst_count = 0 # 爆发粒子数
self.emission_burst_timer = 0.0
self.emission_burst_interval = 0.0 # 爆发间隔
# 粒子默认属性
self.particle_mass = 0.1
self.particle_radius = 0.1
self.particle_size = Vec3(0.1, 0.1, 0.1) # 非球形粒子尺寸
self.particle_lifetime = 5.0
self.particle_color = Vec3(1, 1, 1)
self.particle_shape_type = 'sphere' # sphere, box, capsule
# 发射器属性
self.emitter_position = Point3(0, 0, 0)
self.emitter_velocity = Vec3(0, 0, 0)
self.emitter_spread = 0.1
self.emitter_radius = 0.0
self.emitter_shape = 'point' # point, circle, box, sphere
self.emitter_dimensions = Vec3(1, 1, 1) # 发射器尺寸
# 物理属性
self.gravity_scale = 1.0
self.air_resistance = 0.01
self.bounce_factor = 0.5
self.friction = 0.1
self.restitution = 0.3
self.linear_damping = 0.01
self.angular_damping = 0.01
# 碰撞属性
self.collision_enabled = True
self.collision_group = 1
self.collision_mask = 0xFFFFFFFF
self.collision_response = True # 是否响应碰撞
# 高级粒子属性
self.particle_trail_enabled = False
self.particle_trail_length = 10
self.particle_fade_enabled = True
self.particle_size_variation = 0.2
self.particle_velocity_variation = 0.3
self.particle_rotation_enabled = False
self.particle_rotation_speed = 0.0
self.particle_rotation_variation = 0.5
# 环境影响
self.wind_enabled = False
self.wind_vector = Vec3(0, 0, 0)
self.wind_turbulence = 0.0
self.temperature_effect = 0.0 # 温度影响(浮力等)
# 特殊效果
self.vortex_enabled = False
self.vortex_center = Point3(0, 0, 0)
self.vortex_strength = 0.0
self.vortex_axis = Vec3(0, 0, 1)
# 粒子生命周期行为
self.size_over_lifetime = None # [(time, scale), ...]
self.color_over_lifetime = None # [(time, color), ...]
self.force_over_lifetime = None # [(time, force), ...]
# 粒子子发射器
self.sub_emitter_enabled = False
self.sub_emitter_rate = 0.0
self.sub_emitter_count = 1
self.sub_emitter_lifetime = 1.0
# 性能优化
self.sleeping_threshold = 0.1 # 速度低于此值时休眠
self.culling_distance = 100.0 # 距离摄像机超过此距离时剔除
self.batch_processing = True # 批量处理
# 统计信息
self.emitted_count = 0
self.recycled_count = 0
self.collision_count = 0
self.max_active_particles = 0
# 时间控制
self.time_scale = 1.0
self.paused = False
# 粒子材质
self.particle_material = None
# 粒子纹理(如果需要渲染)
self.particle_texture = None
self.particle_blend_mode = 'alpha' # alpha, additive, multiply
# 粒子死亡回调
self.death_callbacks = []
# 粒子碰撞回调
self.collision_callbacks = []
print(f"粒子系统初始化完成,最大粒子数: {max_particles},类型: {particle_type}")
def set_emitter_properties(self, position=None, velocity=None, spread=None, radius=None,
shape=None, dimensions=None):
"""
设置发射器属性
Args:
position (Point3): 发射器位置
velocity (Vec3): 发射器基础速度
spread (float): 发射扩散角度
radius (float): 发射器半径
shape (str): 发射器形状
dimensions (Vec3): 发射器尺寸
"""
if position is not None:
self.emitter_position = position
if velocity is not None:
self.emitter_velocity = velocity
if spread is not None:
self.emitter_spread = spread
if radius is not None:
self.emitter_radius = radius
if shape is not None:
self.emitter_shape = shape
if dimensions is not None:
self.emitter_dimensions = dimensions
def set_particle_properties(self, mass=None, radius=None, size=None, lifetime=None,
color=None, shape_type=None):
"""
设置粒子属性
Args:
mass (float): 粒子质量
radius (float): 粒子半径(球形)
size (Vec3): 粒子尺寸(非球形)
lifetime (float): 粒子生命周期
color (Vec3): 粒子颜色
shape_type (str): 粒子形状类型
"""
if mass is not None:
self.particle_mass = mass
if radius is not None:
self.particle_radius = radius
if size is not None:
self.particle_size = size
if lifetime is not None:
self.particle_lifetime = lifetime
if color is not None:
self.particle_color = color
if shape_type is not None:
self.particle_shape_type = shape_type
def set_physics_properties(self, gravity_scale=None, air_resistance=None,
bounce_factor=None, friction=None, restitution=None,
linear_damping=None, angular_damping=None):
"""
设置物理属性
Args:
gravity_scale (float): 重力缩放
air_resistance (float): 空气阻力
bounce_factor (float): 弹跳因子
friction (float): 摩擦系数
restitution (float): 恢复系数
linear_damping (float): 线性阻尼
angular_damping (float): 角阻尼
"""
if gravity_scale is not None:
self.gravity_scale = gravity_scale
if air_resistance is not None:
self.air_resistance = air_resistance
if bounce_factor is not None:
self.bounce_factor = bounce_factor
if friction is not None:
self.friction = friction
if restitution is not None:
self.restitution = restitution
if linear_damping is not None:
self.linear_damping = linear_damping
if angular_damping is not None:
self.angular_damping = angular_damping
def set_collision_properties(self, enabled=None, group=None, mask=None, response=None):
"""
设置碰撞属性
Args:
enabled (bool): 是否启用碰撞
group (int): 碰撞组
mask (int): 碰撞掩码
response (bool): 是否响应碰撞
"""
if enabled is not None:
self.collision_enabled = enabled
if group is not None:
self.collision_group = group
if mask is not None:
self.collision_mask = mask
if response is not None:
self.collision_response = response
def set_environmental_effects(self, wind_enabled=None, wind_vector=None,
wind_turbulence=None, temperature_effect=None):
"""
设置环境影响
Args:
wind_enabled (bool): 是否启用风
wind_vector (Vec3): 风向量
wind_turbulence (float): 风湍流
temperature_effect (float): 温度影响
"""
if wind_enabled is not None:
self.wind_enabled = wind_enabled
if wind_vector is not None:
self.wind_vector = wind_vector
if wind_turbulence is not None:
self.wind_turbulence = wind_turbulence
if temperature_effect is not None:
self.temperature_effect = temperature_effect
def set_special_effects(self, vortex_enabled=None, vortex_center=None,
vortex_strength=None, vortex_axis=None):
"""
设置特殊效果
Args:
vortex_enabled (bool): 是否启用涡流
vortex_center (Point3): 涡流中心
vortex_strength (float): 涡流强度
vortex_axis (Vec3): 涡流轴向
"""
if vortex_enabled is not None:
self.vortex_enabled = vortex_enabled
if vortex_center is not None:
self.vortex_center = vortex_center
if vortex_strength is not None:
self.vortex_strength = vortex_strength
if vortex_axis is not None:
self.vortex_axis = vortex_axis
def set_lifetime_behaviors(self, size_curve=None, color_curve=None, force_curve=None):
"""
设置粒子生命周期行为
Args:
size_curve (list): 尺寸随时间变化曲线 [(time, scale), ...]
color_curve (list): 颜色随时间变化曲线 [(time, color), ...]
force_curve (list): 力随时间变化曲线 [(time, force), ...]
"""
if size_curve is not None:
self.size_over_lifetime = size_curve
if color_curve is not None:
self.color_over_lifetime = color_curve
if force_curve is not None:
self.force_over_lifetime = force_curve
def set_sub_emitter(self, enabled=None, rate=None, count=None, lifetime=None):
"""
设置粒子子发射器
Args:
enabled (bool): 是否启用子发射器
rate (float): 子发射器速率
count (int): 每次发射的子粒子数
lifetime (float): 子粒子生命周期
"""
if enabled is not None:
self.sub_emitter_enabled = enabled
if rate is not None:
self.sub_emitter_rate = rate
if count is not None:
self.sub_emitter_count = count
if lifetime is not None:
self.sub_emitter_lifetime = lifetime
def set_performance_options(self, sleeping_threshold=None, culling_distance=None,
batch_processing=None):
"""
设置性能优化选项
Args:
sleeping_threshold (float): 休眠阈值
culling_distance (float): 剔除距离
batch_processing (bool): 批量处理
"""
if sleeping_threshold is not None:
self.sleeping_threshold = sleeping_threshold
if culling_distance is not None:
self.culling_distance = culling_distance
if batch_processing is not None:
self.batch_processing = batch_processing
def set_emission_rate(self, rate):
"""
设置发射速率
Args:
rate (float): 每秒发射粒子数
"""
self.emission_rate = max(0, rate)
def set_emission_burst(self, count, interval=0.0):
"""
设置爆发发射
Args:
count (int): 每次爆发的粒子数
interval (float): 爆发间隔(秒)
"""
self.emission_burst_count = count
self.emission_burst_interval = interval
def emit_particle(self, position=None, velocity=None, lifetime=None, color=None):
"""
发射单个粒子
Args:
position (Point3): 粒子位置
velocity (Vec3): 粒子速度
lifetime (float): 粒子生命周期
color (Vec3): 粒子颜色
Returns:
dict: 粒子信息字典
"""
# 检查是否达到最大粒子数
if len(self.active_particles) >= self.max_particles:
return None
# 从对象池获取粒子或创建新粒子
if self.inactive_particles:
particle_info = self.inactive_particles.popleft()
self.recycled_count += 1
else:
particle_info = self._create_new_particle()
# 设置粒子属性
if position is None:
position = self._calculate_emission_position()
if velocity is None:
velocity = self._calculate_emission_velocity()
if lifetime is None:
lifetime = self.particle_lifetime * random.uniform(
1.0 - self.particle_lifetime * 0.2,
1.0 + self.particle_lifetime * 0.2
)
if color is None:
color = self.particle_color
# 初始化粒子状态
particle_info['position'] = position
particle_info['velocity'] = velocity
particle_info['lifetime'] = lifetime
particle_info['age'] = 0.0
particle_info['color'] = color
particle_info['initial_size'] = particle_info['size']
particle_info['initial_color'] = color
particle_info['initial_velocity'] = velocity
particle_info['rotation'] = random.uniform(0, 360) if self.particle_rotation_enabled else 0.0
particle_info['rotation_speed'] = self.particle_rotation_speed * random.uniform(
1.0 - self.particle_rotation_variation,
1.0 + self.particle_rotation_variation
)
particle_info['trail'] = [] if self.particle_trail_enabled else None
particle_info['collisions'] = 0
particle_info['sleeping'] = False
particle_info['culled'] = False
# 添加到物理世界
self.physics_world.bullet_world.attachRigidBody(particle_info['body'])
# 设置物理属性
particle_info['body'].setLinearVelocity(velocity)
particle_info['body'].setFriction(self.friction)
particle_info['body'].setRestitution(self.restitution)
particle_info['body'].setLinearDamping(self.linear_damping)
particle_info['body'].setAngularDamping(self.angular_damping)
# 设置碰撞属性
if self.collision_enabled:
particle_info['body'].setCollideFilterGroup(self.collision_group)
particle_info['body'].setCollideFilterMask(self.collision_mask)
else:
particle_info['body'].setCollideFilterGroup(0)
particle_info['body'].setCollideFilterMask(0)
# 设置位置
particle_info['node'].setPos(position)
# 添加到活动粒子列表
self.active_particles.append(particle_info)
self.emitted_count += 1
# 更新统计
self.max_active_particles = max(self.max_active_particles, len(self.active_particles))
return particle_info
def _create_new_particle(self):
"""
创建新粒子
Returns:
dict: 粒子信息字典
"""
# 创建粒子形状
if self.particle_shape_type == 'sphere':
size = self.particle_radius * random.uniform(
1.0 - self.particle_size_variation,
1.0 + self.particle_size_variation
)
particle_shape = BulletSphereShape(size)
elif self.particle_shape_type == 'box':
size = self.particle_size * random.uniform(
1.0 - self.particle_size_variation,
1.0 + self.particle_size_variation
)
particle_shape = BulletBoxShape(size * 0.5)
else:
# 默认使用球形
size = self.particle_radius * random.uniform(
1.0 - self.particle_size_variation,
1.0 + self.particle_size_variation
)
particle_shape = BulletSphereShape(size)
# 创建粒子刚体
particle_body = BulletRigidBodyNode(f'Particle_{len(self.particles)}')
particle_body.addShape(particle_shape)
particle_body.setMass(self.particle_mass)
# 创建粒子节点
from panda3d.core import NodePath
dummy_np = NodePath("dummy")
particle_node = dummy_np.attachNewNode(particle_body)
# 创建粒子信息
particle_info = {
'node': particle_node,
'body': particle_body,
'shape': particle_shape,
'position': Point3(0, 0, 0),
'velocity': Vec3(0, 0, 0),
'lifetime': self.particle_lifetime,
'age': 0.0,
'color': self.particle_color,
'size': size,
'mass': self.particle_mass,
'radius': size if self.particle_shape_type == 'sphere' else None,
'type': self.particle_type,
'material': self.particle_material,
'creation_time': time.time(),
}
self.particles.append(particle_info)
return particle_info
def _calculate_emission_position(self):
"""
计算发射位置
Returns:
Point3: 发射位置
"""
if self.emitter_shape == 'point':
return self.emitter_position
elif self.emitter_shape == 'circle':
angle = random.uniform(0, 2 * math.pi)
distance = random.uniform(0, self.emitter_radius)
offset = Point3(
math.cos(angle) * distance,
math.sin(angle) * distance,
0
)
return self.emitter_position + offset
elif self.emitter_shape == 'box':
offset = Point3(
random.uniform(-self.emitter_dimensions.getX() * 0.5, self.emitter_dimensions.getX() * 0.5),
random.uniform(-self.emitter_dimensions.getY() * 0.5, self.emitter_dimensions.getY() * 0.5),
random.uniform(-self.emitter_dimensions.getZ() * 0.5, self.emitter_dimensions.getZ() * 0.5)
)
return self.emitter_position + offset
elif self.emitter_shape == 'sphere':
# 球面均匀分布
theta = random.uniform(0, 2 * math.pi)
phi = math.acos(2 * random.uniform(0, 1) - 1)
radius = self.emitter_radius
offset = Point3(
radius * math.sin(phi) * math.cos(theta),
radius * math.sin(phi) * math.sin(theta),
radius * math.cos(phi)
)
return self.emitter_position + offset
else:
return self.emitter_position
def _calculate_emission_velocity(self):
"""
计算发射速度
Returns:
Vec3: 发射速度
"""
# 基础速度
base_velocity = self.emitter_velocity.copy()
# 添加随机扩散
if self.emitter_spread > 0:
spread_angle = random.uniform(0, self.emitter_spread)
spread_direction = random.uniform(0, 2 * math.pi)
spread_offset = Vec3(
math.cos(spread_direction) * math.sin(spread_angle),
math.sin(spread_direction) * math.sin(spread_angle),
math.cos(spread_angle)
)
base_velocity += spread_offset * base_velocity.length()
# 添加速度变化
if self.particle_velocity_variation > 0:
variation = random.uniform(
1.0 - self.particle_velocity_variation,
1.0 + self.particle_velocity_variation
)
base_velocity *= variation
# 添加随机速度分量
random_velocity = Vec3(
random.uniform(-1, 1),
random.uniform(-1, 1),
random.uniform(-1, 1)
) * base_velocity.length() * 0.1
return base_velocity + random_velocity
def update(self, dt):
"""
更新粒子系统
Args:
dt (float): 时间增量
"""
if self.paused:
return
# 应用时间缩放
dt *= self.time_scale
# 更新现有粒子
for particle in self.active_particles[:]: # 使用副本避免迭代时修改
self._update_particle(particle, dt)
# 发射新粒子
self._emit_particles(dt)
# 处理爆发发射
self._handle_burst_emission(dt)
def _update_particle(self, particle, dt):
"""
更新单个粒子
Args:
particle (dict): 粒子信息
dt (float): 时间增量
"""
# 更新粒子年龄
particle['age'] += dt
# 检查生命周期
if particle['age'] >= particle['lifetime']:
self._destroy_particle(particle)
return
# 检查是否休眠
velocity = particle['body'].getLinearVelocity()
if velocity.length() < self.sleeping_threshold:
particle['sleeping'] = True
else:
particle['sleeping'] = False
# 如果休眠,跳过物理更新
if particle['sleeping']:
return
# 应用重力
if self.gravity_scale > 0:
gravity_force = Vec3(0, 0, -9.81) * self.gravity_scale * particle['mass']
particle['body'].applyCentralForce(gravity_force)
# 应用空气阻力
if self.air_resistance > 0:
drag_force = -velocity * self.air_resistance
particle['body'].applyCentralForce(drag_force)
# 应用风力
if self.wind_enabled and self.wind_vector.length() > 0:
wind_force = self.wind_vector * particle['mass']
if self.wind_turbulence > 0:
turbulence = Vec3(
random.uniform(-1, 1),
random.uniform(-1, 1),
random.uniform(-1, 1)
) * self.wind_turbulence
wind_force += turbulence
particle['body'].applyCentralForce(wind_force)
# 应用涡流力
if self.vortex_enabled and self.vortex_strength > 0:
self._apply_vortex_force(particle)
# 应用温度影响(浮力等)
if self.temperature_effect != 0:
# 简化的浮力计算
buoyancy_force = Vec3(0, 0, 1) * self.temperature_effect * particle['mass']
particle['body'].applyCentralForce(buoyancy_force)
# 应用生命周期力
if self.force_over_lifetime:
self._apply_lifetime_force(particle)
# 更新轨迹
if self.particle_trail_enabled and particle['trail'] is not None:
position = particle['node'].getPos()
particle['trail'].append(position)
if len(particle['trail']) > self.particle_trail_length:
particle['trail'].pop(0)
# 更新旋转
if self.particle_rotation_enabled:
particle['rotation'] += particle['rotation_speed'] * dt
# 更新尺寸
if self.size_over_lifetime:
self._update_particle_size(particle)
# 更新颜色
if self.particle_fade_enabled or self.color_over_lifetime:
self._update_particle_color(particle)
# 处理子发射器
if self.sub_emitter_enabled and self.sub_emitter_rate > 0:
self._handle_sub_emitter(particle, dt)
def _apply_vortex_force(self, particle):
"""
应用涡流力
Args:
particle (dict): 粒子信息
"""
position = particle['node'].getPos()
to_center = self.vortex_center - position
distance = to_center.length()
if distance > 0:
# 径向力(向中心吸引)
radial_force = to_center.normalized() * self.vortex_strength / (distance + 1.0)
# 切向力(绕轴旋转)
tangent_dir = self.vortex_axis.cross(to_center).normalized()
tangent_force = tangent_dir * self.vortex_strength
total_force = (radial_force + tangent_force) * particle['mass']
particle['body'].applyCentralForce(total_force)
def _apply_lifetime_force(self, particle):
"""
应用生命周期力
Args:
particle (dict): 粒子信息
"""
normalized_age = particle['age'] / particle['lifetime']
# 在力曲线上插值
for i in range(len(self.force_over_lifetime) - 1):
time1, force1 = self.force_over_lifetime[i]
time2, force2 = self.force_over_lifetime[i + 1]
if time1 <= normalized_age <= time2:
t = (normalized_age - time1) / (time2 - time1) if time2 != time1 else 0
interpolated_force = force1 + (force2 - force1) * t
particle['body'].applyCentralForce(interpolated_force * particle['mass'])
break
def _update_particle_size(self, particle):
"""
更新粒子尺寸
Args:
particle (dict): 粒子信息
"""
normalized_age = particle['age'] / particle['lifetime']
# 在尺寸曲线上插值
for i in range(len(self.size_over_lifetime) - 1):
time1, scale1 = self.size_over_lifetime[i]
time2, scale2 = self.size_over_lifetime[i + 1]
if time1 <= normalized_age <= time2:
t = (normalized_age - time1) / (time2 - time1) if time2 != time1 else 0
interpolated_scale = scale1 + (scale2 - scale1) * t
# 更新粒子尺寸(这个功能需要与渲染系统集成)
# 这里只是更新记录,实际渲染需要在渲染系统中实现
if isinstance(particle['size'], (int, float)):
particle['size'] = particle['initial_size'] * interpolated_scale
break
def _update_particle_color(self, particle):
"""
更新粒子颜色
Args:
particle (dict): 粒子信息
"""
normalized_age = particle['age'] / particle['lifetime']
# 淡出效果
if self.particle_fade_enabled:
fade_factor = 1.0 - (normalized_age * normalized_age) # 二次淡出
particle['color'] = particle['initial_color'] * fade_factor
# 生命周期颜色变化
if self.color_over_lifetime:
for i in range(len(self.color_over_lifetime) - 1):
time1, color1 = self.color_over_lifetime[i]
time2, color2 = self.color_over_lifetime[i + 1]
if time1 <= normalized_age <= time2:
t = (normalized_age - time1) / (time2 - time1) if time2 != time1 else 0
interpolated_color = color1 + (color2 - color1) * t
if self.particle_fade_enabled:
# 结合淡出效果
particle['color'] = interpolated_color * (1.0 - normalized_age)
else:
particle['color'] = interpolated_color
break
def _handle_sub_emitter(self, particle, dt):
"""
处理子发射器
Args:
particle (dict): 粒子信息
dt (float): 时间增量
"""
# 简化的子发射器实现
pass # 需要更复杂的实现
def _emit_particles(self, dt):
"""
发射粒子
Args:
dt (float): 时间增量
"""
# 基于发射速率发射粒子
self.emission_accumulator += dt * self.emission_rate
while self.emission_accumulator >= 1.0 and len(self.active_particles) < self.max_particles:
self.emit_particle()
self.emission_accumulator -= 1.0
def _handle_burst_emission(self, dt):
"""
处理爆发发射
Args:
dt (float): 时间增量
"""
if self.emission_burst_count > 0 and self.emission_burst_interval > 0:
self.emission_burst_timer += dt
if self.emission_burst_timer >= self.emission_burst_interval:
for _ in range(min(self.emission_burst_count, self.max_particles - len(self.active_particles))):
self.emit_particle()
self.emission_burst_timer = 0.0
def _destroy_particle(self, particle):
"""
销毁粒子
Args:
particle (dict): 粒子信息
"""
if particle in self.active_particles:
# 从物理世界移除
self.physics_world.bullet_world.removeRigidBody(particle['body'])
# 移除节点
if particle['node'] and not particle['node'].isEmpty():
particle['node'].removeNode()
# 从活动列表移除
self.active_particles.remove(particle)
# 添加到对象池
self.inactive_particles.append(particle)
# 调用死亡回调
for callback in self.death_callbacks:
try:
callback(particle)
except Exception as e:
print(f"粒子死亡回调错误: {e}")
def get_particle_count(self):
"""
获取活动粒子数量
Returns:
int: 活动粒子数量
"""
return len(self.active_particles)
def get_total_emitted(self):
"""
获取总发射粒子数
Returns:
int: 总发射粒子数
"""
return self.emitted_count
def get_statistics(self):
"""
获取粒子系统统计信息
Returns:
dict: 统计信息
"""
return {
'active_particles': len(self.active_particles),
'total_emitted': self.emitted_count,
'recycled_particles': self.recycled_count,
'collision_count': self.collision_count,
'max_active_particles': self.max_active_particles,
'emission_rate': self.emission_rate,
'particle_type': self.particle_type,
'system_age': time.time() - (self.particles[0]['creation_time'] if self.particles else time.time()),
}
def clear_particles(self):
"""
清除所有粒子
"""
# 销毁所有活动粒子
for particle in self.active_particles[:]:
self._destroy_particle(particle)
# 清空对象池
self.inactive_particles.clear()
# 清空粒子列表
self.particles.clear()
self.active_particles.clear()
# 重置统计信息
self.emitted_count = 0
self.recycled_count = 0
self.collision_count = 0
self.max_active_particles = 0
# 重置发射器
self.emission_accumulator = 0.0
self.emission_burst_timer = 0.0
print("所有粒子已清除")
def pause(self, paused=True):
"""
暂停或恢复粒子系统
Args:
paused (bool): 是否暂停
"""
self.paused = paused
def set_time_scale(self, scale):
"""
设置时间缩放
Args:
scale (float): 时间缩放因子
"""
self.time_scale = max(0.0, scale)
def add_death_callback(self, callback):
"""
添加粒子死亡回调
Args:
callback (function): 回调函数
"""
if callback not in self.death_callbacks:
self.death_callbacks.append(callback)
def remove_death_callback(self, callback):
"""
移除粒子死亡回调
Args:
callback (function): 回调函数
"""
if callback in self.death_callbacks:
self.death_callbacks.remove(callback)
def add_collision_callback(self, callback):
"""
添加粒子碰撞回调
Args:
callback (function): 回调函数
"""
if callback not in self.collision_callbacks:
self.collision_callbacks.append(callback)
def remove_collision_callback(self, callback):
"""
移除粒子碰撞回调
Args:
callback (function): 回调函数
"""
if callback in self.collision_callbacks:
self.collision_callbacks.remove(callback)
def create_explosion(self, position, particle_count=50, explosion_force=100.0,
particle_radius=None, particle_lifetime=None, explosion_radius=5.0):
"""
创建爆炸效果
Args:
position (Point3): 爆炸位置
particle_count (int): 粒子数量
explosion_force (float): 爆炸力
particle_radius (float): 粒子半径
particle_lifetime (float): 粒子生命周期
explosion_radius (float): 爆炸半径
"""
if particle_radius is None:
particle_radius = self.particle_radius
if particle_lifetime is None:
particle_lifetime = self.particle_lifetime
for i in range(particle_count):
# 随机方向
direction = Vec3(
random.uniform(-1, 1),
random.uniform(-1, 1),
random.uniform(0, 1)
)
direction.normalize()
# 粒子速度
velocity = direction * random.uniform(explosion_force * 0.5, explosion_force)
# 粒子位置(在爆炸半径内)
offset = direction * random.uniform(0, explosion_radius)
particle_position = position + offset
# 创建粒子
particle_shape = BulletSphereShape(particle_radius * random.uniform(0.5, 2.0))
particle_body = BulletRigidBodyNode(f'ExplosionParticle_{self.emitted_count + i}')
particle_body.addShape(particle_shape)
particle_body.setMass(self.particle_mass * 0.5) # 爆炸粒子质量较小
# 设置碰撞属性
if self.collision_enabled:
particle_body.setCollideFilterGroup(self.collision_group)
particle_body.setCollideFilterMask(self.collision_mask)
# 创建粒子节点
from panda3d.core import NodePath
dummy_np = NodePath("dummy")
particle_node = dummy_np.attachNewNode(particle_body)
particle_node.setPos(particle_position)
# 添加到物理世界
self.physics_world.bullet_world.attachRigidBody(particle_body)
# 应用初始力
particle_body.setLinearVelocity(velocity)
# 创建粒子信息
particle_info = {
'node': particle_node,
'body': particle_body,
'shape': particle_shape,
'position': particle_position,
'velocity': velocity,
'lifetime': particle_lifetime * random.uniform(0.5, 1.5),
'age': 0.0,
'color': Vec3(1, random.uniform(0.5, 1), 0), # 橙色/红色
'size': particle_radius,
'mass': self.particle_mass * 0.5,
'radius': particle_radius,
'type': self.TYPE_EXPLOSION,
'material': self.particle_material,
'creation_time': time.time(),
'rotation': random.uniform(0, 360),
'rotation_speed': random.uniform(-10, 10),
'trail': [] if self.particle_trail_enabled else None,
'collisions': 0,
'sleeping': False,
'culled': False
}
self.particles.append(particle_info)
self.active_particles.append(particle_info)
self.emitted_count += 1
print(f"创建爆炸效果,位置: {position}, 粒子数: {particle_count}")
def create_fire_effect(self, position, particle_count=30, rise_speed=5.0,
particle_radius=None, particle_lifetime=None):
"""
创建火焰效果
Args:
position (Point3): 火焰位置
particle_count (int): 粒子数量
rise_speed (float): 上升速度
particle_radius (float): 粒子半径
particle_lifetime (float): 粒子生命周期
"""
if particle_radius is None:
particle_radius = self.particle_radius
if particle_lifetime is None:
particle_lifetime = self.particle_lifetime * 2.0 # 火焰粒子寿命更长
for i in range(particle_count):
# 粒子位置(在火焰底部附近)
offset = Vec3(
random.uniform(-0.5, 0.5),
random.uniform(-0.5, 0.5),
random.uniform(0, 1)
)
particle_position = position + offset
# 粒子速度(向上)
velocity = Vec3(
random.uniform(-0.5, 0.5),
random.uniform(-0.5, 0.5),
random.uniform(rise_speed * 0.8, rise_speed * 1.2)
)
# 创建粒子
particle_shape = BulletSphereShape(particle_radius * random.uniform(0.8, 1.5))
particle_body = BulletRigidBodyNode(f'FireParticle_{self.emitted_count + i}')
particle_body.addShape(particle_shape)
particle_body.setMass(self.particle_mass * 0.1) # 火焰粒子质量很小
# 设置物理属性(火焰粒子受重力影响较小)
particle_body.setFriction(0.1)
particle_body.setRestitution(0.1)
# 创建粒子节点
from panda3d.core import NodePath
dummy_np = NodePath("dummy")
particle_node = dummy_np.attachNewNode(particle_body)
particle_node.setPos(particle_position)
# 添加到物理世界
self.physics_world.bullet_world.attachRigidBody(particle_body)
# 应用初始速度
particle_body.setLinearVelocity(velocity)
# 创建粒子信息
particle_info = {
'node': particle_node,
'body': particle_body,
'shape': particle_shape,
'position': particle_position,
'velocity': velocity,
'lifetime': particle_lifetime * random.uniform(0.8, 1.2),
'age': 0.0,
'color': Vec3(1, random.uniform(0.3, 0.7), 0), # 黄色/橙色
'size': particle_radius,
'mass': self.particle_mass * 0.1,
'radius': particle_radius,
'type': self.TYPE_FIRE,
'material': self.particle_material,
'creation_time': time.time(),
'rotation': random.uniform(0, 360),
'rotation_speed': random.uniform(-5, 5),
'trail': [] if self.particle_trail_enabled else None,
'collisions': 0,
'sleeping': False,
'culled': False
}
self.particles.append(particle_info)
self.active_particles.append(particle_info)
self.emitted_count += 1
print(f"创建火焰效果,位置: {position}, 粒子数: {particle_count}")
def destroy(self):
"""
销毁粒子系统
"""
self.clear_particles()
self.death_callbacks.clear()
self.collision_callbacks.clear()
print("粒子系统已销毁")