0

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!

  • 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](https://stackoverflow.com/questions/27554412/java-shoot-towards-mouse/27554708#27554708); [example](https://stackoverflow.com/questions/27260445/rotating-a-triangle-around-a-point-java/27260788#27260788); [example](https://stackoverflow.com/questions/15607427/java-make-a-directed-line-and-make-it-move/15607737#15607737); – MadProgrammer Jun 05 '22 at 10:36
  • [example](https://stackoverflow.com/questions/26784303/java-move-image-towards-mouse-position/26791886#26791886); [example](https://stackoverflow.com/questions/11911316/java-2d-rotation-in-direction-mouse-point/11911470#11911470); [example](https://stackoverflow.com/questions/72411743/how-to-move-paint-graphics-along-slope/72412859#72412859) – MadProgrammer Jun 05 '22 at 10:41
  • `KeyListener` is going to cause you issues in the long run, use [key bindings](https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) instead – MadProgrammer Jun 05 '22 at 10:49
  • Swing is NOT thread safe, it's not a good idea to use `Thread` as your "main loop". See [Concurrency in Swing](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html) and probably switch over to using a [Swing `Timer`](https://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html) instead – MadProgrammer Jun 05 '22 at 10:50

1 Answers1

1

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();
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • So thank you, I copy pasted the code in a test library and it worked. Only problem now, how do I implement and much more important, sort this code into my own. You wrote everything in one class, so its really hard for me, but thanks already. Oh, and I am not 18 years old by now and I am a german guy, so its much harder for me, to understand the thing you are trying to tell me. I mean, I could copy it, but I am watching an tutolrial series and I want to stay on track with that. Thats why I want to implement it in my existing code. Can you help me there. Also, 336k reputation, not bad :D – T0X1CCR34T0R Jun 05 '22 at 11:25
  • I've simple used inner classes to make the example easier to run. You will to dissect the pieces you need and implement them to your code. There should be no issues taking the inner classes and separating them into their own class files – MadProgrammer Jun 05 '22 at 11:30
  • Oh, and also the rotation point is not right in the middle of the player (rectangle). Some dont need accurancy, I really do. – T0X1CCR34T0R Jun 05 '22 at 11:32
  • @ToxicFactoryGames See updates – MadProgrammer Jun 05 '22 at 22:57