0

I am creating a top down shooter that has zombies follow you and rotate to aim directly at you. If there are up to 5 zombies following there is no flickering. If there are over 5 zombies there is a flicker on all other images drawn to the screen. It seems to me that as more zombies get added it is causing the game to run slower and for it to draw the other images slightly out of place every now and again. I am using Graphics2D and rotating the image. The image after being drawn is then rotated back to the original position so it doesn't affect other images being drawn after it. How can I stop the flickering and slight movement in pixels?

Thanks in advance!

Here is the code for the zombies...

 package com.game.stayalive;

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

public class Zombie extends Sprite {

Zombie() {
    x = -50;
    width = Display.spriteWidth;
    height = Display.spriteHeight;
    DELAY = rand.nextInt(8)+8;
    life = 5;
}

long lastUpdate;
boolean barrierDestroyed = false;
public void drawZombie(Graphics g,int playerX, int playerY) {

    if (!visible) {
        return;
    }
    Graphics2D g2d = (Graphics2D) g;

    direction = Math.atan2(y - playerY, x - playerX);

    g2d.rotate(direction - Math.PI / 2.0, x + width / 2, y + height / 2);
    if(DELAY > 10){
    g2d.setColor(Color.green.darker());
    }else{
        g2d.setColor(Color.red.darker());
    }
    // Main body rectangle
    g2d.drawRect(x, y, width, height);
    // Left arm
    g2d.drawRect(x - width / 2, y, width / 2, height);
    g2d.fillRect(x - width / 2, y, width / 2, height);
    // Right arm
    g2d.drawRect(x + width, y, width / 2, height);
    g2d.fillRect(x + width, y, width / 2, height);
    // Main body filled
    if(DELAY > 10){
    g2d.setColor(Color.green);
}else{
        g2d.setColor(Color.red);
    }
    g2d.fillRect(x, y, width, height);
    g2d.rotate(-(direction - Math.PI / 2.0), x + width / 2, y + height / 2);

}




long waited = System.currentTimeMillis();
long waitTime;
public void setWaitTime(long waitTime){
    this.waitTime = waitTime;
    if (System.currentTimeMillis() - waited < waitTime) {
        return;

    }
}
int moveSpeed = 10;

public void collisionDetection(Zombie zombie1, Zombie zombie2) {
    xAcc += gradientX;
    yAcc += gradientY;
    x = (int) xAcc;
    y = (int) yAcc;
    if (zombie1.getRect().intersects(zombie2.getRect()) && zombie1.visible &&  zombie1.life > 0) {
        xAcc -= gradientX;
        yAcc -= gradientY;
        xAcc += (gradientX/10)+rand.nextDouble()-0.5;
        yAcc += (gradientY/10) + rand.nextDouble()-0.5;
        x = (int) xAcc;
        y = (int) yAcc;
}
    xAcc -= gradientX;
    yAcc -= gradientY;
    x = (int) xAcc;
    y = (int) yAcc;
}

Random rand = new Random();
long spawnRate = 10000, lastZombieAdded;

public void addZombie() {
    if (System.currentTimeMillis() - lastZombieAdded > spawnRate) {
        Display.zombie.add(new Zombie());
        Display.zombie.get(Display.zombie.size()-1).visible = true;
        Display.zombie.get(Display.zombie.size()-1).setWaitTime(500);
        Display.zombie.get(Display.zombie.size()-1).xAcc = rand.nextInt(950);
        Display.zombie.get(Display.zombie.size()-1).yAcc = rand.nextInt(600);
        lastZombieAdded = System.currentTimeMillis();
    }
}
public void removeZombie(){
    for (int i = 0; i < Display.zombie.size(); i++) {
        if(!Display.zombie.get(i).visible ){
        Display.zombie.remove(i);
        }
    }
}

double gradientX, gradientY;

public void move() {
    setWaitTime(waitTime);
    if (life <= 0) {
        visible = false;
    }
    if (System.currentTimeMillis() - lastUpdate > DELAY) {
        xAcc += gradientX;
        yAcc += gradientY;
        x = (int) xAcc;
        y = (int) yAcc;
        lastUpdate = System.currentTimeMillis();
    }gradientX = -Math.cos(direction);
    gradientY = -Math.sin(direction);
    }

}

The JPanel Class...

public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
    player.drawPlayer(g2d);

    for (int i = 0; i < zombie.size(); i++) {
        if (zombie.get(i).visible) {
            zombie.get(i).drawZombie(g2d,playerX,playerY);
        }
    }
    for (int i = 0; i < pistol.size(); i++) {
        if (pistol.get(i).visible) {
            pistol.get(i).drawPistol(g2d);
        }
    }
    for (int i = 0; i < machineGun.size(); i++) {
        if (machineGun.get(i).visible) {
            machineGun.get(i).drawMachineGun(g2d);
        }
    }
    for (int i = 0; i < flamethrower.size(); i++) {
        if (flamethrower.get(i).visible) {
            flamethrower.get(i).draw(g2d);
        }
    }
    reloadBar.drawReloadBar(g2d);
    selector.drawSelector(g2d);
    money.draw(g2d);
    Toolkit.getDefaultToolkit().sync();
    g.dispose();
}
@Override
public void run() {
    long beforeTime, timeDiff, sleep;
    beforeTime = System.currentTimeMillis();
    while (true) {
        cycle();
        repaint();
        timeDiff = System.currentTimeMillis() - beforeTime;
        sleep = DELAY - timeDiff;
        if (sleep < 0) {
            sleep = 2;
        }
        try {
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            System.out.println("interrupted");
        }
        beforeTime = System.currentTimeMillis();
    }
}

}

"zombie" is an Array List of the class Zombie. "Pistol" and the others are also Array Lists used for bullets etc. The cycle method that gets called is used to move all the images, add images and check for collisions. Apologies for how unorganized the code is. (I will soon put it in separate methods to neaten things up)

Michael Haywood
  • 319
  • 2
  • 4
  • 10
  • You should look up double buffering. – BitNinja May 01 '14 at 22:36
  • I have done this in the JPanel if this is what you mean? setDoubleBuffered(true); – Michael Haywood May 01 '14 at 22:37
  • `JPanel` is double buffered by default. An actual [runnable example that demonstrates your problem](https://stackoverflow.com/help/mcve) would involve less guess work and better responses. – MadProgrammer May 01 '14 at 23:08
  • Calling `setDoubleBuffered(true)` should not be necessary. Swing is double buffered by default. More interesting here is the question about HOW and WHERE you are painting this. The `drawZombie` method should be called from the `paintComponent` method of a `JPanel`. In doubt, post the code of the panel from where you are painting this (properly formatted, if possible...) – Marco13 May 01 '14 at 23:09
  • *"It seems to me that as more zombies get added it is causing the game to run slower"* I can show you an example that has over 4500 animated objects on the screen at once that runs "reasonably" well, certainly no flickering. I think there is something else going on - IMHO – MadProgrammer May 01 '14 at 23:09
  • Your `move` worries me, the sprites should not be dictating the wait time, the main loop should, update the state, paint the state and then wait and repeat. – MadProgrammer May 01 '14 at 23:12
  • Sorry for my bad naming of variables. The "waitTime" part is used to make the image wait a second before it starts moving. Although yes each image has a DELAY variable for how often it will take a step forward. This means that each one will have a different speed. – Michael Haywood May 01 '14 at 23:19
  • Don't call `g.dispose();`, you don't create, this could cause nothing to be rendered on some systems – MadProgrammer May 01 '14 at 23:21
  • For what reason is g.dispose() supposed to be used? – Michael Haywood May 01 '14 at 23:26
  • How simple would it be for me to use double buffering in this? What would be the simplest way to do so seeming as I haven't done this before? – Michael Haywood May 01 '14 at 23:30
  • If you are using a `JPanel` (and if you are using it properly), you **are** using double buffering already. Obey what MadProgrammer says, he knows what he's talking about. BTW: `Graphics#dispose()` is intended for `Graphics` objects that, for example, have been obtained from a `BufferedImage`, or created by calling `Graphics#copy()` on another `Graphics` object. You should most likely **not** call this here. – Marco13 May 01 '14 at 23:33
  • `Graphics` is a shared resource, it is used to paint all the components in a given paint cycle. `paintComponent` is the lowest (and typically the first method called that actually makes a change) to the `Graphics` context for a given component, this means that other methods and components may need to use it. By calling `dispose`, you release any of it's native and other resources, which can at best, prevent anybody else from painting to it, at worse, anything from getting painted to the graphics device – MadProgrammer May 01 '14 at 23:37
  • Okay I believe I have followed everything (Correct me if I'm wrong). I am calling the paintComponent method in the JPanel class. It is double buffered according to what you have said. Also I removed the g.dispose(). Thank you for the explanation for that too. I do believe I am using JPanel correctly... Hmm – Michael Haywood May 01 '14 at 23:40
  • What things could possibly be wrong with the panel if no other problems stand out clearly? – Michael Haywood May 01 '14 at 23:44
  • You can use `Graphics#create` to create a "snapshot" of the `Graphics` context, this is great for using things like `rotate` and `translate` where you don't want to try and reverse it, then you can call `Graphics#dispose` on that copy ;) – MadProgrammer May 01 '14 at 23:45
  • Unless you can provide actual [runnable example that demonstrates your problem](https://stackoverflow.com/help/mcve) will be guessing at best – MadProgrammer May 01 '14 at 23:51
  • Okay what would be the most simple way to do so? I can see what you mean but cannot think of how to implement that. – Michael Haywood May 01 '14 at 23:54
  • To send a copy of it for you to try it yourself? – Michael Haywood May 01 '14 at 23:56
  • How can I show you a runnable example on here as I haven't done that before. – Michael Haywood May 02 '14 at 00:15
  • 1- Read the link. 2- Strip down your code to so that it demonstrates your problem but replicates the workflow – MadProgrammer May 02 '14 at 01:04

1 Answers1

2

This is a really simple example I put together...

It basically has a sprite which has a random motion and rotation. The program is capable of rendering between 1 and 10, 000 zombies...seriously...

I've used you "main loop" as well, just to be sure...

Zombies

There's no real optimisation, if I was really doing this, I would have a pool somewhere with zombies that could pulled from and put to in order to reduce the amount of time it takes to create them...

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ZombieLand {

    protected static final Random RND = new Random();

    private static BufferedImage zombie;

    public static void main(String[] args) {
        new ZombieLand();
    }

    public ZombieLand() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                try {
                    zombie = ImageIO.read(getClass().getResource("/Zombie.png"));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }

                final ZombiePane zombiePane = new ZombiePane();

                final JSlider slider = new JSlider(1, 10000);
                slider.setMajorTickSpacing(1000);
                slider.setMinorTickSpacing(100);
                slider.setPaintTicks(true);
                slider.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        JSlider slider = (JSlider) e.getSource();
                        zombiePane.setZombies(slider.getValue());
                    }
                });

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(zombiePane);
                frame.add(slider, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        slider.setValue(10000);
                    }
                });
            }
        });
    }

    public static class ZombiePane extends JPanel {

        private List<ZombieSprite> sprites;
        protected static final Object SPRITE_LOCK = new Object();

        private int desiredCount = 1;

        public ZombiePane() {
            sprites = new ArrayList<>(25);
            sprites.add(new ZombieSprite());
            Thread t = new Thread(new MainLoop());
            t.setDaemon(false);
            t.start();
            Font font = getFont();
            setFont(font.deriveFont(Font.BOLD, 48f));
        }

        public void setZombies(int count) {
            desiredCount = count;
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            synchronized (SPRITE_LOCK) {
                for (ZombieSprite sprite : sprites) {
                    sprite.paint(g2d);
                }
            }
            String text = Integer.toString(sprites.size());
            FontMetrics fm = g2d.getFontMetrics();
            g2d.drawString(text, getWidth() - fm.stringWidth(text), getHeight() - fm.getHeight() + fm.getAscent());
            g2d.dispose();
        }

        protected void cycle() {
            synchronized (SPRITE_LOCK) {
                if (desiredCount != sprites.size()) {
                    int count = 0;
                    int fill = 100;
                    while (sprites.size() > desiredCount && count < fill) {
                        sprites.remove(0);
                        count++;
                    }
                    count = 0;
                    while (sprites.size() < desiredCount && count < fill) {
                        sprites.add(new ZombieSprite());
                        count++;
                    }
                }

                for (ZombieSprite sprite : sprites) {
                    sprite.update(getWidth(), getHeight());
                }
            }
        }

        public class MainLoop implements Runnable {

            private int DELAY = 40;

            public void run() {
                long beforeTime, timeDiff, sleep;
                beforeTime = System.currentTimeMillis();
                while (true) {
                    cycle();
                    repaint();
                    timeDiff = System.currentTimeMillis() - beforeTime;
                    sleep = DELAY - timeDiff;
                    if (sleep < 0) {
                        sleep = 2;
                    }
                    try {
                        Thread.sleep(sleep);
                    } catch (InterruptedException e) {
                        System.out.println("interrupted");
                    }
                    beforeTime = System.currentTimeMillis();
                }
            }
        }
    }

    public static class ZombieSprite {

//        private BufferedImage zombie;
        private Point motionDelta;
        private double rotationDelta;

        private Point location;
        private double angle;

        public ZombieSprite() {
            motionDelta = new Point();
            motionDelta.x = (int) ((Math.random() * 3) + 1);
            motionDelta.y = (int) ((Math.random() * 3) + 1);
            if (Math.random() > 0.5) {
                motionDelta.x *= -1;
            }
            if (Math.random() > 0.5) {
                motionDelta.y *= -1;
            }
            rotationDelta = (int) ((Math.random() * 9) + 1);
            if (Math.random() > 0.5) {
                rotationDelta *= -1;
            }
        }

        public void paint(Graphics2D g2d) {
            if (location != null) {
                Graphics2D g = (Graphics2D) g2d.create();
                AffineTransform at = new AffineTransform();
                at.translate(location.x, location.y);
                at.rotate(Math.toRadians(angle), zombie.getWidth() / 2, zombie.getHeight() / 2);
                g.setTransform(at);
                g.drawImage(zombie, 0, 0, null);
                g.dispose();
            }
        }

        public void update(int width, int height) {
            if (location == null) {
                angle = (Math.random() * 360d);
                location = new Point();
                location.x = (int) (Math.random() * (width - zombie.getWidth()));
                location.y = (int) (Math.random() * (height - zombie.getHeight()));
            } else {
                angle += rotationDelta;
                location.x += motionDelta.x;
                location.y += motionDelta.y;

                if (location.x < 0) {
                    location.x = 0;
                    motionDelta.x *= -1;
                } else if (location.x + zombie.getWidth() > width) {
                    location.x = width - zombie.getWidth();
                    motionDelta.x *= -1;
                }
                if (location.y < 0) {
                    location.y = 0;
                    motionDelta.y *= -1;
                } else if (location.y + zombie.getHeight() > height) {
                    location.y = height - zombie.getHeight();
                    motionDelta.y *= -1;
                }
            }
        }

    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366