简体   繁体   English

为什么我的简单java2d Space Invaders游戏落后?

[英]Why is my simple java2d Space Invaders game lagging?

I'm currently making a space invaders-esque game for my software engineering course. 我目前正在为我的软件工程课程制作一款类似太空入侵者的游戏。 I've already got everything working that satisfies the requirements, so this isn't a 'solve my homework' kind of question. 我已经可以满足要求的所有工作,所以这不是一个“解决我的作业”的问题。 My problem is that the game will lag (at what seems like random times & intervals) to the point where it becomes too frustrating to play. 我的问题是,游戏将滞后(看起来像是随机的时间和间隔),直至无法播放为止。 Some things I think might be causing this - though I'm not positive - are as follows: 我认为可能是造成此问题的一些原因-尽管我不是很肯定-如下所示:

Problem with timer event every 10 ms (I doubt this because of the very limited resources required for this game). 每10毫秒发生一次计时器事件问题(由于该游戏所需的资源非常有限,我对此表示怀疑)。

Problem with collision detection (checking for collision with every visible enemy every 10 ms seems like it would take up a large chunk of resources) 碰撞检测问题(每10毫秒检查一次与每个可见敌人的碰撞似乎会占用大量资源)

Problem with repainting? 重新粉刷有问题吗? This seems unlikely to me however... 但是对我来说这似乎不太可能...

@SuppressWarnings("serial")
public class SIpanel extends JPanel {
    private SIpanel panel;
    private Timer timer;
    private int score, invaderPace, pulseRate, mysteryCount, distanceToEdge;
    private ArrayList<SIthing> cast;
    private ArrayList<SIinvader> invaders, dead;
    private ArrayList<SImissile> missileBase, missileInvader;
    private SIinvader[] bottomRow;
    private SIbase base;
    private Dimension panelDimension;
    private SImystery mysteryShip;
    private boolean gameOver, left, right, mysteryDirection, space, waveDirection;
    private boolean runningTimer;
    private Music sound;


    private void pulse() {
        pace();
        processInputs();
        if (gameOver) gameOver();
        repaint();
    }
    private void pace() {
//      IF invaders still live
        if (!invaders.isEmpty()) {
            invaderPace++;

//          Switch back manager
            if (distanceToEdge <= 10) {
                switchBack();
                pulseRate = (pulseRate >= 16) ? (int) (pulseRate*(0.8)) : pulseRate;
                waveDirection = !waveDirection;
                distanceToEdge = calculateDistanceToEdge();
            }

//              Move invaders left/right
            else if (invaderPace >= pulseRate) {
                invaderPace = 0;
                distanceToEdge = calculateDistanceToEdge();
                moveAI();
                invadersFire();
                if (!dead.isEmpty()) removeDead();
                if (mysteryCount < 1)   tryInitMysteryShip();
            }
//      All invaders are kill, create new wave
        } else if (missileBase.isEmpty() && missileInvader.isEmpty() && !cast.contains(mysteryShip)) {
//          System.out.println("New Wave!");
            newWave();
        }
//      Every pace
        if (!missileBase.isEmpty()) moveMissileBase();
//      Every two paces
        if (invaderPace % 2 == 0)   {
            if (!missileInvader.isEmpty()) moveMissileInvader();
            if (mysteryCount > 0)   moveMysteryShip();
        }
    }
    private void processInputs() {
        if (left)   move(left);
        if (right)  move(!right);
        if (space)  fireMissile(base, true);
    }
    protected void fireMissile(SIship ship, boolean isBase) {
        if(isBase && missileBase.isEmpty()) {
            base.playSound();
            SImissile m = new SImissile(ship.getX()+(ship.getWidth()/2), ship.getY()-(ship.getHeight()/4));
            missileBase.add(m);
            cast.add(m);
        } else if (!isBase && missileInvader.size()<3) {
            base.playSound();
            SImissile m = new SImissile(ship.getX()+(ship.getWidth()/2), ship.getY()+(ship.getHeight()/4));
            missileInvader.add(m);
            cast.add(m);
        }
    }
    private void newWave() {
        pulseRate = 50;
        int defaultY=60, defaultX=120, defaultWidth=30, defaultHeight=24;
    for(int i=0; i<5; i++) {
        for(int j=0; j<10; j++) {
            if (i<1)    invaders.add(new SItop((j*defaultWidth)+defaultX, (i*defaultHeight)+defaultY, defaultWidth, defaultHeight));
            else if (i<3)   invaders.add(new SImiddle((j*defaultWidth)+defaultX, (i*defaultHeight)+defaultY, defaultWidth, defaultHeight));
            else if (i<5)   invaders.add(new SIbottom((j*defaultWidth)+defaultX, (i*defaultHeight)+defaultY, defaultWidth, defaultHeight));
        }
    }
    for (SIinvader s: invaders) {
        cast.add(s);
    }
    if (!cast.contains(base)) {
        cast.add(base);
    }
    bottomRow = getBottomRow();
}
private void tryInitMysteryShip() {
    Random rand = new Random();
    int x=rand.nextInt(1000);
    if (x<=3) {
        mysteryCount = 1;
        if (rand.nextBoolean()) {
            mysteryDirection = true;
        }
        if (mysteryDirection) {
            mysteryShip = new SImystery(0, 60, 36, 18);
        } else {
            mysteryShip = new SImystery(480, 60, 36, 18);
        }
        cast.add(mysteryShip);
    }
}
private void moveMysteryShip() {
    int distance = 0;
    if (mysteryDirection) {
        mysteryShip.moveRight(5);
        distance = getWidth() - mysteryShip.getX();
    } else {
        mysteryShip.moveLeft(5);
        distance = 30+mysteryShip.getX()-mysteryShip.getWidth();
    }
    if (distance <= 5) {
        dead.add(mysteryShip);
        mysteryShip = null;
        mysteryCount = 0;
    }
}
private void removeDead() {
    @SuppressWarnings("unchecked")
    ArrayList<SIinvader> temp = (ArrayList<SIinvader>) dead.clone();
    dead.clear();
    for (SIinvader s : temp) {
        invaders.remove(s);
        cast.remove(s);
    }
    bottomRow = getBottomRow();
}
private void invadersFire() {
    int[] p = new int[bottomRow.length];
    for (int i=0; i<p.length; i++) {
        for (int j=0; j<p.length; j++) {
            p[j] = j;
        }
        Random rand = new Random();
        int a=rand.nextInt(101);
        if (a>=20) {
            int b=rand.nextInt(p.length);
            fireMissile(bottomRow[b], false);
        }
    }
}
private int calculateDistanceToEdge() {
    int distance = 0;
    SIinvader[] outliers = getOutliers();
    if (waveDirection) {
        distance = getWidth() - outliers[0].getX()-outliers[0].getWidth();
    } else {
        distance = outliers[1].getX();
    }
    return distance;
}
private SIinvader[] getOutliers() {
    SIinvader leftMost = invaders.get(0), rightMost = invaders.get(0);
    for (SIinvader s : invaders) {
        if (s.getX() < leftMost.getX()) {
            leftMost = s;
        }
        if (s.getX() > rightMost.getX()) {
            rightMost = s;
        }
    }
    return new SIinvader[] {    rightMost,  leftMost    };
}
private SIinvader[] getBottomRow() {
    SIinvader[] x = new SIinvader[(invaders.size()>10)?10:invaders.size()];
    for (int i=0; i<x.length; i++) {
        x[i] = invaders.get(i);
        for (SIinvader s:invaders) {
            if (s.getX() == x[i].getX()) {
                if (s.getY() > x[i].getY()) {
                    x[i] = s;
                }
            }
        }
    }
    return x;
}
private void move(boolean b) {
    int defaultX = 5;
    if (b) base.moveLeft(defaultX);
    else base.moveRight(defaultX);
}
private void moveAI() {
    for(SIinvader s : invaders) {
        s.changeImage();
        int defaultX = 5;
        if (waveDirection) s.moveRight(defaultX);
        else s.moveLeft(defaultX);
    }
}
private void moveMissileBase() {
    if (invaders.isEmpty()) return;
    int movement = -5, bound = 0;
    SImissile missile = missileBase.get(0);
    missile.moveDown(movement);
    SIinvader lowestInvader = getLowestInvader();
    if (missile.getY() < (lowestInvader.getY() + lowestInvader.getHeight())) {
        for (SIinvader s:bottomRow) {
            if (checkCollision(missile, s)) {
                s.setHit();
                dead.add(s);
                cast.remove(missile);
                missileBase.clear();
                score += s.value;
                return;
            }
        }
        if (mysteryCount > 0) {
            if (checkCollision(missile, mysteryShip)) {
                mysteryShip.setHit();
                dead.add(mysteryShip);
                cast.remove(missile);
                missileBase.clear();
                score += mysteryShip.value;
                return;
            }
        }
        if (missile.getY() < bound) {
            missileBase.remove(missile);
            cast.remove(missile);
        }
    }
}
private SIinvader getLowestInvader() {
    SIinvader lowest = bottomRow[0];
    for (SIinvader invader : bottomRow) {
        if (invader.getY() > lowest.getY()) {
            lowest = invader;
        }
    }
    return lowest;
}
private void moveMissileInvader() {
    int movement = 5, bound = (int) panelDimension.getHeight();
    for (SImissile missile : missileInvader) {
        missile.moveDown(movement);
        if(missile.getY() >= base.getY()) {
            if (checkCollision(missile, base)) {
                base.setHit();
                gameOver = true;;
                missileInvader.remove(missile);
                cast.remove(missile);
                return;
            } else if (missile.getY() >= bound-25) {
                missileInvader.remove(missile);
                cast.remove(missile);
                return;
            }                   
        }
    }
}
private boolean checkCollision(SIthing missile, SIthing ship) {
    Rectangle2D rect1 = new Rectangle2D.Double(
            missile.getX(),
            missile.getY(),
            missile.getWidth(),
            missile.getHeight()
        );
    Rectangle2D rect2 = new Rectangle2D.Double(
            ship.getX(),
            ship.getY(),
            ship.getWidth(),
            ship.getHeight()
        );
    return rect1.intersects(rect2);
}
private void switchBack() {
    int defaultY = 12;
    for (SIinvader s : invaders) {
        if (s.getY() > getHeight()) {
            gameOver = true;
            return;
        }
        s.moveDown(defaultY);
    }
}
private void gameOver() {
    pause(true);
    SI.setGameOverLabelVisibile(true);
}
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    g2.setColor(Color.GREEN);
    Font font = new Font("Arial", 0, 20);
    setFont(font);
    String score = "Score: "+this.score;
    Rectangle2D rect = font.getStringBounds(score, g2.getFontRenderContext());
    int screenWidth = 0;
    try { screenWidth = (int) panelDimension.getWidth(); }
    catch (NullPointerException e) {}
    g2.setColor(Color.GREEN);
    g2.drawString(score, (int) (screenWidth - (10 + rect.getWidth())), 20);
    for(SIthing a:cast) {
        a.paint(g);
    }
}
    public SIpanel() {
        super();
        setBackground(Color.BLACK);
        cast = new ArrayList<SIthing>();
        missileBase = new ArrayList<SImissile>();
        score = invaderPace = mysteryCount = pulseRate = 0;
        sound = new Music("AmbientMusic.wav");
        panel = this;

        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.getKeyCode()) {
                case KeyEvent.VK_LEFT   : left = true; break;
                case KeyEvent.VK_RIGHT  : right = true; break;
                case KeyEvent.VK_SPACE  : space = true; break;
                }
            }
            @Override
            public void keyReleased(KeyEvent e) {
                switch (e.getKeyCode()) {
                case KeyEvent.VK_LEFT   : left = false; break;
                case KeyEvent.VK_RIGHT  : right = false; break;
                case KeyEvent.VK_SPACE  : space = false; break;
                }
            }
        });


        setFocusable(true);

        timer = new Timer(10, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                pulse();
            }
        });
    }
    public void reset() {
        SI.setGameOverLabelVisibile(false);
        score = invaderPace = mysteryCount = 0;
        pulseRate = 50;
        cast = new ArrayList<SIthing>();
        invaders = new ArrayList<SIinvader>();
        dead = new ArrayList<SIinvader>();
        missileBase = new ArrayList<SImissile>();
        missileInvader = new ArrayList<SImissile>();
        base = new SIbase(230, 370, 26, 20);
        waveDirection = true;
        gameOver = false;
        sound.stop();
        sound.loop();
        panelDimension = SI.getFrameDimensions();
        bottomRow = getBottomRow();


        newWave();

        timer.start();
        runningTimer=true;
    }
    public SIpanel getPanel() {
        return this.panel;
    }
    public void pause(boolean paused) {
        if (paused) timer.stop();
        else timer.start();
    }
}

I believe that collision detection may be the reason for lagging and you should simply investigate it by trying to increase and decrease count of enemies or missiles drastically to see if that makes a difference. 我认为碰撞检测可能是造成滞后的原因,您应该简单地尝试通过大幅度增加和减少敌人或导弹的数量来进行调查,以了解是否有所作为。

Consider garbage collector your enemy. 考虑将垃圾收集器当作敌人。 In your checkCollision method you are instantiating two (very simple) objects. 在您的checkCollision方法中,您将实例化两个(非常简单的)对象。 It may not seem like a lot, but consider that your might be creating them for each collision check, and that at 60fps it adds up until it may reach critical mass when GC says "stop the world" and you see noticeable lag. 可能看起来不多,但请考虑您可能是为每次碰撞检查创建它们,并且以60fps的速度累加,直到当GC说“停止世界”时它达到临界质量,您就会看到明显的滞后。

If that is the case, possible solution to that would be to not instantiate any objects in a method called so frequently. 如果是这样,可能的解决方案将是不要在如此频繁调用的方法中实例化任何对象。 You may create Rectangle2D once, and then update its position, instead of creating a new one each time, so you will avoid unnecessary memory allocation. 您可以创建一次Rectangle2D,然后更新其位置,而不是每次都创建一个新的Rectangle2D,这样可以避免不必要的内存分配。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM