Search code examples
javaeclipsejframejpanelpaintcomponent

Objects don't move correctly


I am currently programming a game where you have to dodge obstacles and reach the goal. There are a number of obstacles, based on the difficulty the player chose, which are moving in a straight line, in a random angle. If they reach the borders of the screen, they respawn at a random spawn point (there are three, also created random). The spawn points should remain stationary while you play a round and be recreated at different places when you start the next round.

If I now start the game, the spawn points move along with the obstacles which group and use a combined moving vector when spawning at the same spawn point.

I'll now list the classes that are important to indentify my problem:

  1. The GamePanel (most of the game happens here):

    public class GamePanel extends JPanel
    {
    private JFrame fenster; //Frame for the game
    private final Dimension prefSize=new Dimension(1180, 780);  //preferred dimension
    private boolean gameOver=false,roundWin=false;  //booleans for round win and game over
    private Timer t;    //Timer
    private int c=0,p=0;    //counter and points
    private int[][] max;    //highscore Array
    private Player player=null; //playable game object
    private Goal goal=null; //the goal to end the round and earn points
    private int schwer=2;   //difficulty
    private Obstacle[] obstacles;   //obstacles to avoid
    private Spawn spawn0,spawn1,spawn2; //spawn points 1,2 and 3
    private Coordinate cSpawn0,cSpawn1,cSpawn2; //coordinates for the spawn points
    
    public GamePanel()
    {
        fenster = new JFrame();
        fenster.setTitle("Faster Course");
        fenster.setLocation(50,50);
        fenster.setResizable(false);
        fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        fenster.setVisible(true);
        fenster.setPreferredSize(prefSize);
        fenster.setSize(prefSize.width,prefSize.height);
        //fenster is specified
    
        JMenuBar Menü = new JMenuBar();
        JMenu file = new JMenu("File");
        JMenu game = new JMenu("Game");
        JMenu pref = new JMenu("Preferences");
        fenster.setJMenuBar(Menü);
    
        Menü.add(file);
        Menü.add(game);
        Menü.add(pref);
        //Menu for fenster is 'made'
    
        JMenuItem quit = new JMenuItem("Close");
        file.add(quit);
        quit.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                System.exit(0);     //if this button is clicked, the game exits
            }
        });
    
        JMenuItem diff = new JMenuItem("Difficulty");
        pref.add(diff);
        diff.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                JFrame Schwierigkeit = new JFrame();
                Schwierigkeit.setLayout(null);
                Schwierigkeit.setTitle("Difficulty");
                Schwierigkeit.setLocation(200,200);
                Schwierigkeit.setVisible(true);
                Schwierigkeit.setSize(30,275);
                Schwierigkeit.setResizable(false);                  
    
                JButton kLeicht=new JButton("Easy");
                kLeicht.setBounds(20,10,90,25);
                JButton kNormal=new JButton("Normal");
                kNormal.setBounds(20,50,90,25);
                JButton kSchwer=new JButton("Hard");
                kSchwer.setBounds(20,90,90,25);
                JButton kProfi=new JButton("Pro");
                kProfi.setBounds(20,130,90,25);
                JButton kAlbtraum=new JButton("Nightmare");
                kAlbtraum.setBounds(20,170,90,25);
                JButton kQual=new JButton("Torture");
                kQual.setBounds(20,210,90,25);
    
                Schwierigkeit.add(kLeicht);
                kLeicht.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        Schwierigkeit.dispose();
                        changeDiff(1);                      
                    }
                });
                Schwierigkeit.add(kNormal);
                kNormal.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        Schwierigkeit.dispose();
                        changeDiff(2);
                    }
                });
                Schwierigkeit.add(kSchwer);
                kSchwer.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        Schwierigkeit.dispose();
                        changeDiff(3);
                    }
                });
                Schwierigkeit.add(kProfi);
                kProfi.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        Schwierigkeit.dispose();
                        changeDiff(4);
                    }
                });
                Schwierigkeit.add(kAlbtraum);
                kAlbtraum.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        Schwierigkeit.dispose();
                        changeDiff(5);
                    }
                });
                Schwierigkeit.add(kQual);
                kQual.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        Schwierigkeit.dispose();
                        changeDiff(6);
                    }
                });
            }               
        }); //if difficulty is clicked, a window to choose the difficulty is    opened
            //if a difficulty is chosen, the difficulty may be changed
        JMenuItem pause=new JMenuItem("Pause");
        game.add(pause);
        JMenuItem resume=new JMenuItem("Resume");
        game.add(resume);
        pause.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                pause.setEnabled(false);
                resume.setEnabled(true);            //if pause is clicked, the game is paused and pause 
                pauseGame();                        //cannot be clicked anymore but resume now can be
            }
        });
        resume.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                pause.setEnabled(true);
                resume.setEnabled(false);           //if resume is clicked,    the game continues and resume
                resumeGame();                       //cannot be clicked anymore but pause now can be
            }
        });
    
        max=new int[5][3];
        //highscores: 5 scores with each points[x][0], rounds[x][1] and difficulty[x][2]
    
        fenster.add(this);
        fenster.setVisible(true);   //now all is placed onto the JFrame
        initGame(); //all objects are initialized
        startGame();    //the game starts
    }
    
    public boolean isGameOver()
    {
        return gameOver;
    }
    
    public void setGameOver(boolean gameOver)
    {
        this.gameOver=gameOver;
    }
    
    public boolean isRoundWin()
    {
        return roundWin;
    }
    
    public void setRoundWin(boolean roundWin)
    {
        this.roundWin=roundWin;
    }
    
    public String schwerMax(int i)  //to return the words for the difficulty    numbers
    {
        if(i==1)
        {
            return "Easy";
        }
        else if(i==2)
        {
            return "Normal";
        }
        else if(i==3)
        {
            return "Hard";
        }
        else if(i==4)
        {
            return "Pro";
        }
        else if(i==5)
        {
            return "Nightmare";
        }
        else if(i==6)
        {
            return "Torture";
        }
        return "-";
    }
    
    private void initGame ()
    {
        createGameObjects();
    
        fenster.addKeyListener(new KeyAdapter() //controls and hotkeys
        {
            @Override
            public void keyReleased(KeyEvent e)
            {
                switch(e.getKeyCode())
                {
                    case KeyEvent.VK_A:
                    case KeyEvent.VK_LEFT:
                        player.setAngleLeft(false);
                        break;
                    case KeyEvent.VK_D:
                    case KeyEvent.VK_RIGHT:
                        player.setAngleRight(false);
    
                    case KeyEvent.VK_W:
                    case KeyEvent.VK_UP:
                        player.setAcc(false);
                        break;
                    case KeyEvent.VK_S:
                    case KeyEvent.VK_DOWN:
                        player.setDec(false);
                        break;
                }
            }
    
            @Override
            public void keyPressed(KeyEvent e)
            {
                switch(e.getKeyCode())
                {
                    case KeyEvent.VK_A:
                    case KeyEvent.VK_LEFT:
                        player.setAngleLeft(true);
                        break;
                    case KeyEvent.VK_D:
                    case KeyEvent.VK_RIGHT:
                        player.setAngleRight(true);
                        break;
    
                    case KeyEvent.VK_W:
                    case KeyEvent.VK_UP:
                        player.setAcc(true);
                        break;
                    case KeyEvent.VK_S:
                    case KeyEvent.VK_DOWN:
                        if(player.getMovingDistance()>0)
                        {
                            player.setDec(true);
                        }
                        else
                        {
                            player.setDec(false);
                            player.setMovingDistance(-1);
                        }
                        break;
    
                    case KeyEvent.VK_ESCAPE:                            
                        if(isGameOver()||isRoundWin())
                        {
                            fenster.dispose();
                        }
                        if(t.isRunning())
                        {
                            pauseGame();
                        }
                        else if(!isGameOver())
                        {
                            resumeGame();
                        }
                        break;
    
                    case KeyEvent.VK_E:
                        endGame();
                        break;
    
                    case KeyEvent.VK_ENTER:
                        if(isGameOver())
                        {
                            restartGame();
                        }
                        else if(isRoundWin())
                        {
                            continueGame();
                        }
                        break;
    
                    case KeyEvent.VK_1:
                        changeDiff(1);
                        break;
                    case KeyEvent.VK_2:
                        changeDiff(2);
                        break;
                    case KeyEvent.VK_3:
                        changeDiff(3);
                        break;
                    case KeyEvent.VK_4:
                        changeDiff(4);
                        break;
                    case KeyEvent.VK_5:
                        changeDiff(5);
                        break;
                    case KeyEvent.VK_6:
                        changeDiff(6);
                        break;
                }
            }
        });
    
        t=new Timer(20, new ActionListener()    //timer to control the ticks
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                doOnTick();
            }
        });
    }
    
    public void changeDiff(int schwer)
    {
        Object[] restart={"OK","No"};   //game has to be restarted to change    the difficulty
        int chosenRestart=JOptionPane.showOptionDialog(fenster,"In order to change the difficulty, the game has to be restarted.\nDo you really want to change the difficulty?","Warning!",JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE,null,restart,restart[0]);
        if(chosenRestart==0)    //if yes is chosen, the difficulty will change and the game restarts
        {
            this.schwer=schwer;
            restartGame();
        }
    }
    
    private void createGameObjects()
    {
        if(player==null)    //if there is no player, there will be a new one
        {
            player=new Player(new Coordinate(900,150),20,Math.toRadians(180),5);
        }
    
        if(goal==null)  //if there is no goal, there will be a new one
        {
            goal=new Goal(new Coordinate(prefSize.width-200,100),80,80);
        }
    
        initPlayer();   //player is initialized
        initSpawns();   //spawns are initialized
        obstacles=new Obstacle[schwer];
        initObstacles();    //objects are initialized
    }
    
    private void initPlayer()   //player is initialized
    {
        player.setObjectPosition(new Coordinate(40,700));
        player.setMovingAngle(Math.toRadians(-90));
    }
    
    public void initObstacles() //objects are initialized
    {
        for(int i=0;i<obstacles.length;i++)
        {
            obstacles[i]=new Obstacle(new Coordinate(90,150),70,70,5);
            obstacles[i].setMovingDistance(schwer*2+c);
            obstacleSpawn(i);
        }
    }
    
    public void obstacleSpawn(int i)    //objects spawn at a random spawn
    {
        double zufall=Math.random();
        if(zufall<=0.33)
        {
            obstacles[i].spawn(cSpawn0);
        }
        else if(zufall<=0.67)
        {
            obstacles[i].spawn(cSpawn1);
        }
        else
        {
            obstacles[i].spawn(cSpawn2);
        }
    }
    
    public void initSpawns()    //spawns are initialized
    {
        cSpawn0=new Coordinate((Math.random()*prefSize.width-200)+100,(Math.random()*prefSize.height-200)+100);
        cSpawn1=new Coordinate((Math.random()*prefSize.width-200)+100,(Math.random()*prefSize.height-200)+100);
        cSpawn2=new Coordinate((Math.random()*prefSize.width-200)+100,(Math.random()*prefSize.height-200)+100);
    
        spawn0=new Spawn();
        spawn1=new Spawn();
        spawn2=new Spawn();
    }
    
    private void startGame()    //timer starts
    {
        t.start();
    }
    
    public void pauseGame() //timer stops
    {
        t.stop();
    }
    
    public void resumeGame()    //if game is not over, timer starts again
    {
        if(!isGameOver())
        {
            t.start();
        }
    }
    
    public void continueGame()  //if the current round is won, a new one will start
    {
        if(isRoundWin())
        {
            setRoundWin(false);
            player.setMovingDistance(5);
            createGameObjects();
            startGame();
        }
    }
    
    public void restartGame()   //if a round is lost, a new game will start
    {
        setGameOver(false);
        setRoundWin(false);
        c=0;
        p=0;
        player.setMovingDistance(5);
        createGameObjects();
        startGame();
    }
    
    private void endGame()  //game is set to be over
    {
        setGameOver(true);
        pauseGame();
    }
    
    private void doOnTick() //every tick (controlled by the timer), this will happen:
    {
        if(player.getObjectPosition().getX()   <=0||player.getObjectPosition().getX()>=prefSize.width||player.getObjectPosition().getY()<=0||player.getObjectPosition().getY()>=prefSize.height)
        {
            endGame();
        }
        //if a player leaves the window, the game is over
    
        for(int i=0;i<schwer;i++)
        {
            if(obstacles[i].getObjectPosition().getX()   <=0||obstacles[i].getObjectPosition().getX()>=prefSize.width||obstacles[i].getObjectPosition().getY()<=0||obstacles[i].getObjectPosition().getY()>=prefSize.height)
            {
                obstacleSpawn(i);
            }
            //if an obstacle leaves the window, it will respawn
    
            if(player.touches(obstacles[i])||obstacles[i].touches(player))
            {
                endGame();
            }
            //if the player touches an obstacle, the game is over
        }
    
        if(player.touches(goal)||goal.touches(player))
        {
            setRoundWin(true);
            pauseGame();
            c=c+1;
            p=(int)(p+(player.getMovingDistance()*schwer));
        }
        //if the player touches the goal, the round is won and the new scores    for round and points are
        //calculated
    
        player.makeMove();  //the player is moved
    
        for(int i=0;i<schwer;i++)
        {
            obstacles[i].makeMove();
        }
        //all obstacles are moved
    
        repaint();  //the game is repainted
    }
    
    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);    //everything of the original    paintComponent is done
        Graphics2D g2d = (Graphics2D) g;    //g2d is g
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,    RenderingHints.VALUE_ANTIALIAS_ON);
        //antialiasing is turned on -> Kantenglättung
        setBackground(Color.GRAY);  //background is gray
    
        player.paintMe(g);  //player is painted
    
        for(int i=0;i<schwer;i++)
        {
            obstacles[i].paintMe(g);
        }
        //all obstacles are painted
    
        spawn0.paintMe(g,cSpawn0);
        spawn1.paintMe(g,cSpawn1);
        spawn2.paintMe(g,cSpawn2);
        //all spawns are painted
    
        goal.paintMe(g);    //goal is painted
    
        g.setFont(new Font(Font.SANS_SERIF,Font.BOLD,30));
        g.setColor(Color.BLUE);
        g.drawString("Rounds: "+c,prefSize.width-200,30);
        g.setColor(Color.RED);
        g.drawString("Points: "+p,30,30);
        //rounds and points GUI are painted
    
        if(isGameOver())
        {
            for(int i=0;i<max.length;i++)
            {
                if(p>=max[i][0])
                {
                    for(int j=max.length-1;j>i+1;j--)
                    {
                        max[j][0]=max[j-1][0];
                        max[j][1]=max[j-1][1];
                        max[j][2]=max[j-1][2];
                    }
                    max[i][0]=p;
                    max[i][1]=c;
                    max[i][2]=schwer;
                    break;
                }
            }
            //if the game is lost, all highscores are sized
    
            g.setFont(new Font(Font.SANS_SERIF,Font.BOLD,50));
            g.setColor(Color.RED);
            g.drawString("GAME OVER!",prefSize.width/2-170,prefSize.height/5);
                                        //the "GAME OVER!" is painted on the screen ...
            g.setFont(new Font(Font.SANS_SERIF,Font.BOLD,20));
            g.setColor(Color.ORANGE);
            g.drawString("Highscores",prefSize.width/2-100,prefSize.height/4);
            for(int i=0;i<max.length;i++)
            {
                g.drawString(max[i][0]+" Points",200,300+30*i);
                g.drawString(max[i][1]+" Rounds",500,300+30*i);
                g.drawString(schwerMax(max[i][2]),700,300+30*i);
            }
                                                //... and all the highscores
        }
        if(isRoundWin())
        {
            g.setFont(new Font(Font.SANS_SERIF,Font.BOLD,50));
            g.setColor(Color.BLUE);
            g.drawString("ROUND WON!",prefSize.width/2-130,prefSize.height/5);
        }   //if the round is won, this is painted on the screen
    }
    }
    
  2. The abstract Class GameObject (Player, Obstacle and Goal are game objects; I tried something different for the spawns already):

    public abstract class GameObject
    {
    private Coordinate objectPosition;
    private double width;
    private double height;
    private double movingAngle;
    private double movingDistance;
    
    public GameObject(Coordinate objectPosition,double width,double height)
    {
        this.objectPosition=objectPosition;
        this.width=width;
        this.height=height;
    
        movingAngle=0;
        movingDistance=0;
    }
    
    public Coordinate getObjectPosition()
    {
        return objectPosition;
    }
    
    public void setObjectPosition(Coordinate objectPosition)
    {
        this.objectPosition=objectPosition;
    }
    
    public double getWidth()
    {
        return width;
    }
    
    public void setWidth(double width)
    {
        this.width=width;
    }
    
    public double getHeight()
    {
        return height;
    }
    
    public void setHeight(double height)
    {
        this.height=height;
    }
    
    public double getMovingAngle()
    {
        return movingAngle;
    }
    
    public void setMovingAngle(double movingAngle)
    {
        this.movingAngle=movingAngle;
    }
    
    public double getMovingDistance()
    {
        return movingDistance;
    }
    
    public void setMovingDistance(double movingDistance)
    {
        this.movingDistance=movingDistance;
    }
    
    public boolean isLeftOf(GameObject that)
    {
        return this.getObjectPosition().getX()+this.getWidth()<that.getObjectPosition().getX();
    }
    
    public boolean isAbove(GameObject that)
    {
        return this.getObjectPosition().getY()+this.getHeight()<that.getObjectPosition().getY();
    }
    
    public boolean touches(GameObject that)
    {
        if(this.isLeftOf(that)) return false;
        if(that.isLeftOf(this)) return false;
        if(this.isAbove(that)) return false;
        if(that.isAbove(this)) return false;
    
        return true;
    }
    
    public static Coordinate polarToCartesianCoordinates(double angle)
    {
        double x=Math.cos(angle);
        double y=Math.sin(angle);
    
        return new Coordinate(x,y);
    }
    
    public void moveGameObject()
    {
        Coordinate direction=polarToCartesianCoordinates(movingAngle);
        objectPosition.setX(objectPosition.getX()+direction.getX()*movingDistance);
        objectPosition.setY(objectPosition.getY()+direction.getY()*movingDistance);
    }
    
    public void makeMove()
    {
        moveGameObject();
    }
    
    protected abstract void paintMe(Graphics g);
    }
    
  3. The Obstacle:

    public class Obstacle extends GameObject
    {
    private Shape transformedObstacle=new RoundRectangle2D.Double();
    
    public Obstacle(Coordinate position,double size,double MovingAngle,double MovingDistance)
    {
        super(position,size,size/3);
    
        this.setMovingAngle(MovingAngle);
        this.setMovingDistance(MovingDistance);
    }
    
    public Shape getTransformedObstacle()
    {
        return transformedObstacle;
    }
    
    public void setTransformedObstacle(Shape transformedObstacle)
    {
        this.transformedObstacle=transformedObstacle;
    }
    
    public void spawn(Coordinate spawn)
    {
        this.setObjectPosition(spawn);
        this.setMovingAngle(Math.toRadians(Math.random()*360));
    }
    
    public void paintMe(java.awt.Graphics g)
    {
        Graphics2D g2d=(Graphics2D) g;
        this.paintComponent(g2d);
    }
    
    public void paintComponent(Graphics2D g2d)
    {
        RoundRectangle2D quader=new RoundRectangle2D.Double(this.getObjectPosition().getX(),this.getObjectPosition().getY(),this.getWidth(),this.getHeight(),5,5);
        g2d.setColor(Color.GREEN);
    
        AffineTransform transform=new AffineTransform();
        transform.rotate(this.getMovingAngle(),quader.getCenterX(),quader.getCenterY());
        Shape transformed=transform.createTransformedShape(quader);
        g2d.fill(transformed);
    
        setTransformedObstacle(transformed);
    }
    }
    
  4. The Spawn:

    public class Spawn
    {
    public void paintMe(java.awt.Graphics g,Coordinate position)
    {
        Graphics2D g2d=(Graphics2D) g;
        paintSpawn(g2d,position);
    }
    
    public void paintSpawn(Graphics2D g2d,Coordinate position)
    {
        RoundRectangle2D spawn=new RoundRectangle2D.Double(position.getX(),position.getY(),50,50,100,100);
        g2d.setColor(Color.DARK_GRAY);
    
        g2d.fill(spawn);
    }
    }
    

Here is a video of the gouped obstacles and moving spawns: https://youtu.be/N8Rq3yKblXk

I know, this is a lot of code, but I hope, somebody can help me find the problem.

.

Cheers

Lindwurm


Solution

  • You pass the reference of spawn0 (Coordinate) to Obstacle, this then updated when the Obstacle moves, so now Obstacle and spawn0 have the same location and thus, move along with each other.

    Spawn should be constructed with it's own location (this should not be then shared), Spawn should then become responsible for the life cycle of the Obstacle and spawn new Obstacle as required (and remove old ones)