如何沿斜坡移动绘画图形?

How to move paint graphics along slope?

我有一款坦克游戏,您可以通过 A/D 键旋转并使用 W/S 移动 forward/backward。炮塔跟随屏幕上的鼠标光标。我试图在单击时开始拍摄。我在点击时获得炮塔旋转的初始度数,以及炮塔末端的点。我设置 isShooting() 来检查我当前是否在给定时刻拍摄,点击时设置为 true。我使用 shotFired() 也将点击设置为 true,以判断屏幕上是否有镜头。

我希望每次单击时让子弹沿直线移动,从枪管末端到屏幕外。

目前子弹的移动方向错误并且受炮塔移动的影响。我一直在努力寻找问题,但找不到。

public void update() {
    playerTank.setCenterTurret(new Point2D.Double(playerTank.xPos() + 67, playerTank.yPos() + 125));
    playerTank.setEndTurret(new Point2D.Double(playerTank.xPos() + ((Math.sin(Math.toRadians(playerTank.getTurretDegree())))) + 63, playerTank.yPos() + ((Math.cos(Math.toRadians(playerTank.getTurretDegree())))) - 25));
    playerTank.setCenterBase(new Point2D.Double(playerTank.xPos() + (playerTank_PNG_WIDTH / 2), playerTank.yPos() + (playerTank_PNG_HEIGHT / 2)));

    mouseLoc = MouseInfo.getPointerInfo().getLocation();
    SwingUtilities.convertPointFromScreen(mouseLoc, this);

    mouseLocX = (int) mouseLoc.getX();
    mouseLocY = (int) mouseLoc.getY();

    mouseDistX = mouseLocX - playerTank.getCenterTurret().getX();
    mouseDistY = mouseLocY - playerTank.getCenterTurret().getY();

    mouseDegree = angleInRelation(mouseLoc, playerTank.getCenterTurret());

    if(moveUp) {
        playerTank.setLocation(playerTank.xPos() + (MOVEMENT_SPEED * Math.sin(Math.toRadians(playerTank.getBaseDegree()))), playerTank.yPos() - MOVEMENT_SPEED * Math.cos(Math.toRadians(playerTank.getBaseDegree())));
    }
    if(moveDown) {
        playerTank.setLocation(playerTank.xPos() - (MOVEMENT_SPEED * Math.sin(Math.toRadians(playerTank.getBaseDegree()))), playerTank.yPos() + MOVEMENT_SPEED * Math.cos(Math.toRadians(playerTank.getBaseDegree())));
    }
    if(rotateLeft && playerTank.xPos() >= 0) {
        playerTank.setBaseDegree(playerTank.getBaseDegree() - 5);
    }
    if(rotateRight && playerTank.xPos() + playerTank.getWidth() <= FRAME_WIDTH) {
        playerTank.setBaseDegree(playerTank.getBaseDegree() + 5);
    }

    mouseDegree -= playerTank.getBaseDegree();

    this.setBackground(Color.white);
    repaint();
}

@Override
public void paint(Graphics g) {
    this.setBackground(Color.white);
    Graphics2D g2D = (Graphics2D) g;
    g2D.setBackground(Color.white);
    g2D.setColor(Color.white);

    g2D.fillRect(0, 0, FRAME_WIDTH, FRAME_HEIGHT);

    paintBase(g2D);

    playerTank.setTurretDegree(mouseDegree);
    g2D.rotate(Math.toRadians(playerTank.getTurretDegree()), playerTank.xPos() + 67, playerTank.yPos() + 125);
    paintTurret(g2D);

    paintBullet(g2D);
}

public void paintBullet(Graphics2D g2D) {
    g2D.setColor(Color.black);

    if(playerTank.isShooting()) {
        playerTank.setBulletPos(playerTank.getEndTurret());
        g2D.fillRect((int) playerTank.getEndTurret().getX(), (int) playerTank.getEndTurret().getY(), 8, 18);
        playerTank.setShooting(false);
    }
    if (playerTank.shotFired()) {
        double newX = (BULLET_SPEED * (Math.sin(Math.toRadians(playerTank.getInitialTurretDegree()))));
        double newY = (BULLET_SPEED * (Math.cos(Math.toRadians(playerTank.getInitialTurretDegree()))));
        playerTank.setBulletPos(playerTank.getBulletX() + newX, playerTank.getBulletY() - newY);
        g2D.fillRect((int) playerTank.getBulletX(), (int) playerTank.getBulletY(), 8, 18);

        if((playerTank.getBulletX() > FRAME_WIDTH || playerTank.getBulletY() > FRAME_HEIGHT) || (playerTank.getBulletX() < 0 || playerTank.getBulletY() < 0)) {
            playerTank.setShotFired(false);
        }
    }

非常感谢。

所以,你的问题基本上可以归结为一系列三角问题......是的(你能感受到讽刺吗)。

但我是什么意思?那么,您想根据桶的当前旋转角度找到桶的终点吗?好吧,您需要知道枪管的长度,这将为您提供枪管可以绕其行进的半径,由此您可以简单地执行“圆上的点”计算。

想知道弹丸的路径,根据大于可见区域的半径计算出“圆上的点”,然后在这个点上画一条与枪管之间的线,这就是你的路径。告诉你,三角函数。

所以,我开始创建一个简单的实体,最终看起来像(带有颜色指南)...

炮塔和body是分开的图,但是叠起来画的话,就会像上图一样,这个很重要,省事

然后炮塔就有了指定的半径,可以很容易地围绕body

旋转

如果你仔细看,炮塔实际上并没有以图像的“自然”中心为中心,但通过这种方式布置,我们可以轻松地围绕图像的中点旋转 - 容易得多.

好的,听起来很有趣,让我们从“坦克”实体的基本概念开始...

public class Tank {
    private BufferedImage body;
    private BufferedImage turret;

    private int x;
    private int y;

    private double bodyAngle = 0;
    private double turretAngle = 0;

    private int width;
    private int height;

    private int midX;
    private int midY;

    public Tank() throws IOException {
        body = ImageIO.read(getClass().getResource("/images/TankBody.png"));
        turret = ImageIO.read(getClass().getResource("/images/TankTurret.png"));

        width = body.getWidth();
        height = body.getHeight();

        midX = width / 2;
        midY = height / 2;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public double getBodyAngle() {
        return bodyAngle;
    }

    public double getTurretAngle() {
        return turretAngle;
    }

    public void setBodyAngle(double bodyAngle) {
        this.bodyAngle = bodyAngle;
    }

    public void setTurretAngle(double turretAngle) {
        this.turretAngle = turretAngle;
    }

    // This represents the "unrotated" width
    public int getWidth() {
        return width;
    }

    // This represents the "unrotated" height
    public int getHeight() {
        return height;
    }

    protected int getMidX() {
        return midX;
    }

    protected int getMidY() {
        return midY;
    }

    protected BufferedImage getBody() {
        return body;
    }

    protected BufferedImage getTurret() {
        return turret;
    }

    public void paint(Graphics2D master, ImageObserver observer) {
        Graphics2D g2d = (Graphics2D) master.create();
        g2d.translate(getX() - getMidX(), getY() - getMidY());

        g2d.setColor(Color.RED);
        g2d.drawOval(0, 0, getWidth(), getHeight());

        Graphics2D bodyG = (Graphics2D) g2d.create();
        bodyG.rotate(Math.toRadians(getBodyAngle()), getMidX(), getMidY());
        // >>> Debug
        bodyG.setColor(Color.ORANGE.darker());
        bodyG.drawRect(0, 0, 64, 64);
        // <<< Debug
        bodyG.drawImage(getBody(), 0, 0, observer);
        bodyG.dispose();

        Graphics2D turrtG = (Graphics2D) g2d.create();
        turrtG.rotate(Math.toRadians(getTurretAngle()), getMidX(), getMidY());
        // >>> Debug
        turrtG.setColor(Color.GREEN.darker());
        // I mesured the turrent size in a image editor
        // The acutal image size is the same as the body
        // in order to make the workflow simpler
        turrtG.drawRect((getWidth() - 20) / 2, 0, 20, 44);
        // <<< Debug
        turrtG.drawImage(getTurret(), 0, 0, observer);
        turrtG.dispose();

        g2d.dispose();
    }
}

这里要注意的重要事项是...

  • 坦克的x/y位置代表它的“中心”点
  • body和炮塔可以相互独立旋转

现在,我们可以使用 MouseMotionListener 让炮塔看到鼠标,你猜对了,还有一些三角函数

public class GamePane extends JPanel {

    private Tank tank;
    private Point mousePoint;

    public GamePane() throws IOException {
        tank = new Tank();
        addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                mousePoint = e.getPoint();
            }
        });
        tank.setX(200);
        tank.setY(200);

        Timer timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (mousePoint != null) {
                    double deltaX = tank.getX() - mousePoint.x;
                    double deltaY = tank.getY() - mousePoint.y;
                    // Because the image is pointing up, we need to offset
                    // the rotation by 90 for the API
                    double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
                    tank.setTurretAngle(rotation);
                }
                repaint();
            }
        });
        timer.start();
    }

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

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        tank.paint(g2d, this);
        // I don't trust the tanks paint process ;)
        g2d.dispose();
        g2d = (Graphics2D) g.create();

        // This is all debug
        if (mousePoint != null) {
            // The radius around the tank based on the mouse's current location
            double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
            g2d.setColor(Color.MAGENTA);
            g2d.draw(new Ellipse2D.Double(
                    tank.getX() - radius,
                    tank.getY() - radius,
                    radius * 2,
                    radius * 2));
        }
        g2d.dispose();
    }
}

“核心”功能在 Timer 中,看起来像...

double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));

重要的是要注意 Graphics API 和 Math API 具有不同的 0 概念(是的)。

好的,但是我们如何找到抛射物路径?!嗯,更多的三角函数!

但首先,我们需要一些帮助...

public class Util {
    public static Point2D getPointOnCircle(double degress, double offset, double radius) {
        double rads = Math.toRadians(degress + offset); // 0 becomes the top

        // Calculate the outter point of the line
        double xPosy = Math.cos(rads) * radius;
        double yPosy = Math.sin(rads) * radius;

        return new Point2D.Double(xPosy, yPosy);
    }

    public static Point2D getPointOnCircle(double degress, double offset, double radius, double centerX, double centerY) {
        Point2D poc = getPointOnCircle(degress, offset, radius);
        return new Point2D.Double(poc.getX() + centerX, poc.getY() + centerY);
    }
}

所以,所有这一切只是提供一些基本的“圆上的点”计算。由此,我们可以计算出寻找“世界”中的“点”...

所以,我们可以添加...

public Point2D getBusinessEndOfBarrel() {
    // I've deliberatly set up the images to be the same size, so this
    // can be made easier.  If your turren is a different size/position
    // then you will need calculate this yourself

    // Also, we're calculating this in "world" space
    int centerX = getX();
    int centerY = getY();
    return Util.getPointOnCircle(getTurretAngle(), -90, Math.max(getWidth(), getHeight()) / 2, centerX, centerY);
}

对于我们的 Tank 实体,这将 return 表示桶末端的“世界”坐标。

让我们在 paintComponent...

中添加一些“调试”图形
// This is all debug
if (mousePoint != null) {
    // The radius around the tank based on the mouse's current location
    double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
    g2d.setColor(Color.MAGENTA);
    g2d.draw(new Ellipse2D.Double(
            tank.getX() - radius,
            tank.getY() - radius,
            radius * 2,
            radius * 2));

    // From point, base the turrets current angle
    Point2D fromPoint = tank.getBusinessEndOfBarrel();

    // Mid point of the tank in the world
    int worldMidX = tank.getX();
    int worldMidY = tank.getY();
    // The point on the circle where the mouse is, based on the turrents current angle
    // which the diection the turret is pointing
    Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, radius, worldMidX, worldMidY);

    // The "out of view" radius, this represents the "end point" for our projectile, because, it's easier 
    // to calculate
    double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
    Point2D outOfViewToPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, getWidth() / 2, getHeight() / 2);
    g2d.setColor(Color.CYAN);
    // The projectiles path
    g2d.draw(new Line2D.Double(fromPoint, outOfViewToPoint));

    // A line from the barrel to the mouse point
    g2d.setColor(Color.BLUE);
    g2d.draw(new Line2D.Double(fromPoint, toPoint));
}

这将添加...

  • 表示鼠标创建的目标圆的指南
  • 从枪管到鼠标的弹丸线
  • 从枪管到可见区域外的射弹线

您可能会感到惊讶,这实际上现在提供了问题的答案。你有射弹的起点和终点,现在你只需要一些方法来让它动起来...

说到动画,我更喜欢基于时间的动画,但由于抛射物可能会移动可变距离,我们真正需要的是线性进展,所以如果是长距离或短距离移动,它会以同样的速度行进。

现在,我用头撞了 Google 一下就想出了...

public class Projectile {
    private Point2D from;
    private Point2D to;

    private Point2D current;
    
    private long lastUpdate = 0;
    
    private double deltaX;
    private double deltaY;

    public Projectile(Point2D from, Point2D to) {
        this.from = from;
        this.to = to;
        current = new Point2D.Double(from.getX(), from.getY());

        double deltaX = from.getX() - to.getX();
        double deltaY = from.getY() - to.getY();
        double angle = Math.atan2(deltaY, deltaX) + Math.toRadians(180);
        
        this.deltaY = Math.sin(angle) * 100;
        this.deltaX = Math.cos(angle) * 100;
        lastUpdate = System.currentTimeMillis();
    }

    public Point2D getFrom() {
        return from;
    }

    public Point2D getTo() {
        return to;
    }

    public Point2D getLocation() {
        return current;
    }
    
    public void tick() {
        long elapsedTime = System.currentTimeMillis() - lastUpdate;
        lastUpdate = System.currentTimeMillis();
        
        double x = current.getX();
        double y = current.getY();
        
        x += deltaX * (elapsedTime / 1000d);
        y += deltaY * (elapsedTime / 1000d);
        
        current.setLocation(x, y);
    }
}

这将基本上计算一个 x/y 增量,需要应用该增量才能使用恒定速度将炮弹从当前点移动到目标点。

现在我们可以添加一个MouseListener...

addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        int worldMidX = tank.getX();
        int worldMidY = tank.getY();
        Point2D fromPoint = tank.getBusinessEndOfBarrel();
        double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
        Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, worldMidX, worldMidY);

        Projectile projectile = new Projectile(fromPoint, toPoint);
        projectiles.add(projectile);
    }
});

在我们的主循环中更新射弹...

List<Projectile> outOfScopeProjectiles = new ArrayList<>(8);
Rectangle visibleBounds = new Rectangle(0, 0, getWidth(), getHeight());
for (Projectile projectile : projectiles) {
    projectile.tick();
    Point2D current = projectile.getLocation();
    if (!visibleBounds.contains(current)) {
        outOfScopeProjectiles.add(projectile);
    }
}
projectiles.removeAll(outOfScopeProjectiles);

并更新我们的 paintComponent...

g2d.setColor(Color.RED);
for (Projectile projectile : projectiles) {
    Point2D current = projectile.getLocation();
    g2d.fill(new Ellipse2D.Double(current.getX() - 2, current.getY() - 2, 4, 4));
    // >>> DEBUG
    g2d.draw(new Line2D.Double(projectile.getFrom(), projectile.getTo()));
    // << DEBUG
}

可运行示例...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    JFrame frame = new JFrame();
                    frame.add(new GamePane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    public class GamePane extends JPanel {

        private Tank tank;
        private Point mousePoint;

        private List<Projectile> projectiles = new ArrayList<>(8);

        public GamePane() throws IOException {
            tank = new Tank();
            addMouseMotionListener(new MouseAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    mousePoint = e.getPoint();
                }
            });
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    int worldMidX = tank.getX();
                    int worldMidY = tank.getY();
                    Point2D fromPoint = tank.getBusinessEndOfBarrel();
                    double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
                    Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, worldMidX, worldMidY);

                    Projectile projectile = new Projectile(fromPoint, toPoint);
                    projectiles.add(projectile);
                }
            });
            tank.setX(200);
            tank.setY(200);

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (mousePoint != null) {
                        double deltaX = tank.getX() - mousePoint.x;
                        double deltaY = tank.getY() - mousePoint.y;
                        // Because the image is pointing up, we need to offset
                        // the rotation by 90 for the API
                        double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
                        tank.setTurretAngle(rotation);
                    }

                    List<Projectile> outOfScopeProjectiles = new ArrayList<>(8);
                    Rectangle visibleBounds = new Rectangle(0, 0, getWidth(), getHeight());
                    for (Projectile projectile : projectiles) {
                        projectile.tick();
                        Point2D current = projectile.getLocation();
                        if (!visibleBounds.contains(current)) {
                            outOfScopeProjectiles.add(projectile);
                        }
                    }
                    projectiles.removeAll(outOfScopeProjectiles);

                    repaint();
                }
            });
            timer.start();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            tank.paint(g2d, this);
            // I don't trust the tanks paint process ;)
            g2d.dispose();
            g2d = (Graphics2D) g.create();

            // This is all debug
            if (mousePoint != null) {
                // The radius around the tank based on the mouse's current location
                double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
                g2d.setColor(Color.MAGENTA);
                g2d.draw(new Ellipse2D.Double(
                        tank.getX() - radius,
                        tank.getY() - radius,
                        radius * 2,
                        radius * 2));

                // From point, base the turrets current angle
                Point2D fromPoint = tank.getBusinessEndOfBarrel();

                // Mid point of the tank in the world
                int worldMidX = tank.getX();
                int worldMidY = tank.getY();
                // The point on the circle where the mouse is, based on the turrents current angle
                // which the diection the turret is pointing
                Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, radius, worldMidX, worldMidY);

                // The "out of view" radius, this represents the "end point" for our projectile, because, it's easier 
                // to calculate
                double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
                Point2D outOfViewToPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, getWidth() / 2, getHeight() / 2);
                g2d.setColor(Color.CYAN);
                // The projectiles path
                g2d.draw(new Line2D.Double(fromPoint, outOfViewToPoint));

                // A line from the barrel to the mouse point
                g2d.setColor(Color.BLUE);
                g2d.draw(new Line2D.Double(fromPoint, toPoint));
            }
            g2d.setColor(Color.RED);
            for (Projectile projectile : projectiles) {
                Point2D current = projectile.getLocation();
                g2d.fill(new Ellipse2D.Double(current.getX() - 2, current.getY() - 2, 4, 4));
                // >>> DEBUG
                g2d.draw(new Line2D.Double(projectile.getFrom(), projectile.getTo()));
                // << DEBUG
            }
            g2d.dispose();
        }

    }

    public class Util {
        public static Point2D getPointOnCircle(double degress, double offset, double radius) {
            double rads = Math.toRadians(degress + offset); // 0 becomes the top

            // Calculate the outter point of the line
            double xPosy = Math.cos(rads) * radius;
            double yPosy = Math.sin(rads) * radius;

            return new Point2D.Double(xPosy, yPosy);
        }

        public static Point2D getPointOnCircle(double degress, double offset, double radius, double centerX, double centerY) {
            Point2D poc = getPointOnCircle(degress, offset, radius);
            return new Point2D.Double(poc.getX() + centerX, poc.getY() + centerY);
        }
    }

    public class Projectile {
        private Point2D from;
        private Point2D to;

        private Point2D current;

        private long lastUpdate = 0;

        private double deltaX;
        private double deltaY;

        public Projectile(Point2D from, Point2D to) {
            this.from = from;
            this.to = to;
            current = new Point2D.Double(from.getX(), from.getY());

            double deltaX = from.getX() - to.getX();
            double deltaY = from.getY() - to.getY();
            double angle = Math.atan2(deltaY, deltaX) + Math.toRadians(180);

            this.deltaY = Math.sin(angle) * 100;
            this.deltaX = Math.cos(angle) * 100;
            lastUpdate = System.currentTimeMillis();
        }

        public Point2D getFrom() {
            return from;
        }

        public Point2D getTo() {
            return to;
        }

        public Point2D getLocation() {
            return current;
        }

        public void tick() {
            long elapsedTime = System.currentTimeMillis() - lastUpdate;
            lastUpdate = System.currentTimeMillis();

            double x = current.getX();
            double y = current.getY();

            x += deltaX * (elapsedTime / 1000d);
            y += deltaY * (elapsedTime / 1000d);

            current.setLocation(x, y);
        }

    }

    public class Tank {
        private BufferedImage body;
        private BufferedImage turret;

        private int x;
        private int y;

        private double bodyAngle = 0;
        private double turretAngle = 0;

        private int width;
        private int height;

        private int midX;
        private int midY;

        public Tank() throws IOException {
            body = ImageIO.read(getClass().getResource("/images/TankBody.png"));
            turret = ImageIO.read(getClass().getResource("/images/TankTurret.png"));

            width = body.getWidth();
            height = body.getHeight();

            midX = width / 2;
            midY = height / 2;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public void setX(int x) {
            this.x = x;
        }

        public void setY(int y) {
            this.y = y;
        }

        public double getBodyAngle() {
            return bodyAngle;
        }

        public double getTurretAngle() {
            return turretAngle;
        }

        public void setBodyAngle(double bodyAngle) {
            this.bodyAngle = bodyAngle;
        }

        public void setTurretAngle(double turretAngle) {
            this.turretAngle = turretAngle;
        }

        // This represents the "unrotated" width
        public int getWidth() {
            return width;
        }

        // This represents the "unrotated" height
        public int getHeight() {
            return height;
        }

        protected int getMidX() {
            return midX;
        }

        protected int getMidY() {
            return midY;
        }

        protected BufferedImage getBody() {
            return body;
        }

        protected BufferedImage getTurret() {
            return turret;
        }

        public Point2D getBusinessEndOfBarrel() {
            // I've deliberatly set up the images to be the same size, so this
            // can be made easier.  If your turren is a different size/position
            // then you will need calculate this yourself

            // Also, we're calculating this in "world" space
            int centerX = getX();
            int centerY = getY();
            return Util.getPointOnCircle(getTurretAngle(), -90, Math.max(getWidth(), getHeight()) / 2, centerX, centerY);
        }

        public void paint(Graphics2D master, ImageObserver observer) {
            Graphics2D g2d = (Graphics2D) master.create();
            g2d.translate(getX() - getMidX(), getY() - getMidY());

            g2d.setColor(Color.RED);
            g2d.drawOval(0, 0, getWidth(), getHeight());

            Graphics2D bodyG = (Graphics2D) g2d.create();
            bodyG.rotate(Math.toRadians(getBodyAngle()), getMidX(), getMidY());
            // >>> Debug
            bodyG.setColor(Color.ORANGE.darker());
            bodyG.drawRect(0, 0, 64, 64);
            // <<< Debug
            bodyG.drawImage(getBody(), 0, 0, observer);
            bodyG.dispose();

            Graphics2D turrtG = (Graphics2D) g2d.create();
            turrtG.rotate(Math.toRadians(getTurretAngle()), getMidX(), getMidY());
            // >>> Debug
            turrtG.setColor(Color.GREEN.darker());
            // I mesured the turrent size in a image editor
            // The acutal image size is the same as the body
            // in order to make the workflow simpler
            turrtG.drawRect((getWidth() - 20) / 2, 0, 20, 44);
            // <<< Debug
            turrtG.drawImage(getTurret(), 0, 0, observer);
            turrtG.dispose();

            g2d.dispose();
        }

    }
}