如何提升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
相关产品推荐
相关产品推荐

