Java 实现桌面烟花秀

前言

今天,我们将展示如何使用 Java Swing 创建一个烟花效果,覆盖整个桌面。我们将重点讲解如何在桌面上展示烟花、如何实现发射和爆炸效果,以及如何将这些效果整合到一个完整的程序中。

效果展示

如上图所示,我们在桌面实现了:

  1. 发射烟花
  2. 烟花爆炸的特效

完整代码

下面是整个项目的完整代码。大家可以新建一个名为 FireworkDisplay.java 的文件,将完整代码拷贝进去,运行 main() 方法,就可以在桌面看到一场烟花秀了。

java 复制代码
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));
        }
    }

    @Override
    protected 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的圆形
        }
    }

    @Override
    public 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,我们就能让窗口完全覆盖桌面。

java 复制代码
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 类来管理这些粒子。每个粒子都有位置、速度等属性,它们随着时间不断更新位置,模拟上升的过程。

java 复制代码
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 类来实现这些爆炸后的粒子。

java 复制代码
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,每个粒子的速度和方向都是随机的,这样可以让烟花的爆炸效果更加多样化。

结语

本期的分享就结束了,有兴趣的小伙伴可以思考一下如何实现更多有趣的特效哦。

相关推荐
重生之我在20年代敲代码12 分钟前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
爱上语文13 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people17 分钟前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
qmx_071 小时前
HTB-Jerry(tomcat war文件、msfvenom)
java·web安全·网络安全·tomcat
为风而战1 小时前
IIS+Ngnix+Tomcat 部署网站 用IIS实现反向代理
java·tomcat
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
技术无疆3 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
2401_858286113 小时前
52.【C语言】 字符函数和字符串函数(strcat函数)
c语言·开发语言
铁松溜达py3 小时前
编译器/工具链环境:GCC vs LLVM/Clang,MSVCRT vs UCRT
开发语言·网络
everyStudy3 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript