Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I am creating a Space Invaders Game with just one Invader. The images are stuttering and are only visible ~10% of the time. What is wrong with my code?

package spaceinvader;

import javax.swing.*;
import java.awt.event.*;

import java.awt.*;

public class spaceInvaders extends JApplet implements KeyListener, ActionListener
{
//Declare components and variables

JPanel mainPanel = new JPanel();
ImageIcon carImage = new ImageIcon("ship.png");
ImageIcon invaderImage = new ImageIcon("invader.png");

int intPosX = 240;
int intPosY = 330;
int intXAmount = 15;
boolean shipMoveLeft = false;
boolean shipMoveRight = false;
Timer shipTimer = new Timer(100,this);

int intBulletX = -50;
int intBulletY = -50;
boolean bulletMove = false;
boolean bulletActive = false;
Timer bulletTimer = new Timer(50,this);

int intInvaderX = 0;
int intInvaderY = 0;
int invaderXAmount = 10;
boolean invaderMove = true;
Timer invaderTimer= new Timer(1000,this);

public void init()
{
    addKeyListener(this);
    setFocusable(true);

    resize(600,400);
    setContentPane(mainPanel);

    shipTimer.start();
    bulletTimer.start();
    invaderTimer.start();
}

public void actionPerformed(ActionEvent e)
{
    requestFocus();
    if(shipMoveLeft)
        intPosX += intXAmount;
    else if(shipMoveRight)
        intPosX -= intXAmount;
    if(bulletMove && bulletActive){
        intBulletY -= 15;
        if(intBulletY <= -50){
            bulletMove = false;
            bulletActive = false;
        }
    }
    if(invaderMove){
        intInvaderX += invaderXAmount;
        if(intInvaderX > getWidth() - 60 || intInvaderX < 0){
            intInvaderY += 40;
            invaderXAmount *= -1;
        }
    }
    repaint();
}

public void keyPressed(KeyEvent e)
{
    int key = e.getKeyCode();
    if (key == 37){
        shipMoveRight = true;
    }
    else if (key == 39){
        shipMoveLeft = true;
    }
    else if (key == 32){
        if(bulletActive == false){
            intBulletX = intPosX;
            intBulletY = intPosY;
        }
        bulletMove = true;
        bulletActive = true;
    }
}

public void keyReleased(KeyEvent e)
{
    shipMoveLeft = false;
    shipMoveRight = false;
}

public void keyTyped(KeyEvent e)
{

}

public void paint(Graphics gr)
{
    super.paint(gr);

    gr.setColor(Color.red);
    gr.fillOval(intBulletX, intBulletY, 10, 25);

    carImage.paintIcon(this,gr, intPosX, intPosY); //Draw image in new spot

    invaderImage.paintIcon(this,gr, intInvaderX, intInvaderY);
 }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
120 views
Welcome To Ask or Share your Answers For Others

1 Answer

So there are a number of issues which jump out immediately...

  • Applets are a dead end, most browsers will actively block them and/or have dropped support for the plugin
  • You're adding a JPanel to the applet, but overriding the applet's paint method, because of the way painting can work, the panel can be painted independently of the applet, causing it to paint over whatever you might have painted
  • You don't need multiple timers, you just need to have better delta values (smaller been faster)
  • KeyListener is a poor choice for detecting keyboard input, it's rather low level and has focus related issues which are easily overcome by using the Key Bindings API

I'd start by having a look at Performing Custom Painting, Painting in AWT and Swing and How to Use Key Bindings for more details.

So how would you start fixing it? Start by using a JPanel as you basic container, override it's paintComponent and place all your paint logic here.

Use a single Timer to manage the updates

There are any number of approaches you can take, but I'd start with defining some contracts that define what can go on in the game

public interface GameSpace extends ImageObserver {
    public Dimension getGameSpace();
    public boolean hasInput(Input input);
}

public interface Entity {
    public void paint(GameSpace gameSpace, Graphics2D g2d);
    public boolean update(GameSpace gameSpace);

    public Rectangle getBounds();
}

public abstract class AbstractEntity implements Entity {

    private int x;
    private int y;

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    protected abstract int getWidth();
    protected abstract int getHeight();

    public Rectangle getBounds() {
        return new Rectangle(getX(), getY(), getWidth(), getHeight());
    }

}

public abstract class AbstractImageEntity extends AbstractEntity {

    protected abstract BufferedImage getImage();

    @Override
    protected int getWidth() {
        return getImage().getWidth();
    }

    @Override
    protected int getHeight() {
        return getImage().getHeight();
    }

}

This separates some of management of key elements, allowing for a more flexible design. You might have a lot more entities, one's which could move, ones which could not, some which are painted, some which are not, but all which provide support for the core engine to get work done.

Once you have that you can start defining some core entities you need

public class ShipEntity extends AbstractImageEntity {

    private BufferedImage ship;

    public ShipEntity(GameSpace gameSpace) throws IOException {
        ship = ImageIO.read(getClass().getResource("/resources/ship.png"));
        setY(gameSpace.getGameSpace().height - getBounds().height);
        setX((gameSpace.getGameSpace().width - getBounds().width) / 2);
    }

    @Override
    public BufferedImage getImage() {
        return ship;
    }

    @Override
    public void paint(GameSpace gameSpace, Graphics2D g2d) {
        g2d.drawImage(ship, getX(), getY(), gameSpace);
    }

    @Override
    public boolean update(GameSpace gameSpace) {
        int x = getX();
        if (gameSpace.hasInput(Input.LEFT)) {
            x -= 2;
        }       
        if (gameSpace.hasInput(Input.RIGHT)) {
            x += 2;
        }       

        if (x < 0) {
            x = 0;
        } else if (x + getWidth() > gameSpace.getGameSpace().width) {
            x = gameSpace.getGameSpace().width - getWidth();
        }

        setX(x);

        return true;
    } 

}

public class InvaderEntity extends AbstractImageEntity {
    private BufferedImage invader;

    public InvaderEntity() throws IOException {
        invader = ImageIO.read(getClass().getResource("/resources/Invader.png"));
    }

    @Override
    protected BufferedImage getImage() {
        return invader;
    }

    @Override
    public void paint(GameSpace gameSpace, Graphics2D g2d) {
        g2d.drawImage(invader, getX(), getY(), gameSpace);
    }

    @Override
    public boolean update(GameSpace gameSpace) {
        return true;            
    }

}

public class ProjectileEntity extends AbstractEntity {

    private int delta;

    public ProjectileEntity(int delta) {
        this.delta = delta;
    }

    @Override
    protected int getWidth() {
        return 10;
    }

    @Override
    protected int getHeight() {
        return 10;
    }

    @Override
    public void paint(GameSpace gameSpace, Graphics2D g2d) {
        g2d.setColor(Color.RED);
        int width = getWidth();
        int height = getHeight();
        g2d.fillOval(getX() - width / 2, getY() - height / 2, width, height);
    }

    @Override
    public boolean update(GameSpace gameSpace) {
        int y = getY() + delta;
        setY(getY() + delta);
        return y + getHeight() >= 0 && y + getHeight() <= gameSpace.getGameSpace().height;
    }

}

Basically, they contain the logic required for getting their jobs done.

Finally, you need to setup the actual game UI

public class GamePane extends JPanel implements GameSpace {

    private Set<Input> inputs;

    private Entity playerEntity;
    private List<Entity> projectileEntities;
    private List<Entity> invaderEntities;

    private long timeOfLastProjectile = -1;

    public GamePane() throws IOException {
        setBackground(Color.BLACK);

        inputs = new HashSet<>(2);

        playerEntity = new ShipEntity(this);

        projectileEntities = new ArrayList<>(25);
        invaderEntities = new ArrayList<>(25);

        InvaderEntity invader = new InvaderEntity();
        invader.setX((getGameSpace().width - invader.getBounds().width) / 2);
        invader.setY((getGameSpace().height - invader.getBounds().height) / 2);

        invaderEntities.add(invader);

        addKeyBinding(Input.LEFT, "left", KeyEvent.VK_LEFT);
        addKeyBinding(Input.RIGHT, "right", KeyEvent.VK_RIGHT);
        addKeyBinding(Input.SPACE, "space", KeyEvent.VK_SPACE);

        Timer timer = new Timer(15, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateState();
                processCollisions();
                repaint();
            }
        });
        timer.start();
    }

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

    protected void updateState() {
        playerEntity.update(this);
        if (hasInput(Input.SPACE)) {
            long time = System.currentTimeMillis() - timeOfLastProjectile;
            if (time < 0 || time > 1000) {
                timeOfLastProjectile = System.currentTimeMillis();
                Rectangle bounds = playerEntity.getBounds();
                ProjectileEntity projectile = new ProjectileEntity(-1);
                int x = bounds.x + ((bounds.width - projectile.getWidth()) / 2);
                int y = bounds.y - projectile.getHeight();

                projectile.setX(x);
                projectile.setY(y);
                projectileEntities.add(projectile);
            }
        }

        for (Entity entity : invaderEntities) {
            entity.update(this);
        }

        List<Entity> outOfBounds = new ArrayList<>(25);
        for (Entity entity : projectileEntities) {
            if (!entity.update(this)) {
                outOfBounds.add(entity);
            }
        }
        projectileEntities.removeAll(outOfBounds);
    }

    protected void processCollisions() {
        Set<Entity> hitInvaders = new HashSet<>(25);
        Set<Entity> hitProjectiles = new HashSet<>(25);
        for (Entity invader : invaderEntities) {
            for (Entity projectile : projectileEntities) {
                if (projectile.getBounds().intersects(invader.getBounds())) {
                    // Maybe lots of cool explosiions
                    hitInvaders.add(invader);
                    hitProjectiles.add(projectile);
                }
            }
        }

        invaderEntities.removeAll(hitInvaders);
        projectileEntities.removeAll(hitProjectiles);
    }

    protected void addKeyBinding(Input input, String name, int virtualKey) {
        ActionMap am = getActionMap();
        InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);

        im.put(KeyStroke.getKeyStroke(virtualKey, 0, false), name + ".pressed");
        im.put(KeyStroke.getKeyStroke(virtualKey, 0, true), name + ".released");

        am.put(name + ".pressed", new KeyAction(inputs, input, true));
        am.put(name + ".released", new KeyAction(inputs, input, false));
    }

    @Override
    public Dimension getGameSpace() {
        return getPreferredSize();
    }

    @Override
    public boolean hasInput(Input input) {
        return inputs.contains(input);
    }

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

        Graphics2D g2d = (Graphics2D) g.create();
        playerEntity.paint(this, g2d);
        g2d.dispose();

        for (Entity entity :  invaderEntities) {
            g2d = (Graphics2D) g.create();
            entity.paint(this, g2d);
            g2d.dispose();
        }
        for (Entity entity :  projectileEntities) {
            g2d = (Graphics2D) g.create();
            entity.paint(this, g2d);
            g2d.dispose();
        }
    }

}

public class KeyAction extends AbstractAction {

    private Input input;
    private Set<Input> inputs;
    private boolean pressed;

    public KeyAction(Set<Input> inputs, Input input, boolean pressed) {
        this.input = input;
        this.inputs = inputs;
        this.pressed = pressed;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (pressed) {
            inputs.add(input);
        } else {
            inputs.remove(input);
        }
    }

}

Now you could go a little further and generate an "engine" class which controls the entities and performs all the required updating, but I'm lazy ;)

That's a really rough idea of some the basic concepts you need to develop to be able to move forward, hope it helps


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...