具有预定目的地的重力

时间:2014-04-21 14:41:56

标签: java 2d gravity

我在二维环境中使用重力来进行游戏。 我在游戏中使用的对象都有x和y坐标,它们可以被抛出"进入一个级别,意味着实例化,然后给定一个特定的原点位置,然后给每个框架的新坐标具有以下重力:

public void throwObject(float delta) {
    setX(getX() + velocity.x * delta);
    setY(getY() + velocity.y * delta);

velocity.y += gravity.y * delta;
}

以下内容:

Vector2 GRAVITY = new Vector2(0, -10);     

从给定的originx和originy,对象"移动"因此,这很有效。

现在我想让对象移动到给定目的地,例如:

destinationx = 50; 
destinationy = 350;

如果我使用静态原点x和原点,我如何计算velocity.x和velocity.y,以便用抛射曲线抛出物体到指定的目标坐标?

编辑: 我在确定velocity.x的计算方面取得了一些进展:

 velocity.x = (destinationx - originx) / 100; 

其中100是我设置为静态的帧数。这很有效。

对于velocity.y,我尝试过:

velocity.y = (destinationy - originy) / 100 * delta + Math.sqrt(gravity) * 2;

它给出的结果看起来与正确的公式非常接近,但不完全是

1 个答案:

答案 0 :(得分:2)

首先:有无数的解决方案。包括"琐事"一,速度只是源和目标位置的差异,除以" delta" (也就是说,由于速度为"无限高而且在一个步骤中达到目标的解决方案")。

但是直观地可以想象你想要实现的目标,所以我不会停止挑剔:

您想在笛卡尔坐标坐标中指定速度:(deltaX,deltaY)。用坐标来考虑这个速度更方便:(角度,功率),其中" power"表示初始速度的幅度。 (有关转换规则,请参阅Wikipedia: Polar coordinate system)。

给定固定的初始速度,射弹将击中目标时有零个,一个或两个角度。给定固定的初始角度,射弹将击中目标时可能存在零个或一个初始速度。但是如果你可以选择两个,速度和角度,那么从原点开始并穿过目标就会有无数个弹道轨迹。

我使用维基百科中的一些公式略微扩展了Projectile Shooter example from one of my previous answers,主要来自Wikipedia: Trajectory of a projectile

Image

您可以使用滑块更改角度和力量,并使用鼠标拖动原点和目标。该文本包含两个重要的输出:

  • 所需角度:这是可以击中目标的两个拍摄角度,假设电源未被 修改。如果没有解决方案(例如,当目标超出范围时,无论角度如何),则这些角度可能为NaN
  • 所需功率:这是击中目标所需的功率,假设角度修改。如果没有解决方案(例如,当前角度指向错误的方向时),则此功率可能为NaN

根据您对速度的自由度,您可能会提取相关公式。 (对于缺失的评论道歉,这个" SSCCE"(简短,自包含,可编辑,示例)已逐渐变成" LSCCE" ...)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Path2D;
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.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

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);

        final ProjectileShooter projectileShooter = 
            new ProjectileShooter();
        final ProjectileShooterPanel projectileShooterPanel = 
            new ProjectileShooterPanel(projectileShooter);
        projectileShooter.setPaintingComponent(projectileShooterPanel);

        ProjectileShooterMouseControl mouseControl = 
            new ProjectileShooterMouseControl(
                projectileShooter, projectileShooterPanel);
        projectileShooterPanel.addMouseMotionListener(mouseControl);
        projectileShooterPanel.addMouseListener(mouseControl);

        JPanel controlPanel = new JPanel(new GridLayout(1,0));

        controlPanel.add(new JLabel("Angle: ", SwingConstants.RIGHT));
        final JSlider angleSlider = new JSlider(0, 180, 45);
        angleSlider.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                int angleDeg = angleSlider.getValue();
                projectileShooter.setAngle(Math.toRadians(angleDeg));
            }
        });
        controlPanel.add(angleSlider);

        controlPanel.add(new JLabel("Power:", SwingConstants.RIGHT));
        final JSlider powerSlider = new JSlider(0, 50, 25);
        controlPanel.add(powerSlider);
        powerSlider.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                int power = powerSlider.getValue();
                projectileShooter.setPower(power);
            }
        });

        JButton shootButton = new JButton("Shoot");
        shootButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                projectileShooter.shoot();
            }
        });
        controlPanel.add(shootButton);

        f.getContentPane().setLayout(new BorderLayout());
        f.getContentPane().add(controlPanel, BorderLayout.NORTH);
        f.getContentPane().add(projectileShooterPanel, BorderLayout.CENTER);
        f.setSize(1200,800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class ProjectileShooterMouseControl 
    implements MouseListener, MouseMotionListener
{
    private final ProjectileShooter projectileShooter;
    private final ProjectileShooterPanel projectileShooterPanel;

    private boolean draggingOrigin = false;
    private boolean draggingTarget = false;

    ProjectileShooterMouseControl(
        ProjectileShooter projectileShooter, 
        ProjectileShooterPanel projectileShooterPanel)
    {
        this.projectileShooter = projectileShooter;
        this.projectileShooterPanel = projectileShooterPanel;
    }

    private Point2D toWorld(Point2D screen)
    {
        return new Point2D.Double(screen.getX(), 
            projectileShooterPanel.getHeight() - screen.getY());
    }

    @Override
    public void mouseMoved(MouseEvent e)
    {
    }

    @Override
    public void mouseDragged(MouseEvent e)
    {
        Point2D p = toWorld(e.getPoint());
        if (draggingOrigin)
        {
            projectileShooter.setOrigin(p);
        }
        else if (draggingTarget)
        {
            projectileShooter.setTarget(p);
        }
    }

    @Override
    public void mousePressed(MouseEvent e)
    {
        Point2D p = toWorld(e.getPoint());

        Point2D origin = projectileShooter.getOrigin();
        Point2D target = projectileShooter.getTarget();
        if (origin.distance(p) < 10)
        {
            draggingOrigin = true;
        }
        else if (target.distance(p) < 10)
        {
            draggingTarget = true;
        }
    }

    @Override
    public void mouseReleased(MouseEvent e)
    {
        draggingOrigin = false;
        draggingTarget = false;
    }

    @Override
    public void mouseClicked(MouseEvent e)
    {
    }

    @Override
    public void mouseEntered(MouseEvent e)
    {
    }

    @Override
    public void mouseExited(MouseEvent e)
    {
    }

}


class ProjectileShooter
{
    private static final double TIME_SCALE = 20;
    static final double GRAVITY = 9.81 * 0.1;

    private double angleRad = Math.toRadians(45);
    private double power = 25;

    private final Point2D origin = new Point2D.Double(50, 50);
    private final Point2D target = new Point2D.Double(500, 100);

    private Projectile projectile;
    private JComponent paintingComponent;

    void setPaintingComponent(JComponent paintingComponent)
    {
        this.paintingComponent = paintingComponent;
    }

    void setOrigin(Point2D origin)
    {
        this.origin.setLocation(origin);
        this.paintingComponent.repaint();
    }

    Point2D getOrigin()
    {
        return new Point2D.Double(origin.getX(), origin.getY());
    }

    void setTarget(Point2D target)
    {
        this.target.setLocation(target);
        this.paintingComponent.repaint();
    }

    Point2D getTarget()
    {
        return new Point2D.Double(target.getX(), target.getY());
    }

    void setAngle(double angleRad)
    {
        this.angleRad = angleRad;
        this.paintingComponent.repaint();
    }

    double getAngle()
    {
        return angleRad;
    }


    void setPower(double power)
    {
        this.power = power;
        this.paintingComponent.repaint();
    }

    double getPower()
    {
        return power;
    }

    double computeY(double x)
    {
        // http://de.wikipedia.org/wiki/Wurfparabel
        //  #Mathematische_Beschreibung
        double g = GRAVITY;
        double b = angleRad;
        double v0 = power;
        if (b > Math.PI / 2)
        {
            b = Math.PI - b;
        }
        double cb = Math.cos(b);
        return x * Math.tan(b) - g / (2 * v0 * v0 * cb * cb) * x * x;
    }

    double computeRange(double h0)
    {
        // http://de.wikipedia.org/wiki/Wurfparabel
        //  #Reichweite_bei_von_Null_verschiedener_Anfangsh.C3.B6he
        double g = GRAVITY;
        double b = angleRad;
        double v0 = power;
        double sb = Math.sin(b);
        double f0 =(v0 * v0) / (g + g) * Math.sin(b + b);
        double i = 1.0 + (2 * g * h0) / (v0 * v0 * sb * sb);
        double f1 = 1.0 + Math.sqrt(i);
        return f0 * f1;
    }

    Point2D computeRequiredAngles()
    {
        // http://en.wikipedia.org/wiki/Trajectory_of_a_projectile
        //  #Angle_required_to_hit_coordinate_.28x.2Cy.29
        double v0 = power;
        double g = GRAVITY;
        double vv = v0 * v0;
        double dx = target.getX() - origin.getX();
        double dy = target.getY() - origin.getY();
        double radicand = vv * vv - g * (g * dx * dx + 2 * dy * vv);
        double numerator0 = vv + Math.sqrt(radicand);
        double numerator1 = vv - Math.sqrt(radicand);
        double angle0 = Math.atan(numerator0 / (g*dx));
        double angle1 = Math.atan(numerator1 / (g*dx));
        return new Point2D.Double(angle0, angle1);
    }

    double computeRequiredPower()
    {
        // WolframAlpha told me so...
        double R = target.getX() - origin.getX();
        double h0 = origin.getY() - target.getY();
        double g = GRAVITY;
        double b = angleRad;
        double sb = Math.sin(b);
        double isb = 1.0 / sb;
        double v0 = 
            Math.sqrt(2) * Math.sqrt(g) * R * 
            Math.sqrt(1 / Math.sin(2*b)) /
            Math.sqrt(h0 * Math.sin(2*b) * isb * isb + 2*R);
        return v0;
    }



    void shoot()
    {
        Thread t = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                executeShot();
            }
        });
        t.setDaemon(true);
        t.start();
    }

    private static Point2D polarToCartesian(
        Point2D polar, Point2D cartesian)
    {
        double x = Math.cos(polar.getX()) * polar.getY();
        double y = Math.sin(polar.getX()) * polar.getY();
        if (cartesian == null)
        {
            cartesian = new Point2D.Double();
        }
        cartesian.setLocation(x, y);
        return cartesian;
    }

    private void executeShot()
    {
        if (projectile != null)
        {
            return;
        }
        projectile = new Projectile();
        projectile.setPosition(origin);

        Point2D velocity = polarToCartesian(
            new Point2D.Double(angleRad, power), null); 
        projectile.setVelocity(velocity);

        long prevTime = System.nanoTime();
        while (projectile.getPosition().getY() >= 0)
        {
            long currentTime = System.nanoTime();
            double dt = TIME_SCALE * (currentTime - prevTime) / 1e9;
            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 static final Point2D ACCELERATION = 
        new Point2D.Double(0, -ProjectileShooter.GRAVITY);

    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;

        double angleDeg = Math.toDegrees(projectileShooter.getAngle());
        double power = projectileShooter.getPower();
        Point2D origin = projectileShooter.getOrigin();
        Point2D target = projectileShooter.getTarget();
        double range = projectileShooter.computeRange(origin.getY());
        double impactX = range+origin.getX();

        g.setColor(Color.BLACK);
        int textY = 20;

        g.drawString("Drag origin and target with mouse ", 10, textY+=16);
        g.drawString("Angle: "+angleDeg, 10, textY+=16);
        g.drawString("Power: "+power, 10, textY+=16);
        g.drawString("Range: "+range, 10, textY+=16);
        g.drawString("Origin: "+origin, 10, textY+=16);
        g.drawString("Impact: "+impactX, 10, textY+=16);
        g.drawString("Target: "+target, 10, textY+=16);

        Point2D requiredAngles = projectileShooter.computeRequiredAngles();
        double requiredAngleDeg0 = Math.toDegrees(requiredAngles.getX());
        double requiredAngleDeg1 = Math.toDegrees(requiredAngles.getY());
        g.drawString("Assuming fixed power:", 10, textY+=16);
        g.drawString("Required angle 0: "+requiredAngleDeg0, 10, textY+=16);
        g.drawString("Required angle 1: "+requiredAngleDeg1, 10, textY+=16);

        double requiredPower = projectileShooter.computeRequiredPower();
        g.drawString("Assuming fixed angle:", 10, textY+=16);
        g.drawString("Required power: "+requiredPower, 10, textY+=16);

        Projectile projectile = projectileShooter.getProjectile();
        if (projectile != null)
        {
            g.setColor(Color.RED);
            Point2D position = projectile.getPosition();
            int px = (int)position.getX();
            int py = getHeight() - (int)position.getY();
            g.fillOval(px-10, py-10, 20, 20);
        }

        g.setColor(Color.GREEN);
        int ox = (int)origin.getX();
        int oy = getHeight() - (int)origin.getY();
        g.fillOval(ox-10, oy-10, 20, 20);

        g.setColor(Color.BLUE);
        int tx = (int)target.getX();
        int ty = getHeight() - (int)target.getY();
        g.fillOval(tx-10, ty-10, 20, 20);

        g.setColor(Color.BLACK);
        g.drawLine((int)impactX, 0, (int)impactX, getHeight());

        g.setColor(Color.GRAY);

        Shape trajectory = createTrajectory(
            Math.min(origin.getX(), impactX),
            Math.max(origin.getX(), impactX));
        g.draw(trajectory);
    }

    private Shape createTrajectory(double x0, double x1)
    {
        Path2D path = new Path2D.Double();
        Point2D origin = projectileShooter.getOrigin();
        double y0 = projectileShooter.computeY(Math.abs(origin.getX()-x0));
        path.moveTo(x0, getHeight()-(y0+origin.getY()));
        for (double x=x0; x<=x1; x++)
        {
            double y = projectileShooter.computeY(Math.abs(origin.getX()-x));
            path.lineTo(x, getHeight()-(y+origin.getY()));
        }
        return path;
    }

}