2

So I'm making a game and I have the EnemyAI as well as the player and they both extend JPanel. The world has a null layout, so I'm using setBounds(); to "move" (I actually just move the world image) the entities (player and AI) around and position them properly. But when I add (what seems like, I tested for the smallest number possible) 5, it invokes repaint() completely on it's own. This causes the player to visually be walking in place. The more entities I add, the faster the interval gets (i.e. 5 entities invokes repaint() a lot slower than 500 does).

NOTE: window in the below class is just a JFrame.

Main Class:

public class Game(){
public static boolean fighting = false;
public static void startGame(){
    WorldPanel game = new WorldPanel();
    game.setPreferredSize(new Dimension(window.getWidth(), window.getHeight()));
    PlayerPane player = new PlayerPane(32,32, "Player 1");
    game.addKeyListener(new KeyListener(){

        public void keyPressed(KeyEvent arg0) {
            if(fighting == false){
                move(player, game, arg0.isShiftDown(), arg0.getKeyCode());
            } else {
                System.out.println("Fighting = " + fighting);
            }
        }
        @Override
        public void keyReleased(KeyEvent arg0) {    
            gameTimer.stop();
            player.setIndex(0);
            game.repaint();
        }
        @Override
        public void keyTyped(KeyEvent arg0) {
        }
    });
    window.add(game);
    game.setLayout(null);
    game.requestFocus();
    setImages(player, PLAYER_DOWN);
    player.setDrawY(349);
    player.setDrawX(493);
    player.updateBounds();
    game.add(player);
    entities.add(player);
    addEntities(game);
    redoWindow();
}
public static void updateEntityBounds(){
    PlayerPane player = null;
    EnemyAI enemy = null;
    for(int i = 0; i < entities.size(); i++){
        if(i == 0){
            player = (PlayerPane) entities.get(i);
        } else {
            enemy = (EnemyAI) entities.get(i);
            if(enemy.getBounds().intersects(player.getBounds())){
                startFight(i);
            }
        }
        if(player != null){
            player.updateBounds();
        }
        if(enemy != null){
            enemy.updateBounds();
        }
    }
}
public static void addEntities(WorldPanel game){
    EnemyAI enemies[] = new EnemyAI[5];
    for(int i = 0; i < enemies.length; i++){
        if(i%6 == 0){
            try{
                enemies[i] = new EnemyAI(32,32, ImageIO.read(new File("H:\\Java\\Game\\src\\res\\SlimeLv3Still.png")));
                enemies[i].start();
            }catch(IOException e){
                e.printStackTrace();
            }
        }else if (i % 2 == 0){
            try{
                enemies[i] = new EnemyAI(32,32, ImageIO.read(new File("H:\\Java\\Game\\src\\res\\SlimeLv2Still.png")));
                enemies[i].setEnX(enemies[i].getRandomX());
                enemies[i].setEnY(enemies[i].getRandomY());
                enemies[i].start();
            }catch(IOException e){
                e.printStackTrace();
            }
        } else {
            try{
                enemies[i] = new EnemyAI(32,32, ImageIO.read(new File("H:\\Java\\Game\\src\\res\\SlimeLv1Still.png")));
                enemies[i].setEnX(enemies[i].getRandomX());
                enemies[i].setEnY(enemies[i].getRandomY());
                enemies[i].start();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
        game.add(enemies[i]);
        entities.add(enemies[i]);
    }
}
    public static void move(PlayerPane player, WorldPanel game, boolean shiftDown, int keyPressed){
     gameTimer = new Timer(50, new ActionListener(){
        public void actionPerformed(ActionEvent e){
            updateEntityBounds();
            redoWindow();
            gameTimer.stop();
        }
    });
    if(gameTimer.isRepeats()){
        gameTimer.setRepeats(false);
    }
        if(shiftDown){
            if(keyPressed == KeyEvent.VK_W && ((game.getImageY() == 0 && player.getDrawY() > 10) || player.getDrawY() >= 349)){
                player.setDrawY(player.getDrawY() - 2);
                setImages(player, PLAYER_UP);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_S && ((game.getImageY() == -3868 && player.getDrawY() < 681) || player.getDrawY() <= 349)){
                player.setDrawY(player.getDrawY() + 2);
                setImages(player, PLAYER_DOWN);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_A && ((game.getImageX() == 0 && player.getDrawX() > 10) || player.getDrawX() > 493 )){
                player.setDrawX(player.getDrawX() - 2);
                setImages(player, PLAYER_LEFT);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_D && ((game.getImageX() == -5126 && player.getDrawX() < player.getHeight() - 10) || player.getDrawX() < 493 )){
                player.setDrawX(player.getDrawX() + 2);
                setImages(player, PLAYER_RIGHT);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_W && game.getImageY() < 0){
                if(game.getImageY() == -1){
                    game.setImageY(game.getImageY() + 1);
                } else {
                    game.setImageY(game.getImageY() + 2);
                }
                for(int i = 1; i < entities.size(); i++){
                    EnemyAI enemy = (EnemyAI)entities.get(i);
                    enemy.setEnY(enemy.getEnY() + 2);
                }
                setImages(player, PLAYER_UP);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_A && game.getImageX() < 0){
                if(game.getImageX() == -1){
                    game.setImageX(game.getImageX() + 1);
                } else {
                    game.setImageX(game.getImageX() + 2);
                }
                for(int i = 1; i < entities.size(); i++){
                    EnemyAI enemy = (EnemyAI)entities.get(i);
                    enemy.setEnX(enemy.getEnX() + 2);
                }
                setImages(player, PLAYER_LEFT);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_S && game.getImageY() > -3868){
                if(game.getImageY() == -3867){
                    game.setImageY(game.getImageY() - 1);
                } else {
                    game.setImageY(game.getImageY() - 2);
                }
                for(int i = 1; i < entities.size(); i++){
                    EnemyAI enemy = (EnemyAI)entities.get(i);
                    enemy.setEnY(enemy.getEnY() - 2);
                }
                setImages(player, PLAYER_DOWN);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_D && game.getImageX() > -5126){
                if(game.getImageX() == -5125){
                    game.setImageX(game.getImageX() - 1);
                } else {
                    game.setImageX(game.getImageX() - 2);
                }
                for(int i = 1; i < entities.size(); i++){
                    EnemyAI enemy = (EnemyAI)entities.get(i);
                    enemy.setEnX(enemy.getEnX() - 2);
                }
                setImages(player, PLAYER_RIGHT);
                gameTimer.start();

            }
        } else {
            if(keyPressed == KeyEvent.VK_W && ((game.getImageY() == 0 && player.getDrawY() > 10) || player.getDrawY() > 349)){
                player.setDrawY(player.getDrawY() - 1);
                setImages(player, PLAYER_UP);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_S && ((game.getImageY() == -3868 && player.getDrawY() < 681) || player.getDrawY() < 349)){
                player.setDrawY(player.getDrawY() + 1);
                setImages(player, PLAYER_DOWN);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_A && ((game.getImageX() == 0 && player.getDrawX() > 10) || player.getDrawX() > 493 )){
                player.setDrawX(player.getDrawX() - 1);
                setImages(player, PLAYER_LEFT);
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_D && ((game.getImageX() == -5126 && player.getDrawX() < player.getHeight() - 10) || player.getDrawX() < 493 )){
                player.setDrawX(player.getDrawX() + 1);
                setImages(player, PLAYER_RIGHT);
                gameTimer.start();
            } else
                if(keyPressed == KeyEvent.VK_W && game.getImageY() < 0){
                game.setImageY(game.getImageY() + 1);
                setImages(player, PLAYER_UP);
                for(int i = 1; i < entities.size(); i++){
                    EnemyAI enemy = (EnemyAI)entities.get(i);
                    enemy.setEnY(enemy.getEnY() + 1);
                }
                gameTimer.start();
            } else
            if(keyPressed == KeyEvent.VK_A && game.getImageX() < 0){
                game.setImageX(game.getImageX() + 1);
                setImages(player, PLAYER_LEFT);
                for(int i = 1; i < entities.size(); i++){
                    EnemyAI enemy = (EnemyAI)entities.get(i);
                    enemy.setEnX(enemy.getEnX() + 1);
                }
                gameTimer.start();

            } else
            if(keyPressed == KeyEvent.VK_S && game.getImageY() > -3868){
                game.setImageY(game.getImageY() - 1);
                setImages(player, PLAYER_DOWN);
                for(int i = 1; i < entities.size(); i++){
                    EnemyAI enemy = (EnemyAI)entities.get(i);
                    enemy.setEnY(enemy.getEnY() - 1);
                }
                gameTimer.start();                              
            } else
            if(keyPressed == KeyEvent.VK_D && game.getImageX() > -5126){
                game.setImageX(game.getImageX() - 1);
                setImages(player, PLAYER_RIGHT);
                for(int i = 1; i < entities.size(); i++){
                    EnemyAI enemy = (EnemyAI)entities.get(i);
                    enemy.setEnX(enemy.getEnX() - 1);
                }
                gameTimer.start();
            }
        }
    }
}

Player:

public class PlayerPane extends JPanel{
    private static final long serialVersionUID = 8946273935579723365L;
    private ImageIcon images[] = new ImageIcon[9];
    public static final int PLAYER_FRAMES = 9;
    private String name;
    private int index;
    private int imageX, imageY;
    private int HP = 100;



    public PlayerPane(int width, int height, String playerName) {
            setPreferredSize(new Dimension(width, height));
            imageX = imageY = 0;
            name = playerName;
        //      Border border = BorderFactory.createBevelBorder(BevelBorder.RAISED);
        //      PlayerPane.this.setBorder(border);
        }
        public String getName(){
            return name;
        }
        public void paintComponent(Graphics g){
            try {
                g.drawImage(ImageIO.read(new File("H:\\Java\\Game\\src\\res\\TransparentImg.png")),0,0,getWidth(),getHeight(), null);
            } catch (IOException e) {
                e.printStackTrace();
            }
                g.drawImage(images[index].getImage(), 0,0, null);

            if(index == images.length-1){
                index = 0;
            } else {
                index++;
            }
        }
        public void setIndex(int index){
            this.index = index;
        }
        public void stop(){
            index = 0;
        }
        public void addImage(ImageIcon image, int x){
            images[x] = image;
        }
        public void setDrawX(int x){
            imageX = x;
        }
        public void setDrawY(int y){
            imageY = y;
        }
        public int getDrawY(){
            return imageY;
        }
        public int getDrawX(){
            return imageX;
        }
        public void updateBounds(){
            setBounds(imageX, imageY, 32,32);
        }
        public int getHP(){
                return HP;
            }
            public void setHP(int hp){
                HP = hp;
            }
        }

EnemyAI class:

public class EnemyAI extends AI{
    private static final long serialVersionUID = -2417438750134536982L;

    private Rectangle rect;
    private BufferedImage backgroundImg;
    private int HP = 1;
    private int damage = 15;

    public EnemyAI(int width, int height, BufferedImage backgroundImg){
        super();
        rect = new Rectangle(width, height);
        this.backgroundImg = backgroundImg;
    }
    @Override
    public void paintComponent(Graphics g) {
        try{
            g.drawImage(ImageIO.read(new File("H:\\Java\\Game\\src\\res\\TransparentImg.png")), 0,0,null);
        }catch(IOException e){
            e.printStackTrace();
        }

        g.drawImage(backgroundImg, 0, 0, null);
    }
    public boolean intersects(Rectangle r){
        return rect.intersects(r);
    }
    public int getRandomX(){
        Random ran = new Random();
        int rand = ran.nextInt(6144);
        return rand;
    }
    public int getRandomY(){
        Random ran = new Random();
        int rand = ran.nextInt(4608);
        return rand;
    }
    public int getHP(){
        return HP;
    }
    public void setHP(int hp){
        HP = hp;
    }
    public int getDamage(){
        return damage;
    }
}

AI Class that the above class extends:

public abstract class AI extends JPanel implements Runnable{
    private static final long serialVersionUID = 283692586329054555L;

    private boolean running = false;
    private Thread moveThread;
    private int x = 0, y = 0;

    public AI(){
        moveThread = new Thread(this);
    }
    public void start(){
        running = true;
        moveThread.start();
    }
    public void stop(){
        running = false;
    }
    public boolean isRunning(){
        return running;
    }

    public void run(){
        while(running){
            if(Game.fighting == false){
                Random direction = new Random();
                int dir = direction.nextInt(4);
                switch(dir){
                case 1: 
                    if(this.getX() < Game.getWindowWidth()){
                    this.setEnX(this.getX() + 1);
                    }
                    break;
                case 2:
                    if(this.getX() > 0){
                        this.setEnX(this.getX() - 1);
                    }
                    break;
                case 3:
                    if(this.getX() < Game.getWindowHeight()){
                        this.setEnY(this.getY() + 1);
                    }
                    break;
                case 4:
                    if(this.getX() < 0){
                        this.setEnY(this.getY() - 1);
                    }
                    break;
                }
                updateBounds();
                try{
                    Thread.sleep(200);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    public void setEnX(int x){
        this.x = x;
    }
    public void setEnY(int y){
        this.y = y;
    }
    public int getEnX(){
        return x;
    }
    public int getEnY(){
        return y;
    }
    public void updateBounds(){
        setBounds(x, y, 32,32);
    }
    public abstract void paintComponent(Graphics g);
}

I understand that I just threw a lot of code at you guys. I tried not to, but looking back at it I'm trying to provide an example that runs. If I missed any code, tell me and I'll add it.

Anyways, what I need to know is how to make setBounds() stop invoking repaint() (Other than just using less than 5 entities). Also, I've removed EnemyAI.start() when adding entities and it did stop it. So I have reason to believe that the problem is within the AI class run() method. Which pretty much just calls setBounds().

Nicholas Eason
  • 290
  • 4
  • 13

1 Answers1

5

This is a normal behavior and it's why you can't modify state inside paintComponent. We don't have control over when repaints happen: the system does them on its own sometimes.

Here's an example of what I mean, which you shouldn't be doing:

public class PlayerPane extends JPanel{
    ...

    public void paintComponent(Graphics g){
        ...

        // modifying index
        if(index == images.length-1){
            index = 0;
        } else {
            index++;
        }
    }
}

You need to go through all of your code looking for every place you've modified a variable like this inside paintComponent, and move it out to somewhere else.

As a side note, you should also move your ImageIO.read calls so they are not inside paintComponent. Load your images once when the program starts, in to static variables or something like that.

And as a general tip, you should look in to animation with just painting instead of trying to animate components. It will do you huge favors in the long run for a game.


So in summary:

  • Keep paintComponent stateless.
  • Wrap images with game/animation state in non-UI objects.
  • Paint these images in paintComponent.

Here's a minimal example which demonstrates this by animating shapes falling down the window:

falling shapes

import java.net.*;
import javax.swing.*;
import javax.imageio.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.Graphics;
import java.util.List;
import java.util.ArrayList;
import java.util.Random;

class FallingShapes implements Runnable {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new FallingShapes());
    }

    @Override
    public void run() {
        List<Entity> entities = new ArrayList<Entity>();

        int w = 0;
        int h = 0;

        for (BufferedImage img : Resources.images) {
            entities.add(new Entity(img));

            w += img.getWidth();
            h += img.getHeight();
        }

        PaintPanel p = new PaintPanel(entities);
        p.setPreferredSize(new Dimension(w, (2 * h)));

        JFrame f = new JFrame();

        f.setContentPane(p);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setResizable(false);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);

        new Animator((1000 / 60), p, entities).start();
    }

    static class Animator implements ActionListener {
        int period;
        JPanel context;
        int height;
        List<Entity> entities;

        Animator(int period, JPanel context, List<Entity> entities) {
            this.context  = context;
            this.height   = context.getHeight();
            this.period   = period;
            this.entities = entities;
        }

        @Override
        public void actionPerformed(ActionEvent a) {
            for (Entity e : entities) {
                double dist =
                    (period / 1000.0) * (height * e.rate);

                e.y += dist;
                e.y %= height;
            }

            context.repaint();
        }

        void start() {
            Random r = new Random();
            int    x = 0;
            for (Entity e : entities) {
                e.x    = x;
                e.y    = r.nextInt(height);
                e.rate = (0.25 + (0.75 * r.nextDouble()));

                x += e.width;
            }

            new Timer(period, this).start();
        }
    }

    static class Entity {
        BufferedImage img;

        double x, y, rate;
        int width, height;

        Entity(BufferedImage img) {
            this.img    = img;
            this.width  = img.getWidth();
            this.height = img.getHeight();
        }

        void paint(Graphics g, JPanel context) {
            int x = (int) Math.round(this.x);
            int y = (int) Math.round(this.y);
            g.drawImage(img, x, y, null);

            int cHeight = context.getHeight();
            if ((y + height) > cHeight) {
                g.drawImage(img, x, y - cHeight, null);
            }
        }
    }

    static class PaintPanel extends JPanel {
        List<Entity> entities;

        PaintPanel(List<Entity> entities) {
            this.entities = entities;

            setBackground(Color.white);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            for (Entity e : entities) {
                e.paint(g, this);
            }
        }
    }

    static class Resources {
        static final String[] paths = {
            "https://i.stack.imgur.com/wCF8S.png",
            "https://i.stack.imgur.com/5v2TX.png",
            "https://i.stack.imgur.com/F0JHK.png",
            "https://i.stack.imgur.com/4EVv1.png",
            "https://i.stack.imgur.com/xj49g.png",
        };

        static final List<BufferedImage> images =
            new ArrayList<BufferedImage>();
        static {
            for (String path : paths) {
                try {
                    images.add(ImageIO.read(new URL(path)));
                } catch (Exception e) {
                    throw new AssertionError(e);
                }
            }
        }
    }
}

(Images from here.)

Other useful examples of animation and painting:

Community
  • 1
  • 1
Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • For the most part, the animation is done with painting. Aside from entity movements (Which I just created and will endure further optimization), and when the player needs to move off-center (towards edges when the map cant move any further), all the movements are simulated via me moving the ground. – Nicholas Eason May 11 '15 at 19:16
  • And the only times I've ever experienced the System calling `repaint` on it's own is when I move the window in-between monitors. So why would it just start happening when I add entities on screen? – Nicholas Eason May 11 '15 at 19:18
  • Well you're using a JPanel as a container for an image and animation it with `setBounds(x, y, ...)`. You could instead create an object that wraps a `BufferedImage` and paint all the images on to a single panel with `g.drawImage(img, x, y, ...)`. See e.g. http://stackoverflow.com/a/13022788 – Radiodef May 11 '15 at 19:19
  • *"And the only times I've ever experienced the System calling `repaint` on it's own is when I move the window in-between monitors. So why would it just start happening when I add entities on screen?"* The system calls repaint for all kinds of scenarios and the reason you haven't noticed it yet is likely due to limited testing. – Radiodef May 11 '15 at 19:21
  • The only reason the `player` and `entities` are inside of their own respective `JPanel` is for collision detection, and to reference them when needed (i.e. change Health). How could I do that if I just draw them in? – Nicholas Eason May 11 '15 at 19:30
  • You can do collision detection without using components. `java.awt.Rectangle`, `java.awt.Point`, `java.awt.geom.*`, etc. provide many useful utilities for this. See my update for an example of animation without components. – Radiodef May 11 '15 at 20:47