Java Eclipse游戏滞后太多

Java Eclipse游戏滞后太多,java,eclipse,Java,Eclipse,我正在youtube上制作游戏和教程。这是该频道的链接。我解释我所拥有的第一部分,以及为什么我拥有它,因为我知道这有助于填补你的空缺 链接到第1部分,然后观看其余部分@克里斯,这有助于解决问题,所以不要在帖子上打标记 我在测试第4部分的代码时注意到了这一点。在录制之前,游戏落后得很厉害。我有很多代码,任何帮助都是非常感谢的 游戏类别: import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.Actio

我正在youtube上制作游戏和教程。这是该频道的链接。我解释我所拥有的第一部分,以及为什么我拥有它,因为我知道这有助于填补你的空缺

链接到第1部分,然后观看其余部分@克里斯,这有助于解决问题,所以不要在帖子上打标记

我在测试第4部分的代码时注意到了这一点。在录制之前,游戏落后得很厉害。我有很多代码,任何帮助都是非常感谢的

游戏类别:

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;

@SuppressWarnings("serial")
public class Game extends JPanel implements ActionListener{

Timer mainTimer;

Paddle paddle;
Ball ball;
int blockCount = 16;
static ArrayList<Block> blocks = new ArrayList<Block>();

public Game() {

    setFocusable(true);

    paddle = new Paddle(250, 300);
    addKeyListener(new KeyAdapt(paddle));

    ball = new Ball(275, 280);

    mainTimer = new Timer(10, this);
    mainTimer.start();
}
public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2d = (Graphics2D) g;

    ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/background.png");
    g2d.drawImage(ic.getImage(), 0, 0, null);


    paddle.draw(g2d);
    ball.draw(g2d);
    for(int i = 0; i < blockCount; i++) {
        Block b = blocks.get(i);
        b.draw(g2d);
    }

}
@Override
public void actionPerformed(ActionEvent arg0) {
    paddle.update();
    ball.update();

    for(int i = 0; i < blocks.size(); i++) {
        Block b = blocks.get(i);
        b.update();
    }


    repaint();

    startGame();
}

public void addBlock(Block b) {
    blocks.add(b);
}

public static void removeBlock(Block b) {
    blocks.remove(b);
}

public static ArrayList<Block> getBlockList() {
    return blocks;
}

public void startGame() {

    for(int i = 0; i < blockCount; i++) {
        addBlock(new Block(i*60 + 7, 20));
        addBlock(new Block(i*60 + 7, 0));
    }

}
}
关键适应类:

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

public class KeyAdapt extends KeyAdapter{

    Paddle p;

    public KeyAdapt(Paddle paddle) {
        p = paddle;
    }

    public void keyPressed(KeyEvent e) {
        p.keyPressed(e);
    }

    public void keyReleased(KeyEvent e) {
        p.keyReleased(e);
    }
}
桨类:

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;

import javax.swing.ImageIcon;

public class Paddle {

    int velX;
    int speed = 3;
    static int x1, y1;
    public Paddle(int x1, int y1) {
        this.x1 = x1;
        this.y1 = y1;
    }

    public void update() {
        x1+=velX;
        checkCollisions();
    }

    public void draw(Graphics2D g2d) {
        g2d.drawImage(getPaddleImg(), x1, y1, null);
    }

    public static Image getPaddleImg() {
        ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/paddle.png");
        return ic.getImage();
    }

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

        if(key==KeyEvent.VK_D) {
            velX = speed;
        } else if(key==KeyEvent.VK_A){
            velX = -speed;
        }
    }

    public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();

        if(key==KeyEvent.VK_D) {
            velX = 0;
        } else if(key==KeyEvent.VK_A){
            velX = 0;
        }
    }

    public void checkCollisions() {
        if(getBounds().getX() + getBounds().getWidth() >= 500) {
            x1 = 440;
        } else if(getBounds().getX() <= 0) {
            x1 = 0;
        }
    }
    public static Rectangle getBounds() {
        return new Rectangle(x1, y1 - 1, getPaddleImg().getWidth(null), getPaddleImg().getHeight(null));
    }

}
我的桌面上还有一个名为Eclipse Game的文件夹,我在代码中引用了它


再一次,我明白这很重要,但是任何减少滞后的想法都是有帮助的。此外,观看教程的开头部分,查看关于制作我到目前为止完成的内容的链接,将有助于您了解代码的工作原理。游戏严重滞后,我不能玩。

有多个问题

第一,正如我在评论中已经提到的,您正在计时器操作侦听器中调用StartName:

@Override
public void actionPerformed(ActionEvent arg0) {
    paddle.update();
    ball.update();
    for(int i = 0; i < blocks.size(); i++) {
        Block b = blocks.get(i);
        b.update();
    }
    repaint();
    startGame();
}
另一个真正的大问题是,你一直在不断地重新加载图像。例如,请看以下代码段:

if(getBounds().intersects(b.getBounds()) && velX!=-speed) {
    velY=speed;
    velX =- speed;
    Game.removeBlock(b);
}
else if(getBounds().intersects(b.getBounds())) {
    velY=speed;
    velX = speed;
    Game.removeBlock(b);
}
这是对getBounds的4个调用,如果我们看一下:

return new Rectangle(x2, y2, getBlockImg().getWidth(null), getBlockImg().getHeight(null));
您正在加载2个图像,总共是每10ms加载4*2*blockCount图像,仅此一种方法。不要一直加载图像,请执行以下操作:

class GameResources {
    static Image ballImage;
    static Image paddleImage;
    static Image blockImage;

    // call GameResources.loadResources() at the
    // beginning of main() or something
    static void loadResources() {
        // load all 3 images once here and be done
        ballImage = ...;
        paddleImage = ...;
        blockImage = ...;
}
Iterator<Block> iter = Game.getBlockList().iterator();
while (it.hasNext()) {
    Block b = it.next();

    if(getBounds().intersects(b.getBounds()) && velX!=-speed) {
        velY=speed;
        velX =- speed;
        // safely removing
        it.remove();
    }
    else if(getBounds().intersects(b.getBounds())) {
        velY=speed;
        velX = speed;
        // safely removing
        it.remove();
    }
}
最后,在对列表进行迭代时,您遇到了从列表中删除项的问题,Ball.checkCollisions:

对于这样的简单迭代,您应该为每个迭代使用:

for(Block b : blocks) {
    b.draw(g2d);
}
在所有这些之后,游戏运行得非常顺利,除了一些我没有时间去解决的关键侦听器的问题。晚饭后我可能会再看一遍

编辑:

我注意到了很多其他的小事情,所以这里是我的评论修正了一点的程序

有些类不再是公共的,因为我把它们都放在一个源文件中

import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JOptionPane;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyAdapter;
import java.util.ArrayList;
import java.util.Iterator;
import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Dimension;
import java.net.URL;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.io.File;

public class BlockGame {
    public static void main(String[] args) {
        // Swing program should always begin on the Swing
        // thread with a call to invokeLater.
        // See https://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    // change this to
                    //           .loadImages();
                    GameResources.loadInternetImages();
                } catch (IOException x) {
                    x.printStackTrace();
                    return;
                }

                JFrame frame = new JFrame("Game");
//                frame.setSize(500, 400);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//                frame.add(new Game());
                // Instead of calling setSize on the JFrame
                // directly, set a preferred size on the game
                // panel, then call pack() on the JFrame
                Game game = new Game();
                game.setPreferredSize(new Dimension(500, 400));
                frame.add(game);
                frame.pack();
                frame.setResizable(false);
                frame.setVisible(true);
                // I started the game here instead
                // of in the game loop, so the panel
                // is visible and stuff beforehand.
                game.startGame();
            }
        });
    }
}

class Game extends JPanel implements ActionListener {
    Timer mainTimer;
    Paddle paddle;
    Ball ball;

    // I removed this because it's only ever
    // used by startGame.
//    int blockCount = 16;

    // I changed this to an instance variable
    // (not static) and passed the game in to
    // update so the game objects can access
    // it.
    ArrayList<Block> blocks = new ArrayList<Block>();

    public Game() {
        setFocusable(true);
        paddle = new Paddle(250, 300);
        addKeyListener(new KeyAdapt(paddle));
        ball = new Ball(275, 280);
        mainTimer = new Timer(10, this);
        // I moved this to the startGame() method
//        mainTimer.start();
    }

    // Swing programs should override paintComponent
    // instead of paint.
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        // You should create a copy instead of
        // directly using the graphics object which
        // the component uses.
        // This is so any changes you make to it
        // don't affect the Swing paint routines.
        Graphics2D g2d = (Graphics2D) g.create();

//        ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/background.png");
//        g2d.drawImage(ic.getImage(), 0, 0, null);

        // Painting static resource.
        g2d.drawImage(GameResources.backgroundImage, 0, 0, null);

        paddle.draw(g2d);
        ball.draw(g2d);

        // This loop will throw an out of bounds
        // exception once the first block is removed.
        //                 vvvvvvvvvv
//        for(int i = 0; i < blockCount; i++) {
//            Block b = blocks.get(i);
//            b.draw(g2d);
//        }

        // using for each
        for (Block b : blocks) {
            b.draw(g2d);
        }

        // Dispose the copied graphics when you're done.
        g2d.dispose();
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        paddle.update(this);
        ball.update(this);
//        for(int i = 0; i < blocks.size(); i++) {
//            Block b = blocks.get(i);
//            b.update();
//        }
        for (Block b : blocks) {
            b.update(this);
        }
        repaint();
        // I moved this to main
//        startGame();
    }

    public void addBlock(Block b) {
        blocks.add(b);
    }

    public void removeBlock(Block b) {
        blocks.remove(b);
    }

    public ArrayList<Block> getBlockList() {
        return blocks;
    }

    // I added this method so that the
    // ball can access the paddle without
    // static variables.
    public Paddle getPaddle() {
        return paddle;
    }

    public void startGame() {
        // So the method won't be called twice
        // and put the game in some unexpected
        // state.
        if (mainTimer.isRunning()) {
            throw new IllegalStateException("game already started");
        }

        int initialBlockCount = 16;
        for(int i = 0; i < initialBlockCount; i++) {
            addBlock(new Block(i*60 + 7, 20));
            addBlock(new Block(i*60 + 7, 0));
        }

        mainTimer.start();
    }
}

// Generally speaking you should use
// Swing key bindings now, instead of
// key listeners.
//
// Key listeners have problems with
// the focus system: Swing components
// only send out key events when they
// have the focus.
//
// Key bindings don't have this issue.
//
// You can set up key bindings so they
// trigger any time the key is pressed
// in the focused window.
//
// https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
//
class KeyAdapt extends KeyAdapter {
    Paddle p;
    public KeyAdapt(Paddle paddle) {
        p = paddle;
    }
    public void keyPressed(KeyEvent e) {
        p.keyPressed(e);
    }
    public void keyReleased(KeyEvent e) {
        p.keyReleased(e);
    }
}

class Paddle {
    int velX;
    int speed = 3;
    // I changed these from static
    // to instance variables.
    int x1, y1;

    // I added these variables to
    // help with the key listener
    // logic.
    boolean leftPressed, rightPressed;

    public Paddle(int x1, int y1) {
        this.x1 = x1;
        this.y1 = y1;
    }

    public void update(Game game) {
        x1 += velX;
        checkCollisions();
    }

    public void draw(Graphics2D g2d) {
        g2d.drawImage(GameResources.paddleImage, x1, y1, null);
    }

//    public static Image getPaddleImg() {
//        ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/paddle.png");
//        return ic.getImage();
//    }

    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        // This logic is a little more robust
        // because it handles cases where both
        // keys are being held at the same time.
        // Also see computeVelX().
        if (key == KeyEvent.VK_D) {
            leftPressed = true;
//            velX = speed;
        } else if (key == KeyEvent.VK_A) {
            rightPressed = true;
//            velX = -speed;
        }
        computeVelX();
    }

    public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();
        // This logic is a little more robust
        // because it handles cases where both
        // keys are being held at the same time.
        // Also see computeVelX().
        if (key == KeyEvent.VK_D) {
            leftPressed = false;
//            velX = 0;
        } else if (key == KeyEvent.VK_A) {
            rightPressed = false;
//            velX = 0;
        }
        computeVelX();
    }

    public void computeVelX() {
        // This way the keys will never
        // "stick". If both keys are
        // held at the same time, velX
        // is just 0 until one of the
        // keys is released.
        velX = 0;
        if (leftPressed) {
            velX += speed;
        }
        if (rightPressed) {
            velX -= speed;
        }
    }

    public void checkCollisions() {
        // I used a variable instead of calling
        // getBounds() repeatedly.
        Rectangle bounds = getBounds();

        if (bounds.getX() + bounds.getWidth() >= 500) {
            x1 = 440;
        } else if (bounds.getX() <= 0) {
            x1 = 0;
        }
    }

    // I change this from static to an instance method.
    public Rectangle getBounds() {
//        return new Rectangle(x1, y1 - 1, getPaddleImg().getWidth(null), getPaddleImg().getHeight(null));
        int width  = GameResources.paddleImage.getWidth(null);
        int height = GameResources.paddleImage.getHeight(null);
        return new Rectangle(x1, y1 - 1, width, height);
    }
}

class Ball {
    int velX;
    int velY;
    int speed = 3;
    int x, y;

    public Ball(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void update(Game game) {
        x += velX;
        y += velY;
        checkCollisions(game);
    }

    public void draw(Graphics2D g2d) {
//        g2d.drawImage(getBallImg(), x, y, null);
        g2d.drawImage(GameResources.ballImage, x, y, null);
    }

//    public Image getBallImg() {
//        ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/ball.png");
//        return ic.getImage();
//    }

    public void checkCollisions(Game game) {
        // Using an iterator instead of looping with size()
        // directly, because we want to remove items from
        // the list while iterating.
        // The problem with removing while iterating with
        // size() is that once you remove an element, the
        // list shifts all the other elements back by 1,
        // so on the next iteration of the loop you end
        // up skipping an item.
        // (Say you remove the element at index 5. Then
        // all the elements shift back, so that e.g. the
        // element at index 6 is now at index 5. The variable
        // i is incremented, so you end up skipping the element
        // that was at index 6 before the removal.
        Iterator<Block> iter = game.getBlockList().iterator();
        Rectangle bounds = getBounds();

        while (iter.hasNext()) {
            Block b = iter.next();
            Rectangle bBounds = b.getBounds();

            if (bounds.intersects(bBounds) && velX != -speed) {
                velY = speed;
                velX =- speed;
//                Game.removeBlock(b);
                iter.remove();
            } else if (bounds.intersects(bBounds)) {
                velY = speed;
                velX = speed;
//                Game.removeBlock(b);
                iter.remove();
            }
        }
        //
        Rectangle pBounds = game.getPaddle().getBounds();

        if (bounds.intersects(pBounds)) {
            velY = -speed;
        } else if (bounds.getY() <= 0 && velX != speed) {
            velY = speed;
            velX =- speed;
        } else if (bounds.getY() <= 0 && velX != -speed) {
            velY = speed;
            velX = speed;
        } else if (bounds.getY() >= 400) {
            JOptionPane.showMessageDialog(null, "You Lost!  :( ");
            System.exit(0);
        }

        if (bounds.getX() <= 0) {
            velX = speed;
        } else if(bounds.getX() >= 500 - bounds.getWidth()) {
            velX = -speed;
        }
    }

    public Rectangle getBounds() {
//        return new Rectangle(x, y, getBallImg().getWidth(null), getBallImg().getHeight(null));
        int width  = GameResources.ballImage.getWidth(null);
        int height = GameResources.ballImage.getHeight(null);
        return new Rectangle(x, y, width, height);
    }
}

class Block {
    int x2, y2;

    public Block(int x2, int y2) {
        this.x2 = x2;
        this.y2 = y2;
    }

    public void update(Game game) {
    }

    public void draw(Graphics2D g2d){
//        g2d.drawImage(getBlockImg(), x2, y2, null);
        g2d.drawImage(GameResources.blockImage, x2, y2, null);
    }

//    public static Image getBlockImg() {
//        ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/block.png");
//        return ic.getImage();
//    }

    public Rectangle getBounds() {
//        return new Rectangle(x2, y2, getBlockImg().getWidth(null), getBlockImg().getHeight(null));
        int width  = GameResources.blockImage.getWidth(null);
        int height = GameResources.blockImage.getHeight(null);
        return new Rectangle(x2, y2, width, height);
    }
}

class GameResources {
    public static Image backgroundImage;
    public static Image blockImage;
    public static Image ballImage;
    public static Image paddleImage;

    public static void loadImages() throws IOException {
        // Load images once here.
        // I didn't test this method since I don't have the images, but it
        // should work. ImageIO.read will give better error messages than
        // using ImageIcon. ImageIcon.getImage() will just return null if
        // there was a problem, which doesn't tell you what the problem
        // actually was.
        paddleImage =
            ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/paddle.png"));
        ballImage =
            ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/ball.png"));
        blockImage =
            ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/block.png"));
        backgroundImage =
            ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/background.png"));
    }

    public static void loadInternetImages() throws IOException {
        // These images are from
        // http://stackoverflow.com/questions/19209650/example-images-for-code-and-mark-up-qas
        paddleImage =
            ImageIO.read(new URL("http://i.stack.imgur.com/gYxHm.png"));
        ballImage =
            ImageIO.read(new URL("http://i.stack.imgur.com/gJmeJ.png"));
        blockImage =
            ImageIO.read(new URL("http://i.stack.imgur.com/F0JHK.png"));
        backgroundImage =
            ImageIO.read(new URL("http://i.stack.imgur.com/P59NF.png"));
    }
}

你在给我打电话;每次定时器触发时。除非我弄错了,否则每10毫秒增加32个块,所以每秒3200个块。我还看到了一些其他问题,比如在checkCollisions中,您在Game.getBlockList.size上循环,同时可能会从列表中删除项目,这可能会引发越界异常;在公共游戏{}结束时进入游戏类。这就大大停止了延迟。请参阅我的编辑以获得更完整的答案。我只是在代码中注释,而不是试图写散文,只是因为这样更容易。我希望这有帮助。
class GameResources {
    static Image ballImage;
    static Image paddleImage;
    static Image blockImage;

    // call GameResources.loadResources() at the
    // beginning of main() or something
    static void loadResources() {
        // load all 3 images once here and be done
        ballImage = ...;
        paddleImage = ...;
        blockImage = ...;
}
for(int i = 0; i < Game.getBlockList().size(); i++) {
    Block b = Game.getBlockList().get(i);
    if(getBounds().intersects(b.getBounds()) && velX!=-speed) {
        velY=speed;
        velX =- speed;
        // removeBlock changes blocks.size()
        Game.removeBlock(b);
    }
    else if(getBounds().intersects(b.getBounds())) {
        velY=speed;
        velX = speed;
        // removeBlock changes blocks.size()
        Game.removeBlock(b);
    }
}
Iterator<Block> iter = Game.getBlockList().iterator();
while (it.hasNext()) {
    Block b = it.next();

    if(getBounds().intersects(b.getBounds()) && velX!=-speed) {
        velY=speed;
        velX =- speed;
        // safely removing
        it.remove();
    }
    else if(getBounds().intersects(b.getBounds())) {
        velY=speed;
        velX = speed;
        // safely removing
        it.remove();
    }
}
//        using blockCount after possibly
//        removing items from the list
//                 vvvvvvvvvv
for(int i = 0; i < blockCount; i++) {
    Block b = blocks.get(i);
    b.draw(g2d);
}
for(Block b : blocks) {
    b.draw(g2d);
}
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JOptionPane;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyAdapter;
import java.util.ArrayList;
import java.util.Iterator;
import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Dimension;
import java.net.URL;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.io.File;

public class BlockGame {
    public static void main(String[] args) {
        // Swing program should always begin on the Swing
        // thread with a call to invokeLater.
        // See https://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    // change this to
                    //           .loadImages();
                    GameResources.loadInternetImages();
                } catch (IOException x) {
                    x.printStackTrace();
                    return;
                }

                JFrame frame = new JFrame("Game");
//                frame.setSize(500, 400);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//                frame.add(new Game());
                // Instead of calling setSize on the JFrame
                // directly, set a preferred size on the game
                // panel, then call pack() on the JFrame
                Game game = new Game();
                game.setPreferredSize(new Dimension(500, 400));
                frame.add(game);
                frame.pack();
                frame.setResizable(false);
                frame.setVisible(true);
                // I started the game here instead
                // of in the game loop, so the panel
                // is visible and stuff beforehand.
                game.startGame();
            }
        });
    }
}

class Game extends JPanel implements ActionListener {
    Timer mainTimer;
    Paddle paddle;
    Ball ball;

    // I removed this because it's only ever
    // used by startGame.
//    int blockCount = 16;

    // I changed this to an instance variable
    // (not static) and passed the game in to
    // update so the game objects can access
    // it.
    ArrayList<Block> blocks = new ArrayList<Block>();

    public Game() {
        setFocusable(true);
        paddle = new Paddle(250, 300);
        addKeyListener(new KeyAdapt(paddle));
        ball = new Ball(275, 280);
        mainTimer = new Timer(10, this);
        // I moved this to the startGame() method
//        mainTimer.start();
    }

    // Swing programs should override paintComponent
    // instead of paint.
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        // You should create a copy instead of
        // directly using the graphics object which
        // the component uses.
        // This is so any changes you make to it
        // don't affect the Swing paint routines.
        Graphics2D g2d = (Graphics2D) g.create();

//        ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/background.png");
//        g2d.drawImage(ic.getImage(), 0, 0, null);

        // Painting static resource.
        g2d.drawImage(GameResources.backgroundImage, 0, 0, null);

        paddle.draw(g2d);
        ball.draw(g2d);

        // This loop will throw an out of bounds
        // exception once the first block is removed.
        //                 vvvvvvvvvv
//        for(int i = 0; i < blockCount; i++) {
//            Block b = blocks.get(i);
//            b.draw(g2d);
//        }

        // using for each
        for (Block b : blocks) {
            b.draw(g2d);
        }

        // Dispose the copied graphics when you're done.
        g2d.dispose();
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        paddle.update(this);
        ball.update(this);
//        for(int i = 0; i < blocks.size(); i++) {
//            Block b = blocks.get(i);
//            b.update();
//        }
        for (Block b : blocks) {
            b.update(this);
        }
        repaint();
        // I moved this to main
//        startGame();
    }

    public void addBlock(Block b) {
        blocks.add(b);
    }

    public void removeBlock(Block b) {
        blocks.remove(b);
    }

    public ArrayList<Block> getBlockList() {
        return blocks;
    }

    // I added this method so that the
    // ball can access the paddle without
    // static variables.
    public Paddle getPaddle() {
        return paddle;
    }

    public void startGame() {
        // So the method won't be called twice
        // and put the game in some unexpected
        // state.
        if (mainTimer.isRunning()) {
            throw new IllegalStateException("game already started");
        }

        int initialBlockCount = 16;
        for(int i = 0; i < initialBlockCount; i++) {
            addBlock(new Block(i*60 + 7, 20));
            addBlock(new Block(i*60 + 7, 0));
        }

        mainTimer.start();
    }
}

// Generally speaking you should use
// Swing key bindings now, instead of
// key listeners.
//
// Key listeners have problems with
// the focus system: Swing components
// only send out key events when they
// have the focus.
//
// Key bindings don't have this issue.
//
// You can set up key bindings so they
// trigger any time the key is pressed
// in the focused window.
//
// https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
//
class KeyAdapt extends KeyAdapter {
    Paddle p;
    public KeyAdapt(Paddle paddle) {
        p = paddle;
    }
    public void keyPressed(KeyEvent e) {
        p.keyPressed(e);
    }
    public void keyReleased(KeyEvent e) {
        p.keyReleased(e);
    }
}

class Paddle {
    int velX;
    int speed = 3;
    // I changed these from static
    // to instance variables.
    int x1, y1;

    // I added these variables to
    // help with the key listener
    // logic.
    boolean leftPressed, rightPressed;

    public Paddle(int x1, int y1) {
        this.x1 = x1;
        this.y1 = y1;
    }

    public void update(Game game) {
        x1 += velX;
        checkCollisions();
    }

    public void draw(Graphics2D g2d) {
        g2d.drawImage(GameResources.paddleImage, x1, y1, null);
    }

//    public static Image getPaddleImg() {
//        ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/paddle.png");
//        return ic.getImage();
//    }

    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        // This logic is a little more robust
        // because it handles cases where both
        // keys are being held at the same time.
        // Also see computeVelX().
        if (key == KeyEvent.VK_D) {
            leftPressed = true;
//            velX = speed;
        } else if (key == KeyEvent.VK_A) {
            rightPressed = true;
//            velX = -speed;
        }
        computeVelX();
    }

    public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();
        // This logic is a little more robust
        // because it handles cases where both
        // keys are being held at the same time.
        // Also see computeVelX().
        if (key == KeyEvent.VK_D) {
            leftPressed = false;
//            velX = 0;
        } else if (key == KeyEvent.VK_A) {
            rightPressed = false;
//            velX = 0;
        }
        computeVelX();
    }

    public void computeVelX() {
        // This way the keys will never
        // "stick". If both keys are
        // held at the same time, velX
        // is just 0 until one of the
        // keys is released.
        velX = 0;
        if (leftPressed) {
            velX += speed;
        }
        if (rightPressed) {
            velX -= speed;
        }
    }

    public void checkCollisions() {
        // I used a variable instead of calling
        // getBounds() repeatedly.
        Rectangle bounds = getBounds();

        if (bounds.getX() + bounds.getWidth() >= 500) {
            x1 = 440;
        } else if (bounds.getX() <= 0) {
            x1 = 0;
        }
    }

    // I change this from static to an instance method.
    public Rectangle getBounds() {
//        return new Rectangle(x1, y1 - 1, getPaddleImg().getWidth(null), getPaddleImg().getHeight(null));
        int width  = GameResources.paddleImage.getWidth(null);
        int height = GameResources.paddleImage.getHeight(null);
        return new Rectangle(x1, y1 - 1, width, height);
    }
}

class Ball {
    int velX;
    int velY;
    int speed = 3;
    int x, y;

    public Ball(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void update(Game game) {
        x += velX;
        y += velY;
        checkCollisions(game);
    }

    public void draw(Graphics2D g2d) {
//        g2d.drawImage(getBallImg(), x, y, null);
        g2d.drawImage(GameResources.ballImage, x, y, null);
    }

//    public Image getBallImg() {
//        ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/ball.png");
//        return ic.getImage();
//    }

    public void checkCollisions(Game game) {
        // Using an iterator instead of looping with size()
        // directly, because we want to remove items from
        // the list while iterating.
        // The problem with removing while iterating with
        // size() is that once you remove an element, the
        // list shifts all the other elements back by 1,
        // so on the next iteration of the loop you end
        // up skipping an item.
        // (Say you remove the element at index 5. Then
        // all the elements shift back, so that e.g. the
        // element at index 6 is now at index 5. The variable
        // i is incremented, so you end up skipping the element
        // that was at index 6 before the removal.
        Iterator<Block> iter = game.getBlockList().iterator();
        Rectangle bounds = getBounds();

        while (iter.hasNext()) {
            Block b = iter.next();
            Rectangle bBounds = b.getBounds();

            if (bounds.intersects(bBounds) && velX != -speed) {
                velY = speed;
                velX =- speed;
//                Game.removeBlock(b);
                iter.remove();
            } else if (bounds.intersects(bBounds)) {
                velY = speed;
                velX = speed;
//                Game.removeBlock(b);
                iter.remove();
            }
        }
        //
        Rectangle pBounds = game.getPaddle().getBounds();

        if (bounds.intersects(pBounds)) {
            velY = -speed;
        } else if (bounds.getY() <= 0 && velX != speed) {
            velY = speed;
            velX =- speed;
        } else if (bounds.getY() <= 0 && velX != -speed) {
            velY = speed;
            velX = speed;
        } else if (bounds.getY() >= 400) {
            JOptionPane.showMessageDialog(null, "You Lost!  :( ");
            System.exit(0);
        }

        if (bounds.getX() <= 0) {
            velX = speed;
        } else if(bounds.getX() >= 500 - bounds.getWidth()) {
            velX = -speed;
        }
    }

    public Rectangle getBounds() {
//        return new Rectangle(x, y, getBallImg().getWidth(null), getBallImg().getHeight(null));
        int width  = GameResources.ballImage.getWidth(null);
        int height = GameResources.ballImage.getHeight(null);
        return new Rectangle(x, y, width, height);
    }
}

class Block {
    int x2, y2;

    public Block(int x2, int y2) {
        this.x2 = x2;
        this.y2 = y2;
    }

    public void update(Game game) {
    }

    public void draw(Graphics2D g2d){
//        g2d.drawImage(getBlockImg(), x2, y2, null);
        g2d.drawImage(GameResources.blockImage, x2, y2, null);
    }

//    public static Image getBlockImg() {
//        ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/block.png");
//        return ic.getImage();
//    }

    public Rectangle getBounds() {
//        return new Rectangle(x2, y2, getBlockImg().getWidth(null), getBlockImg().getHeight(null));
        int width  = GameResources.blockImage.getWidth(null);
        int height = GameResources.blockImage.getHeight(null);
        return new Rectangle(x2, y2, width, height);
    }
}

class GameResources {
    public static Image backgroundImage;
    public static Image blockImage;
    public static Image ballImage;
    public static Image paddleImage;

    public static void loadImages() throws IOException {
        // Load images once here.
        // I didn't test this method since I don't have the images, but it
        // should work. ImageIO.read will give better error messages than
        // using ImageIcon. ImageIcon.getImage() will just return null if
        // there was a problem, which doesn't tell you what the problem
        // actually was.
        paddleImage =
            ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/paddle.png"));
        ballImage =
            ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/ball.png"));
        blockImage =
            ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/block.png"));
        backgroundImage =
            ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/background.png"));
    }

    public static void loadInternetImages() throws IOException {
        // These images are from
        // http://stackoverflow.com/questions/19209650/example-images-for-code-and-mark-up-qas
        paddleImage =
            ImageIO.read(new URL("http://i.stack.imgur.com/gYxHm.png"));
        ballImage =
            ImageIO.read(new URL("http://i.stack.imgur.com/gJmeJ.png"));
        blockImage =
            ImageIO.read(new URL("http://i.stack.imgur.com/F0JHK.png"));
        backgroundImage =
            ImageIO.read(new URL("http://i.stack.imgur.com/P59NF.png"));
    }
}