为什么我的简单java2d太空入侵者游戏落后了?

为什么我的简单java2d太空入侵者游戏落后了?,java,2d,game-engine,Java,2d,Game Engine,我目前正在为我的软件工程课程制作一个太空入侵者式的游戏。我已经做了所有符合要求的工作,所以这不是“解决我的家庭作业”之类的问题。我的问题是,游戏会延迟(在似乎是随机时间和间隔的情况下)到令人沮丧的程度而无法玩。我认为可能导致这种情况的一些因素——尽管我不是积极的——如下所示: 计时器事件每10毫秒出现问题(我对此表示怀疑,因为这个游戏所需的资源非常有限) 碰撞检测问题(每10毫秒检查一次与每个可见敌人的碰撞似乎会占用大量资源) 重新喷漆有问题吗?但我觉得这似乎不太可能 @SuppressWarn

我目前正在为我的软件工程课程制作一个太空入侵者式的游戏。我已经做了所有符合要求的工作,所以这不是“解决我的家庭作业”之类的问题。我的问题是,游戏会延迟(在似乎是随机时间和间隔的情况下)到令人沮丧的程度而无法玩。我认为可能导致这种情况的一些因素——尽管我不是积极的——如下所示:

计时器事件每10毫秒出现问题(我对此表示怀疑,因为这个游戏所需的资源非常有限)

碰撞检测问题(每10毫秒检查一次与每个可见敌人的碰撞似乎会占用大量资源)

重新喷漆有问题吗?但我觉得这似乎不太可能

@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();
    }
}
@SuppressWarnings(“串行”)
公共类SIpanel扩展了JPanel{
私人SIpanel小组;
私人定时器;
私人整数分数、侵入面、脉冲数、神秘计数、距离边缘;
私人ArrayList演员阵容;
私人ArrayList入侵者,已死;
private ArrayList missileBase,missileInvader;
私人入侵者[]底层;
私有SIbase库;
私人层面;
私密的神秘感;
私有布尔gameOver,左,右,mysteryDirection,空格,waveDirection;
专用布尔运行计时器;
私人音乐声;
私人空间脉冲(){
步调();
processInputs();
如果(gameOver)gameOver();
重新油漆();
}
私人空间(){
//如果入侵者还活着
如果(!invaders.isEmpty()){
inverspace++;
//切换管理器
如果(距离边缘=16)?(int)(脉冲速率*(0.8)):脉冲速率;
waveDirection=!waveDirection;
distanceToEdge=CalculatedInstanceToEdge();
}
//向左/向右移动入侵者
else if(Investerface>=pulseRate){
侵入面=0;
distanceToEdge=CalculatedInstanceToEdge();
moveAI();
入侵者火力();
如果(!dead.isEmpty())删除了dead();
if(mysteryCount<1)tryInitMysteryShip();
}
//所有入侵者都被杀死,创造新的浪潮
}else if(missileBase.isEmpty()&&missileInvader.isEmpty()&&!cast.contains(mysteryShip)){
//System.out.println(“新浪潮!”);
newWave();
}
//每一步
如果(!missileBase.isEmpty())moveMissileBase();
//每隔两步
如果(入侵空间%2==0){
如果(!missleinvader.isEmpty())移动missleinvader();
如果(mysteryCount>0)moveMysteryShip();
}
}
私有void processInputs(){
如果(左)移动(左);
如果(右)移动(!右);
if(太空)火力导弹(基地,真);
}
受保护的空火导弹(SIship飞船,布尔isBase){
if(isBase&&missileBase.isEmpty()){
base.playSound();
SImissile m=新的SImissile(ship.getX()+(ship.getWidth()/2),ship.getY()-(ship.getHeight()/4));
增加(m);
增加(m);

}否则,如果(!isBase&&missleinvader.size()我认为碰撞检测可能是滞后的原因,您应该通过尝试大幅增加和减少敌人或导弹的数量来进行调查,看看这是否会产生影响

认为垃圾收集器是你的敌人。在你的检查碰撞方法中,你正在实例化两个(非常简单的)对象。它看起来不太多,但是考虑到你可能正在为每个碰撞检查创建它们,并且在60fps时它会增加,直到GC在“停止世界”时看到临界质量,并且你看到明显的滞后。


如果是这种情况,可能的解决方案是不在频繁调用的方法中实例化任何对象。您可以创建一次矩形2D,然后更新其位置,而不是每次创建一个新的,这样您就可以避免不必要的内存分配。

“为什么我的**简单**java2d空间入侵者游戏落后?”——你已经发布了375行代码,所有的代码都在一个大类中,这甚至不是整个程序——不是一个简单的程序来确定。考虑通过创建一个有用的工具来隔离你的问题,一个有用的工具可以帮助你看到你的问题赤裸裸的,和我们,因为它允许我们涉猎更少的代码,更多的释放。ant代码和runnable代码。@HoverCraftfullOfels谢谢你的回答。我将看看你说的话,然后更新我的问题。我已经实现了这一点,但我仍然反复出现打破游戏规则的延迟。延迟是否来自于从数组列表中移除死船?我已将checkCollision方法设置为始终返回fa通过注释掉rect1.intersect(rect2)实现lse。这将消除游戏中的所有延迟。这有什么原因吗?检查交叉点本身似乎会导致延迟,而不是一次又一次地创建新的矩形。是的,这可能是一个代价高昂的操作,具体取决于它的实现。要诊断它,我建议使用探查器。此问题的可能解决方案之一可能是执行所谓的限制(也称为“延迟”)。此技术不涉及检查每个帧中每个对象之间的碰撞,而是只检查单个帧标记中的一小部分对象,然后检查下一帧标记中的另一小部分对象,以便覆盖设定帧数的每个对象(即10帧,它使碰撞计算在大约1/6秒的时间内不太精确)此外,我注意到您在每个对象之间执行碰撞检查,这意味着O(n^2)二次增长。根据你有多少个实体,你可以考虑使用空间分割算法,如四叉树。我确定了问题并修正了它。