Java Swing — Key Input with JPanel added to JOptionpane

廉价感情. 提交于 2019-11-28 14:43:31
MadProgrammer

KeyListener is well known for having focus related issues. Not only does the component need to be focusable, but it has to have keyboard focus before it will register key events. This is an issue as it's all to easy for your component to lose focus for any number of reasons.

The solution is to use the key bindings API which was designed to help solve this issue. See How to Use Key Bindings for more details.

Swing uses a passive rendering approach. That is, updates occur when ever the API decides something needs to be updated. See Painting in AWT and Swing for more details

What you really need is some kind of active rendering approach, where updates are made on a regular bases. This can be accomplished in a verity of ways, the simplest would be to use a Swing Timer, as it's safe to update the UI (or values that the UI relies on) without the risk of introducing race conditions. See How to use Swing Timers for more details

So, this example basically abstracts the input into four basic movements, rotate left/right and up/down. The code doesn't care how those inputs are generated, only that they can be.

It then uses the key bindings API to register actions for the inputs. This is all done with a single Action class, which just adds the Input to a Set, which the Timer uses to determine what actions should be applied before requesting the UI be repainted.

The rotation is accomplished through the use of a AffineTransform. First we translate the origin to the sprites current x/y position, then rotate it around the center of the sprite. This reduces a lot of the complexity/issues related to rotating things (me simple)

AffineTransform at = AffineTransform.getTranslateInstance(xPos, yPos);
at.rotate(Math.toRadians(angle), sprite.getWidth() / 2, sprite.getHeight() / 2);
g2d.setTransform(at);
g2d.drawImage(sprite, 0, 0, this);

Just beware, a Graphics context is a shared resource, before making these type of changes, you need to make a copy of it (and dispose of it when you are finished)

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class FlyingPoniesWithGuns {

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

    public FlyingPoniesWithGuns() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }

                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public enum Input {
        ROTATE_LEFT,
        ROTATE_RIGHT,
        UP,
        DOWN
    }

    public class TestPane extends JPanel {

        private BufferedImage sprite;
        private double angle;
        private int xPos, yPos;
        private double xDelta, yDelta;

        private Set<Input> inputs;

        public TestPane() throws IOException {
            inputs = new HashSet<>(25);
            sprite = ImageIO.read(getClass().getResource("/Pony.png"));
            xPos = (400 - sprite.getWidth()) / 2;
            yPos = (400 - sprite.getHeight()) / 2;

            addKeyBinding("rotate-left", KeyEvent.VK_A, Input.ROTATE_LEFT);
            addKeyBinding("rotate-right", KeyEvent.VK_D, Input.ROTATE_RIGHT);

            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (inputs.contains(Input.ROTATE_LEFT)) {
                        angle -= 5;
                    } else if (inputs.contains(Input.ROTATE_RIGHT)) {
                        angle += 5;
                    }
                    repaint();
                }
            });
            timer.start();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

            AffineTransform at = AffineTransform.getTranslateInstance(xPos, yPos);
            at.rotate(Math.toRadians(angle), sprite.getWidth() / 2, sprite.getHeight() / 2);
            g2d.setTransform(at);
            g2d.drawImage(sprite, 0, 0, this);
            g2d.dispose();
        }

        protected void addKeyBinding(String name, int keyCode, Input input) {
            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();

            inputMap.put(KeyStroke.getKeyStroke(keyCode, 0, false), name + ".pressed");
            actionMap.put(name + ".pressed", new InputAction(input, true));

            inputMap.put(KeyStroke.getKeyStroke(keyCode, 0, true), name + ".released");
            actionMap.put(name + ".released", new InputAction(input, false));
        }

        protected class InputAction extends AbstractAction {

            private Input input;
            private boolean pressed;

            public InputAction(Input input, boolean pressed) {
                this.input = input;
                this.pressed = pressed;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                if (pressed) {
                    inputs.add(input);
                } else {
                    inputs.remove(input);
                }
            }

        }

    }

}

Okay, what about movement? You could have a look at How do I make an entity move in a direction? and How can I move a sprite in the direction it is facing? for more ideas on how you might be able to achieve that

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!