Java Swing — Key Input with JPanel added to JOptio

2020-02-07 10:51发布

When I run the code, the added Example1 class to the JOptionPane (in Frame) should get keyInput and then change the y value of the player instance (in example1), but it doesn't work. Also, how would I be able to rotate the ship on its axis and then move in the direction its facing? Currently it moves in the direction its rotated towards, but it rotates on what seems the coordinates 0,0.

Frame

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

/**
* Created by griffin on 12/7/2015.
*/
public class Frame extends JFrame {
    public Frame() {
        initUI();
}

private void initUI() {
    JTabbedPane jtp = new JTabbedPane();
    Example1 e1 = new Example1();
    Example2 e2 = new Example2();
    getContentPane().add(jtp);
    jtp.add(e1);
    jtp.add(e2);
    jtp.addTab("Example 1", e1);
    jtp.addTab("Example 2", e2);
    setResizable(false);
    pack();
    setTitle("NLTP");
    setLocationRelativeTo(null);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
            JFrame frame =  new Frame();
            frame.setVisible(true);
        }
    });
}
}

Example1

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;

/**
* Created by griffin on 12/7/2015.
*/
public class Example1 extends JPanel implements Runnable {

private final int B_WIDTH = 640;
private final int B_HEIGHT = 480;
private int DELAY = 25;
private Thread thread;

Ship player = new Ship(320, 240);

public Example1() {
    initScreen();
}

private void initScreen() {
    JButton explanation = new JButton("Explanation");
    explanation.setBounds(0, 0, 150, 30);
    this.setLayout(null);
    add(explanation);

    addKeyListener(new keyInput());
    setFocusable(true);

    setBackground(Color.BLACK);
    setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));
    setDoubleBuffered(true);
}

@Override
public void addNotify() {
    super.addNotify();

    thread = new Thread(this);
    thread.start();
}

public void paintComponent(Graphics g) {
    super.paintComponent(g);
    draw(g);
}

public void draw(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    AffineTransform old = g2d.getTransform();
    g2d.rotate(Math.toRadians(player.angle));
    g2d.drawImage(player.ship, player.x, player.y, null);
    g2d.setTransform(old);


    Toolkit.getDefaultToolkit().sync();
}


public void cycle() {
    player.y += player.vY;
}


@Override
public void run() {
    long beforeTime, timeDiff, sleep;

    beforeTime = System.currentTimeMillis();

    while (true) {


        cycle();
        repaint();

        timeDiff = System.currentTimeMillis() - beforeTime;
        sleep = DELAY - timeDiff;

        if (sleep < 0) {
            sleep = 2;
        }

        try {
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            System.out.println("Interrupted: " + e.getMessage());
        }

        beforeTime = System.currentTimeMillis();
    }
}

public class keyInput extends KeyAdapter {
    @Override
    public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();

        if(key == KeyEvent.VK_W) {
        }
    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    @Override
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();

        if (key == KeyEvent.VK_W) {
            player.vY -= 1;
        }

        if(key == KeyEvent.VK_A) {
            player.angle++;
        }

        if(key == KeyEvent.VK_D) {
            player.angle--;
        }
    }
}
}

1条回答
▲ chillily
2楼-- · 2020-02-07 11:29

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)

Ponies

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

查看更多
登录 后发表回答