0

I need to calculate a 2 ovals collision in a java swing mini game.

I have a JPanel which draws my ArrayList of balls Thread's and my player ball. In the run() method of my non player balls i check for a collision between the player ball and the balls Thread ArrayList.

The problem is my collision method is never executed. It's not even getting to my collision if statement. Just never calls the method.

Ball.java:

public class Ball extends Thread
{
    int x;
    int y;
    int velocity = 1;
    public int radius = 20;
    public boolean directionX = true; // True - right, false - left
    public boolean directionY = false; // True - up, false - down

    public static final int Y_STARTING_POSITION = 0;
    public static final int X_STARTING_POSITION = 0;

    public Ball()
    {   
        switch (this.getStartingSide())
        {
            case 0: // Left
            {
                this.directionX = true;
                this.directionY = true;

                this.x = this.getRandomHeight();
                this.y = this.getRandomWidth();

                break;
            }

            case 1: // Right
            {
                this.directionX = false;
                this.directionY = false;

                this.x = this.getRandomHeight();
                this.y = this.getRandomWidth();

                break;
            }

            case 2: // Top
            {
                this.directionX = true;
                this.directionY = false;

                this.x = this.getRandomWidth();
                this.y = this.getRandomHeight();

                break;
            }

            case 3: // Bottom
            {
                this.directionX = false;
                this.directionY = true;

                this.x = this.getRandomWidth();
                this.y = this.getRandomHeight();

                break;
            }

        }
    }


    public int getX() 
    {
        return this.x;
    }
    public void setX(int x) 
    {
        this.x = x;
    }
    public int getY()
    {
        return this.y;
    }
    public void setY(int y) 
    {
        this.y = y;
    }

    public void move()
    {
        if (this.directionX) // Right
        {
            this.x += this.velocity;
        }
        else // Left
        {
            this.x -= this.velocity;
        }
        if (this.directionY) // Up
        {
            this.y -= this.velocity;
        }
        else // Down
        {
            this.y += this.velocity;
        }
    }

    @Override
    public void run()
    {
        try
        {
            this.isCollision(); // Never called
            Thread.sleep(20);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    /**
     * Get a random number varies from 0 to the screen width.
     * 
     * @return The random number.
     * 
     */
    public int getRandomWidth()
    {
        Random random = new Random();
        return random.nextInt((Program.getPanelWidth() - 0) + 1) + 0; // Minimum = 0,maximum = Program.panelWidth
    }
    /**
     * Get a random number varies from 0 to the screen height.
     * 
     * @return The random number.
     * 
     */
    public int getRandomHeight()
    {
        Random random = new Random();
        return random.nextInt((Program.getPanelHeight() - 0) + 1) + 0; // Minimum = 0,maximum = Program.panelHeight
    }
    /**
     * Get the starting side of a ball.
     * 
     * Left - 0.
     * Right - 1.
     * Top - 2.
     * Bottom - 3.
     * 
     * @return
     * 
     */
    public int getStartingSide()
    {
        Random random = new Random();
        return random.nextInt((4 - 0) + 1) + 0; // Minimum = 0,maximum = 3
    }


    public void isCollision()
    {
        System.out.println("SSSSSSSSSSSSSSSS");
        if (Math.sqrt(Math.pow(MyPanel.playerX + MyPanel.playerRadius - this.getX() + this.radius,2)
                + Math.pow(MyPanel.playerY + MyPanel.playerRadius - this.getY() + this.radius,2))
                <= MyPanel.playerRadius + this.radius) // A collision
        {
            System.exit(0);
        }
    }


} // End of Ball class

MyPanel.java:

public class MyPanel extends JPanel implements KeyListener
{
    private static final long serialVersionUID = 1L;

    private static final Color BACKGROUND_COLOR = Color.WHITE; 
    private static final Color NPC_BALLS_COLOR = Color.RED;

    // The player is an oval
    public static int playerRadius = 35;

    public static int playerX;
    public static int playerY;

    // True - first player position, false - otherwise
    private boolean playerPosition = true;

    // Array of all the balls threads
    public static ArrayList<Ball> balls = new ArrayList<Ball>();


    public MyPanel()
    {
        this.setBackground(MyPanel.BACKGROUND_COLOR);
        this.setFocusable(true);
        this.addKeyListener(this);

        new Timer(10,new UpdateUI());
    }


    // Drawing

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        final double PANEL_WIDTH = this.getWidth();
        final double PANEL_HEIGHT = this.getHeight();

        if (this.playerPosition)
        {
            MyPanel.playerX = (int)(PANEL_WIDTH / 2 - MyPanel.playerRadius);
            MyPanel.playerY = (int)(PANEL_HEIGHT / 2 - MyPanel.playerRadius);
            this.playerPosition = false;
        }

        // Drawing the player
        g.setColor(Color.BLACK);
        g.fillOval(playerX,playerY, MyPanel.playerRadius * 2, MyPanel.playerRadius * 2);

        // Drawing npc's balls
        g.setColor(MyPanel.NPC_BALLS_COLOR);
        for (Ball ball: MyPanel.balls) // ConcurrentModificationException
        {
            if (ball.isAlive())
            {
                ball.start();
            }

            ball.move();
            repaint();
            g.fillOval(ball.getX(), ball.getY(), 
                    ball.radius * 2, ball.radius * 2);
        }

    }

    // Keyboard listeners

    @Override
    public void keyPressed(KeyEvent e) 
    {
        switch (e.getKeyCode())
        {
            case KeyEvent.VK_W: // Up
            {
                MyPanel.playerY -= 10;
                repaint();
                break;
            }
            case KeyEvent.VK_S: // Down
            {
                MyPanel.playerY += 10;
                repaint();
                break;
            }
            case KeyEvent.VK_D: // Right
            {
                MyPanel.playerX += 10;
                repaint();
                break;
            }
            case KeyEvent.VK_A: // Left
            {
                MyPanel.playerX -= 10;
                repaint();
                break;
            }
        }
    }

    @Override
    public void keyReleased(KeyEvent e) 
    {

    }

    @Override
    public void keyTyped(KeyEvent e) 
    {

    }

    public class UpdateUI implements ActionListener
    {

        @Override
        public void actionPerformed(ActionEvent e)
        {
            repaint();
        }

    }






} // End of MyPanel class

Program.java:

public class Program
{
    private static int panelWidth;
    private static int panelHeight;

    public static void main(String[] args) 
    {
        MyFrame frame = new MyFrame();

        Program.panelWidth = frame.getWidth();
        Program.panelHeight = frame.getHeight();

        // Generate a ball each 2 seconds
        while (true)
        {
            try
            {
                MyPanel.balls.add(new Ball());


                Thread.sleep(500);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }

    } // End of main method

    public static int getPanelWidth() 
    {
        return Program.panelWidth;
    }


    public static int getPanelHeight() 
    {
        return Program.panelHeight;
    }




}

The JFrame is nothing special and just adds the JPanel to it and so.

So my isCollision() method is never called even if It's on my run() method of my Thread. How come?

God
  • 1,238
  • 2
  • 18
  • 45

3 Answers3

1

You need to call the repaint method in your while loop in the main method so run() can be called. You can do it with this:

panel.repaint():

Also, You must call the run method. You can do it in ball or in the panel.

Albert
  • 337
  • 1
  • 10
1

ball.isAlive() always return false bucause your thread has not been started. If you want to start your threads in paintComponent() method you shouldn't use this method.

public class MyPanel extends JPanel implements KeyListener
{
    private static final long serialVersionUID = 1L;

    private static final Color BACKGROUND_COLOR = Color.WHITE; 
    private static final Color NPC_BALLS_COLOR = Color.RED;

    // The player is an oval
    public static int playerRadius = 35;

    public static int playerX;
    public static int playerY;

    // True - first player position, false - otherwise
    private boolean playerPosition = true;
    private boolean ballsStarted;

    // Array of all the balls threads
    public static ArrayList<Ball> balls = new ArrayList<Ball>();


    public MyPanel()
    {
        this.setBackground(MyPanel.BACKGROUND_COLOR);
        this.setFocusable(true);
        this.addKeyListener(this);

        new Timer(10,new UpdateUI());
    }


    // Drawing

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        final double PANEL_WIDTH = this.getWidth();
        final double PANEL_HEIGHT = this.getHeight();

        if (this.playerPosition)
        {
            MyPanel.playerX = (int)(PANEL_WIDTH / 2 - MyPanel.playerRadius);
            MyPanel.playerY = (int)(PANEL_HEIGHT / 2 - MyPanel.playerRadius);
            this.playerPosition = false;
        }

        // Drawing the player
        g.setColor(Color.BLACK);
        g.fillOval(playerX,playerY, MyPanel.playerRadius * 2, MyPanel.playerRadius * 2);

        // Drawing npc's balls
        g.setColor(MyPanel.NPC_BALLS_COLOR);
        for (Ball ball: MyPanel.balls) // ConcurrentModificationException
        {
            if (!ballsStarted)
            {
                ball.start();
            }

            ball.move();
            repaint();
            g.fillOval(ball.getX(), ball.getY(), 
                    ball.radius * 2, ball.radius * 2);
        }
        ballsStarted = true;
    }

    // Keyboard listeners

    @Override
    public void keyPressed(KeyEvent e) 
    {
        switch (e.getKeyCode())
        {
            case KeyEvent.VK_W: // Up
            {
                MyPanel.playerY -= 10;
                repaint();
                break;
            }
            case KeyEvent.VK_S: // Down
            {
                MyPanel.playerY += 10;
                repaint();
                break;
            }
            case KeyEvent.VK_D: // Right
            {
                MyPanel.playerX += 10;
                repaint();
                break;
            }
            case KeyEvent.VK_A: // Left
            {
                MyPanel.playerX -= 10;
                repaint();
                break;
            }
        }
    }

    @Override
    public void keyReleased(KeyEvent e) 
    {

    }

    @Override
    public void keyTyped(KeyEvent e) 
    {

    }

    public class UpdateUI implements ActionListener
    {

        @Override
        public void actionPerformed(ActionEvent e)
        {
            repaint();
        }

    }






}
Sergiy Medvynskyy
  • 11,160
  • 1
  • 32
  • 48
1

You never started the Thread implemented by your Ball class.

The ConcurrentModificationException is due to the fact that you are adding balls to an ArrayList which is by nature a thread-unsafe datastructure if you don't synchronize it externally. Here you are adding in a thread while iterating the list in the EDT (Event Dispatch Thread). Either you synchronize on the list, or you use a thread-safe data structure (for iteration you still may need to lock the full list).

Have a look at Collections.synchronizedList or CopyOnWriteArrayList (the latter doesn't need synchronizing for iteration).

However, synchronization on each insertion and each iteration seems very inefficient to me, especially because you are aiming for a game which requires fast rendering and background processing. It may be sufficient for a basic game though.

As a sidenote: a better technique for a game would be to use double buffering: render the game graphics in a background thread e.g. on a BufferedImage, and when done, switch the two buffers so that the one you just drew is now displayed on screen, and the other one can be used again for drawing the next frame. You might need a synchronization checkpoint between frames. But this is slightly more advanced, so if you are just starting in Java, I wouldn't spend too much time on it and keep things simple.

Timmos
  • 3,215
  • 2
  • 32
  • 40
  • Which `Thread` safe data structures there is? And how do i synchronize an `ArrayList`? – God Apr 19 '16 at 07:03