简体   繁体   English

如何为 JComponent 添加视觉

[英]How to add vision to a JComponent

I have a dynamic number of JComponents moving in a JPanel.我有动态数量的 JComponents 在 JPanel 中移动。 I want to make them stop when another Jcomponent is in it's way.我想让它们在另一个 Jcomponent 阻碍时停止。 I've tried to make a search about 2d raycasting, unfortunately i don't know how to implement it in my code.我试图对 2d 光线投射进行搜索,不幸的是我不知道如何在我的代码中实现它。 Stackoferflow have some answers for this but there they're building colliders, I need something more similar to raycasting. Stackoferflow 对此有一些答案,但他们正在构建对撞机,我需要更类似于光线投射的东西。

Heres the JPanel code Map.java:继承人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();
    }
}

Heres JComponent code Car.java:继承人 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. Don't call updateUI , it's not doing what you think it's doing.不要调用updateUI ,它没有做你认为它在做的事情。 To schedule a new paint pass, you should be calling repaint on the component which has changed.要安排新的绘制过程,您应该在已更改的组件上调用repaint
  2. I'd, personally, not use components this way.我个人不会以这种方式使用组件。
  3. Your "main loop" should be ensuring that any changes to a "entity" won't cause a collision, if they do, then you need to take appropriate action您的“主循环”应该确保对“实体”的任何更改都不会导致冲突,如果发生冲突,则您需要采取适当的措施
  4. Calling this.setLocation(new Point(x, y));调用this.setLocation(new Point(x, y)); inside your paintComponent is going to cause you no end up issues, as the Graphics context is actually translated (so that 0x0 is the location of the component within it's parent) before paintComponent is called在您的paintComponent内部将不会导致您最终出现问题,因为在调用paintComponent之前实际上已经翻译了Graphics上下文(因此0x0是组件在其父组件中的位置)
  5. Swing Timer does not scale well. Swing Timer不能很好地扩展。 That is, having more Timer s could actually have a departmental effect on performance.也就是说,拥有更多的Timer实际上会对性能产生部门影响。 Better to have a single Timer onto which you could attach multiple listeners, or better yet, a single Timer and single listener acting as the "main loop"最好有一个可以附加多个监听器的Timer ,或者更好的是,一个Timer和单个监听器充当“主循环”

What you mean by "main loop"? “主循环”是什么意思?

"Main loop" is a common term (especially in gaming, but Swing will call it the "Event Dispatching Thread" or "main even loop"). “主循环”是一个常用术语(尤其是在游戏中,但 Swing 将其称为“事件调度线程”或“主偶数循环”)。 In context to you, this would be the Timer 's ActionListener in your Map class.在上下文中,这将是Map类中TimerActionListener

This should be called on a regular bases, it is responsible for updating the entities, based on their needs, performing collision detection, updating the state in any other meaningful way and scheduling repaints.这应该定期调用,它负责根据需要更新实体,执行碰撞检测,以任何其他有意义的方式更新状态并安排重绘。

Example例子

The following example simplifies the idea.下面的例子简化了这个想法。 It uses a single "main loop" which is responsible for updating the "entities" position, performing bounds and collision detection and scheduling the repaints.它使用单个“主循环”负责更新“实体”位置、执行边界和碰撞检测以及调度重绘。

Please, beware that this is an overtly simplified example.请注意,这是一个明显简化的示例。 I would, personally, make Entity an interface and then make a dedicated Car class which implemented, but this is just to demonstrate the core concepts我个人会让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();
        }
    }
}

Why wouldn't use "component" based entities?为什么不使用基于“组件”的实体?

The main reasons are, they are "heavy".主要原因是,它们很“重”。 They have a lot of core functionality wrapped around them which is designed to do other work, they just aren't well designed for this kind of work.它们有很多核心功能围绕着它们,旨在完成其他工作,它们只是不适合这种工作。

Components already have a lot of location/size operations associated with, which is generally managed by the layout management API, so you could (all too easily) end up fighting that.组件已经有很多与之相关的位置/大小操作,这些操作通常由布局管理 API 管理,因此您可能(很容易)最终与它作斗争。

Components are, generally, not well suited to this kind of work.组件通常不太适合这种工作。

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

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