Draw on JPanel from other class

后端 未结 1 493
感情败类
感情败类 2021-01-27 22:29

In my program I try to paint on a JPanel when the mouse is pressed. The mousePressed method is just to test the painting from another class. Later on the spawn meth

相关标签:
1条回答
  • 2021-01-27 23:15

    Basically, you want to decouple your code and centralise the responsible to the classes. So the "data" should be maintained by a model of some kind, the rendering should be handle by some kind of view and the updates to the model and view should be handled by some kind of controller.

    This makes it easier to swap out any one part with out requiring a whole bunch of new code or other changes. It also means that each class has a defined domain of responsibility and discourages you from trying to, for example, make changes to state from within the view which should be handled by the model (which could put the state into disarray)

    Let's start with something that what's to be painted

    public interface Sprite {
    
        public void paint(Graphics2D g2d);
    
    }
    
    public interface MoveableSprite extends Sprite {
    
        public void update(Container container);
    
    }
    

    These represent either a static sprite (like a tree for example) or a sprite which is moving (and wants to be updated on a regular bases)

    These are contained within a model

    public interface GameModel {
    
        public List<Sprite> getSprites();
    
        public void setObserver(Observer<MoveableSprite> observer);
    
        public Observer<MoveableSprite> getObserver();
    
        public void spawnSprite();
    
    }
    

    Which provides some means by which it can notify (in this case, a single) interested party about some kind of state change. For this example, that means a new MoveableSprite has become available

    The Observer is pretty basic and just has a single call back...

    public interface Observer<T> {
    
        public void stateChanged(T parent);
    
    }
    

    And an "engine" to help drive it...

    public class GameEngine {
    
        private GameModel model;
        private SurfacePane surface;
        private Timer timer;
    
        public GameEngine(GameModel model, SurfacePane surface) {
            this.model = model;
            this.surface = surface;
    
            model.setObserver(new Observer<MoveableSprite>() {
                @Override
                public void stateChanged(MoveableSprite sprite) {
                    sprite.update(getSurface());
                }
            });
    
            timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    for (Sprite sprite : getModel().getSprites()) {
                        if (sprite instanceof MoveableSprite) {
                            ((MoveableSprite) sprite).update(getSurface());
                        }
                    }
                    getSurface().repaint();
                }
            });
        }
    
        public GameModel getModel() {
            return model;
        }
    
        public SurfacePane getSurface() {
            return surface;
        }
    
        public void start() {
            timer.start();
        }
    
        public void stop() {
            timer.stop();
        }
    
    }
    

    This is a pretty basic example, but basically, it updates the position of MoveableSprite and asks the surface to repaint itself. It's also observing the GameModel for any new sprites and it will update their position immediately, so they don't appear in some "weird" place

    Okay, now we actually need to implement some of this to make it work

    public class DefaultGameModel implements GameModel {
    
        private Observer<MoveableSprite> observer;
        private List<Sprite> sprites;
    
        public DefaultGameModel() {
            sprites = new ArrayList<>(25);
            for (int index = 0; index < 10; index++) {
                spawnSprite();
            }
        }
    
        @Override
        public List<Sprite> getSprites() {
            return Collections.unmodifiableList(sprites);
        }
    
        public void spawnSprite() {
            try {
                ZombieSprite sprite = new ZombieSprite();
                sprites.add(sprite);
    
                Observer<MoveableSprite> observer = getObserver();
                if (observer != null) {
                    observer.stateChanged(sprite);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void setObserver(Observer<MoveableSprite> observer) {
            this.observer = observer;
        }
    
        @Override
        public Observer<MoveableSprite> getObserver() {
            return observer;
        }
    
    }
    
    public class ZombieSprite implements MoveableSprite {
    
        private int x;
        private int y;
    
        private int xDelta;
        private int yDelta;
    
        private BufferedImage img;
    
        private Observer<Sprite> observer;
        private boolean initialised = false;
    
        public ZombieSprite() throws IOException {
            img = ImageIO.read(getClass().getResource("/LogoZombi.png"));
        }
    
        @Override
        public void update(Container container) {
            if (!initialised) {
                x = (int) (Math.random() * container.getWidth());
                y = (int) (Math.random() * container.getHeight());
    
                Random rnd = new Random();
                xDelta = rnd.nextBoolean() ? 1 : -1;
                yDelta = rnd.nextBoolean() ? 1 : -1;
                initialised = true;
            }
            x += xDelta;
            y += yDelta;
    
            if (x < 0) {
                x = 0;
                xDelta *= -1;
            } else if (x + img.getWidth() > container.getWidth()) {
                x = container.getWidth() - img.getWidth();
                xDelta *= -1;
            }
            if (y < 0) {
                y = 0;
                yDelta *= -1;
            } else if (y + img.getHeight() > container.getHeight()) {
                y = container.getHeight() - img.getHeight();
                yDelta *= -1;
            }
        }
    
        @Override
        public void paint(Graphics2D g2d) {
            g2d.drawImage(img, x, y, null);
        }
    
    }
    

    These two classes implement the GameModel and MoveableSprite interfaces. We use interfaces to decouple the code, which makes it easier to change the way in which things work and provides a jumping off point for agreed to contracts and exceptions of the implemenations

    And finally, something that actually paints the current state...

    public class SurfacePane extends JPanel {
    
        private GameModel model;
    
        public SurfacePane(GameModel model) {
            this.model = model;
    
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    getModel().spawnSprite();
                }
            });
        }
    
        public GameModel getModel() {
            return model;
        }
    
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            GameModel model = getModel();
            for (Sprite sprite : model.getSprites()) {
                sprite.paint(g2d);
            }
            g2d.dispose();
        }
    
    }
    

    You'll not that this class has the MouseListener, this is kind of deliberate, as other components which might be added to this container could prevent the MouseListener from been notified, so don't do that. But the MouseListener just calls the model to spawn another zombie...

    And finally, we need to plumb it altogether...

    GameModel model = new DefaultGameModel();
    SurfacePane surfacePane = new SurfacePane(model);
    GameEngine engine = new GameEngine(model, surfacePane);
    
    JFrame frame = new JFrame("Testing");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(surfacePane);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    
    engine.start();
    

    And just because I know that's a lot of disjointed concepts to put together, a complete example...

    import java.awt.Container;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Random;
    import javax.imageio.ImageIO;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.event.ChangeListener;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    GameModel model = new DefaultGameModel();
                    SurfacePane surfacePane = new SurfacePane(model);
                    GameEngine engine = new GameEngine(model, surfacePane);
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(surfacePane);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
    
                    engine.start();
                }
            });
        }
    
        public class SurfacePane extends JPanel {
    
            private GameModel model;
    
            public SurfacePane(GameModel model) {
                this.model = model;
    
                addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        getModel().spawnSprite();
                    }
                });
            }
    
            public GameModel getModel() {
                return model;
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                GameModel model = getModel();
                for (Sprite sprite : model.getSprites()) {
                    sprite.paint(g2d);
                }
                g2d.dispose();
            }
    
        }
    
        public class GameEngine {
    
            private GameModel model;
            private SurfacePane surface;
            private Timer timer;
    
            public GameEngine(GameModel model, SurfacePane surface) {
                this.model = model;
                this.surface = surface;
    
                model.setObserver(new Observer<MoveableSprite>() {
                    @Override
                    public void stateChanged(MoveableSprite sprite) {
                        sprite.update(getSurface());
                    }
                });
    
                timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        for (Sprite sprite : getModel().getSprites()) {
                            if (sprite instanceof MoveableSprite) {
                                ((MoveableSprite) sprite).update(getSurface());
                            }
                        }
                        getSurface().repaint();
                    }
                });
            }
    
            public GameModel getModel() {
                return model;
            }
    
            public SurfacePane getSurface() {
                return surface;
            }
    
            public void start() {
                timer.start();
            }
    
            public void stop() {
                timer.stop();
            }
    
        }
    
        public interface Observer<T> {
    
            public void stateChanged(T parent);
    
        }
    
        public interface Sprite {
    
            public void paint(Graphics2D g2d);
    
        }
    
        public interface MoveableSprite extends Sprite {
    
            public void update(Container container);
    
        }
    
        public interface GameModel {
    
            public List<Sprite> getSprites();
    
            public void setObserver(Observer<MoveableSprite> observer);
    
            public Observer<MoveableSprite> getObserver();
    
            public void spawnSprite();
    
        }
    
        public class DefaultGameModel implements GameModel {
    
            private Observer<MoveableSprite> observer;
            private List<Sprite> sprites;
    
            public DefaultGameModel() {
                sprites = new ArrayList<>(25);
                for (int index = 0; index < 10; index++) {
                    spawnSprite();
                }
            }
    
            @Override
            public List<Sprite> getSprites() {
                return Collections.unmodifiableList(sprites);
            }
    
            public void spawnSprite() {
                try {
                    ZombieSprite sprite = new ZombieSprite();
                    sprites.add(sprite);
    
                    Observer<MoveableSprite> observer = getObserver();
                    if (observer != null) {
                        observer.stateChanged(sprite);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void setObserver(Observer<MoveableSprite> observer) {
                this.observer = observer;
            }
    
            @Override
            public Observer<MoveableSprite> getObserver() {
                return observer;
            }
    
        }
    
        public class ZombieSprite implements MoveableSprite {
    
            private int x;
            private int y;
    
            private int xDelta;
            private int yDelta;
    
            private BufferedImage img;
    
            private Observer<Sprite> observer;
            private boolean initialised = false;
    
            public ZombieSprite() throws IOException {
                img = ImageIO.read(getClass().getResource("/LogoZombi.png"));
            }
    
            @Override
            public void update(Container container) {
                if (!initialised) {
                    x = (int) (Math.random() * container.getWidth());
                    y = (int) (Math.random() * container.getHeight());
    
                    Random rnd = new Random();
                    xDelta = rnd.nextBoolean() ? 1 : -1;
                    yDelta = rnd.nextBoolean() ? 1 : -1;
                    initialised = true;
                }
                x += xDelta;
                y += yDelta;
    
                if (x < 0) {
                    x = 0;
                    xDelta *= -1;
                } else if (x + img.getWidth() > container.getWidth()) {
                    x = container.getWidth() - img.getWidth();
                    xDelta *= -1;
                }
                if (y < 0) {
                    y = 0;
                    yDelta *= -1;
                } else if (y + img.getHeight() > container.getHeight()) {
                    y = container.getHeight() - img.getHeight();
                    yDelta *= -1;
                }
            }
    
            @Override
            public void paint(Graphics2D g2d) {
                g2d.drawImage(img, x, y, null);
            }
    
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题