aW1wb3J0IHB5Z2FtZQppbXBvcnQgbnVtcHkgYXMgbnAKaW1wb3J0IG1hdGgKCiMg5Yid5aeL5YyWUHlnYW1lCnB5Z2FtZS5pbml0KCkKd2lkdGgsIGhlaWdodCA9IDgwMCwgNjAwCnNjcmVlbiA9IHB5Z2FtZS5kaXNwbGF5LnNldF9tb2RlKCh3aWR0aCwgaGVpZ2h0KSkKcHlnYW1lLmRpc3BsYXkuc2V0X2NhcHRpb24oJnF1b3Q757KS5a2Q54ix5b+D5rGH6IGa5pWI5p6cJnF1b3Q7KQpjbG9jayA9IHB5Z2FtZS50aW1lLkNsb2NrKCkKZnBzID0gNjAKCiMg57KS5a2Q57G777yI5aKe5Yqg55Sf5ZG95ZGo5pyf5ZKM5Y+R5bCE5bGe5oCn77yJCmNsYXNzIFBhcnRpY2xlOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHRhcmdldF94LCB0YXJnZXRfeSwgdGFyZ2V0X3opOgogICAgICAgICMg5Yid5aeL5L2N572u77yI5LuO5bqV6YOo6ZqP5py65L2N572u5Y+R5bCE77yJCiAgICAgICAgc2VsZi5wb3MgPSBucC5hcnJheShbbnAucmFuZG9tLnVuaWZvcm0oLTUwLCA1MCksIDUwLCBucC5yYW5kb20udW5pZm9ybSgtMjAsIDIwKV0sIGR0eXBlPWZsb2F0KQogICAgICAgIHNlbGYudGFyZ2V0X3BvcyA9IG5wLmFycmF5KFt0YXJnZXRfeCwgdGFyZ2V0X3ksIHRhcmdldF96XSwgZHR5cGU9ZmxvYXQpICAjIOeIseW/g+ebruagh+S9jee9rgogICAgICAgIHNlbGYudmVsID0gbnAuYXJyYXkoWzAsIDAsIDBdLCBkdHlwZT1mbG9hdCkKICAgICAgICBzZWxmLmFjYyA9IG5wLmFycmF5KFswLCAwLCAwXSwgZHR5cGU9ZmxvYXQpCiAgICAgICAgc2VsZi5saWZlID0gMTAwICAjIOeykuWtkOeUn+WRveWRqOacn++8iOW4p+aVsO+8iQogICAgICAgIHNlbGYubWF4X2xpZmUgPSAxMDAKICAgICAgICBzZWxmLnNpemUgPSAyCgogICAgZGVmIHVwZGF0ZShzZWxmKToKICAgICAgICAjIOWQkeebruagh+S9jee9rueahOWQuOW8leWKmwogICAgICAgIGRpcl90b190YXJnZXQgPSBzZWxmLnRhcmdldF9wb3MgLSBzZWxmLnBvcwogICAgICAgIGF0dHJhY3RfZm9yY2UgPSBkaXJfdG9fdGFyZ2V0ICogMC4wMDggICMg5ZC45byV5Yqb5by65bqmCiAgICAgICAgc2VsZi5hcHBseV9mb3JjZShhdHRyYWN0X2ZvcmNlKQoKICAgICAgICAjIOmHjeWKm++8iOiuqeeykuWtkOS4i+iQvea2iOaVo++8iQogICAgICAgIGdyYXZpdHkgPSBucC5hcnJheShbMCwgMC4wNSwgMF0pIGlmIHNlbGYubGlmZSAmbHQ7IDIwIGVsc2UgbnAuYXJyYXkoWzAsIDAsIDBdKQogICAgICAgIHNlbGYuYXBwbHlfZm9yY2UoZ3Jhdml0eSkKCiAgICAgICAgIyDniannkIbmm7TmlrAKICAgICAgICBzZWxmLnZlbCArPSBzZWxmLmFjYwogICAgICAgIHNlbGYucG9zICs9IHNlbGYudmVsCiAgICAgICAgc2VsZi5hY2MgKj0gMAoKICAgICAgICAjIOeUn+WRveWRqOacn+mAkuWHjwogICAgICAgIHNlbGYubGlmZSAtPSAxCgogICAgZGVmIGFwcGx5X2ZvcmNlKHNlbGYsIGZvcmNlKToKICAgICAgICBzZWxmLmFjYyArPSBmb3JjZQoKICAgIGRlZiBnZXRfY29sb3Ioc2VsZik6CiAgICAgICAgIyDnspLlrZDpopzoibLpmo/nlJ/lkb3lkajmnJ/lj5jljJbvvIjok53pnZLoibLmuJDlj5jvvIkKICAgICAgICBhbHBoYSA9IHNlbGYubGlmZSAvIHNlbGYubWF4X2xpZmUKICAgICAgICBibHVlID0gaW50KDEwMCArIGFscGhhICogMTU1KQogICAgICAgIGN5YW4gPSBpbnQoMjAwICsgYWxwaGEgKiA1NSkKICAgICAgICByZXR1cm4gKDAsIGJsdWUsIGN5YW4pCgogICAgZGVmIGlzX2FsaXZlKHNlbGYpOgogICAgICAgIHJldHVybiBzZWxmLmxpZmUgJmd0OyAwCgojIOeUn+aIkOeIseW/g+ebruagh+S9jee9rgpkZWYgY3JlYXRlX2hlYXJ0X3RhcmdldHMobnVtX3BhcnRpY2xlcyk6CiAgICB0YXJnZXRzID0gW10KICAgIGZvciBfIGluIHJhbmdlKG51bV9wYXJ0aWNsZXMpOgogICAgICAgIHRoZXRhID0gbnAucmFuZG9tLnVuaWZvcm0oMCwgMiAqIG1hdGgucGkpCiAgICAgICAgciA9IDE2ICogbWF0aC5zaW4odGhldGEpICoqIDMKICAgICAgICB4ID0gciAqIG1hdGguY29zKHRoZXRhKQogICAgICAgIHkgPSAxMyAqIG1hdGguY29zKHRoZXRhKSAtIDUgKiBtYXRoLmNvcygyKnRoZXRhKSAtIDIgKiBtYXRoLmNvcygzKnRoZXRhKSAtIG1hdGguY29zKDQqdGhldGEpCiAgICAgICAgeiA9IG5wLnJhbmRvbS51bmlmb3JtKC01LCA1KQogICAgICAgIHRhcmdldHMuYXBwZW5kKCh4ICogOCwgeSAqIDgsIHogKiA4KSkgICMg57yp5pS+54ix5b+D5aSn5bCPCiAgICByZXR1cm4gdGFyZ2V0cwoKIyDliJ3lp4vljJbnm67moIfkvY3nva7lkoznspLlrZDmsaAKdGFyZ2V0cyA9IGNyZWF0ZV9oZWFydF90YXJnZXRzKDgwMCkgICMgODAw5Liq57KS5a2Q5pu057uG6IW7CnBhcnRpY2xlcyA9IFtdCnBhcnRpY2xlX2luZGV4ID0gMAoKIyAzROinhuinkuWPguaVsApjYW1lcmFfeiA9IDEyMApyb3RhdGlvbiA9IDAKCnJ1bm5pbmcgPSBUcnVlCndoaWxlIHJ1bm5pbmc6CiAgICAjIOS6i+S7tuWkhOeQhgogICAgZm9yIGV2ZW50IGluIHB5Z2FtZS5ldmVudC5nZXQoKToKICAgICAgICBpZiBldmVudC50eXBlID09IHB5Z2FtZS5RVUlUOgogICAgICAgICAgICBydW5uaW5nID0gRmFsc2UKCiAgICBzY3JlZW4uZmlsbCgoMCwgMCwgMCkpICAjIOm7keiJsuiDjOaZrwoKICAgICMg6YCQ5q2l55Sf5oiQ57KS5a2Q77yI5b6q546v5aSN55So55uu5qCH5L2N572u77yJCiAgICBpZiBwYXJ0aWNsZV9pbmRleCAmbHQ7IGxlbih0YXJnZXRzKSBhbmQgbGVuKHBhcnRpY2xlcykgJmx0OyA2MDA6CiAgICAgICAgdHgsIHR5LCB0eiA9IHRhcmdldHNbcGFydGljbGVfaW5kZXhdCiAgICAgICAgcGFydGljbGVzLmFwcGVuZChQYXJ0aWNsZSh0eCwgdHksIHR6KSkKICAgICAgICBwYXJ0aWNsZV9pbmRleCA9IChwYXJ0aWNsZV9pbmRleCArIDEpICUgbGVuKHRhcmdldHMpCgogICAgIyDml4vovazlj4LmlbDmm7TmlrAKICAgIHJvdGF0aW9uICs9IDAuMDA1CiAgICBjb3Nfcm90ID0gbWF0aC5jb3Mocm90YXRpb24pCiAgICBzaW5fcm90ID0gbWF0aC5zaW4ocm90YXRpb24pCgogICAgIyDmm7TmlrDlubbnu5jliLbnspLlrZAKICAgIGFsaXZlX3BhcnRpY2xlcyA9IFtdCiAgICBmb3IgcCBpbiBwYXJ0aWNsZXM6CiAgICAgICAgaWYgcC5pc19hbGl2ZSgpOgogICAgICAgICAgICBwLnVwZGF0ZSgpCgogICAgICAgICAgICAjIDNE5peL6L2s77yI57uVeei9tO+8iQogICAgICAgICAgICB4LCB5LCB6ID0gcC5wb3MKICAgICAgICAgICAgeF9yb3QgPSB4ICogY29zX3JvdCAtIHogKiBzaW5fcm90CiAgICAgICAgICAgIHpfcm90ID0geCAqIHNpbl9yb3QgKyB6ICogY29zX3JvdAoKICAgICAgICAgICAgIyDpgI/op4bmipXlvbEKICAgICAgICAgICAgZmFjdG9yID0gY2FtZXJhX3ogLyAoY2FtZXJhX3ogKyB6X3JvdCkKICAgICAgICAgICAgeF9zY3JlZW4gPSB3aWR0aCAvLyAyICsgeF9yb3QgKiBmYWN0b3IKICAgICAgICAgICAgeV9zY3JlZW4gPSBoZWlnaHQgLy8gMiArIHkgKiBmYWN0b3IKICAgICAgICAgICAgc2l6ZSA9IHAuc2l6ZSAqIGZhY3RvcgoKICAgICAgICAgICAgIyDnu5jliLbnspLlrZAKICAgICAgICAgICAgaWYgMCAmbHQ7IHhfc2NyZWVuICZsdDsgd2lkdGggYW5kIDAgJmx0OyB5X3NjcmVlbiAmbHQ7IGhlaWdodCBhbmQgZmFjdG9yICZndDsgMDoKICAgICAgICAgICAgICAgIGNvbG9yID0gcC5nZXRfY29sb3IoKQogICAgICAgICAgICAgICAgcHlnYW1lLmRyYXcuY2lyY2xlKHNjcmVlbiwgY29sb3IsIChpbnQoeF9zY3JlZW4pLCBpbnQoeV9zY3JlZW4pKSwgbWF4KDEsIGludChzaXplKSkpCiAgICAgICAgICAgIGFsaXZlX3BhcnRpY2xlcy5hcHBlbmQocCkKICAgIHBhcnRpY2xlcyA9IGFsaXZlX3BhcnRpY2xlcyAgIyDnp7vpmaTmrbvkuqHnspLlrZAKCiAgICBweWdhbWUuZGlzcGxheS5mbGlwKCkKICAgIGNsb2NrLnRpY2soZnBzKQoKcHlnYW1lLnF1aXQoKQo=
import pygame
import numpy as np
import math
# 初始化Pygame
pygame.init()
width, height = 800, 600
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("粒子爱心汇聚效果")
clock = pygame.time.Clock()
fps = 60
# 粒子类(增加生命周期和发射属性)
class Particle:
def __init__(self, target_x, target_y, target_z):
# 初始位置(从底部随机位置发射)
self.pos = np.array([np.random.uniform(-50, 50), 50, np.random.uniform(-20, 20)], dtype=float)
self.target_pos = np.array([target_x, target_y, target_z], dtype=float) # 爱心目标位置
self.vel = np.array([0, 0, 0], dtype=float)
self.acc = np.array([0, 0, 0], dtype=float)
self.life = 100 # 粒子生命周期(帧数)
self.max_life = 100
self.size = 2
def update(self):
# 向目标位置的吸引力
dir_to_target = self.target_pos - self.pos
attract_force = dir_to_target * 0.008 # 吸引力强度
self.apply_force(attract_force)
# 重力(让粒子下落消散)
gravity = np.array([0, 0.05, 0]) if self.life < 20 else np.array([0, 0, 0])
self.apply_force(gravity)
# 物理更新
self.vel += self.acc
self.pos += self.vel
self.acc *= 0
# 生命周期递减
self.life -= 1
def apply_force(self, force):
self.acc += force
def get_color(self):
# 粒子颜色随生命周期变化(蓝青色渐变)
alpha = self.life / self.max_life
blue = int(100 + alpha * 155)
cyan = int(200 + alpha * 55)
return (0, blue, cyan)
def is_alive(self):
return self.life > 0
# 生成爱心目标位置
def create_heart_targets(num_particles):
targets = []
for _ in range(num_particles):
theta = np.random.uniform(0, 2 * math.pi)
r = 16 * math.sin(theta) ** 3
x = r * math.cos(theta)
y = 13 * math.cos(theta) - 5 * math.cos(2*theta) - 2 * math.cos(3*theta) - math.cos(4*theta)
z = np.random.uniform(-5, 5)
targets.append((x * 8, y * 8, z * 8)) # 缩放爱心大小
return targets
# 初始化目标位置和粒子池
targets = create_heart_targets(800) # 800个粒子更细腻
particles = []
particle_index = 0
# 3D视角参数
camera_z = 120
rotation = 0
running = True
while running:
# 事件处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((0, 0, 0)) # 黑色背景
# 逐步生成粒子(循环复用目标位置)
if particle_index < len(targets) and len(particles) < 600:
tx, ty, tz = targets[particle_index]
particles.append(Particle(tx, ty, tz))
particle_index = (particle_index + 1) % len(targets)
# 旋转参数更新
rotation += 0.005
cos_rot = math.cos(rotation)
sin_rot = math.sin(rotation)
# 更新并绘制粒子
alive_particles = []
for p in particles:
if p.is_alive():
p.update()
# 3D旋转(绕y轴)
x, y, z = p.pos
x_rot = x * cos_rot - z * sin_rot
z_rot = x * sin_rot + z * cos_rot
# 透视投影
factor = camera_z / (camera_z + z_rot)
x_screen = width // 2 + x_rot * factor
y_screen = height // 2 + y * factor
size = p.size * factor
# 绘制粒子
if 0 < x_screen < width and 0 < y_screen < height and factor > 0:
color = p.get_color()
pygame.draw.circle(screen, color, (int(x_screen), int(y_screen)), max(1, int(size)))
alive_particles.append(p)
particles = alive_particles # 移除死亡粒子
pygame.display.flip()
clock.tick(fps)
pygame.quit()