最近我一直在尝试用我在CS课上获得的知识来构建一个小游戏(2d,没什么大不了的)。在阅读了大约 2 周的那些图形相关类的文档后,我最终遇到了这种情况:我有一个正在运行的系统,以每秒 60 次游戏逻辑更新/每秒 60 帧的速度运行(:D,这个运行得很好)。作为第一个小测试,我想让图像在屏幕上移动。这是代码(部分是我的,部分来自一些教程):
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Game extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
public static final String NAME= "PokeCraft PRE-ALPHA";
public static final int HEIGHT=720;
public static final int WIDTH=HEIGHT*16/9;
public static final int SCALE=1;
private int fps=0;
private int tps=0;
private boolean running;
private int tickCount;
public void start(){
running = true;
new Thread(this).start();
}
public void stop(){
running = false;
}
public void render(){
BufferStrategy bufferStrategy =getBufferStrategy();
if(bufferStrategy==null){
this.createBufferStrategy(3);
return;
}
/* render function */
Graphics g = (Graphics) bufferStrategy.getDrawGraphics();
g.clearRect(0, 0, super.getWidth(), super.getHeight());
Image img = null;
try{
String imgPath = "data/MF.png";
img = ImageIO.read(getClass().getResourceAsStream(imgPath));
} catch(Exception e){
System.out.println(e);
}
g.drawImage(img, tickCount, 0, null);
Font font = new Font("Verdana",0,11);
g.setFont(font);
g.setColor(Color.RED);
g.drawString(NAME+" / "+fps+" fps, "+tps+"tps", 5, 15);
g.dispose();
bufferStrategy.show();
}
public void run() {
long lastTime= System.nanoTime();
double unprocessed = 0;
double nsPerTick = 1000000000.0/60.0;
int frames = 0;
int ticks = 0;
long lastTimer1 = System.currentTimeMillis();
while(running){
long now = System.nanoTime();
unprocessed += (now-lastTime)/nsPerTick;
lastTime= now;
boolean shouldRender= false;
while(unprocessed >= 1){
ticks++;
tick();
unprocessed -= 1;
shouldRender = true;
}
if(shouldRender){
frames++;
render();
}
if(System.currentTimeMillis()-lastTimer1 > 1000){
lastTimer1 += 1000;
System.out.println(ticks+" ticks, "+frames + " fps");
fps=frames;
tps=ticks;
ticks = 0;
frames = 0;
}
}
}
public void tick(){
tickCount++;
}
public static void main(String[] args){
Game game= new Game();
game.setPreferredSize(new Dimension(WIDTH*SCALE, HEIGHT*SCALE));
game.setMinimumSize(new Dimension(WIDTH*SCALE, HEIGHT*SCALE));
game.setMaximumSize(new Dimension(WIDTH*SCALE, HEIGHT*SCALE));
JFrame frame = new JFrame(Game.NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(game);
frame.pack();
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
game.start();
}
}
ImageIO.read(...)确实对性能造成了很大的冲击(根据VisualVM,每次运行需要~200ms)。我该如何解决这个问题?
读取图像本身是一项成本高昂的操作。
因此,您应该在启动游戏时读取一次图像,并将其保存在内存中以便稍后访问。
避免在每次渲染图像时加载图像。使其成为类变量并仅加载一次。喜欢这个:
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
public static final String NAME = "PokeCraft PRE-ALPHA";
public static final int HEIGHT = 720;
public static final int WIDTH = HEIGHT * 16 / 9;
public static final int SCALE = 1;
private int fps = 0;
private int tps = 0;
private Image img = null;
private boolean running;
private int tickCount;
public void start() {
running = true;
new Thread(this).start();
}
public void stop() {
running = false;
}
public void render() {
BufferStrategy bufferStrategy = getBufferStrategy();
if (bufferStrategy == null) {
this.createBufferStrategy(3);
return;
}
/* render function */
Graphics g = (Graphics) bufferStrategy.getDrawGraphics();
g.clearRect(0, 0, super.getWidth(), super.getHeight());
if (img == null) {
try {
String imgPath = "data/MF.png";
img = ImageIO.read(getClass().getResourceAsStream(imgPath));
} catch (Exception e) {
System.out.println(e);
}
}
g.drawImage(img, tickCount, 0, null);
Font font = new Font("Verdana", 0, 11);
g.setFont(font);
g.setColor(Color.RED);
g.drawString(NAME + " / " + fps + " fps, " + tps + "tps", 5, 15);
g.dispose();
bufferStrategy.show();
}
public void run() {
long lastTime = System.nanoTime();
double unprocessed = 0;
double nsPerTick = 1000000000.0 / 60.0;
int frames = 0;
int ticks = 0;
long lastTimer1 = System.currentTimeMillis();
while (running) {
long now = System.nanoTime();
unprocessed += (now - lastTime) / nsPerTick;
lastTime = now;
boolean shouldRender = false;
while (unprocessed >= 1) {
ticks++;
tick();
unprocessed -= 1;
shouldRender = true;
}
if (shouldRender) {
frames++;
render();
}
if (System.currentTimeMillis() - lastTimer1 > 1000) {
lastTimer1 += 1000;
System.out.println(ticks + " ticks, " + frames + " fps");
fps = frames;
tps = ticks;
ticks = 0;
frames = 0;
}
}
}
public void tick() {
tickCount++;
}
public static void main(String[] args) {
Game game = new Game();
game.setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
JFrame frame = new JFrame(Game.NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(game);
frame.pack();
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
game.start();
}
}
字体也是如此,尽管这可能不像图像加载那样影响性能。
磁盘操作非常慢,并且不需要每个循环访问文件,而这正是您当前正在执行的操作。使 img 变量成为类变量,并在 run() 方法中进入 while(running) 循环之前对其进行实例化。