So I am following a series of tutorials to learn how to make a game in java(i am still pretty new), and i think i followed his code exactly, but it still prints the stack trace when i run it. Here is my code...
Game.java
package com.game.src.main;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.JFrame;
public class Game extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
public static final int WIDTH = 320;
public static final int HEIGHT = WIDTH / 12 * 9;
public static final int SCALE = 2;
public final String TITLE = "2D Space Game";
private JFrame frame = new JFrame(TITLE);
private boolean running = false;
private Thread thread;
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
private BufferedImage spriteSheet = null;
private BufferedImage player;
public void init(){
BufferedImageLoader loader = new BufferedImageLoader();
spriteSheet = loader.loadImage("/sprite_sheet.png");
SpriteSheet ss = new SpriteSheet(spriteSheet);
player = ss.grabImage(1, 1, 32, 32);
}
public void run(){
long lastTime = System.nanoTime();
final double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long now = 0;
int updates = 0, frames = 0;
long timer = System.currentTimeMillis();
init();
while(running){
now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
if(delta >= 1){
tick();
updates++;
delta--;
}
render();
frames++;
if(System.currentTimeMillis() - timer > 1000){
timer += 1000;
frame.setTitle("2-D Space Game || Updates: " + updates + ", FPS: " + frames);
frames = 0;
updates = 0;
}
}
stop();
}
private void tick(){
}
private void render(){
BufferStrategy bs = this.getBufferStrategy();
try {
Thread.sleep(2);
} catch (InterruptedException e){
e.printStackTrace();
}
if(bs == null){
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
/////////////////////////////////////
g.drawImage(image, 0, 0, getWidth(), getHeight(), this);
g.drawImage(player, 100, 100, this);
/////////////////////////////////////
g.dispose();
bs.show();
}
private synchronized void start(){
if(running) return;
running = true;
thread = new Thread(this);
thread.start();
}
private synchronized void stop(){
if(!running) return;
try{
thread.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.exit(1);
}
public static void main(String args[]){
Game game = new Game();
game.setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.frame.add(game);
game.frame.pack();
game.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.frame.setResizable(false);
game.frame.setLocationRelativeTo(null);
game.frame.setVisible(true);
game.start();
}
}
SpriteSheet.java
package com.game.src.main;
import java.awt.image.BufferedImage;
public class SpriteSheet {
private BufferedImage image;
public SpriteSheet(BufferedImage image){
this.image = image;
}
public BufferedImage grabImage(int col, int row, int width, int height){
return image.getSubimage((col * 32) - 32, (row * 32) - 32, width, height);
}
}
BufferedImageLoader.java
package com.game.src.main;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class BufferedImageLoader {
private BufferedImage image;
public BufferedImage loadImage(String path){
try {
image = ImageIO.read(getClass().getResource(path));
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return image;
}
}
Now here is the stacktrace that shows up:
Exception in thread "Thread-0" java.lang.IllegalArgumentException: input == null!
at javax.imageio.ImageIO.read(ImageIO.java:1388)
at com.game.src.main.BufferedImageLoader.loadImage(BufferedImageLoader.java:13)
at com.game.src.main.Game.init(Game.java:30)
at com.game.src.main.Game.run(Game.java:45)
at java.lang.Thread.run(Thread.java:745)
Can anyone help?
BTW:
sprite_sheet.png does exist
Ok I figured it out. My sprite_sheet.png was in my "res" folder, and i needed to add it to the build path
The issue here is with the way you are addressing your png, and that the location you are specifying is not in your build path, or where you expect it to be. If you provide me the pwdon your location I will update with actual path you should use
You either need to provide the full path
/home/me/myprogram/sprite_sheet.png
or use relative path
./sprite_sheet.png
Your program is looking for the file in the root directory /
You need to provide a full path relative to your CLASSPATH, or a relative path relative to the current class's package.
Related
I'm learning to make games using a a tutorial on youtube. Everything seems fine except when I run the program. A frame shows up with the accurate width I want but the height looks like it sets to a default no matter what value I give it.
package ca.vanzeben.game;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
public static final int WIDTH = 160;
public static final int Height = WIDTH/ 12*9;
public static final int SCALE = 3; // able to move screen
public static final String NAME = "Juego";
private JFrame frame;
public boolean running = false;
public int tickCount = 0;
private BufferedImage image = new BufferedImage(WIDTH,HEIGHT,BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
public Game() { //game constructor
setMinimumSize(new Dimension(WIDTH*SCALE,HEIGHT*SCALE));
setMaximumSize(new Dimension(WIDTH*SCALE,HEIGHT*SCALE));
setPreferredSize(new Dimension(WIDTH*SCALE,HEIGHT*SCALE));
frame = new JFrame(NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // closes game completely
frame.setLayout(new BorderLayout());
frame.add(this,BorderLayout.CENTER); //adds canvas to JFrame and centers it
frame.pack();//keeps everything sized correctly
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public synchronized void start() {//so u can start from the applet
running = true;
new Thread(this).start();
}
public synchronized void stop(){
running = false;
}
public void run() {
long lastTime = System.nanoTime();
double nsPerTick = 1000000000.0/60; //nanoseconds per tick or per update
int ticks = 0;
int frames = 0;
long lastTimer = System.currentTimeMillis();
double delta = 0.0; //how many unprocessed nano seconds
while(running){
long now = System.nanoTime();
delta+=(now-lastTime)/nsPerTick;
lastTime = now;
boolean shouldRender = true;
while(delta>=1){
ticks++;
tick();
delta -= 1;
shouldRender = true;
}
try{
Thread.sleep(2);
}catch(InterruptedException e){
e.printStackTrace();
}
if(shouldRender){
frames++;
render();
}
if(System.currentTimeMillis()-lastTimer>=1000){
lastTimer += 1000;
System.out.println(frames + " frames " + ticks + " ticks ");
frames = 0;
ticks = 0;
}
}
}
public void tick(){ //updates the game, updates the logic
tickCount++;
for(int i =0;i<pixels.length;i++){
pixels[i] = i + tickCount;
}
}
public void render(){ //prints out ^
BufferStrategy bs = getBufferStrategy();
if(bs == null){
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g.setColor(Color.BLACK);
g.drawRect(0, 0, getWidth(), getHeight());
g.dispose();
bs.show();
}
public static void main(String[] args){
new Game().start();
}
}
You defined Height and used HEIGHT. Change it to:
public static final int HEIGHT = WIDTH / 12 * 9;
This is how it looked like to me:
Before:
After:
So, first of all this is my first time posting and I am trying to make a top-down game with java(1.8) and IntelliJ, nothing else. I have all of my code good and done(for making the sprite show up), I test it and it keeps giving me error messages saying that certain parts are invalid. It is supposed to display an image (character) on the screen in the top right here is all code and please keep in mind that I do not have this fully finished:
Game.java
package main;
import main$entities.Player;
import main$gfx.ImageLoader;
import main$gfx.SpriteSheet;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.lang.*;
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
public static final int WIDTH = 700, HEIGHT = 400, SCALE = 1;
public static boolean running = false;
public Thread gameThread;
private BufferedImage spriteSheet;
private Player player;
public void init(){
ImageLoader loader = new ImageLoader();
spriteSheet = loader.load("./spritesheet.png");
SpriteSheet ss = new SpriteSheet(spriteSheet);
player = new Player(0, 0, ss);
}
public synchronized void start(){
if(running)return;
running = true;
gameThread = new Thread(this);
gameThread.start();
}
public synchronized void stop(){
if(!running)return;
running = false;
try {
gameThread.join();
} catch (InterruptedException e) {e.printStackTrace();}
}
public void run(){
long lastTime = System.nanoTime();
final double amountOfTicks = 60D;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
while(running){
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
if(delta >= 1){
tick();
delta--;
}
render();
}
stop();
}
public void tick(){
player.tick();
}
public void render(){
BufferStrategy bs = this.getBufferStrategy();
if(bs == null){
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
// Render Here
g.fillRect(0, 0, WIDTH * SCALE, HEIGHT * SCALE);
player.render(g);
// End Render
g.dispose();
bs.show();
}
public static void main(String[] args){
Game game = new Game();
game.setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
JFrame frame = new JFrame("Tile RPG");
frame.setSize(WIDTH *SCALE, HEIGHT * SCALE);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(game);
frame.setVisible(true);
game.start();
}
}
ImageLoader.java
package main$gfx;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class ImageLoader {
public BufferedImage load(String path){
try {
return ImageIO.read(getClass().getResource(path));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
SpriteSheet.java
package main$gfx;
import java.awt.image.BufferedImage;
public class SpriteSheet {
private BufferedImage sheet;
public SpriteSheet(BufferedImage sheet){
this.sheet = sheet;
}
public BufferedImage crop(int col, int row, int w, int h){
return sheet.getSubimage(col * 16, row * 16, w, h);
}
}
Player.java
package main$entities;
import main.Game;
import main$gfx.SpriteSheet;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Player implements KeyListener{
private int x, y;
private SpriteSheet ss;
private boolean up = false, dn = false, lt = false, rt = false;
private final int SPEED = 3;
public Player(int x, int y, SpriteSheet ss){
this.x = x;
this.y = y;
this.ss = ss;
}
public void tick(){
if(up){
y -= SPEED;
}
if(dn){
y += SPEED;
}
if(lt){
x -= SPEED;
}
if(rt){
x += SPEED;
}
}
public void render(Graphics g){
g.drawImage(ss.crop(0 ,0, 16, 16), x, y, 16 * Game.SCALE, 16 * Game.SCALE, null);
}
public void keyTyped(KeyEvent e) {}
public void keyPressed(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
}
The error messages(It gives me multiple messages, each time is different // = what is on that line, so you don't have to find it by counting, your welcome):
First time I run the Game.java:
Exception in thread "Thread-2" java.lang.NullPointerException
at main.Game.tick(Game.java:64) // player.tick();
at main.Game.run(Game.java:56) // tick();
at java.lang.Thread.run(Thread.java:745) // this is in the JDK itself
Process finished with exit code 0
Second(and all that proceed) time I run the Game.java:
Exception in thread "Thread-2" java.lang.NullPointerException
at main.Game.render(Game.java:77) // player.render(g);
at main.Game.run(Game.java:59) // render();
at java.lang.Thread.run(Thread.java:745) // this is in the JDK itself
Process finished with exit code 0
That game.start(); takes all the responsibility. Your Game class is not extending a Thread, the thread itself will start automatically right after you instantiate your game's class, since the thread is created inside the game class.
So remove :
game.start();
Btw you fall on trap :
public static final int WIDTH = 700, HEIGHT = 400;
won't work, because those fields hide another fields, why? because
WIDTH & HEIGHT is preserved arguments for
.fillRect(...);
It's because you never call the init() method and, therefore the player and the spriteheet never gets created
You can add a constructor to the Game class like this:
public Game() {
init();
}
Or you can add the following line in the main method:
public static void main(String[] args){
Game game = new Game();
game.setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
JFrame frame = new JFrame("Tile RPG");
frame.setSize(WIDTH *SCALE, HEIGHT * SCALE);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(game);
frame.setVisible(true);
// Add this line
game.init();
game.start();
}
}
But I prefer the first way
I have linked the file I want to load and when I debug, the SpriteSheet constructor shows that the path variable is storing the path I specify. When it trying to run the image = ImageIO.read(SpriteSheet.class.getResourceAsStream(path)); line of code, it crashes with an Illegal Argument Exception. The only thing I can think is wrong is that the file I specified will not load, but I have no idea why.
Game class:
package com.swainchris.twodgame;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import com.swainchris.twodgame.gfx.SpriteSheet;
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
public static final int WIDTH = 160;
public static final int HEIGHT = WIDTH / 12 * 9;
public static final int SCALE = 3;
public static final String NAME = "2D Game";
public static boolean running = false;
private JFrame frame;
public int tickCount = 0;
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
private SpriteSheet spriteSheet = new SpriteSheet("/SS.png");
public Game() {
setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
frame = new JFrame(NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(this, BorderLayout.CENTER);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public synchronized void start() {
running = true;
new Thread(this).start();
}
public synchronized void stop() {
running = false;
}
public void tick(){
tickCount++;
for(int i = 0; i < pixels.length; i++){
pixels[i] = i - tickCount;
}
}
public void render(){
BufferStrategy bs = getBufferStrategy();
if(bs==null){
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.drawImage(image,0,0,getWidth(),getHeight(),null);
g.setColor(Color.BLACK);
g.fillOval(50,50,50,50);
g.dispose();
bs.show();
}
public void run() {
long lastTime = System.nanoTime();
double nsPerTick = 1000000000D/60D;
int ticks = 0;
int frames = 0;
long lastTimer = System.currentTimeMillis();
double delta = 0;
while(running){
long now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
boolean shouldRender = true;
while(delta >= 1){
ticks++;
tick();
delta--;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(shouldRender){
frames++;
render();
}
if(System.currentTimeMillis() - lastTimer >= 1000){
lastTimer += 1000;
frame.setTitle("2D Game! FPS: " + frames + " UPS: " + ticks);
frames = 0;
ticks = 0;
}
}
}
public static void main(String[] args) {
new Game().start();
}
}
SpriteSheet class:
package com.swainchris.twodgame.gfx;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public class SpriteSheet {
public String path;
public int width;
public int height;
public int[] pixels;
public SpriteSheet(String path){
BufferedImage image = null;
try {
image = ImageIO.read(SpriteSheet.class.getResourceAsStream(path));
} catch (IOException e) {
e.printStackTrace();
}
if(image == null){
return;
}
this.path = path;
this.width = image.getWidth();
this.height = image.getHeight();
pixels = image.getRGB(0,0,width,height,null,0,width);
for(int i = 0; i < pixels.length; i++){
pixels[i] = (pixels[i] & 0xff)/64;
}
for(int i = 0; i<8; i++){
System.out.println(pixels[i]);
}
}
}
Error:
Exception in thread "main" java.lang.IllegalArgumentException: input == null!
at javax.imageio.ImageIO.read(Unknown Source)
at com.swainchris.twodgame.gfx.SpriteSheet.<init>(SpriteSheet.java:20)
at com.swainchris.twodgame.Game.<init>(Game.java:30)
at com.swainchris.twodgame.Game.main(Game.java:137)
Works now, all i did was copy all the code to a new project file. Didn't change anything at all besides the name of the project. No idea why this worked but it did.
I have some errors atm while im coding with JAVA, I have been trying to fix this for along time, also trying to find oterh ppl who have same problem and fixed it but nothing work...
Well.. here is the code
package ca.vanzeben.game;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
public class Game extends Canvas implements Runnable {
private static final long serialVerisionUID = 1L;
public static final int WIDTH = 160;
public static final int HEIGHT = WIDTH / 12*9;
public static final int SCALE = 3;
public static final String NAME = "Game";
public boolean running = false;
public int tickCount = 0;
private JFrame frame;
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_3BYTE_BGR);
private int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
public Game(){
setMinimumSize(new Dimension(WIDTH*SCALE, HEIGHT * SCALE));
setMaximumSize(new Dimension(WIDTH*SCALE, HEIGHT * SCALE));
setPreferredSize(new Dimension(WIDTH*SCALE, HEIGHT * SCALE));
frame = new JFrame(NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(this, BorderLayout.CENTER);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public synchronized void start() {
running = true;
new Thread(this).start();
}
public synchronized void stop() {
running = false;
}
public void run(){
long lastTime = System.nanoTime();
double nsPerTick = 1000000000D/60D;
int ticks = 0;
int frames = 0;
long lastTimer = System.currentTimeMillis();
double delta = 0;
while(running){
long now = System.nanoTime();
delta +=(now - lastTime) / nsPerTick;
lastTime = now;
boolean shouldRender = true;
while(delta >= 1){
ticks++;
tick();
delta -= 1;
shouldRender = true;
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (shouldRender){
frames++;
render();
}
if(System.currentTimeMillis() - lastTimer >= 1000){
lastTimer += 1000;
System.out.println(ticks + " ticks, " + frames + " frames");
frames = 0;
ticks = 0;
}
}
}
public void tick() {
tickCount++;
}
public void render(){
BufferStrategy bs = getBufferStrategy();
if(bs == null) {
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
g.dispose();
bs.show();
}
public static void main(String[] args) {
new Game().start();
}
}
And the error is:
Exception in thread "main" java.lang.ClassCastException: java.awt.image.DataBufferByte cannot be cast to java.awt.image.DataBufferInt
at ca.vanzeben.game.Game.<init>(Game.java:30)
at ca.vanzeben.game.Game.main(Game.java:122)
To solve your problem, you need to change the BufferedImage type of
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT,
BufferedImage.TYPE_3BYTE_BGR);
and change it to
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
the problem is that BufferedImage.TYPE_3BYTE_BGR uses byte[3] to represent each pixel and
BufferedImage.TYPE_INT_RGB just uses an int
The problem is that image.getRaster().getDataBuffer() is returning a DataBufferByte, and you're attempting to cast to a DataBufferInt. Those are two distinct classes, both subclasses of DataBuffer, but one is not a subclass of the other, so casting between them is not possible.
The spec for Raster doesn't clearly describe what determines whether getDataBuffer returns a DataBufferByte or a DataBufferInt (or perhaps some other flavor of DataBuffer). But presumably this varies depending on the type of image being dissected. You're probably dissecting a byte-per-pixel image and the code, as it stands, expects 32-bits-per-pixel.
As it is, you probably need to remove some of that logic from the <init> section and add it to the explicit constructor, so you can test the type of DataBuffer returned and handle it accordingly, rather than unconditionally casting it to DataBufferInt.
image.getRaster().getDataBuffer().getDataType() delivers a type constant from class DataBuffer, e.g. TYPE_BYTE, TYPE_SHORT, TYPE_INT, each of these binding a unique DataBuffer[Type] subclass of DataBuffer. So you can detect, whether a typed buffer cast will succeed.
Conversion between int and byte[4] basically depends on a byte-order-rule, which is not defined on the level Java internal types and so a reinterpret cast of arrays of such cannot be well-defined.
If you are not the originator the image as the accepted answer assumes, you need to define and apply type conversions.
EDIT
DataInputStream.readInt actually uses a defined conversion: Big-endianness
Conversion from byte[] to int[] may be performed be reading integers from a DataInputStream with ByteArrayInputStream as source, initialized with the byte array.
Lately I've been trying to build a little game (2d, nothing big) off the knowledge I acquired in my CS class. After reading documentations of those Graphic-related classes for like 2 weeks, I ended up with this situation:
I have a running system that operates at 60 game logic updates/60 frames per second (that one works quite nicely :D). As a first little test, I wanted to make a image move on the screen. Thats the code (partly mine, partly from some tutorials):
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();
}
}
The ImageIO.read(...) is really hitting the performance really hard (according to VisualVM, it takes ~200ms/run). How can I tackle that problem?
Reading an image is an inherently costly operation.
Therefore, you should read the image once, when you start the game, and hold it in memory to access later.
Avoid loading the image each time it is rendered. Make it a class variable and load it only once. Like this:
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();
}
}
Same goes for the font, although that may not be impacting performance as much as the image loading.
Disk operations are incredibly slow, and there's no need to access the file each loop, which is what you're currently doing. Make your img variable a class variable and instantiate it before entering the while(running) loop in your run() method.