You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何提升Java版Flappy Bird克隆游戏的运行效率?

Java Flappy Bird克隆游戏卡顿优化建议

我正在开发一款Flappy Bird克隆游戏用于编程练习,目前功能正常但存在掉帧和卡顿问题,不知道该如何优化Java程序的运行流畅度。是否需要统计方法耗时并针对性优化,或是有其他优化方案?相关Java游戏性能优化资料较少,恳请提供实用建议。


现有代码

Hazards类

package entity;

import java.util.ArrayList;

public class Hazards {

    public ArrayList<Horizontal> hors;
    public ArrayList<Vertical> verts;
    
    public Hazards(int width, int height, int thickness) {
        hors = new ArrayList<Horizontal>();
        hors.add(new Horizontal(0, 0, width, thickness));
        hors.add(new Horizontal(0, height-thickness, width, thickness));
        verts = new ArrayList<Vertical>();
    }
}

Horizontal类

package entity;

import java.awt.Rectangle;

public class Horizontal {

    public int xPos, yPos, width, height;
    public Rectangle bounds;

    public Horizontal(int x, int y, int w, int h) {
        this.xPos = x;
        this.yPos = y;
        this.width = w;
        this.height = h;
        this.bounds = new Rectangle(x, y, w, h);
    }
    
    public void updateBounds(int x, int y, int w, int h) {
        this.xPos = x;
        this.yPos = y;
        this.width = w;
        this.height = h;
        this.bounds = new Rectangle(x, y, w, h);
    }
}

Vertical类

package entity;

import java.awt.Rectangle;
import java.util.Random;

public class Vertical {

    public int xPos, width, gapSize, gapPos;
    public boolean scoredOn = false;
    public Rectangle top, bottom;

    public Vertical(int xPos, int width, int roofHeight, int floorHeight, int gapSize) {
        this.xPos = xPos;
        this.width = width;
    
        this.gapPos = new Random().nextInt(floorHeight - gapSize) + roofHeight;
        
        this.top = new Rectangle();
        this.bottom = new Rectangle();
        this.top.setBounds(xPos, 0, width, gapPos);
        this.bottom.setBounds(xPos, gapPos + gapSize, width, floorHeight - roofHeight + top.height + gapSize);

    }

}

Content类

package main;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;

import javax.swing.JPanel;

public class Content extends JPanel {

    private static final long serialVersionUID = 1L;

    private Engine e;
    private Rectangle bounds;

    public Content(Engine engine) {
        e = engine;
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.clearRect(0, 0, this.getWidth(), this.getHeight());
        
        // Roof and floor
        g.setColor(Color.black);
        for (int x = 0; x < e.e.hors.size(); x++) {
            bounds = e.e.hors.get(x).bounds;
            g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
        }
        
        // Pipes
        g.setColor(Color.black);
        for(int x = 0; x < e.e.verts.size(); x++) {
            bounds = e.e.verts.get(x).top;
            g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
            bounds = e.e.verts.get(x).bottom;
            g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
        }
        
        // Player
        g.setColor(Color.black);
        g.fillRect((int) e.p.xPos, (int) e.p.yPos, e.p.size, e.p.size);
        
        // Score
        g.setColor(Color.blue);
        g.setFont(new Font("Monospaced", Font.PLAIN, 40));
        g.drawString(Integer.toString(e.p.score), e.width/2, 80);
    }

}

Engine类

package main;

import java.awt.Dimension;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import entity.Hazards;
import entity.Vertical;
import screen.TitleScreen;

public class Engine implements Runnable {

    public JFrame f;
    public String title = "Flappy Bird";
    public int width = 500, height = 500;
    public Content c;

    public boolean running = false;
    public boolean playing = false;
    public Thread t;

    public Player p;

    public Hazards e;
    
    public TitleScreen ts;
    
    public JPanel mainPanel;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                Engine e = new Engine();
                e.execute();
            }
            
        });
    }
    
    public void execute() {
        ts = new TitleScreen(width, height);
        e = new Hazards(width, height, 30);
        p = new Player(this);
        c = new Content(this);
        c.setPreferredSize(new Dimension(width, height));
        c.setLayout(null);
        c.addKeyListener(p);
        mainPanel = new JPanel();
        mainPanel.setPreferredSize(new Dimension(width, height));
        mainPanel.setLayout(null);
        
        f = new JFrame();
        f.setTitle(title);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setResizable(false);
        f.add(c);
        // f.add(mainPanel);
        f.pack();
        f.createBufferStrategy(2);
        f.setLayout(null);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        c.requestFocus();
        //ts.setScreen(mainPanel);
        start();
    }

    public synchronized void start() {
        if (running)
            return;
        running = true;
        t = new Thread(this);
        t.start();
    }

    public synchronized void stop() {
        if (!running)
            return;
        running = false;
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        long lastime = System.nanoTime();
        double AmountOfTicks = 60;
        double ns = 1000000000 / AmountOfTicks;
        double delta = 0;
        int tick = 0;
        while (running) {
            long now = System.nanoTime();
            delta += (now - lastime) / ns;
            lastime = now;
            if (delta >= 1) {
                // Call all updates here
                if (playing) {
                    p.updatePos();
                    tick++;
                    if (tick == 60) {
                        tick = 0;
                        p.distance += p.speed;
                        System.out.println(p.distance);
                        if ((p.distance % 4) == 0) {
                            System.out.println("Making new pipes-----------------------------------------------------");
                            e.verts.add(new Vertical(600, 10, 30, height - 30, 100));
                        }
                    }
                    for (int x = 0; x < e.verts.size(); x++) {
                        e.verts.get(x).top.x -= p.speed;
                        e.verts.get(x).bottom.x -= p.speed;
                        
                        if(e.verts.get(x).top.x<-50) {
                            e.verts.remove(x);
                            System.out.println("removed a pipe");
                        }
                        
                        if(p.xPos>e.verts.get(x).top.x && !e.verts.get(x).scoredOn) {
                            e.verts.get(x).scoredOn = true;
                            p.score++;
                        }
                        
                    }
                }
                mainPanel.revalidate();
                mainPanel.repaint();
                f.revalidate();
                f.repaint();
                delta--;
            }
        }
    }
}

Player类

package main;

import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class Player implements KeyListener {

    public int size = 10;
    public double xPos = 50;
    public double yPos = 240;
    public double gravity = 3.4;
    public double jumpForce = 16.6;
    public double weight = 1;
    public int speed = 2;
    public int score = 0;
    public int distance = 0;

    public boolean jumping = false;
    public double jumpTime = 10;
    public int timed = 0;
    

    public Rectangle bounds, temp, top, bottom;
    
    public Engine en;

    public Player(Engine engine) {
        en = engine;
        bounds = new Rectangle((int)xPos, (int)yPos, size, size);
    }

    public void keyTyped(KeyEvent e) {
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W && en.playing) {
            jumping = true;
        } else if (e.getKeyCode() == KeyEvent.VK_SPACE) {
            en.playing = !en.playing;
        }
        
        if(jumping) {
            timed = 0;
            jumpForce = 16.6;
        }
    }

    public void keyReleased(KeyEvent e) {
    }

    public void updatePos() {
        // collide with floor or ceiling
        for(int x = 0; x < en.e.hors.size(); x++) {
            temp = en.e.hors.get(x).bounds;
            if(bounds.intersects(temp)) {
                en.playing = false;
                jumping = false;
                timed = 0;
                jumpForce = 0;
                yPos = 240;
                score = 0;
                en.e.verts.clear();
                distance = 0;
                gravity = 3.8;
            }
        }
        
        // collide with pipe
        for(int x =0; x <en.e.verts.size();x++) {
            top = en.e.verts.get(x).top;
            bottom = en.e.verts.get(x).bottom;
            if(bounds.intersects(top)||bounds.intersects(bottom)) {
                en.playing = false;
                jumping = false;
                timed = 0;
                jumpForce = 0;
                yPos = 240;
                score = 0;
                gravity =3.4;
                en.e.verts.clear();
                distance = 0;
            }
        }
        
        if (jumping && en.playing) {
            gravity = 3.4;
            yPos -= jumpForce;
            jumpForce -= weight;
            if (jumpForce == 0) {
                jumping = false;
                jumpForce = 16.6;
            }
        }

        //if(!jumping && en.playing) {
            gravity += 0.1;
        //}
        
            
        System.out.println(gravity);
        yPos += gravity;
        bounds.setBounds((int)xPos, (int)yPos, size, size);
    }
}

针对性优化方案

1. 修复渲染逻辑(最核心问题)

当前Engine.run()里的渲染代码完全错误,频繁调用revalidate()和全局repaint()会导致大量无效布局计算和重绘,直接替换为缓冲策略渲染:

// 替换原run方法中mainPanel.revalidate()等四行代码
BufferStrategy bs = f.getBufferStrategy();
if (bs == null) {
    f.createBufferStrategy(2);
    return;
}
Graphics g = bs.getDrawGraphics();
c.paintComponent(g);
g.dispose();
bs.show();

2. 移除所有调试控制台输出

System.out.println是同步IO操作,会严重拖慢游戏循环,直接删除Player.updatePos()中的System.out.println(gravity)、Engine.run()中的打印管道相关语句。

3. 优化管道遍历与删除逻辑

遍历列表时直接删除元素会导致索引错乱,改用迭代器安全删除:

// 替换Engine.run()中管道遍历的for循环
Iterator<Vertical> iter = e.verts.iterator();
while (iter.hasNext()) {
    Vertical v = iter.next();
    v.top.x -= p.speed;
    v.bottom.x -= p.speed;
    
    if(v.top.x < -50) {
        iter.remove();
    }
    
    if(p.xPos > v.top.x && !v.scoredOn) {
        v.scoredOn = true;
        p.score++;
    }
}

4. 减少对象频繁创建

  • Horizontal.updateBounds复用已有Rectangle,避免频繁触发GC:
public void updateBounds(int x, int y, int w, int h) {
    this.xPos = x;
    this.yPos = y;
    this.width = w;
    this.height = h;
    this.bounds.setBounds(x, y, w, h);
}
  • Vertical类复用静态Random对象,避免每次创建管道都新建Random:
private static Random random = new Random();

public Vertical(int xPos, int width, int roofHeight, int floorHeight, int gapSize) {
    // ...
    this.gapPos = random.nextInt(floorHeight - gapSize) + roofHeight;
    // ...
}
  • Content类提前初始化分数字体,避免每次渲染都新建Font:
private Font scoreFont;

public Content(Engine engine) {
    e = engine;
    scoreFont = new Font("Monospaced", Font.PLAIN, 40);
}

// 渲染分数时直接使用
g.setFont(scoreFont);

5. 控制帧率上限

在游戏循环末尾添加休眠逻辑,避免CPU占用过高,稳定帧率:

// 放在run方法的while循环末尾
long sleepTime = (long)(1000/60 - (System.nanoTime() - now)/1000000);
if(sleepTime > 0) {
    try {
        Thread.sleep(sleepTime);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
}

内容的提问来源于stack exchange,提问作者INeedHelp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 04:00:57