Java面向对象程序设计——随机小球动画

一、项目简介

创建一个窗口页面,点击即可生成一个随机大小、随机颜色、随机速度的小球,小球间会相互碰撞,碰撞之后将反弹,小球同样会与窗口边界发生碰撞。窗口内存在暂停和清除页面按钮,用于控制小球的运动状态,以及清空页面和存储的数据。也可通过单击或双击小球对单个小球实现暂停或清除功能。总而言之是一个简单的随机动画。

二、功能概述

同上,需要实现的操作就是暂停和清除,还有将小球数据存储在文件中,具体就是把数据用逗号分割成字段,再将字段存储在字符串数组中,进行保存,这样下次可以从中解析字段,在窗口中直接加载出已存储的小球。

三、代码体现

1.首先我要创建出一个窗口,最好是一个带有按钮的弹窗,这样方便下一步的添加按钮操作。我最初的设想只是设计一个窗口,再添加按钮,绘制小球。也许是绘制小球需要把原有的轨迹遮盖,导致我后续添加的按钮无法持续显示在窗口中,最后我放弃了原来的代码,使用了现在的框架。

新建一个类,命名MyStart


import javax.swing.SwingUtilities;

public class MyStart {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            ButtonFrame frame = new ButtonFrame();
            frame.setVisible(true);
        });
    }
}

这样使得后续的操作更方便,多线程也更好的提升了运行性能,减少出错。

还有就是学会使用Lambda表达式可以很好的简化代码。

2.现在有了一个最初始的窗口界面,就可以继续设计这个窗口的布局。定义一个ButtonFrame类,新建对象,先给出一个标题,确定窗口的尺寸,然后把窗口置于中间,这样方便观看。然后创建一个按钮面板,采用流式布局放到窗口中间,比较美观,而且在窗口上增添面板就可以解决按钮被覆盖的问题,就像在画板上放上画纸后再绘画。

新建出需要的按钮对象


import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

class ButtonFrame extends JFrame {
    private BallPanel panel;

    public ButtonFrame() {
        setTitle("随机小球动画");
        setSize(800, 600);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        panel = new BallPanel();
        add(panel);
        
        panel.loadShapes();
   
        JPanel buttonPanel = new JPanel(); // 创建按钮面板
        buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER)); // 设置布局

        // 暂停和继续按钮
        JButton pauseButton = new JButton("Pause/Restart");
        pauseButton.addActionListener(e -> panel.toggleAnimation());
        buttonPanel.add(pauseButton); // 将按钮添加到面板

        // 清空按钮
        JButton clearButton = new JButton("Clear Shapes");
        clearButton.addActionListener(e -> panel.clearShapes());
        buttonPanel.add(clearButton); // 将按钮添加到面板

        add(buttonPanel, BorderLayout.SOUTH); // 将按钮面板添加到框架

由于关闭窗口时需要保存数据,以便下次读取,即关闭窗口也是一个操作,所以设置窗口监听,调用后面BallPanel的saveShapes操作。

//      当关闭窗口时,保存图形形态,再次打开时恢复
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                panel.saveShapes();
            }
        });

    }

3.同样的,小球也用面板实现。我们先给出小球的定义,设计各个数据,如大小,速度,颜色等,以及运动状态和轨迹。我用了三种颜色的随机数值混合出小球颜色,稍微麻烦点,但是出来的颜色更好看,更柔和。小球运动会产生相应的碰撞,也体现在相应的代码中。

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;

import javax.print.attribute.standard.MediaSize.Other;

class BallShape implements Runnable {
	private BallPanel panel; // 引用panel
    private Point position;
    private int radius;
    private boolean paused;
    private Random random;
    private Color color;
    
    // 移动方向和速度字段
    private double dx, dy;
    private double angle;
    private double speed;

    public BallShape(BallPanel panel, Point initialPosition, int radius) {
    	this.panel = panel;
        this.position = initialPosition;
        this.random = new Random();
        if (radius <= 0)
        	this.radius = random.nextInt(20) + 10;
        else
        	this.radius = radius;
        
        paused = false;
        
        // 初始化移动方向和速度
        angle = random.nextDouble() * 2 * Math.PI;
        speed = 5 + random.nextDouble() * 5; // 速度在5到10之间
        dx = Math.cos(angle) * speed;
        dy = 1;//Math.sin(angle) * speed;
        int r = random.nextInt(256);
        int g = random.nextInt(256);
        int b = random.nextInt(256);
        this.color = new Color(r, g, b);
        
    }

    public void setPaused(boolean paused) {
        this.paused = paused;
    }

    public void draw(Graphics g) {
    	g.setColor(color);
    	g.fillOval(position.x - radius, position.y - radius, radius * 2, radius * 2);
                
    }

    public void setColor(Color color) {
		this.color = color;
	}
    
    @Override
    public void run() {
        while (true) {
            if (!paused) {
                // 移动逻辑
                position.x += dx;
               // position.y += dy;

                // 使形状沿着正弦轨迹移动
                 position.y += dy * Math.sin(position.x / 100.0) * 10;
                 
                 // 碰撞检测和响应
                 for (BallShape other : panel.getShapes()) {
                     if (this != other && this.collidesWith(other)) {
                         dx = -dx;
                         position.x += dx;
                         dy = -dy;
                         position.y += dy * Math.sin(position.x / 100.0) * 10;
                     }
                 }

                // 保持形状在面板内
                if (position.x < radius || position.x > panel.getWidth() - radius) {
                    dx = -dx;
                    position.x += dx;
                }
                if (position.y < radius || position.y > panel.getHeight() - radius) {
                    dy = -dy;
                    position.y += dy * Math.sin(position.x / 100.0) * 10;
                }
            }

            panel.repaint();

            try {
                Thread.sleep(50); // 控制动画速度
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public boolean isPaused() {
        return paused;
    }

    public boolean contains(Point p) {
        double distance = Math.sqrt(Math.pow(position.x - p.x, 2) + Math.pow(position.y - p.y, 2));
        return distance <= radius;
    }
    
    @Override
    public String toString() {

        return position.x + "," + position.y + "," + radius + "," + paused + "," + color.getRed() + "," + color.getGreen() + "," + color.getBlue();
    }
    
//    检测碰撞方法
    public boolean collidesWith(BallShape other) {
        double distance = position.distance(other.position);
        return distance < this.radius + other.radius;
    }

}

接下来设计的是小球面板,包括的操作有单击暂停小球和双击清除小球,以及小球数据的存储与读取。这两个方面由我的组员负责实现,因此在BallPanel中我只负责部分方法的重写。


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Scanner;
import java.util.concurrent.CopyOnWriteArrayList;
import java.awt.color.*;
import javax.swing.JPanel;
import javax.swing.plaf.multi.MultiInternalFrameUI;

class BallPanel extends JPanel {
    private CopyOnWriteArrayList<BallShape> shapes;
    private boolean paused;
    public BallPanel() {
        shapes = new CopyOnWriteArrayList<>();
        paused = false;
    //鼠标监听,双击时清除小球
        addMouseListener(new MouseAdapter() {
        	@Override
        	public void mouseClicked(MouseEvent e) {
        	    if (e.getClickCount() == 2) {
        	        removeShapeAt(e.getPoint());
        	    } else {
        	        for (BallShape shape : shapes) {
        	            if (shape.contains(e.getPoint())) {
        	                shape.setPaused(!shape.isPaused());
        	                repaint();
        	                return;
        	            }
        	        }
        	        addNewShape(e.getPoint());
        	    }
        	}

        });
    }
    
    public CopyOnWriteArrayList<BallShape> getShapes() {
        return shapes;
    }

    public void toggleAnimation() {
        paused = !paused;
        for (BallShape shape : shapes) {
            shape.setPaused(paused);
        }
    }

    private void addNewShape(Point point) {
        BallShape newShape = new BallShape(this, point, 0);
        shapes.add(newShape);
        new Thread(newShape).start();
    }

	private void removeShapeAt(Point point) {
	    shapes.removeIf(shape -> shape.contains(point));
	    repaint();
	}

    
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (BallShape shape : shapes) {
            shape.draw(g);
        }
    }
    
    public void saveShapes() {
        try (PrintWriter out = new PrintWriter("shapes.txt")) {
            for (BallShape shape : shapes) {
                out.println(shape.toString());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    public void loadShapes() {
        try (Scanner in = new Scanner(new File("shapes.txt"))) {
            shapes.clear();
        	while (in.hasNext()) {
        		String line = in.nextLine();
        		String[] parts = line.split(",");
        		
        		 int x = Integer.parseInt(parts[0]);
                 int y = Integer.parseInt(parts[1]);
                 int radius = Integer.parseInt(parts[2]);
                 boolean paused = Boolean.parseBoolean(parts[3]);
                 int r = Integer.parseInt(parts[4]);
                 int g = Integer.parseInt(parts[5]);
                 int b = Integer.parseInt(parts[6]);
                 Color color = new Color(r, g, b);
//                int x = in.nextInt();
//                int y = in.nextInt();
//                int radius = in.nextInt();
                BallShape shape = new BallShape(this, new Point(x, y), radius);
                shape.setPaused(paused);
                shape.setColor(color);
                shapes.add(shape);
                new Thread(shape).start();
            }
        } catch (FileNotFoundException e) {
            // 文件未找到,可以忽略或记录日志
        	 e.printStackTrace();
        }
    }

    public void clearShapes() {
        shapes.clear();
        repaint();
    }
}

以上就是所有代码 ,运行的时候可能要手动设置一下desktop。

成品如下:

虽然是简单的东西,但也花费了很大的精力去学习。

  • 26
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值