- python版本 ```python import pygame import random import math vector = pygame.math.Vector2 gravity = vector(0, 0.3) DISPLAY_WIDTH = DISPLAY_HEIGHT = 800 trail_colours = [(45, 45, 45), (60, 60, 60), (75, 75, 75), (125, 125, 125), (150, 150, 150)] dynamic_offset = 1 static_offset = 3 MAX_FIREWORKS = 200 MAX_PARTICLES = 5000 class Firework: def __init__(self): self.colour = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) self.colours = ( (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)), (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)), (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))) self.firework = Particle(random.randint(0, DISPLAY_WIDTH), DISPLAY_HEIGHT, True, self.colour) self.exploded = False self.particles = [] self.min_max_particles = (100, 225) def update(self, win): if not self.exploded: self.firework.apply_force(gravity) self.firework.move() for tf in self.firework.trails: tf.show(win) self.show(win) if self.firework.vel.y >= 0: self.exploded = True self.explode() else: for particle in self.particles: particle.apply_force(vector(gravity.x + random.uniform(-1, 1) / 20, gravity.y / 2 + (random.randint(1, 8) / 100))) particle.move() for t in particle.trails: t.show(win) particle.show(win) def explode(self): amount = random.randint(*self.min_max_particles) for i in range(amount): self.particles.append(Particle(self.firework.pos.x, self.firework.pos.y, False, self.colours)) def show(self, win): pygame.draw.circle(win, self.colour, (int(self.firework.pos.x), int(self.firework.pos.y)), self.firework.size) def remove(self): if self.exploded: for p in self.particles: if p.remove is True: self.particles.remove(p) if len(self.particles) == 0: return True else: return False class Particle: def __init__(self, x, y, firework, colour): self.firework = firework self.pos = vector(x, y) self.origin = vector(x, y) self.radius = 20 self.remove = False self.explosion_radius = random.randint(5, 18) self.life = 0 self.acc = vector(0, 0) self.trails = [] self.prev_posx = [-10] * 10 self.prev_posy = [-10] * 10 if self.firework: self.vel = vector(0, -random.randint(17, 20)) self.size = 5 self.colour = colour for i in range(5): self.trails.append(Trail(i, self.size, True)) else: self.vel = vector(random.uniform(-1, 1), random.uniform(-1, 1)) self.vel.x *= random.randint(3, self.explosion_radius) self.vel.y *= random.randint(3, self.explosion_radius) self.size = random.randint(2, 4) self.colour = random.choice(colour) for i in range(5): self.trails.append(Trail(i, self.size, False)) def apply_force(self, force): self.acc += force def move(self): if not self.firework: self.vel.x *= 0.9 self.vel.y *= 0.9 self.vel += self.acc self.pos += self.vel self.acc *= 0 if self.life == 0 and not self.firework: distance = math.sqrt((self.pos.x - self.origin.x) ** 2 + (self.pos.y - self.origin.y) ** 2) if distance > self.explosion_radius: self.remove = True if self.vel.length() < 0.5: self.remove = True self.decay() self.trail_update() self.life += 1 def show(self, win): pygame.draw.circle(win, (self.colour[0], self.colour[1], self.colour[2], 0), (int(self.pos.x), int(self.pos.y)), self.size) def decay(self): if 50 > self.life > 10: ran = random.randint(0, 30) if ran == 0: self.remove = True elif self.life > 50: ran = random.randint(0, 5) if ran == 0: self.remove = True def trail_update(self): self.prev_posx.pop() self.prev_posx.insert(0, int(self.pos.x)) self.prev_posy.pop() self.prev_posy.insert(0, int(self.pos.y)) for n, t in enumerate(self.trails): if t.dynamic: t.get_pos(self.prev_posx[n + dynamic_offset], self.prev_posy[n + dynamic_offset]) else: t.get_pos(self.prev_posx[n + static_offset], self.prev_posy[n + static_offset]) class Trail: def __init__(self, n, size, dynamic): self.pos_in_line = n self.pos = vector(-10, -10) self.dynamic = dynamic if self.dynamic: self.colour = trail_colours[n] self.size = int(size - n / 2) else: self.colour = (255, 255, 200) self.size = size - 2 if self.size < 0: self.size = 0 def get_pos(self, x, y): self.pos = vector(x, y) def show(self, win): pygame.draw.circle(win, self.colour, (int(self.pos.x), int(self.pos.y)), self.size) def update(win, fireworks): to_remove = [] for fw in fireworks: fw.update(win) if fw.remove(): to_remove.append(fw) for fw in to_remove: fireworks.remove(fw) pygame.display.update() def main(): pygame.init() pygame.font.init() pygame.display.set_caption("HI") win = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT)) clock = pygame.time.Clock() fireworks = [Firework() for i in range(2)] running = True while running: clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_1: if len(fireworks) < MAX_FIREWORKS: fireworks.append(Firework()) if event.key == pygame.K_2: for i in range(10): if len(fireworks) < MAX_FIREWORKS: fireworks.append(Firework()) if event.key == pygame.K_3: for i in range(100): if len(fireworks) < MAX_FIREWORKS: fireworks.append(Firework()) win.fill((20, 20, 30)) if random.randint(0, 20) == 1 and len(fireworks) < MAX_FIREWORKS: fireworks.append(Firework()) total_particles = sum(len(fw.particles) for fw in fireworks) if total_particles > MAX_PARTICLES: for fw in fireworks: fw.particles = fw.particles[:MAX_PARTICLES // len(fireworks)] update(win, fireworks) pygame.quit() quit() if __name__ == '__main__': main() ``` - javascript版本 ```javascript const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); document.body.appendChild(canvas); const gravity = { x: 0, y: 0.3 }; const DISPLAY_WIDTH = 800; const DISPLAY_HEIGHT = 800; const trail_colours = [ [45, 45, 45], [60, 60, 60], [75, 75, 75], [125, 125, 125], [150, 150, 150] ]; const dynamic_offset = 1; const static_offset = 3; const MAX_FIREWORKS = 200; const MAX_PARTICLES = 5000; class Firework { constructor() { this.colour = [ Math.floor(Math.random() * 256), Math.floor(Math.random() * 256), Math.floor(Math.random() * 256) ]; this.colours = [ [ Math.floor(Math.random() * 256), Math.floor(Math.random() * 256), Math.floor(Math.random() * 256) ], [ Math.floor(Math.random() * 256), Math.floor(Math.random() * 256), Math.floor(Math.random() * 256) ], [ Math.floor(Math.random() * 256), Math.floor(Math.random() * 256), Math.floor(Math.random() * 256) ] ]; this.firework = new Particle( Math.floor(Math.random() * DISPLAY_WIDTH), DISPLAY_HEIGHT, true, this.colour ); this.exploded = false; this.particles = []; this.min_max_particles = [100, 225]; } update() { if (!this.exploded) { this.firework.apply_force(gravity); this.firework.move(); this.firework.show(); if (this.firework.vel.y >= 0) { this.exploded = true; this.explode(); } } else { for (const particle of this.particles) { particle.apply_force({ x: gravity.x + (Math.random() - 0.5) / 20, y: gravity.y / 2 + (Math.floor(Math.random() * 8) + 1) / 100 }); particle.move(); particle.show(); } } } explode() { const amount = Math.floor( Math.random() * (this.min_max_particles[1] - this.min_max_particles[0] + 1) + this.min_max_particles[0] ); for (let i = 0; i < amount; i++) { this.particles.push( new Particle(this.firework.pos.x, this.firework.pos.y, false, this.colours) ); } } remove() { if (this.exploded) { this.particles = this.particles.filter((p) => !p.remove); return this.particles.length === 0; } return false; } } class Particle { constructor(x, y, firework, colour) { this.firework = firework; this.pos = { x, y }; this.origin = { x, y }; this.radius = 20; this.remove = false; this.explosion_radius = Math.floor(Math.random() * 14) + 5; this.life = 0; this.acc = { x: 0, y: 0 }; this.trails = []; this.prev_posx = Array(10).fill(-10); this.prev_posy = Array(10).fill(-10); if (this.firework) { this.vel = { x: 0, y: -Math.floor(Math.random() * 4) - 17 }; this.size = 5; this.colour = colour; for (let i = 0; i < 5; i++) { this.trails.push(new Trail(i, this.size, true)); } } else { this.vel = { x: (Math.random() - 0.5) * 2 * Math.floor(Math.random() * this.explosion_radius), y: (Math.random() - 0.5) * 2 * Math.floor(Math.random() * this.explosion_radius) }; this.size = Math.floor(Math.random() * 3) + 2; this.colour = colour[Math.floor(Math.random() * colour.length)]; for (let i = 0; i < 5; i++) { this.trails.push(new Trail(i, this.size, false)); } } } apply_force(force) { this.acc.x += force.x; this.acc.y += force.y; } move() { if (!this.firework) { this.vel.x *= 0.9; this.vel.y *= 0.9; } this.vel.x += this.acc.x; this.vel.y += this.acc.y; this.pos.x += this.vel.x; this.pos.y += this.vel.y; this.acc.x = 0; this.acc.y = 0; if (this.life === 0 && !this.firework) { const distance = Math.sqrt( (this.pos.x - this.origin.x) ** 2 + (this.pos.y - this.origin.y) ** 2 ); if (distance > this.explosion_radius) { this.remove = true; } } if (Math.abs(this.vel.x) < 0.5 && Math.abs(this.vel.y) < 0.5) { this.remove = true; } this.decay(); this.trail_update(); this.life++; } show() { ctx.beginPath(); ctx.arc(this.pos.x, this.pos.y, this.size, 0, Math.PI * 2); ctx.fillStyle = `rgba(${this.colour[0]}, ${this.colour[1]}, ${this.colour[2]}, 1)`; ctx.fill(); } decay() { if (this.life > 10 && this.life < 50 && Math.random() < 0.03) { this.remove = true; } else if (this.life >= 50 && Math.random() < 0.2) { this.remove = true; } } trail_update() { this.prev_posx.pop(); this.prev_posx.unshift(this.pos.x); this.prev_posy.pop(); this.prev_posy.unshift(this.pos.y); for (let n = 0; n < this.trails.length; n++) { const t = this.trails[n]; if (t.dynamic) { t.get_pos(this.prev_posx[n + dynamic_offset], this.prev_posy[n + dynamic_offset]); } else { t.get_pos(this.prev_posx[n + static_offset], this.prev_posy[n + static_offset]); } } } } class Trail { constructor(n, size, dynamic) { this.pos_in_line = n; this.pos = { x: -10, y: -10 }; this.dynamic = dynamic; if (this.dynamic) { this.colour = `rgb(${trail_colours[n][0]}, ${trail_colours[n][1]}, ${trail_colours[n][2]})`; this.size = size - n / 2; } else { this.colour = 'rgb(255, 255, 200)'; this.size = size - 2; if (this.size < 0) { this.size = 0; } } } get_pos(x, y) { this.pos = { x, y }; } show() { ctx.beginPath(); ctx.arc(this.pos.x, this.pos.y, this.size, 0, Math.PI * 2); ctx.fillStyle = this.colour; ctx.fill(); } } function update(fireworks) { const to_remove = []; fireworks.forEach((fw) => { fw.update(); if (fw.remove()) { to_remove.push(fw); } }); to_remove.forEach((fw) => { const index = fireworks.indexOf(fw); fireworks.splice(index, 1); }); } function main() { canvas.width = DISPLAY_WIDTH; canvas.height = DISPLAY_HEIGHT; const fireworks = Array.from({ length: 2 }, () => new Firework()); setInterval(() => { if (fireworks.length < MAX_FIREWORKS && Math.random() < 0.05) { fireworks.push(new Firework()); } }, 100); setInterval(() => { if (fireworks.length < MAX_FIREWORKS && Math.random() < 0.1) { for (let i = 0; i < 10; i++) { if (fireworks.length < MAX_FIREWORKS) { fireworks.push(new Firework()); } } } }, 500); setInterval(() => { if (fireworks.length < MAX_FIREWORKS && Math.random() < 0.2) { for (let i = 0; i < 100; i++) { if (fireworks.length < MAX_FIREWORKS) { fireworks.push(new Firework()); } } } }, 1000); function animate() { ctx.fillStyle = 'rgba(20, 20, 30, 0.2)'; ctx.fillRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); update(fireworks); requestAnimationFrame(animate); } animate(); } main(); ```