Java 实现桌面烟花秀
前言
今天,我们将展示如何使用 Java Swing 创建一个烟花效果,覆盖整个桌面。我们将重点讲解如何在桌面上展示烟花、如何实现发射和爆炸效果,以及如何将这些效果整合到一个完整的程序中。
效果展示
如上图所示,我们在桌面实现了:
- 发射烟花
- 烟花爆炸的特效
完整代码
下面是整个项目的完整代码。大家可以新建一个名为 FireworkDisplay.java
的文件,将完整代码拷贝进去,运行 main()
方法,就可以在桌面看到一场烟花秀了。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;// 发射粒子类
class LaunchParticle {public int x, y; // 位置public int vx, vy; // 速度public boolean exploded = false; // 是否已经爆炸public Color color; // 粒子颜色public int targetHeight; // 目标高度public LaunchParticle(int startX, int startY, int velocityX, int velocityY, int targetHeight, Color color) {this.x = startX;this.y = startY;this.vx = velocityX;this.vy = velocityY;this.targetHeight = targetHeight;this.color = color;}// 更新发射粒子的位置public void update() {x += vx;y += vy;vy += 1; // 模拟重力// 当粒子到达目标高度或者开始下落时爆炸if (y <= targetHeight || vy >= 0) {exploded = true;}}
}// 烟花爆炸粒子类
class FireworkParticle {public int x, y; // 位置public int vx, vy; // 速度public int life; // 粒子寿命public Color color; // 粒子颜色public FireworkParticle(int x, int y, int vx, int vy, int life, Color color) {this.x = x;this.y = y;this.vx = vx;this.vy = vy;this.life = life;this.color = color;}// 更新爆炸粒子的位置public void update() {x += vx;y += vy;vy += 1; // 模拟重力life--;}public boolean isDead() {return life <= 0;}
}// 显示烟花的面板
class FireworkPanel extends JPanel implements ActionListener {private final ArrayList<FireworkParticle> particles = new ArrayList<>();private final ArrayList<LaunchParticle> launchParticles = new ArrayList<>();private final Random random = new Random();private Timer timer;private int screenHeight;// 预定义一组颜色段,确保在任何背景下都好看private final Color[] colorPalette = {new Color(255, 128, 0), // 橙色new Color(255, 51, 51), // 红色new Color(51, 204, 255), // 天蓝色new Color(51, 255, 51), // 绿色new Color(204, 51, 255), // 紫色new Color(255, 255, 51), // 黄色new Color(255, 102, 178) // 粉色};public FireworkPanel(int screenHeight) {this.screenHeight = screenHeight;// 设置面板为透明this.setOpaque(false);timer = new Timer(30, this); // 每 30 毫秒更新一次timer.start();}// 选择随机颜色,从预定义的颜色段中选取private Color getRandomColor() {return colorPalette[random.nextInt(colorPalette.length)];}// 添加发射粒子public void addLaunchParticle() {int width = getWidth();int height = getHeight();if (width <= 0 || height <= 0) return;int startX = random.nextInt(width); // 发射起始位置int startY = height; // 从屏幕底部发射int targetHeight = (int) (screenHeight * 0.2); // 烟花将在屏幕高度的 4/5 处爆炸int velocityY = -(random.nextInt(10) + 50); // 增加初始向上速度,确保高度合适Color color = getRandomColor(); // 从预定义颜色中选取launchParticles.add(new LaunchParticle(startX, startY, 0, velocityY, targetHeight, color));}// 添加爆炸粒子,使用极坐标生成圆形分布,增加中心粒子密度public void addExplosion(int x, int y, Color color) {int numParticles = 200; // 每个烟花的粒子数量for (int i = 0; i < numParticles; i++) {double angle = 2 * Math.PI * random.nextDouble(); // 每个粒子的角度随机int speed = random.nextInt(15) + 5; // 粒子的速度增加随机性,靠近中心的速度会更小int vx = (int) (Math.cos(angle) * speed); // 使用极坐标计算x方向速度int vy = (int) (Math.sin(angle) * speed); // 使用极坐标计算y方向速度int life = random.nextInt(50) + 50; // 粒子寿命particles.add(new FireworkParticle(x, y, vx, vy, life, color));}}@Overrideprotected void paintComponent(Graphics g) {super.paintComponent(g);Graphics2D g2d = (Graphics2D) g;// 绘制发射粒子for (LaunchParticle launchParticle : launchParticles) {g2d.setColor(launchParticle.color);g2d.fillOval(launchParticle.x - 3, launchParticle.y - 3, 6, 10); // 缩小发射粒子尺寸,使用半径为3的圆形}// 绘制爆炸粒子for (FireworkParticle particle : particles) {g2d.setColor(new Color(particle.color.getRed(), particle.color.getGreen(), particle.color.getBlue(), Math.max(particle.life * 255 / 100, 0)));g2d.fillOval(particle.x - 3, particle.y - 3, 6, 6); // 缩小爆炸粒子尺寸,使用半径为3的圆形}}@Overridepublic void actionPerformed(ActionEvent e) {// 更新发射粒子ArrayList<LaunchParticle> toRemoveLaunch = new ArrayList<>();for (LaunchParticle launchParticle : launchParticles) {launchParticle.update();if (launchParticle.exploded) {addExplosion(launchParticle.x, launchParticle.y, launchParticle.color); // 发射粒子爆炸toRemoveLaunch.add(launchParticle);}}launchParticles.removeAll(toRemoveLaunch);// 更新爆炸粒子ArrayList<FireworkParticle> deadParticles = new ArrayList<>();for (FireworkParticle particle : particles) {particle.update();if (particle.isDead()) {deadParticles.add(particle);}}particles.removeAll(deadParticles);// 持续生成发射粒子,保证烟花不断生成if (random.nextInt(15) == 0) { // 增加生成频率addLaunchParticle(); // 随机生成多个烟花}// 重新绘制repaint();}
}public class FireworkDisplay {public static void main(String[] args) {// 获取屏幕大小Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();// 创建全屏的透明窗口JWindow window = new JWindow();window.setSize(screenSize);window.setBackground(new Color(0, 0, 0, 0)); // 设置背景透明window.setLayout(new BorderLayout()); // 使用 BorderLayout 布局// 添加烟花面板FireworkPanel panel = new FireworkPanel(screenSize.height); // 传递屏幕高度window.add(panel, BorderLayout.CENTER); // 确保 FireworkPanel 占满整个窗口window.setAlwaysOnTop(true); // 窗口始终在最前面window.setVisible(true);// 强制重绘刷新RepaintManager.currentManager(panel).markCompletelyDirty(panel);// 启动后立即生成发射粒子panel.addLaunchParticle();}
}
全屏透明窗口
为了让烟花在桌面上显示而不是局限在普通的应用程序窗口中,我们需要创建一个 全屏透明窗口。我们使用 JWindow
类,这是一个没有边框和标题栏的窗口,非常适合用于全屏特效展示。
为什么需要全屏透明窗口?
普通的窗口只会在应用程序区域内显示,而我们希望烟花能够在整个桌面上绽放。通过设置一个透明的 JWindow
,我们就能让窗口完全覆盖桌面。
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // 获取屏幕大小
JWindow window = new JWindow(); // 创建全屏窗口
window.setSize(screenSize); // 设置窗口为屏幕大小
window.setBackground(new Color(0, 0, 0, 0)); // 使窗口背景完全透明
window.setLayout(new BorderLayout());
window.setBackground(new Color(0, 0, 0, 0));
中的最后一个参数 0
表示完全透明。这样一来,窗口显示的部分就是我们后续的烟花效果,桌面背景不会被覆盖。
粒子系统
我们通过粒子的方式来模拟烟花的发射效果。首先,发射粒子从屏幕底部向上移动。我们创建 LaunchParticle
类来管理这些粒子。每个粒子都有位置、速度等属性,它们随着时间不断更新位置,模拟上升的过程。
class LaunchParticle {public int x, y; // 粒子的位置public int vx, vy; // 粒子的速度public boolean exploded = false; // 是否已爆炸public LaunchParticle(int startX, int startY, int velocityX, int velocityY) {this.x = startX;this.y = startY;this.vx = velocityX;this.vy = velocityY;}public void update() {this.x += this.vx; // 更新水平位置this.y += this.vy; // 更新垂直位置}
}
每个粒子都有初始的位置和速度,我们通过 update()
方法来不断改变它们的位置,形成粒子上升的效果。
当发射粒子上升到一定高度时,粒子就会爆炸。爆炸后,它们会分裂成多个小粒子,这些粒子四处扩散,就可以模拟出烟花绽放的效果。我们通过 FireworkParticle
类来实现这些爆炸后的粒子。
class FireworkParticle {public int x, y;public int vx, vy; // 爆炸粒子的速度public int life; // 粒子的生命值public FireworkParticle(int x, int y, int vx, int vy, int life) {this.x = x;this.y = y;this.vx = vx;this.vy = vy;this.life = life;}public void update() {this.x += this.vx; // 更新位置this.y += this.vy;this.life--; // 生命值减少}public boolean isDead() {return life <= 0; // 粒子是否存活}
}
在爆炸发生时,我们会生成多个 FireworkParticle
,每个粒子的速度和方向都是随机的,这样可以让烟花的爆炸效果更加多样化。
结语
本期的分享就结束了,有兴趣的小伙伴可以思考一下如何实现更多有趣的特效哦。