简体   繁体   English

具有键绑定的线程

[英]Threads with Key Bindings

I'm new to Java graphics and threads, and I'm trying to make a game (specifically, Pong). 我是Java图形和线程的新手,我正在尝试制作一个游戏(特别是Pong)。 The idea is that two people can play on the same keyboard (ie there are two paddles that are controlled through different keys). 这个想法是两个人可以在同一个键盘上演奏(即,有两个通过不同键控制的拨片)。 Currently, both players can't move their paddle at the same time. 目前,两个玩家都无法同时移动其球拍。

Is there a solution to this? 有针对这个的解决方法吗? Are separate threads the answer? 单独的线程是答案吗?

If possible, I'd like the paddles to be able to move (at least seemingly) at the same time. 如果可能的话,我希望这些拨片能够同时(至少在表面上)移动。

Update: It seems like using a Set<Integer> to store pressed keys is the best option. 更新:似乎使用Set<Integer>存储按下的键是最好的选择。 I've done that (and it works), but I'm wondering if any of this code is not on the Event Dispatching Thread (EDT) and if I need to use SwingUtilities.invokeLater(); 我已经做到了(并且可以正常工作),但是我想知道是否其中的任何代码都不在事件调度线程(EDT)上,是否需要使用SwingUtilities.invokeLater(); . Here's the necessary code: 这是必要的代码:

private Set<Integer> keysDown = Collections.synchronizedSet(new HashSet<Integer>());

public void keyPressed(KeyEvent e) 
{
    keysDown.add(e.getKeyCode());
}

public void keyReleased(KeyEvent e) 
{    
    keysDown.remove(e.getKeyCode());
}

public void updatePaddlePositions()
{
    if (keysDown.contains(KeyEvent.VK_W)) 
        paddleOne.move(-PADDLE_MOVE_INCREMENT);

    if (keysDown.contains(KeyEvent.VK_S))
        paddleOne.move(PADDLE_MOVE_INCREMENT);

    if (keysDown.contains(KeyEvent.VK_UP))
        paddleTwo.move(-PADDLE_MOVE_INCREMENT);

    if (keysDown.contains(KeyEvent.VK_DOWN))
        paddleTwo.move(PADDLE_MOVE_INCREMENT);

    try {
        Thread.sleep(DELAY);
    } catch (InterruptedException e) {
        System.out.println("You Interrupted the game!");
    }

    canvas.repaint();
}

Here's the paintComponent method of the canvas object: 这是canvas对象的paintComponent方法:

public void paintComponent(Graphics g) 
{
    super.paintComponent(g);
    paddleOne.paint(g);
    paddleTwo.paint(g);
    updatePaddlePositions(); // Does this need to be SwingUtilities.invokeLater(this)?
                             // And should updatePaddlePositions() be run() as a result?
}

And here's the paint method of the paddleOne and paddleTwo objects: 这是paddleOnepaddleTwo对象的paint方法:

    public void paint(Graphics g)
    {
        g.setColor(Color.BLACK);
        g.fillRect(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
    }

Feel free to comment on my design if anything pops out as "bad" to you. 如果发现任何问题对您不利,请随时对我的设计发表评论。 Lastly, what does Collections.synchronizedSet(new HashSet<Integer>()) mean/do? 最后, Collections.synchronizedSet(new HashSet<Integer>())是什么意思?

Update #2: It seems like key bindings are the way to go (even though they take more code, in this case). 更新#2:似乎键绑定是要走的路(即使在这种情况下,即使它们需要更多的代码)。 What make key bindings better? 什么使键绑定更好? Is it that they work separately from everything else (and don't need window focus like key listeners)? 它们是否与其他所有功能分开工作(并且不需要像键侦听器一样需要窗口焦点)?

Also, I understand that HashSet<Integer> is just a subclass of Set<Integer> , but what is the purpose of Collections.synchronizedSet(...) ? 另外,我知道HashSet<Integer>只是Set<Integer>的子类,但是Collections.synchronizedSet(...)的目的是什么? I'm assuming it has to do with threads, but I don't know why it's needed in this program (if it is at all). 我假设它与线程有关,但是我不知道为什么在程序中需要它(如果有的话)。

First off, use Swing KeyBinding s Also I see no real need for multi-threadung unless you want your game multi-threaded. 首先,使用Swing KeyBinding 。另外,除非您希望游戏是多线程的,否则我并不真正需要多线程。

To clarify the problem is you need to be able for 2 players to press different keys? 为了弄清楚问题,您需要让2个玩家按下不同的键吗?

If so: 如果是这样的话:

Solution: 解:

Simply use boolean s to flag whether or not a key is pressed down, you would than of course have to reset the flag when the key is released. 只需使用boolean s来标记是否按下了某个键,那么您当然必须在释放键时重置该标志。

In your game logic you would check the states if the booleans and act appropriately. 在您的游戏逻辑中,您将检查布尔值的状态并采取适当的措施。

See my below example (both paddles can move independently using W and S and UP and DOWN keys): 参见下面的示例(两个拨片都可以使用W和S以及UP和DOWN键独立移动):

在此处输入图片说明

public class GameLogic {

    public GameLogic() {
        initComponents();
    }

    private void initComponents() {
        JFrame frame = new JFrame("Game Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //create the gamepanel
        final GamePanel gp = new GamePanel(600, 500);

        //create panel to hold buttons to start pause and stop the game
        JPanel buttonPanel = new JPanel();
        buttonPanel.setOpaque(false);

        final JButton startButton = new JButton("Start");
        final JButton pauseButton = new JButton("Pause");
        final JButton stopButton = new JButton("Stop");

        pauseButton.setEnabled(false);
        stopButton.setEnabled(false);

        //add listeners to buttons (most of the actions - excuse the pun - takes palce here :)
        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                //clear enitites currently in array
                gp.clearEntities();


                ArrayList<BufferedImage> ballEntityImages = new ArrayList<>();
                ArrayList<Long> ballEntityTimings = new ArrayList<>();
                ballEntityImages.add(createColouredImage("white", 50, 50, true));
                ballEntityTimings.add(500l);

                Entity ballEntity = new Entity(gp.getWidth() / 2, gp.getHeight() / 2, ballEntityImages, ballEntityTimings);
                ballEntity.RIGHT = true;

                //create images for entities
                ArrayList<BufferedImage> advEntityImages = new ArrayList<>();
                ArrayList<Long> advEntityTimings = new ArrayList<>();
                advEntityImages.add(createColouredImage("orange", 10, 100, false));
                advEntityTimings.add(500l);
                advEntityImages.add(createColouredImage("blue", 10, 100, false));
                advEntityTimings.add(500l);

                //create entities
                AdvancedSpritesEntity player1Entity = new AdvancedSpritesEntity(0, 100, advEntityImages, advEntityTimings);


                ArrayList<BufferedImage> entityImages = new ArrayList<>();
                ArrayList<Long> entityTimings = new ArrayList<>();
                entityImages.add(createColouredImage("red", 10, 100, false));
                entityTimings.add(500l);//as its the only image it doesnt really matter what time we put we could use 0l
                //entityImages.add(createColouredImage("magenta", 100, 100));
                //entityTimings.add(500l); 

                Entity player2Entity = new Entity(gp.getWidth() - 10, 200, entityImages, entityTimings);


                gp.addEntity(player1Entity);
                gp.addEntity(player2Entity);//just a standing still Entity for testing
                gp.addEntity(ballEntity);//just a standing still Entity for testing

                //create Keybingings for gamepanel
                GameKeyBindings gameKeyBindings = new GameKeyBindings(gp, player1Entity, player2Entity);

                GamePanel.running.set(true);

                //start the game loop which will repaint the screen
                runGameLoop(gp);

                startButton.setEnabled(false);
                pauseButton.setEnabled(true);
                stopButton.setEnabled(true);
            }
        });

        pauseButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                boolean b = GamePanel.paused.get();

                GamePanel.paused.set(!b);//set it to the opposite of what it was i.e paused to unpaused and vice versa

                if (pauseButton.getText().equals("Pause")) {
                    pauseButton.setText("Un-pause");
                } else {
                    pauseButton.setText("Pause");
                }
            }
        });

        stopButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                GamePanel.running.set(false);
                GamePanel.paused.set(false);

                /* if we want enitites to be cleared and a blank panel shown
                 gp.clearEntities();
                 gp.repaint(); 
                 */
                if (!pauseButton.getText().equals("Pause")) {
                    pauseButton.setText("Pause");
                }
                startButton.setEnabled(true);
                pauseButton.setEnabled(false);
                stopButton.setEnabled(false);
            }
        });

        //add buttons to panel
        buttonPanel.add(startButton);
        buttonPanel.add(pauseButton);
        buttonPanel.add(stopButton);

        //add gamepanel to jframe and button panel
        frame.add(gp);
        frame.add(buttonPanel, BorderLayout.SOUTH);

        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

    }

    //Simply used for testing (to simulate sprites) can create different colored images
    public static BufferedImage createColouredImage(String color, int w, int h, boolean circular) {
        BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = img.createGraphics();
        switch (color.toLowerCase()) {
            case "green":
                g2.setColor(Color.GREEN);
                break;
            case "magenta":
                g2.setColor(Color.MAGENTA);
                break;
            case "red":
                g2.setColor(Color.RED);
                break;
            case "yellow":
                g2.setColor(Color.YELLOW);
                break;
            case "blue":
                g2.setColor(Color.BLUE);
                break;
            case "orange":
                g2.setColor(Color.ORANGE);
                break;
            case "cyan":
                g2.setColor(Color.CYAN);
                break;
            case "gray":
                g2.setColor(Color.GRAY);
                break;
            default:
                g2.setColor(Color.WHITE);
                break;
        }
        if (!circular) {
            g2.fillRect(0, 0, img.getWidth(), img.getHeight());
        } else {
            g2.fillOval(0, 0, img.getWidth(), img.getHeight());
        }
        g2.dispose();
        return img;
    }

    //Starts a new thread and runs the game loop in it.
    private void runGameLoop(final GamePanel gp) {
        Thread loop = new Thread(new Runnable() {
            @Override
            public void run() {
                gp.gameLoop();
            }
        });
        loop.start();
    }

    //Code starts here
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {

                try {//attempt to set look and feel to nimbus Java 7 and up
                    for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                        if ("Nimbus".equals(info.getName())) {
                            UIManager.setLookAndFeel(info.getClassName());
                            break;
                        }
                    }
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                GameLogic gameLogic = new GameLogic();
            }
        });
    }
}

class GameKeyBindings {

    public GameKeyBindings(JComponent gp, final Entity entity, final Entity entity2) {

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "W pressed");
        gp.getActionMap().put("W pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.UP = true;
            }
        });
        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "W released");
        gp.getActionMap().put("W released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.UP = false;
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "S pressed");
        gp.getActionMap().put("S pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.DOWN = true;
            }
        });
        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "S released");
        gp.getActionMap().put("S released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.DOWN = false;
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down pressed");
        gp.getActionMap().put("down pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity2.DOWN = true;
            }
        });
        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down released");
        gp.getActionMap().put("down released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity2.DOWN = false;
            }
        });
        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up pressed");
        gp.getActionMap().put("up pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity2.UP = true;
            }
        });
        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up released");
        gp.getActionMap().put("up released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity2.UP = false;
            }
        });
    }
}

class AdvancedSpritesEntity extends Entity {

    public AdvancedSpritesEntity(int x, int y, ArrayList<BufferedImage> images, ArrayList<Long> timings) {
        super(x, y, images, timings);
    }

    void setAnimation(ArrayList<BufferedImage> images, ArrayList<Long> timings) {
        reset();//reset variables of animator class
        setFrames(images, timings);//set new frames for animation
    }
}

class Entity extends Animator {

    private int speed = 5;
    public boolean UP = false, DOWN = false, LEFT = false, RIGHT = false, visible;
    private Rectangle2D.Double rect;

    public Entity(int x, int y, ArrayList<BufferedImage> images, ArrayList<Long> timings) {
        super(images, timings);
        UP = false;
        DOWN = false;
        LEFT = false;
        RIGHT = false;
        visible = true;
        rect = new Rectangle2D.Double(x, y, getCurrentImage().getWidth(), getCurrentImage().getHeight());
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public int getSpeed() {
        return speed;
    }

    public boolean isVisible() {
        return visible;
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    public HashSet<String> getMask(Entity e) {
        HashSet<String> mask = new HashSet<>();
        int pixel, a;
        BufferedImage bi = e.getCurrentImage();//gets the current image being shown
        for (int i = 0; i < bi.getWidth(); i++) { // for every (x,y) component in the given box, 
            for (int j = 0; j < bi.getHeight(); j++) {
                pixel = bi.getRGB(i, j); // get the RGB value of the pixel
                a = (pixel >> 24) & 0xff;
                if (a != 0) {  // if the alpha is not 0, it must be something other than transparent
                    mask.add((e.getX() + i) + "," + (e.getY() - j)); // add the absolute x and absolute y coordinates to our set
                }
            }
        }
        return mask;  //return our set
    }

    // Returns true if there is a collision between object a and object b   
    public boolean checkPerPixelCollision(Entity b) {
        // This method detects to see if the images overlap at all. If they do, collision is possible
        int ax1 = (int) getX();
        int ay1 = (int) getY();

        int ax2 = ax1 + (int) getWidth();
        int ay2 = ay1 + (int) getHeight();

        int bx1 = (int) b.getX();
        int by1 = (int) b.getY();

        int bx2 = bx1 + (int) b.getWidth();

        int by2 = by1 + (int) b.getHeight();

        if (by2 < ay1 || ay2 < by1 || bx2 < ax1 || ax2 < bx1) {
            return false; // Collision is impossible.
        } else { // Collision is possible.
            // get the masks for both images
            HashSet<String> maskPlayer1 = getMask(this);
            HashSet<String> maskPlayer2 = getMask(b);
            maskPlayer1.retainAll(maskPlayer2);  // Check to see if any pixels in maskPlayer2 are the same as those in maskPlayer1
            if (maskPlayer1.size() > 0) {  // if so, than there exists at least one pixel that is the same in both images, thus
                return true;
            }
        }
        return false;
    }

    public void move() {
        if (UP) {
            rect.y -= speed;
        }
        if (DOWN) {
            rect.y += speed;
        }
        if (LEFT) {
            rect.x -= speed;
        }
        if (RIGHT) {
            rect.x += speed;
        }
    }

    @Override
    public void update(long elapsedTime) {
        super.update(elapsedTime);
        getWidth();//set the rectangles height accordingly after image update
        getHeight();//set rectangles height accordingle after update
    }

    public boolean intersects(Entity e) {
        return rect.intersects(e.rect);
    }

    public double getX() {
        return rect.x;
    }

    public double getY() {
        return rect.y;
    }

    public double getWidth() {
        if (getCurrentImage() == null) {//there might be no image (which is unwanted ofcourse but  we must not get NPE so we check for null and return 0
            return rect.width = 0;
        }

        return rect.width = getCurrentImage().getWidth();
    }

    public double getHeight() {
        if (getCurrentImage() == null) {
            return rect.height = 0;
        }
        return rect.height = getCurrentImage().getHeight();
    }
}

class Animator {

    private ArrayList<BufferedImage> frames;
    private ArrayList<Long> timings;
    private int currIndex;
    private long animationTime;
    private long totalAnimationDuration;
    private AtomicBoolean done;//used to keep track if a single set of frames/ an animtion has finished its loop

    public Animator(ArrayList<BufferedImage> frames, ArrayList< Long> timings) {
        currIndex = 0;
        animationTime = 0;
        totalAnimationDuration = 0;
        done = new AtomicBoolean(false);
        this.frames = new ArrayList<>();
        this.timings = new ArrayList<>();
        setFrames(frames, timings);
    }

    public boolean isDone() {
        return done.get();
    }

    public void reset() {
        totalAnimationDuration = 0;
        done.getAndSet(false);
    }

    public void update(long elapsedTime) {
        if (frames.size() > 1) {
            animationTime += elapsedTime;
            if (animationTime >= totalAnimationDuration) {
                animationTime = animationTime % totalAnimationDuration;
                currIndex = 0;
                done.getAndSet(true);
            }
            while (animationTime > timings.get(currIndex)) {
                currIndex++;
            }
        }
    }

    public BufferedImage getCurrentImage() {
        if (frames.isEmpty()) {
            return null;
        } else {
            try {
                return frames.get(currIndex);
            } catch (Exception ex) {//images might have been altered so we reset the index and return first image of the new frames/animation 
                currIndex = 0;
                return frames.get(currIndex);
            }
        }
    }

    public void setFrames(ArrayList<BufferedImage> frames, ArrayList< Long> timings) {
        if (frames == null || timings == null) {//so that constructor super(null,null) cause this to throw NullPointerException
            return;
        }
        this.frames = frames;
        this.timings.clear();
        for (long animTime : timings) {
            totalAnimationDuration += animTime;
            this.timings.add(totalAnimationDuration);
        }
    }
}

class GamePanel extends JPanel {

    private final static RenderingHints textRenderHints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    private final static RenderingHints imageRenderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    private final static RenderingHints colorRenderHints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    private final static RenderingHints interpolationRenderHints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    private final static RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    public static AtomicBoolean running = new AtomicBoolean(false), paused = new AtomicBoolean(false);
    private int width, height, frameCount = 0, fps = 0;
    private ArrayList<Entity> entities = new ArrayList<>();
    private final Random random = new Random();

    public GamePanel(int w, int h) {
        super(true);//make sure double buffering is enabled
        setIgnoreRepaint(true);//mustnt repaint itself the gameloop will do that
        width = w;
        height = h;
    }

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

    public void addEntity(Entity e) {
        entities.add(e);
    }

    void clearEntities() {
        entities.clear();
    }

    //Only run this in another Thread!
    public void gameLoop() {
        //This value would probably be stored elsewhere.
        final double GAME_HERTZ = 30.0;
        //Calculate how many ns each frame should take for our target game hertz.
        final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
        //At the very most we will update the game this many times before a new render.
        //If you're worried about visual hitches more than perfect timing, set this to 1.
        final int MAX_UPDATES_BEFORE_RENDER = 5;
        //We will need the last update time.
        double lastUpdateTime = System.nanoTime();
        //Store the last time we rendered.
        double lastRenderTime = System.nanoTime();

        //If we are able to get as high as this FPS, don't render again.
        final double TARGET_FPS = 60;
        final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;

        //Simple way of finding FPS.
        int lastSecondTime = (int) (lastUpdateTime / 1000000000);
        //store the time we started this will be used for updating map and charcter animations
        long currTime = System.currentTimeMillis();

        while (running.get()) {

            if (!paused.get()) {

                double now = System.nanoTime();
                long elapsedTime = System.currentTimeMillis() - currTime;
                currTime += elapsedTime;
                int updateCount = 0;

                //Do as many game updates as we need to, potentially playing catchup.
                while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) {

                    updateGame(elapsedTime);//Update the entity movements and collision checks etc (all has to do with updating the games status i.e  call move() on Enitites)

                    lastUpdateTime += TIME_BETWEEN_UPDATES;
                    updateCount++;
                }

                //If for some reason an update takes forever, we don't want to do an insane number of catchups.
                //If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
                if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) {
                    lastUpdateTime = now - TIME_BETWEEN_UPDATES;
                }

                drawGame();//draw the game by invokeing repaint (which will call paintComponent) on this JPanel

                lastRenderTime = now;

                //Update the frames we got.
                int thisSecond = (int) (lastUpdateTime / 1000000000);

                if (thisSecond > lastSecondTime) {
                    //System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
                    fps = frameCount;
                    frameCount = 0;
                    lastSecondTime = thisSecond;
                }

                //Yield until it has been at least the target time between renders. This saves the CPU from hogging.
                while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
                    //allow the threading system to play threads that are waiting to run.
                    Thread.yield();

                    //This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
                    //You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
                    //FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
                    //On my OS(Windows 7 x64 intel i3) it does not allow for time to make enityt etc move thus all stands still
                    try {
                        Thread.sleep(1);
                    } catch (Exception e) {
                    }
                    now = System.nanoTime();
                }
            }
        }
        fps = 0;//no more running set fps to 0
    }

    private void drawGame() {
        //Both revalidate and repaint are thread-safe — you need not invoke them from the event-dispatching thread. http://docs.oracle.com/javase/tutorial/uiswing/layout/howLayoutWorks.html
        repaint();
    }

    private void updateGame(long elapsedTime) {
        updateEntityMovements(elapsedTime);
        checkForCollisions();
    }

    private void checkForCollisions() {
        if (entities.get(0).intersects(entities.get(2)) || entities.get(1).intersects(entities.get(2))) {
            if (entities.get(2).LEFT) {
                entities.get(2).RIGHT = true;
                entities.get(2).LEFT = false;
            } else {
                entities.get(2).LEFT = true;
                entities.get(2).RIGHT = false;

            }
            System.out.println("Intersecting");
        } /*
         //This is best used when images have transparent and non-transparent pixels (only detects pixel collisions of non-transparent pixels)
         if (entities.get(0).checkPerPixelCollision(entities.get(2)) | entities.get(1).checkPerPixelCollision(entities.get(2))) {
         if (entities.get(2).LEFT) {
         entities.get(2).RIGHT = true;
         entities.get(2).LEFT = false;
         } else {
         entities.get(2).LEFT = true;
         entities.get(2).RIGHT = false;

         }
         System.out.println("Intersecting");
         }
         */
    }

    private void updateEntityMovements(long elapsedTime) {
        for (Entity e : entities) {
            e.update(elapsedTime);
            e.move();
        }
    }

    @Override
    protected void paintComponent(Graphics grphcs) {
        super.paintComponent(grphcs);
        Graphics2D g2d = (Graphics2D) grphcs;

        applyRenderHints(g2d);
        drawBackground(g2d);
        drawEntitiesToScreen(g2d);
        drawFpsCounter(g2d);

        frameCount++;
    }

    public static void applyRenderHints(Graphics2D g2d) {
        g2d.setRenderingHints(textRenderHints);
        g2d.setRenderingHints(imageRenderHints);
        g2d.setRenderingHints(colorRenderHints);
        g2d.setRenderingHints(interpolationRenderHints);
        g2d.setRenderingHints(renderHints);
    }

    private void drawEntitiesToScreen(Graphics2D g2d) {
        for (Entity e : entities) {
            if (e.isVisible()) {
                g2d.drawImage(e.getCurrentImage(), (int) e.getX(), (int) e.getY(), null);
            }
        }
    }

    private void drawFpsCounter(Graphics2D g2d) {
        g2d.setColor(Color.WHITE);
        g2d.drawString("FPS: " + fps, 5, 10);
    }

    private void drawBackground(Graphics2D g2d) {
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, getWidth(), getHeight());

        //thanks to trashgod for lovely testing background :) http://stackoverflow.com/questions/3256269/jtextfields-on-top-of-active-drawing-on-jpanel-threading-problems/3256941#3256941
        g2d.setColor(Color.BLACK);
        for (int i = 0; i < 128; i++) {
            g2d.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));//random color
            g2d.drawLine(getWidth() / 2, getHeight() / 2, random.nextInt(getWidth()), random.nextInt(getHeight()));
        }
    }
}

This may be a legitimate use case for the low-level access afforded to a KeyListener . 对于提供给KeyListener的低级访问,这可能是合法的用例。 See How to Write a Key Listener and KeyEventDemo ; 请参见如何编写密钥侦听器KeyEventDemo ;请参见如何编写密钥侦听器 don't forget to requestFocusInWindow() . 不要忘了requestFocusInWindow() Also consider offering keyboard control for one player and mouse control for the other. 还可以考虑为一个播放器提供键盘控制,为另一个播放器提供鼠标控制。

Addendum: @David Kroukamp has adduced an appealing counter-example using key bindings. 附录:@David Kroukamp使用键绑定引用了一个有吸引力的反例 For reference, the example cited here illustrates one approach to managing key preferences. 作为参考, 此处引用的示例说明了一种管理键首选项的方法。

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

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