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