繁体   English   中英

如何为 JComponent 添加视觉

[英]How to add vision to a JComponent

我有动态数量的 JComponents 在 JPanel 中移动。 我想让它们在另一个 Jcomponent 阻碍时停止。 我试图对 2d 光线投射进行搜索,不幸的是我不知道如何在我的代码中实现它。 Stackoferflow 对此有一些答案,但他们正在构建对撞机,我需要更类似于光线投射的东西。

继承人JPanel 代码Map.java:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Map extends JPanel implements ActionListener {
    final int windowWidth = 1300;
    final int windowHeight = 750;

    Map() {
        this.setPreferredSize(new Dimension(windowWidth, windowHeight));
        Timer timer = new Timer(3000, this);
        timer.start();
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        // painting map
    }

    // Update method of the frame
    @Override
    public void actionPerformed(ActionEvent e) {
        Car car = new Car();
        this.add(car);
        // For loop responds for data cleaning
        for(int i=0;i<this.getComponents().length;i++) {
            Car iCar = (Car) this.getComponents()[i];
            if(iCar.x >= 1400 || iCar.x <= -300 || iCar.y <= -300 || iCar.y >= 950) {
                this.remove(i);
            }
        }
        System.out.println(this.getComponentCount());
        this.updateUI();
    }
}

继承人 JComponent 代码Car.java:

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

public class Car extends JComponent implements ActionListener {
    private BufferedImage car;
    private final GeoPoint startPoint = GeoSides.pickGeoPoint();
    public int x = 0;
    public int y = 0;
    private int xVelocity;
    private int yVelocity;
    private final int imageX = 160;
    private final int imageY = 120;
    private double turnAngle = 0;
    Timer timer;

    // Initializing Car() object
    Car() {
        this.setPreferredSize(new Dimension(170, 130));
        this.timer = new Timer(1, this); // Setting up the timer for update method
        this.timer.start();
        try {
            Random randomize = new Random();
            car = ImageIO.read(new File("src/assets/" + (randomize.nextInt(10) + 1) + ".png")); // Getting a random image of car
        } catch (IOException e) {
            e.printStackTrace();
        }
        // Setting start position
    }

    // This method should be called when another car is in the way
    public void stopCar() {
        xVelocity = 0;
        yVelocity = 0;
    }

    // Method scales the image and rotate it if needed
    private void drawCar(Graphics2D render2D, double rotationAngle) {
        //Image scaling and rotating
    }

    // Inherited method from JComponent
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        this.setLocation(new Point(x, y));

        Graphics2D render2D  = (Graphics2D) g;
        drawCar(render2D, turnAngle);
    }

    // Frame update of car's x, y speed and turn angle
    @Override
    public void actionPerformed(ActionEvent e) {
        switch (startPoint){
            // Setting destination points
        }

        x += xVelocity;
        y += yVelocity;
        this.repaint();

    }
}
  1. 不要调用updateUI ,它没有做你认为它在做的事情。 要安排新的绘制过程,您应该在已更改的组件上调用repaint
  2. 我个人不会以这种方式使用组件。
  3. 您的“主循环”应该确保对“实体”的任何更改都不会导致冲突,如果发生冲突,则您需要采取适当的措施
  4. 调用this.setLocation(new Point(x, y)); 在您的paintComponent内部将不会导致您最终出现问题,因为在调用paintComponent之前实际上已经翻译了Graphics上下文(因此0x0是组件在其父组件中的位置)
  5. Swing Timer不能很好地扩展。 也就是说,拥有更多的Timer实际上会对性能产生部门影响。 最好有一个可以附加多个监听器的Timer ,或者更好的是,一个Timer和单个监听器充当“主循环”

“主循环”是什么意思?

“主循环”是一个常用术语(尤其是在游戏中,但 Swing 将其称为“事件调度线程”或“主偶数循环”)。 在上下文中,这将是Map类中TimerActionListener

这应该定期调用,它负责根据需要更新实体,执行碰撞检测,以任何其他有意义的方式更新状态并安排重绘。

例子

下面的例子简化了这个想法。 它使用单个“主循环”负责更新“实体”位置、执行边界和碰撞检测以及调度重绘。

请注意,这是一个明显简化的示例。 我个人会让Entity成为一个interface ,然后创建一个专用的Car类来实现,但这只是为了演示核心概念

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new MainPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class MainPane extends JPanel {

        private Timer ticker;

        private List<Entity> entities;

        public MainPane() {
            entities = new ArrayList<>(32);
            // For demonstration purposes
            int y = (200 - 20) / 2;

            entities.add(new Entity(0, y, 1, 0, Color.RED));
            entities.add(new Entity(400 - 20, y, -1, 0, Color.BLUE));
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 200);
        }

        @Override
        public void addNotify() {
            super.addNotify();
            if (ticker != null) {
                ticker.stop();
                ticker = null;
            }
            ticker = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    tickDidOccur();
                }
            });
            ticker.start();
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            if (ticker != null) {
                ticker.stop();
                ticker = null;
            }
        }

        // It's more efficent then creating a local variable each time
        private List<Entity> entitiesToBeRemoved = new ArrayList<>(32);

        protected void tickDidOccur() {
            entitiesToBeRemoved.clear();
            // So some choices.  You can either update ALL the enties and then
            // do the collision detection, or, you can do the collision detection
            // after each enity is been updated.

            Rectangle viewBounds = getBounds();
            for (Entity entity : entities) {
                Rectangle bounds = entity.peekNextPosition();
                if (!viewBounds.intersects(bounds)) {
                    entitiesToBeRemoved.add(entity);
                } else {
                    boolean conflict = false;
                    for (Entity other : entities) {
                        if (other == entity) {
                            continue;
                        }
                        // Please note, depending on the amount of change
                        // it's possible that the enities may be positioned
                        // further apart.  You will need to access this based
                        // on your needs.
                        // Bascially what this is going to do, is check to see
                        // if the next update can be perform or not, if there
                        // is a conflict, the entities are stopped and the
                        // current entities update is discarded
                        if (bounds.intersects(other.bounds)) {
                            other.stop();
                            entity.stop();
                            conflict = true;
                        }
                    }
                    if (!conflict) {
                        // Commit the next position
                        entity.update();
                    }
                }
                // This is more of a "long winded" bounds check, but as you
                // can see, we can simply make use of the functionality
                // Rectangle provides to do the same thing
                //if (bounds.x + bounds.width < 0 || bounds.y + bounds.height < 0) {
                //    entitiesToBeRemoved.add(entity);
                //} else if (bounds.x > getWidth() || bounds.y > getHeight()) {
                //    entitiesToBeRemoved.add(entity);
                //}
            }

            entities.removeAll(entitiesToBeRemoved);
            repaint();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            for (Entity entity : entities) {
                // Because I trust no one
                Graphics2D g2d = (Graphics2D) g.create();
                entity.paint(g2d);
                g2d.dispose();
            }
        }

    }

    public class Entity {

        private Rectangle bounds;
        private Point velocity;

        private Color color;

        public Entity(int x, int y, int xDelta, int yDelta, Color color) {
            bounds = new Rectangle(x, y, 20, 20);
            velocity = new Point(xDelta, yDelta);
            this.color = color;
        }

        public Rectangle getBounds() {
            return bounds;
        }

        public Color getColor() {
            return color;
        }

        public void stop() {
            velocity = new Point(0, 0);
        }

        public void start(int xDelta, int yDelta) {
            velocity = new Point(0, 0);
        }

        public Rectangle peekNextPosition() {
            Rectangle next = new Rectangle(bounds);
            int x = next.x + velocity.x;
            int y = next.y + velocity.y;

            next.setLocation(x, y);
            return next;
        }

        public Rectangle update() {
            int x = bounds.x + velocity.x;
            int y = bounds.y + velocity.y;

            bounds.setLocation(x, y);
            return bounds;
        }

        public void paint(Graphics2D g) {
            Graphics2D g2d = (Graphics2D) g.create();
            // Now what ever we do wont' have a cascading effect on what
            // ever comes next.  This is really helpful for when you need
            // to apply AffineTransformation
            g2d.setColor(getColor());
            g2d.fill(bounds);
            g2d.dispose();
        }
    }
}

为什么不使用基于“组件”的实体?

主要原因是,它们很“重”。 它们有很多核心功能围绕着它们,旨在完成其他工作,它们只是不适合这种工作。

组件已经有很多与之相关的位置/大小操作,这些操作通常由布局管理 API 管理,因此您可能(很容易)最终与它作斗争。

组件通常不太适合这种工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM