Spawning multiple enemies.

Alright, so i have this program that all it does so far is a path finding AI that just follows you based on your coordinates. Now, what i want to do is be able to have MULTIPLE AI’s on the screen. How would i go about doing that? I heard that i have to use an ArrayList, then what? I just shove them in there and have them drawn? If someone could provide a detailed discription of what to do that would be awesome. :c

My code for my Player and main game:


package src;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;

public class Game extends JFrame implements Runnable{
    
	// Global Variables
	
    static Rectangle player = new Rectangle(30, 50, 15, 15);
    static Rectangle object = new Rectangle(275, 175, 15, 15);
    
    private Image dbImage;
    private Graphics dbg;
    
    static AI AI = new AI(object, player);
    
    Color Player = new Color(255,202,102);
    
    int xDirection, yDirection;
    
    // Setting up the Game Window
    
    public Game(){
        setSize(700,400);
        setResizable(false);
        setLocationRelativeTo(null);
        setTitle("ZOMBIE BLOCK");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
        addKeyListener(new AL());
        
    }
    
    // Double-Buffer Graphics
    
    @Override
    public void paint(Graphics g){
        dbImage = createImage(getWidth(), getHeight());
        dbg = dbImage.getGraphics();
        paintComponent(dbg);
        g.drawImage(dbImage, 0, 0, this);
    }
    
    // Drawing the Player and AI
    
    private void paintComponent (Graphics g)  {
        g.setColor(Player);
        g.fillRect(player.x, player.y, player.width, player.height);
        AI.draw(g);
      
        repaint();
    }
    
    // Collision Detection
    
	 public void detectEdges(){
	        if(player.x <= 0)
	            setXDirection(1);
	        if(player.x >= 700-player.width)
	            setXDirection(-1);
	        if(player.y <= 25)
	            setYDirection(1);
	        if(player.y > 400-player.height)
	            setYDirection(-1);
	    }
	
    
	 // Letting the player move
	 
    public void move(){
        player.x += xDirection;
        player.y += yDirection;
    }
    
    public void setYDirection(int ydir){
        yDirection = ydir;
    }
    public void setXDirection(int xdir){
        xDirection = xdir;
    }
    
    // Key Inputs
    
    public class AL extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e){
            int keyCode = e.getKeyCode();
            if(keyCode == e.VK_A){
            	setXDirection(-1);
            }
            if(keyCode == e.VK_D){
                setXDirection(1);
            }
            if(keyCode == e.VK_W){
                setYDirection(-1);
            }
            if(keyCode == e.VK_S){
            	setYDirection(1);
            }
            	
            }
        

		@Override
        public void keyReleased(KeyEvent e){
            int keyCode = e.getKeyCode();
            if(keyCode == e.VK_A){
                setXDirection(0);
            }
            if(keyCode == e.VK_D){
                setXDirection(0);
            }
            if(keyCode == e.VK_W){
                setYDirection(0);
            }
            if(keyCode == e.VK_S){
                setYDirection(0);
            }
     
       }
    }

    
    // Starting the threads
    
    public static void main(String[] args) {
        Game Game = new Game();
        Thread t = new Thread(Game);
        t.start();
        Thread t1 = new Thread(AI);
        t1.start();
    }
    
    // Letting the threads run.
    
    @Override
    public void run(){
    	
        try{
            while(true){
                move();
                detectEdges();
                Thread.sleep(5);
            }
        }catch(Exception e){
            System.err.println(e.getMessage());
        }
    }
}

And this is the code for my AI or my Zombie:


package src;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Random;

public class AI implements Runnable{
	
	Rectangle AI, target;
	
	boolean resting;
	boolean shouldSetRandDir = true;
	
	static int xDirection, yDirection;
	
	Color Zombie = new Color(0,149,0);

	public AI (Rectangle r, Rectangle t) {
		
		AI = r;
		target = t;
		
	}
	
	public void draw(Graphics g){
		
		g.setColor(Zombie);
		if (AI != null) {
			
			// Draw Rectangle
			g.fillRect(AI.x, AI.y, AI.width, AI.height);
			
			
		}
		
	}
	
	public void findPathToTarget(){
		
		if (AI.x < target.x) {
			setxDir(1);
		}
		if (AI.x > target.x) {
			setxDir(-1);
		}
		if (AI.y < target.y) {
			setyDir(1);
		}
		if (AI.y > target.y) {
			setyDir(-1);
		}
		
		
	}
	
	
	 public void detectEdges(){
	        if(AI.x <= 0)
	            setxDir(1);
	        if(AI.x >= 700-AI.width)
	            setxDir(-1);
	        if(AI.y <= 25)
	            setyDir(1);
	        if(AI.y > 400-AI.height)
	            setyDir(-1);
	    }
	
	public void setxDir(int xdir){
		
		xDirection = xdir;
		
	}
	public void setyDir(int ydir){
		
		yDirection = ydir;
		
	}
	public void move(){
		
		AI.x += xDirection;
		AI.y += yDirection;
		
	}
	
	@Override
	public void run() {
		
		try {
			
			while(true){
				
				if (!resting) {
					
					
					long start = System.currentTimeMillis();
					long end = start + 1*1000;
					while(System.currentTimeMillis() < end ) {	
						findPathToTarget();
						move();
						detectEdges();
						// Speed in which it moves
						Thread.sleep(15);
					}
					
					resting = true;
					
				} else {
					
					Thread.sleep(50);
					shouldSetRandDir = true;
					resting = false;
					
				}
			}
			
		}catch(Exception e) {
			
			System.out.println("ERROR");
			
		}
		
		
	}

}


Here’s some pseudo-code.

List<Entity> entities = new ArrayList<Entity>();
	
	public void init() {
		for (int i = 0; i < ENTITIES_TO_BE_SPAWNED; i++) {
			entities.add(new Mob());
		}
		
		entities.add(new Player());
	}
	
	public void tick() {
		for (Entity entity : entities) entity.tick();
	}
	
	public void render() {
		for (Entity entity : entities) entity.render();
	}

Then just have Player and Mob extend Entity.

Hope this helps. If you have any other questions, please ask.

-Nathan

Thank you for responding so quickly! (Love your profiles avatar by the way) now try to add another AI instance into the ArrayList im needing to enter two Rectangles as parameters. The only two rectangles i have that i have declared is my AI and my Player? But that wouldnt make sense having to pass through my player for a zombie? How would i go about doing that?

Are the rectangles used by the player and the mob the same every time? If so, just put them in their respective classes.

If the rectangles are created differently based on coordinates or something, then make a constructor in the class and define the rectangle then.


public class Mob extends Entity {
     public Mob(int x, int y, int width, int height) {
          super(int x, int y, int width, int height);
     }
}


public class Entity {
     public Rectangle rectangle;

     public Entity(int x, int y, int width, int height) {
          this.rectangle = new Rectangle(x, y, width, height);
     }
}

Or something like that.

-Nathan

Alright, so i entered the code and fixed it to my projects liking. When i run it i still have only 1 AI on the screen. Why is that? By the way the code below has the Output() method which i used to debug what was wrong. Apparently there’s 10 zombies in the list.

Here’s the new Main Code:


package src;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;

public class Game extends JFrame implements Runnable{
    
	// Global Variables
	
    static Rectangle player = new Rectangle(30, 50, 15, 15);
    static Rectangle object = new Rectangle(275, 175, 15, 15);
    
    List<AI> zombies = new ArrayList<AI>();
    
    private Image dbImage;
    private Graphics dbg;
    
    static AI AI = new AI(object, player);
    
    Color Player = new Color(255,202,102);
    
    int xDirection, yDirection;
    
    
    // Setting up the Game Window
    
    public Game(){
        setSize(700,400);
        setResizable(false);
        setLocationRelativeTo(null);
        setTitle("zombies BLOCK");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
        addKeyListener(new AL());
        
     	for (int i = 0; i < 10; i++) {
            zombies.add(new AI(object, player));
         }
        
    }
    
    public void output () {
    	
    	int len = zombies.size();
    	System.out.println(len);
    	
    }
    
    // Double-Buffer Graphics
    
    @Override
    public void paint(Graphics g){
        dbImage = createImage(getWidth(), getHeight());
        dbg = dbImage.getGraphics();
        paintComponent(dbg);
        g.drawImage(dbImage, 0, 0, this);
    }
    
    // Drawing the Player and AI
    
    private void paintComponent (Graphics g)  {
        g.setColor(Player);
        g.fillRect(player.x, player.y, player.width, player.height);
        for (AI AI : zombies) AI.draw(g);
        repaint();
    }
    
    // Collision Detection
    
	 public void detectEdges(){
	        if(player.x <= 0)
	            setXDirection(1);
	        if(player.x >= 700-player.width)
	            setXDirection(-1);
	        if(player.y <= 25)
	            setYDirection(1);
	        if(player.y > 400-player.height)
	            setYDirection(-1);
	    }
	
    
	 // Letting the player move
	 
    public void move(){
        player.x += xDirection;
        player.y += yDirection;
    }
    
    public void setYDirection(int ydir){
        yDirection = ydir;
    }
    public void setXDirection(int xdir){
        xDirection = xdir;
    }
    
    // Key Inputs
    
    public class AL extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e){
            int keyCode = e.getKeyCode();
            if(keyCode == e.VK_A){
            	setXDirection(-1);
            }
            if(keyCode == e.VK_D){
                setXDirection(1);
            }
            if(keyCode == e.VK_W){
                setYDirection(-1);
            }
            if(keyCode == e.VK_S){
            	setYDirection(1);
            }
            	
            }
        

		@Override
        public void keyReleased(KeyEvent e){
            int keyCode = e.getKeyCode();
            if(keyCode == e.VK_A){
                setXDirection(0);
            }
            if(keyCode == e.VK_D){
                setXDirection(0);
            }
            if(keyCode == e.VK_W){
                setYDirection(0);
            }
            if(keyCode == e.VK_S){
                setYDirection(0);
            }
     
       }
    }

    
    // Starting the threads
    
    public static void main(String[] args) {
        Game Game = new Game();
        Thread t = new Thread(Game);
        t.start();
        Thread t1 = new Thread(AI);
        t1.start();
    }
    
    // Letting the threads run.
    
    @Override
    public void run(){
    	
        try{
            while(true){
            	output();
                move();
                detectEdges();
                Thread.sleep(5);
            }
        }catch(Exception e){
            System.err.println(e.getMessage());
        }
    }
}

and this is the AI’s code:


package src;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Random;

public class AI implements Runnable{
	
	Rectangle AI, target;

	boolean resting;
	boolean shouldSetRandDir = true;
	
	static int xDirection, yDirection;
	
	Color Zombie = new Color(0,149,0);
	
	public AI (Rectangle r, Rectangle t) {
		
		AI = r;
		target = t;
		
	}


	public void draw(Graphics g){
		
		g.setColor(Zombie);
		if (AI != null) {
			
			// Draw Rectangle
			g.fillRect(AI.x, AI.y, AI.width, AI.height);
			
			
		}
		
	}
	
	public void findPathToTarget(){
		
		if (AI.x < target.x) {
			setxDir(1);
		}
		if (AI.x > target.x) {
			setxDir(-1);
		}
		if (AI.y < target.y) {
			setyDir(1);
		}
		if (AI.y > target.y) {
			setyDir(-1);
		}
		
		
	}
	
	
	 public void detectEdges(){
	        if(AI.x <= 0)
	            setxDir(1);
	        if(AI.x >= 700-AI.width)
	            setxDir(-1);
	        if(AI.y <= 25)
	            setyDir(1);
	        if(AI.y > 400-AI.height)
	            setyDir(-1);
	    }
	
	public void setxDir(int xdir){
		
		xDirection = xdir;
		
	}
	public void setyDir(int ydir){
		
		yDirection = ydir;
		
	}
	public void move(){
		
		AI.x += xDirection;
		AI.y += yDirection;
		
	}
	
	@Override
	public void run() {
		
		try {
			
			while(true){
				
				if (!resting) {
					
					
					long start = System.currentTimeMillis();
					long end = start + 1*1000;
					while(System.currentTimeMillis() < end ) {	
						findPathToTarget();
						move();
						detectEdges();
						// Speed in which it moves
						Thread.sleep(15);
					}
					
					resting = true;
					
				} else {
					
					Thread.sleep(50);
					shouldSetRandDir = true;
					resting = false;
					
				}
			}
			
		}catch(Exception e) {
			
			System.out.println("ERROR");
			
		}
		
		
	}

}


You ARE drawing all 10 of them, but you are drawing them at the exact same coordinates.

Try this:


Rectangle object;
	Random random = new Random();
	private int WIDTH = 700, HEIGHT = 400, objectWidth = 15, objectHeight = 15;
	
		for (int i = 0; i < 10; i++) {
			object = new Rectangle(random.nextInt(WIDTH - objectWidth), random.nextInt(HEIGHT - objectHeight), objectWidth, objectHeight);
			zombies.add(new AI(object, player));
		}
	}

Also… why are you passing the player instance to every zombie?

-Nathan

EDIT: Actually, try removing this line, too:


static AI AI = new AI(object, player);

Since the AI object is gone i have nothing to point the t1 Thread too. :frowning: Im sorry for being such a noob, bro.

In that case, simply rename this

for (AI AI : zombies) AI.draw(g);

to this

for (AI zombie : zombies) zombie.draw(g);

See if that works. Otherwise, I’ll ask ra4king to take a look.

-Nathan

You never start the threads of the new AI-objects you create in the for-loop. There’s probably a smarter way to implement this, but I’ll show you something that should work.
Assuming you’ve already implemented the fix posted by StonePickaxes, do this.

Change your entire Main to this:


public static void main(String[] args) {
// Create a helper to control the threads. It can execute any Runnable class.
ExecutorService threadExecutor = Executors.newCachedThreadPool();

// Instanciate your Game-class, which creates the AI-instances in it's constructor
Game Game = new Game();

// Starts your zombies
for(AI zombie : game.getZombies()) {
   threadExecutor.execute(zombie);
}

// Execute your runnable game-class
threadExecutor.execute(t);

// When all threads are done running, ThreadExecutor cleans up
threadExecutor.shutdown();
}

OH GOD…NEVER…EVER…EVER…use threads for each entity…EVER :persecutioncomplex:

True that! But its a nice way to learn about threads. I’m hoping this isn’t for something bigger, though :wink:

If you want this to end up being a game or any sort of bigger program than it already is, you should really take a look at the threads on game-loops around this forum. Take a days chill and read. It’ll be good for you. I spent 3 weeks chugging at that log, hehe. ra4king really helped me on that one.

Here’s an example:
Game-class with a few balls bouncing
Ball-class
I’ve commented it a bit.

Here’s a great post on game-loops

Using threads: +1
Using threads like that: -1

People need to start threading games soon or we’ll be in deep shit in the near future.

Absolutely right. I remember my first game, I did that by accident. RAM usage went up and up and up…
like 1300 threads on the profiler…

FTFY ;D

Thank you StonePickaxe and Ultroman! :smiley: I used Ultromans last little code for my main. But the getZombies() you had wasn’t a method. So i just used my ArrayList and that worked. Even though that worked is that still considered ‘bad practice’?

WHEW. I did not expect something that seems as simple as this to get so complicated. xD

I assumed you had a getter for that ArrayList, or that you’d catch the drift :wink:
I’m happy it worked for you. But no, it is a very intricate way you’ve gone about this. There’s no need to have a separate thread for each of them. If you take a look at the example posted in my last post, and consider the Balls as your zombies, you can get an idea of how to build it better.

The method-calls in your run() in your AI-class should be moved to an update(int deltaTime) method in AI, which you call just like I call the update-method in my Ball-class. Then you can add a delay-timer (adding a new global variable int timer; as such (pseudo-code!):

public void update(int deltaTime){
if(timer>=150){
run the methods
timer=0;
}
else timer += deltaTime;
}

This will make your zombies update/move every 150ms (or whatever you wish). You should also move them with a double velocity like I move the balls around.

I’ve seen “static AI AI = new AI(object, player);” somewhere (just copied it and moved downwards). It just felt wrong somehow.
If you said that you get only ONE AI, that may be the problem?

Maybe you could rename the “AI” class to “Zombie” class? It would sure make the code easier to read. But that may be a personal thing.

On using threads: I’d avoid using them like the plague before finishing your first game. They’re a source of much complexity and unhappiness, and little gain in most situations.

Good luck with the zombies :slight_smile:

Excuse me if this has been said before.

Usually, I just make a List (either LinkedList, or ArrayList depending on the situation) and roll through it for updating and drawing.


init() {
   arrayList<Entity> = new ArrayList<Entity>();
   // fill it!
}

update(delta i) {
   for (Entity t : arrayList) {
      // update one entity in a uniform manner
   }
}

render(Graphics g) {
   for (Entity t : arrayList) {
      // render one entity in a uniform manner.
      // you can grab the fields from the specific entity using the t-object
   }
}

You can’t add/delete entities this way though - that’s a different problem.

I personally like the symmetry of having ‘tick’ matching ‘draw’ or ‘render’ matching ‘update’. That’s just me though.