Search code examples
javaswingappletphysicsjapplet

How to code the projectile of a ball of different force and angle in Java Swing?


I have written the following function for projectile motion of different force and angle, but it doesn't work properly. Where have I gone wrong? I want something like the Angry Birds game.

Code:

public void shootBall(int timeCounter){
    int gravity = 4;

    double time = timeCounter/40.0;
    int velocity = force_value;
    double radians = currentangle*Math.PI/180;
    ball.setX((int)((ball.getX()+10)*Math.cos(radians) + velocity*Math.cos(radians)*time));
    ball.setY((int)((ball.getY()+10)*Math.sin(radians) + velocity*Math.sin(radians)*time - 0.5*gravity*time*time));
    updateGame();
}

I want the ball to be thrown from bottom left corner.

enter image description here


Solution

  • As pointed out in the comment (and in the answer https://stackoverflow.com/a/21785385 ) : In order to achieve a "realistic" ballistic trajectory for the projectile, it is important to take the velocity into account - as well as the change of velocity for the given acceleration (based on the gravity force). Admittedly, I did not entirely understand what you wanted to achive with the sin/cos computation in your current position update. But I already had some SSCE here that was close to what you want to achieve, so I adapted it a little bit. Most of this is q&d-boilerplate code, but you might want to have a look at the Projectile class and how the velocity and position are updated in its performTimeStep method.

    BTW: This approach has the nice advantage that it can easily be extended to model something like wind: Just use a different acceleration. For example, not (0,-9.81) but (1,-9.81) to simulate a light wind from the left.

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.Point2D;
    
    import javax.swing.JButton;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JSlider;
    import javax.swing.SwingUtilities;
    
    public class ProjectileShooterTest
    {
        public static void main(String[] args)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {
                    createAndShowGUI();
                }
            });
        }
    
        private static void createAndShowGUI()
        {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setSize(600,600);
    
            final ProjectileShooter projectileShooter = 
                new ProjectileShooter();
            ProjectileShooterPanel projectileShooterPanel = 
                new ProjectileShooterPanel(projectileShooter);
            projectileShooter.setPaintingComponent(projectileShooterPanel);
    
            JPanel controlPanel = new JPanel(new GridLayout(1,0));
    
            controlPanel.add(new JLabel("Angle"));
            final JSlider angleSlider = new JSlider(0, 90, 45);
            controlPanel.add(angleSlider);
    
            controlPanel.add(new JLabel("Power"));
            final JSlider powerSlider = new JSlider(0, 100, 50);
            controlPanel.add(powerSlider);
    
            JButton shootButton = new JButton("Shoot");
            shootButton.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    int angleDeg = angleSlider.getValue();
                    int power = powerSlider.getValue();
                    projectileShooter.setAngle(Math.toRadians(angleDeg));
                    projectileShooter.setPower(power);
                    projectileShooter.shoot();
                }
            });
            controlPanel.add(shootButton);
    
            f.getContentPane().setLayout(new BorderLayout());
            f.getContentPane().add(controlPanel, BorderLayout.NORTH);
            f.getContentPane().add(projectileShooterPanel, BorderLayout.CENTER);
            f.setVisible(true);
        }
    }
    
    class ProjectileShooter
    {
        private double angleRad = Math.toRadians(45);
        private double power = 50;
        private Projectile projectile;
        private JComponent paintingComponent;
    
        void setPaintingComponent(JComponent paintingComponent)
        {
            this.paintingComponent = paintingComponent;
        }
    
        void setAngle(double angleRad)
        {
            this.angleRad = angleRad;
        }
    
        void setPower(double power)
        {
            this.power = power;
        }
    
        void shoot()
        {
            Thread t = new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    executeShot();
                }
            });
            t.setDaemon(true);
            t.start();
        }
    
        private void executeShot()
        {
            if (projectile != null)
            {
                return;
            }
            projectile = new Projectile();
    
            Point2D velocity = 
                AffineTransform.getRotateInstance(angleRad).
                    transform(new Point2D.Double(1,0), null);
            velocity.setLocation(
                velocity.getX() * power * 0.5, 
                velocity.getY() * power * 0.5);
            projectile.setVelocity(velocity);
    
            //System.out.println("Initial "+velocity);
    
            long prevTime = System.nanoTime();
            while (projectile.getPosition().getY() >= 0)
            {
                long currentTime = System.nanoTime();
                double dt = 3 * (currentTime - prevTime) / 1e8;
                projectile.performTimeStep(dt);
    
                prevTime = currentTime;
                paintingComponent.repaint();
                try
                {
                    Thread.sleep(10);
                }
                catch (InterruptedException e)
                {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
    
            projectile = null;
            paintingComponent.repaint();
        }
    
        Projectile getProjectile()
        {
            return projectile;
        }
    }
    
    class Projectile
    {
        private final Point2D ACCELERATION = new Point2D.Double(0, -9.81 * 0.1);
    
        private final Point2D position = new Point2D.Double();
        private final Point2D velocity = new Point2D.Double();
    
        public Point2D getPosition()
        {
            return new Point2D.Double(position.getX(), position.getY());
        }
        public void setPosition(Point2D point)
        {
            position.setLocation(point);
        }
    
        public void setVelocity(Point2D point)
        {
            velocity.setLocation(point);
        }
    
        void performTimeStep(double dt)
        {
            scaleAddAssign(velocity, dt, ACCELERATION);
            scaleAddAssign(position, dt, velocity);
    
            //System.out.println("Now at "+position+" with "+velocity);
        }
    
        private static void scaleAddAssign(
            Point2D result, double factor, Point2D addend)
        {
            double x = result.getX() + factor * addend.getX();
            double y = result.getY() + factor * addend.getY();
            result.setLocation(x, y);
        }
    
    }
    
    class ProjectileShooterPanel extends JPanel
    {
        private final ProjectileShooter projectileShooter;
    
        public ProjectileShooterPanel(ProjectileShooter projectileShooter)
        {
            this.projectileShooter = projectileShooter;
        }
    
        @Override
        protected void paintComponent(Graphics gr)
        {
            super.paintComponent(gr);
            Graphics2D g = (Graphics2D)gr;
    
            Projectile projectile = projectileShooter.getProjectile();
            if (projectile != null)
            {
                g.setColor(Color.RED);
                Point2D position = projectile.getPosition();
                int x = (int)position.getX();
                int y = getHeight() - (int)position.getY();
                g.fillOval(x-01, y-10, 20, 20);
            }
        }
    }