Search code examples
javaexceptiongraphicsapplet

Error solution and help improving enemie spawn methode


I tried myself on a little "project" . It is basically a survival game. You move by using W,A,S,D and you shoot in different directions by using the arrow keys. You basically have to survive for as long as possible. The enemies follow you and they freeze on hit and start moving again after about 3 seconds.

The code is the following (execute the "window" class)

Window-class

package TestGame;
    import java.awt.Graphics;



public class Window extends GameIntern{

public void init(){
    setSize(windowX,windowY);   
    Thread th = new Thread(this);
    th.start();
    offscreen = createImage(windowX,windowY);
    d = offscreen.getGraphics();
    addKeyListener(this);
}


public void paint(Graphics g){
    d.clearRect(0,0,windowX,windowY);//clear window
    d.drawString(numberEnemies.toString(), 10, 10);//"Score" number of enemies displayed
    d.drawRect(x, y, playerWidth, playerHeight);//draw player
    for(Enemy e : enemies){//draw all enemies
        d.drawRect(e.getx(), e.gety(), playerWidth, playerHeight);
    }
    for(Bullet b : bullets){//draw all bullets
        d.drawOval(b.getx(), b.gety(), bulletSize, bulletSize);
    }
    g.drawImage(offscreen,0,0,this);
}


public void update(Graphics g){
    paint(g);
}
}

GameIntern-class

    package TestGame;


import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Random;

public class GameIntern extends Applet implements Runnable , KeyListener {

    public int windowX = 854;//size of the window in x direction
    public int windowY = 460;//size of the window in y direction
    public static int x;//x-coordinate of player
    public static int y;//y-coordinate of player
    public int playerpositionX = x;
    public int playerpositionY = y;
    public int playerHeight = 20;//player height
    public int playerWidth = 20;//player width
    public int playerSpeed = 3;//pixel per frame
    public int bulletSize = 5;//diameter of bullets
    public int spawnTime = 4;//time for new enemies to spawn in seconds
    public int enemySleepTime = 180;//Time an enemy does nothing in Frames per second (180 in 60fps = 3sec)
    public boolean alive = true;
    public Image offscreen;
    public Graphics d;
    public boolean up,down,left,right;
    private int delay;
    private Random random= new Random();
    public Integer numberEnemies = new Integer(enemies.size());
    protected static ArrayList<Enemy> enemies = new ArrayList<Enemy>(); //List of all enemies
    protected static ArrayList<Bullet> bullets = new ArrayList<Bullet>();//List of all bullets
    protected static ArrayList<PowerUps> pUps = new ArrayList<PowerUps>();//List of all powerUps


    public void run() {
        this.x = 400;//startingposition x
        this.y = 240;//startingposition y

        double ns = 1000000000.0 / 60.0;    //60 "frames" 
        double delta = 0;
        long lastTime = System.nanoTime();
        while (alive) {
            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;
            while (delta >= 1) {
                repaint();
                tick();
                collisionEnemy();
//              collisionPowerUp();
                delta--;
            }
        }
    }

    /**
     * Method to calculate all objects and their positions per frame 
     */
    private void tick() {
        if(left == true){
            if(x>=0 + playerSpeed){
                x-=playerSpeed;
            }else{ x=0;}//Farthest left x-coordinate
            repaint();
        }
        if(right == true){
            if(x<=windowX - playerWidth - playerSpeed){
                x+=playerSpeed;
            }else{ x=windowX - playerWidth;}//Farthest right x-coordinate
            repaint();
        }
        if(up == true){
            if(y>=0 + playerSpeed){
                y-=playerSpeed;
            }else{ y=0;}//Highest y-coordinate
            repaint();
        }
        if(down == true){
            if(y<=windowY - playerHeight - playerSpeed){
                y+=playerSpeed;
            }else{y=windowY - playerHeight;}//Lowest y-coordinate
            repaint();
        }
        for (Enemy e : enemies) {   //Tick every enemy
            e.tick();
        }
        for (Bullet b : bullets){   //Tick every bullet
            b.tick();
        }
        if(delay % (60 * spawnTime) == 0){  //Spawn enemy 
            enemies.add(new Enemy(random.nextInt(windowX), random.nextInt(windowY)));
            numberEnemies++;
        }
        delay++;

        for(Enemy e : enemies){         //collision : enemy & bullet
            for(Bullet b : bullets){
                if(b.getx()+bulletSize >= e.getx() && b.getx() <= e.getx()+20){
                    if(b.gety()+bulletSize >= e.gety() && b.gety() <= e.gety()+20){
                        e.setHit();
                        b.setRemove();
                    }
                }
            }
        }
        for(int i = 0; i< bullets.size(); i++){ //Remove bullets from ArrayList 
            if(bullets.get(i).remove){
                bullets.remove(i);
            }
        }

    }

    public void keyPressed(KeyEvent e) {

        if(e.getKeyCode() == 65){//W
            left=true;
        }
        if(e.getKeyCode() == 87){//A
            up=true;
        }
        if(e.getKeyCode() == 68){//S
            right=true;
        }
        if(e.getKeyCode() == 83){//D
            down=true;
        }
    }


    public void keyReleased(KeyEvent e) {

        if(e.getKeyCode() == 65){//Arrowkey left
            left=false;
        }
        if(e.getKeyCode() == 87){//Arrowkey up
            up=false;
        }
        if(e.getKeyCode() == 68){//Arrowkey right
            right=false;
        }
        if(e.getKeyCode() == 83){//Arrowkey dowm
            down=false;
        }
        if(e.getKeyCode() == 37){//Arrowkey left
            bullets.add(new Bullet(x,y,false,false,true,false)); //Direction the bullet has to go 
        }
        if(e.getKeyCode() == 38){//Arrowkey up
            bullets.add(new Bullet(x,y,true,false,false,false));//Direction the bullet has to go
        }
        if(e.getKeyCode() == 39){//Arrowkey right
            bullets.add(new Bullet(x,y,false,false,false,true));//Direction the bullet has to go
        }
        if(e.getKeyCode() == 40){//Arrowkey down
            bullets.add(new Bullet(x,y,false,true,false,false));//Direction the bullet has to go
        }
    }

    public void keyTyped(KeyEvent e){}

    /**
     * Method to see if the player collided with an enemy
     */
    public void collisionEnemy(){
        for(Enemy e : enemies){ 
            for(int i = 0;i <= playerWidth; i++){
                if(GameIntern.x+i >= e.getx() && GameIntern.x+i <= e.getx()+playerWidth){
                    if(GameIntern.y+i >= e.gety() && GameIntern.y+i <= e.gety()+playerHeight){
                        alive = false;
                    }
                }
            }
        }
    }

//  public void addEnemy(){
//      enemies.add(new Enemy(random.nextInt(windowX), random.nextInt(windowY)));
//      
//      //Spawn enemies inside the filed, not outside the boarder
//      if (playerpositionX < playerWidth * 2 || playerpositionX * 2 > windowX - 2*playerWidth || playerpositionY * 2 > windowY - 2*playerHeight || playerpositionY < playerHeight * 2){
//              enemies.add(new Enemy(random.nextInt(windowX - 3*playerWidth), random.nextInt(windowY - 3*playerHeight)+3*playerHeight));   
//      }else {
//          int temp1 = random.nextInt(windowX-3*playerWidth);
//          if (temp < playerpositionX){
//              
//          }
//          enemies.add(new Enemy(random.nextInt(windowX), random.nextInt(windowY)));
//      }
//  
//  }
}

Bullet-class

package TestGame;

public class Bullet extends GameIntern{
    public int x,y;
    public boolean up,down,left,right;
    public boolean remove;

    public Bullet(int x, int y,boolean up,boolean down, boolean left, boolean right){
        this.x = x + 8;
        this.y = y + 8;
        this.up = up;
        this.down = down;
        this.left = left;
        this.right = right;
    }

    public int getx(){
        return this.x;
    }
    public int gety(){
        return this.y;
    }
    public void setRemove(){
        remove=true;
    }

    public void tick() {

        if (up == true) y-=2;
        if (down == true) y+=2;
        if (left == true) x-=2;
        if (right == true) x+=2;
        if(x < 0){
            remove = true;
        }
        if(x > 840){
            remove = true;
        }
        if(y < 0){
            remove = true;
        }
        if(y > 470){
            remove = true;
        }
    }
}

Enemy-class

package TestGame;

public class Enemy extends GameIntern {

    public int x,y;
    public boolean hit = false;
    public int counter = 0;

    public Enemy(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getx(){
        return this.x;
    }
    public int gety(){
        return this.y;
    }
    public void setHit(){
        hit = true;
        counter = enemySleepTime;
    }

    public void tick() {
        if(counter == 0){
            if(hit == true){
                hit=false;
            }
            if (x < GameIntern.x) x++;
            if (x > GameIntern.x) x--;
            if (y < GameIntern.y) y++;
            if (y > GameIntern.y) y--;
        }else {counter--;}
    }
}

After playing for a while i get a

java.util.ConcurrentModificationException

What does that mean?

Also, I struggle to improve the enemy spawn prcess. Right now it sometimes happens, that enemies spawn inside the player. I want to have an imaginary box around the player where enemies dont spawn inside and enemies should always spawn inside the window.

If you have any questions, please feeld free to ask :)

sincerely Viktor


Solution

  • From the JavaDocs:

    The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException.

    The problem is likely caused because you're removing bullets from the ArrayList while trying to paint them in a separate thread. The issue is that the two threads are iterating over the bullets list simultaneously.

        //The following for loop is likely the cause
        for(int i = 0; i< bullets.size(); i++){ 
            if(bullets.get(i).remove){
                bullets.remove(i); // <-- this is the problem 
            }
        }
    

    Instead, try using a thread-safe implementation, e.g.

    protected static List<Enemy> enemies = 
        Collections.synchronizedList(new ArrayList<Enemy>()); //List of all enemies
    protected static List<Bullet> bullets = 
        Collections.synchronizedList(new ArrayList<Bullet>());//List of all bullets
    

    Furthermore, you should change the remove code to avoid an IndexOutOfBoundsException:

        LinkedList<Bullet> bulletsToRemove = new LinkedList<>();
        for(Enemy e : enemies){         //collision : enemy & bullet
            for(Bullet b : bullets){
                if(b.getx()+bulletSize >= e.getx() && b.getx() <= e.getx()+20){
                    if(b.gety()+bulletSize >= e.gety() && b.gety() <= e.gety()+20){
                        e.setHit();
                        bulletsToRemove.add(b);
                    }
                }
            }
        }
        for(Bullet b : bulletsToRemove){ //Remove bullets from ArrayList 
            bullets.remove(b);
        }
    

    Regarding the spawning process, a simplistic approach is to just define a minimum distance from the player, call it minDist. Now just pick a random location anywhere on the screen, call it p1. If p1 is less than minDist away from the player, pick a new random location. As long as the player and enemy sprites are relatively small compared to the screen area, this approach should work well.