Ouroboros
Ouroboros

Reputation: 29

How do you make, that your Player in an Java2d Top Down Game alwayls looks towards the mouse

So I'm trying to awnser this question for like 2-3 hours now, but I can't quite find an fix or resolution for my problem. Like there are no Video tutorials. And because I am new to programming, especially with Java, I just don't know, how to rewrite code, that it matches my code and perfectly works. Here is what I have right now:

Also my project is all based on 2D and you only see your player for right above. So I dont really need Animation for the Player and Entity models, just in case you wondered.

Class: GamePanel

package main;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

import entity.Player;

public class GamePanel extends JPanel implements Runnable{
    
    // SCREEN SETTINGS
    final int originalTitleSize = 16; // 16x16 title
    final int scale = 3; //16x3(scale) = 48
    
    public final int tileSize = originalTitleSize * scale; //48x48 title
    final int maxScreenCol = 16;
    final int maxScreenRow = 12;
    final int screenWidth = tileSize * maxScreenCol; // 768 pixels
    final int screenHeight = tileSize * maxScreenRow; // 576 pixels
    
    //FPS
    int FPS = 60;
    
    KeyHandler keyH = new KeyHandler();
    Thread gameThread;
    Player player = new Player(this,keyH);
    
    
    // Set player's default position
    int playerX = 100;
    int playerY = 100;
    int playerSpeed = 4;
    
    
    public GamePanel() {
        
        this.setPreferredSize(new Dimension(screenWidth, screenHeight));
        this.setBackground(Color.BLACK);
        this.setDoubleBuffered(true);
        this.addKeyListener(keyH);
        this.setFocusable(true);
    }
    
    public void startGameThread() {
        
        gameThread = new Thread(this);
        gameThread.start();
    }


    @Override
//  public void run() {
//      
//      double drawInterval = 1000000000/FPS; // 0.0166666... seconds
//      double nextDrawTime = System.nanoTime() + drawInterval;
//      
//      
//  
//      while(gameThread != null) {
//          
//          // System.out.println("The game loop is running");
//          
//          // 1 UPDATE: update information such as character positions
//          update();
//          
//          
//          
//          // 2 DRAW: draw the screen with the updated information
//          repaint();
//          
//          try {
//              double remainingTime = nextDrawTime - System.nanoTime();
//              remainingTime = remainingTime/1000000;
//              
//              if(remainingTime < 0) {
//                  remainingTime  = 0;
//              }
//              
//              Thread.sleep((long) remainingTime);
//              
//              nextDrawTime += drawInterval;
//              
//          } catch (InterruptedException e) {
//              e.printStackTrace();
//          }
//      }
//  }
    public void run() {
        
        double drawInterval = 1000000000/FPS;
        double delta = 0;
        long lastTime = System.nanoTime();
        long currentTime;
        long timer = 0;
        int drawCount = 0;
        
        while(gameThread != null) {
            currentTime =System.nanoTime();
                    
            delta += (currentTime - lastTime) / drawInterval;
            timer += (currentTime -lastTime);
            lastTime = currentTime;
            
            if(delta >=1) {
                update();
                repaint();
                delta--;
                drawCount++;
            }
            
            if(timer >= 1000000000) {
                System.out.println("FPS:" + drawCount);
                drawCount = 0;
                timer = 0;
            }
            
            
        }
    }
    public void update() {
        
        player.update();
        
    }
    public void paintComponent(Graphics g) {
        
        super.paintComponent(g);
        
        Graphics2D g2 = (Graphics2D)g;
        
        player.draw(g2);
        
        g2.dispose();
    }

}

Class: KeyHandler

package main;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class KeyHandler implements KeyListener{
    
    public boolean upPressed, downPressed, leftPressed, rightPressed;

    @Override
    public void keyTyped(KeyEvent e) {
        
    }

    @Override
    public void keyPressed(KeyEvent e) {
        
        int code = e.getKeyCode();
        
        if(code == KeyEvent.VK_W) {
            upPressed = true;
        }
        if(code == KeyEvent.VK_S) {
            downPressed = true;
        }
        if(code == KeyEvent.VK_A) {
            leftPressed = true;
        }
        if(code == KeyEvent.VK_D) {
            rightPressed = true;
        }
        
    }

    @Override
    public void keyReleased(KeyEvent e) {
        
        int code = e.getKeyCode();
        
        if(code == KeyEvent.VK_W) {
            upPressed = false;
        }
        if(code == KeyEvent.VK_S) {
            downPressed = false;
        }
        if(code == KeyEvent.VK_A) {
            leftPressed = false;
        }
        if(code == KeyEvent.VK_D) {
            rightPressed = false;
        }
    }

}

Class: Main

package main;

import javax.swing.JFrame;

public class Main {

    public static void main(String[] args) {
        
        JFrame window = new JFrame();
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setResizable(false);
        window.setTitle("Zombio 0.0.0.01");
        
        GamePanel gamePanel = new GamePanel();
        window.add(gamePanel);
        
        window.pack();
        
        window.setLocationRelativeTo(null);
        window.setVisible(true);
        
        gamePanel.startGameThread();

    }

}

I also have an Player:

package entity;

import java.awt.Color;
import java.awt.Graphics2D;

import main.GamePanel;
import main.KeyHandler;

public class Player extends Entity{
    
    GamePanel gp;
    KeyHandler keyH;
    
    public Player(GamePanel gp, KeyHandler keyH) {
        
        this.gp = gp;
        this.keyH = keyH;
        
        setDefaultValues();
    }
    
    public void setDefaultValues() {
        
        x = 100;
        y = 100;
        speed = 4;
    }
    public void update() {
        if(keyH.upPressed == true) {
            y -= speed;
        }
        else if(keyH.downPressed == true) {
            y += speed;
        }
        else if(keyH.leftPressed == true) {
            x -= speed;
        }
        else if(keyH.rightPressed == true) {
            x += speed;
        }
    }
    
    public void draw(Graphics2D g2) {
        g2.setColor(Color.yellow);
        
        g2.fillRect(x,  y,  gp.tileSize,  gp.tileSize); //(x,  y,  width,  height)
    }
}

And this Player is based on the normal-entity:

package entity;

public class Entity {
    
    public int x, y;
    public int speed;
}

I know, this is a lot you need to look through, but I really don't know, what exaclty you need. I would like to implement the rotation in the Player Class, if this is possible for you. Also please not only write Code and set it as awnser, I really have no expirience, so take my by the hand :)

Thanks for your help, appreciate it!

Upvotes: 0

Views: 181

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347332

You will want to have a look at:

  • Key bindings - these will solve all the focus related issues with KeyListener
  • Concurrency in Swing and probably switch over to using a Swing Timer. Swing is NOT thread safe, it's not a good idea to use Thread as your "main loop".

So your basic problem is a "simple" trigonometry problem (I as say simple, but I'm an idiot). You have two points in space and need to calculate the angle between them, for example...

 // Radians
 -Math.atan2(startY - endY, startX - endX)

Runnable example...

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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
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() {
                GamePanel gamePanel = new GamePanel();
                JFrame frame = new JFrame();
                frame.add(gamePanel);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                gamePanel.startGameThread();
            }
        });
    }

    public class GamePanel extends JPanel { //implements Runnable {

        // SCREEN SETTINGS
        final int originalTitleSize = 16; // 16x16 title
        final int scale = 3; //16x3(scale) = 48

        public final int tileSize = originalTitleSize * scale; //48x48 title
        final int maxScreenCol = 16;
        final int maxScreenRow = 12;
        final int screenWidth = tileSize * maxScreenCol; // 768 pixels
        final int screenHeight = tileSize * maxScreenRow; // 576 pixels

        //FPS
        int FPS = 60;

        Player player = new Player(this);

        private Timer timer;
        private Set<KeyAction.Direction> movementState = new HashSet<>();
        private Point lastKnownMousePoint;

        public GamePanel() {
            this.setBackground(Color.BLACK);

            addMouseMotionListener(new MouseAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    lastKnownMousePoint = e.getPoint();
                }
            });

            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Pressed.up");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Released.up");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Pressed.down");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Released.down");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Pressed.left");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Released.left");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Pressed.right");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Released.right");

            ActionMap am = getActionMap();

            am.put("Pressed.up", new KeyAction(KeyAction.Direction.UP, true, movementState));
            am.put("Released.up", new KeyAction(KeyAction.Direction.UP, false, movementState));
            am.put("Pressed.down", new KeyAction(KeyAction.Direction.DOWN, true, movementState));
            am.put("Released.down", new KeyAction(KeyAction.Direction.DOWN, false, movementState));
            am.put("Pressed.left", new KeyAction(KeyAction.Direction.LEFT, true, movementState));
            am.put("Released.left", new KeyAction(KeyAction.Direction.LEFT, false, movementState));
            am.put("Pressed.right", new KeyAction(KeyAction.Direction.RIGHT, true, movementState));
            am.put("Released.right", new KeyAction(KeyAction.Direction.RIGHT, false, movementState));
        }

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

        public void startGameThread() {
            if (timer == null) {
                timer = new Timer((int) Math.floor(1000f / FPS), new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        update();
                        repaint();
                    }
                });
            }
            timer.start();
        }

        public void update() {
            if (movementState.contains(KeyAction.Direction.UP)) {
                player.y -= player.speed;
            } else if (movementState.contains(KeyAction.Direction.DOWN)) {
                player.y += player.speed;
            } else if (movementState.contains(KeyAction.Direction.LEFT)) {
                player.x -= player.speed;
            } else if (movementState.contains(KeyAction.Direction.RIGHT)) {
                player.x += player.speed;
            }

            if (lastKnownMousePoint != null) {
                // This assumes that character is facing "UP" by default
                // That is, 0 has the character entity facing towards to the
                // top of the sceen.  If the character is facing in a different
                // direction, then you will need to offset this calculation
                // to compenstate, but that might be better done in the player
                // entity
                double angle = -Math.toDegrees(Math.atan2(player.x - lastKnownMousePoint.x, player.y - lastKnownMousePoint.y));
                player.angleInDegrees = angle;
            }
        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            player.draw(g2);
            g2.dispose();
        }

    }

    public class KeyAction extends AbstractAction {
        enum Direction {
            UP, DOWN, LEFT, RIGHT
        }

        private Direction direction;
        private boolean activate;
        private Set<Direction> inputState;

        public KeyAction(Direction direction, boolean activate, Set<Direction> inputState) {
            this.direction = direction;
            this.activate = activate;
            this.inputState = inputState;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (activate) {
                inputState.add(direction);
            } else {
                inputState.remove(direction);
            }
        }
    }

    public class Entity {
        public int x;
        public int y;
        public int speed;
    }

    public class Player extends Entity {

        GamePanel gp;

        double angleInDegrees = 0;

        public Player(GamePanel gp) {
            this.gp = gp;
            setDefaultValues();
        }

        public void setDefaultValues() {
            x = 100;
            y = 100;
            speed = 4;
        }

        public void draw(Graphics2D g2) {
            g2 = (Graphics2D) g2.create();
            g2.translate(x, y);
            g2.rotate(Math.toRadians(angleInDegrees), (gp.tileSize / 2), (gp.tileSize / 2));
            g2.setColor(Color.yellow);
            g2.fillRect(0, 0, gp.tileSize, gp.tileSize);
            g2.setColor(Color.RED);
            g2.drawLine((gp.tileSize / 2), (gp.tileSize / 2), (gp.tileSize / 2), 0);
            g2.dispose();
        }
    }
}

Oh, and also the rotation point is not right in the middle of the player (rectangle). Some dont need accurancy, I really do

Just beware, you're probably never going to find the "exact" solutions to your problems and you're going to need to take the time to experiment ;)

Okay, admittedly, that seemed to work for me, until I started debugging it. The problem was, the original code was calculating the angle from the mouse point and the players current x/y point. It should be using the players "rotation" point, which, in this example, is the players mid point.

So, I added...

public Point midPoint() {
    return new Point(x + (gp.tileSize / 2), y + (gp.tileSize / 2));
}

to Player, so we can easily get the player's mid point (and not have to retype that a lot)

I then updated the angle calculation to make use of it, for example..

Point playerMidPoint = player.midPoint();
double angle = Math.toDegrees(Math.atan2(lastKnownMousePoint.y - playerMidPoint.y, lastKnownMousePoint.x - playerMidPoint.x)) + 90d;
player.angleInDegrees = angle;

enter image description here

Runnable example...

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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
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() {
                GamePanel gamePanel = new GamePanel();
                JFrame frame = new JFrame();
                frame.add(gamePanel);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                gamePanel.startGameThread();
            }
        });
    }

    public class GamePanel extends JPanel { //implements Runnable {

        // SCREEN SETTINGS
        final int originalTitleSize = 16; // 16x16 title
        final int scale = 3; //16x3(scale) = 48

        public final int tileSize = originalTitleSize * scale; //48x48 title
        final int maxScreenCol = 16;
        final int maxScreenRow = 12;
        final int screenWidth = tileSize * maxScreenCol; // 768 pixels
        final int screenHeight = tileSize * maxScreenRow; // 576 pixels

        //FPS
        int FPS = 60;

        Player player = new Player(this);

        private Timer timer;
        private Set<KeyAction.Direction> movementState = new HashSet<>();
        private Point lastKnownMousePoint;

        public GamePanel() {
            this.setBackground(Color.BLACK);

            addMouseMotionListener(new MouseAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    lastKnownMousePoint = new Point(e.getPoint());
                }
            });

            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Pressed.up");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Released.up");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Pressed.down");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Released.down");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Pressed.left");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Released.left");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Pressed.right");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Released.right");

            ActionMap am = getActionMap();

            am.put("Pressed.up", new KeyAction(KeyAction.Direction.UP, true, movementState));
            am.put("Released.up", new KeyAction(KeyAction.Direction.UP, false, movementState));
            am.put("Pressed.down", new KeyAction(KeyAction.Direction.DOWN, true, movementState));
            am.put("Released.down", new KeyAction(KeyAction.Direction.DOWN, false, movementState));
            am.put("Pressed.left", new KeyAction(KeyAction.Direction.LEFT, true, movementState));
            am.put("Released.left", new KeyAction(KeyAction.Direction.LEFT, false, movementState));
            am.put("Pressed.right", new KeyAction(KeyAction.Direction.RIGHT, true, movementState));
            am.put("Released.right", new KeyAction(KeyAction.Direction.RIGHT, false, movementState));
        }

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

        public void startGameThread() {
            if (timer == null) {
                timer = new Timer((int) Math.floor(1000f / FPS), new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        update();
                        repaint();
                    }
                });
            }
            timer.start();
        }

        public void update() {
            if (movementState.contains(KeyAction.Direction.UP)) {
                player.y -= player.speed;
            } else if (movementState.contains(KeyAction.Direction.DOWN)) {
                player.y += player.speed;
            } else if (movementState.contains(KeyAction.Direction.LEFT)) {
                player.x -= player.speed;
            } else if (movementState.contains(KeyAction.Direction.RIGHT)) {
                player.x += player.speed;
            }

            if (lastKnownMousePoint != null) {
                // This assumes that character is facing "UP" by default
                // That is, 0 has the character entity facing towards to the
                // top of the sceen.  If the character is facing in a different
                // direction, then you will need to offset this calculation
                // to compenstate, but that might be better done in the player
                // entity
                Point playerMidPoint = player.midPoint();
                double angle = Math.toDegrees(Math.atan2(lastKnownMousePoint.y - playerMidPoint.y, lastKnownMousePoint.x - playerMidPoint.x)) + 90d;
                player.angleInDegrees = angle;
                repaint();
            }
        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g.create();
            player.draw(g2);
            g2.dispose();
            if (lastKnownMousePoint != null) {
                g2 = (Graphics2D) g;
                g2.setColor(Color.GREEN);

                int midX = player.x + (tileSize / 2);
                int midY = player.y + (tileSize / 2);
                g2.drawLine(midX, midY, lastKnownMousePoint.x, lastKnownMousePoint.y);
                g2.dispose();
            }
        }

    }

    public class KeyAction extends AbstractAction {
        enum Direction {
            UP, DOWN, LEFT, RIGHT
        }

        private Direction direction;
        private boolean activate;
        private Set<Direction> inputState;

        public KeyAction(Direction direction, boolean activate, Set<Direction> inputState) {
            this.direction = direction;
            this.activate = activate;
            this.inputState = inputState;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (activate) {
                inputState.add(direction);
            } else {
                inputState.remove(direction);
            }
        }
    }

    public class Entity {
        public int x;
        public int y;
        public int speed;
    }

    public class Player extends Entity {

        GamePanel gp;

        double angleInDegrees = 0;

        public Player(GamePanel gp) {
            this.gp = gp;
            setDefaultValues();
        }

        public void setDefaultValues() {
            x = 100;
            y = 100;
            speed = 4;
        }

        public Point midPoint() {
            return new Point(x + (gp.tileSize / 2), y + (gp.tileSize / 2));
        }

        public void draw(Graphics2D g2) {
            g2 = (Graphics2D) g2.create();
            AffineTransform at = AffineTransform.getTranslateInstance(x, y);
            at.rotate(Math.toRadians(angleInDegrees), (gp.tileSize / 2d), (gp.tileSize / 2d));
            g2.transform(at);
            g2.setColor(Color.yellow);
            g2.fillRect(0, 0, gp.tileSize, gp.tileSize);
            g2.setColor(Color.RED);
            g2.drawLine((gp.tileSize / 2), (gp.tileSize / 2), (gp.tileSize / 2), 0);
            g2.dispose();
        }
    }
}

Upvotes: 1

Related Questions