This is a follow up question to an earlier one of mine. I am making a java game, and it is basically a JFrame with a character image, some healthbars consisting of fillRect()s which are all on top of a background image. The problem is the healthbars and character are appearing but the background image isn't.
Here is a shortened version of the Game class which has the main() and render() methods:
public class Game extends Canvas implements Runnable{
public static boolean running = false;
public Thread gameThread;
private BufferedImage playerSpriteSheet;
private ImageManager im;
private static Player player;
private static HealthBar healthBars;
private static BackgroundImage backgroundImage;
public void init(){
ImageLoader loader = new ImageLoader();
playerSpriteSheet = loader.load("/spriteSheet.png");
SpriteSheet pss = new SpriteSheet(playerSpriteSheet);
im = new ImageManager(pss);
backgroundImage = new BackgroundImage("/background.png");
player = new Player(800, 250, im);
healthBars = new HealthBar(200, 200);
this.addKeyListener(new KeyManager());
}
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void run() {
init();
long lastTime = System.nanoTime();
final double amountOfTicks = 60D;
double ns = 1_000_000_000/amountOfTicks;
double delta = 0;
long now = System.nanoTime();
while(running)
{
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); //Use 5 at most
return;
}
Graphics g = bs.getDrawGraphics();
//RENDER HERE
backgroundImage.render(g);
player.render(g);
healthBars.render(g);
//END RENDER
g.dispose();
bs.show();
}
public static void main(String[] args)
{
JLabel backgroundImage;
JLabel controlKeyPanel;
JLabel statusLabel;
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("Title");
frame.setResizable(false);
frame.setSize(WIDTH * SCALE, HEIGHT * SCALE);
frame.setLayout(new BorderLayout());
backgroundImage = new JLabel(new ImageIcon("/background.png"));
String htmlButtonGuide = "words";
controlKeyPanel = new JLabel(htmlButtonGuide);
statusLabel = new JLabel("label");
frame.add(backgroundImage, BorderLayout.CENTER);
frame.add(controlKeyPanel, BorderLayout.EAST);
frame.add(statusLabel, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(game);
frame.setVisible(true);
game.start();
//Program seems to continue running after ESC
}
public static Player getPlayer() {
return player;
}
}
Here is the BackGroundImage class:
public class BackgroundImage {
private Image background = null;
public BackgroundImage(String s) {
if(s == null)
{
background = getImage(s);
}
}
public void render(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(background, 0, 0, 1200, 600, null);
}
public Image getImage(String path) {
Image tempImage = null;
File image2 = new File(path);
try {
tempImage = ImageIO.read(image2);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return tempImage;
}
}
I am concerned with the render() method reusing the "g" Graphics object for adding all 3 things to the screen. I was told to not combine lightweight Swing health bars with the heavy AWT background and character? Can anyone point me in the right direction to get the background to show? Should the render method not take care of the background? I only need the background to be put up once. It doesn't need to be constantly updated like the health bars and characters right?
Let's start with...
backgroundImage = new BackgroundImage("/background.png");
Which becomes...
File image2 = new File(path);
or
File image2 = new File("/background.png");
so you can see it...can you see a problem with this? This is requesting a file which resides at the root location of the current drive...not really what I think you want...
The images are stored in a folder called "res" in the main project folder
Which would suggest you want to use...
backgroundImage = new BackgroundImage("res/background.png");
Assuming that the images are not embedded resources....
Next...
public BackgroundImage(String s) {
if (s == null) {
background = getImage(s);
}
}
So, you only ever want to try a load the image when it's reference is null???
Side notes...
frame.setSize(WIDTH * SCALE, HEIGHT * SCALE); is a bad idea, as frames have borders which occupy space within side the frame itself.
Better to override the getPreferredSize method of Canvas and provide a default size value you want to use and then call pack on the frame instead. This will calculate the size of the frame as the preferred size of it's content PLUS the frame border requirements...
You "game loop" is running wild...
while (running) {
delta += (now - lastTime) / ns;
lastTime = now;
if (delta >= 1) {
tick();
delta--;
}
render();
}
Basically, this will run as fast as it possibly can and will reduce the opportunity for other threads to run, eventually bringing your game (and probably your PC) to it's knees
This is "simple" concept of a run loop...
public void run() {
init();
final long amountOfTicks = 60;
long ns = Math.round(1_000_000_000 / (double)amountOfTicks);
int frames = 0;
long frameStart = System.currentTimeMillis();
while (running) {
long startedAt = System.nanoTime();
tick();
render();
long completedAt = System.nanoTime();
long duration = completedAt - startedAt;
long frameEnd = System.currentTimeMillis();
if (frameEnd - frameStart >= 1000) {
System.out.println(frames);
frames = 0;
frameStart = System.currentTimeMillis();
} else {
frames++;
}
long rest = ns - duration;
if (rest > 0) {
rest = TimeUnit.MILLISECONDS.convert(rest, TimeUnit.NANOSECONDS);
try {
Thread.sleep(rest);
} catch (InterruptedException ex) {
}
}
}
stop();
}
Basically, it tries to ensure that there is enough delay between each iteration in order to maintain the 60fps you are trying to target...without starving the system...
Related
If this were to be my image:
private BufferedImage LVL140;
try
{
LVL140 = ImageIO.read(new File("SplendorIm/LVL140.png"));
} catch (IOException e)
{
e.printStackTrace();
}
g.drawImage(LVL140,1050,0,null);
How can I load the same buffered image again multiple times onto my panel without having to create the same buffered image again?
Once you loaded the image, use a while {} with a Thread.sleep() an the g.drawImage() inside it.
Here an example:
public class DrawSystem extends JPanel implements Runnable {
private int width, height;
private BufferedImage img;
private boolean running;
private Graphics2D g;
//Theses lines could be in a configuration, but let's do it here
private void int FPS = 60;
private long targetTime = 1000 / FPS;
private BufferedImage LVL140;
public DrawSystem(Dimension dimensions) {
width = (int) dimensions.getWidth();
height = (int) dimensions.getHeight();
//here's your LVL140 image
LVL140 = ImageIO.read(new File("SplendorIm/LVL140.png"));
}
//you just have to start the thred with (variable).start();
#Override
public void run() {
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.getGraphics();
running = true;
long start;
long elapsed;
long wait;
while(running) {
start = System.nanoTime();
draw();
drawToScreen();
elapsed = System.nanoTime() - start;
wait = targetTime - elapsed / 1000000;
if(wait > 0) {
try{
Thread.sleep(wait);
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
//Draw pixels of "LVL140" on "img". You can draw others things without impact "LVL140" thanks to this
private void draw() {
g.drawImage(LVL140, 0, 0, null);
}
//draw pixels of "img" on the JPanel.
private void drawToScreen() {
Graphics g2 = getGraphics();
if(g2 != null) {
g2.drawImage(img, 0, 0, width, height, null);
g2.dispose();
}
}
}
Notice that you could use addNotify() and then start the Thread from there but don't embarassing yourself it's your firsts steps in java.
Juste create a JFrame, add the DrawSystem and do :
DrawSystem sys = new DrawSystem(new Dimension(1000, 500));
<(your JFrame)>.add(sys);
Thread th = new Thread(sys);
//call the `run()` method
th.start();
As you can see, you create your BufferedImage once, then use it multiple time in the draw() method.
I hope this will help you.
When I run my game the JFrame is just white. Can someone explain?
I have no idea why this is happening and I'm having a really hard time finding out what could be the issue. I hope one of you can explain/know the answer. I look forward to hearing your answer and I would love to continue coding but I'm stuck atm, - Artycal.
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class Game extends Canvas implements Runnable {
static GraphicsDevice device = GraphicsEnvironment
.getLocalGraphicsEnvironment().getScreenDevices()[0];
private static JFrame frame;
private static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private BufferedImage image = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
private BufferedImage spriteSheet = null;
private static double width = screenSize.getWidth();
private static double height = screenSize.getHeight();
private boolean running;
private Thread thread;
private BufferedImage player;
public void init() {
BufferedImageLoader loader = new BufferedImageLoader();
try{
spriteSheet = loader.loadImage("/sprite_sheet.png");
}catch (IOException e){
e.printStackTrace();
}
SpriteSheet ss = new SpriteSheet(spriteSheet);
player = ss.grabImage(1, 1, 32, 32);
}
public void run() {
init();
long lastTime = System.nanoTime();
final double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
int updates = 0;
int frames = 0;
long timer = System.currentTimeMillis();
while (running){
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
if (delta >= 1){
tick();
updates++;
delta--;
}
render();
frames++;
if (System.currentTimeMillis() - timer > 1000){
timer += 1000;
System.out.println(updates + " Ticks, Fps " + frames);
updates = 0;
frames = 0;
}
}
stop();
}
private void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null){
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(new Color(81, 218, 221));
g.drawRect(0, 0, getWidth(), getHeight());
g.setColor(new Color(81, 218, 221));
g.drawImage(image, 0, 0, getWidth(), getHeight(), this);
g.setColor(new Color(255, 174, 80));
g.drawImage(player, 100, 100, this);
g.dispose();
bs.show();
}
private void tick() {
}
public static void main(String[] args){
Game game = new Game();
frame = new JFrame("Game");
frame.setMaximumSize(new Dimension((int)width, (int)height));
frame.setMinimumSize(new Dimension((int)width, (int)height));
frame.setPreferredSize(new Dimension((int)width, (int)height));
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.add(game);
frame.pack();
frame.setVisible(true);
//device.setFullScreenWindow(frame);
game.start();
}
private synchronized void start() {
if (running)
return;
running = true;
thread = new Thread(this);
thread.start();
}
private synchronized void stop() {
if (!running)
return;
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(1);
}
}
some things to note:
INT_ARGB for BufferedImage has a default alpha of 0 (completely clear) so unless you change the alpha value of the pixels you wont see anything.
drawRect draws only the outline of the rectangle. use fillRect to draw the entire rectangle.
as far as I know, drawing BufferedImages does not use the current color of the Graphics object as they are mainly defined as a rectangle made of different colored pixels. try drawing to the Images own Graphics object if you want to change its color.
you are exactly overlapping two drawings (the image and the rect) so you will only ever see one.
I hope I have finally been of some help.
I have a game JPanel that is added to a frame. The gamePanel (posted below) is meant to render movements every frame. public Game() {
public Game() {
Dimension dimension = new Dimension(Game.WIDTH, Game.HEIGHT);
setPreferredSize(dimension);
setMaximumSize(dimension);
setMinimumSize(dimension);
setVisible(true);
player = new Player(Game.WIDTH / 2, Game.HEIGHT / 2);
level = new Level("/map/map.png");
spriteSheet = new SpriteSheet("/sprites/sprites.png");
new Texture();
start();
setFocusable(true);I cant seem to get the action to happen.
//other game functions like keybindings.
The code that handles the frame rate and what is supposed to paint() to the frame is...
#Override
public void run() {
requestFocus();// This does not require the mouse to click frame for it to work
int fps = 0;
double timer = System.currentTimeMillis();
long lastTime = System.nanoTime();
double targetTick = 60.0;
double delta = 0;
double ns = 1000000000 / targetTick;
while (isRunning) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
this.tick();
repaint();//not sure if this is right I just want the frame to update the position of the player and the level
fps++;
delta--;
}
if (System.currentTimeMillis() - timer >= 1000) {
fps = 0;
timer += 1000;
}
}
stop();
}
public synchronized void start() {
System.out.println("start accessed");//this does get accessed
if (isRunning) return;
isRunning = true;
thread = new Thread(this);//UPDATED
thread.start();
}
public synchronized void stop() {
if (!isRunning) return;
isRunning = false;
try {
thread.join();// what does thread join do?
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void tick() {
if (enter) { //enter boolean
System.out.println(enter);
player.tick();
level.tick();
repaint();
revalidate();
}
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, Game.WIDTH, Game.HEIGHT);
player.render(g);
level.render(g);
}
essentially the level and the player move over time. I had it working with on a canvas however since then converted the program to use JPanel.
Thanks for any help.
I am making a java game and at it's heart, it consists of a JFrame that holds a a JLabel that holds the background image. Early in the project this was being displayed. However, after I implemented a Game render method that put healthbars and a character on screen, the background image no longer displays. Here is the Game's render() and main() methods.
public class Game extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
public static final int WIDTH = 1200, HEIGHT = 600, SCALE = 1;
public static boolean running = false;
public Thread gameThread;
private BufferedImage playerSpriteSheet;
private ImageManager im;
private static Player player;
private static HealthBar healthBars;
public void init(){
ImageLoader loader = new ImageLoader();
playerSpriteSheet = loader.load("/spriteSheet.png");
SpriteSheet pss = new SpriteSheet(playerSpriteSheet);
im = new ImageManager(pss);
player = new Player(0, 0, im);
healthBars = new HealthBar(200, 200);
this.addKeyListener(new KeyManager());
}
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void run() {
init();
long lastTime = System.nanoTime();
final double amountOfTicks = 60D;
double ns = 1_000_000_000/amountOfTicks;
double delta = 0;
long now = System.nanoTime();
while(running)
{
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); //Use 5 at most
return;
}
Graphics g = bs.getDrawGraphics();
//RENDER HERE
player.render(g);
healthBars.render(g);
//END RENDER
g.dispose();
bs.show();
}
public static void main(String[] args)
{
JLabel backgroundImage;
JLabel controlKeyPanel;
JLabel statusLabel;
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("Java Game");
frame.setResizable(false);
frame.setSize(WIDTH * SCALE, HEIGHT * SCALE);
frame.setLayout(new BorderLayout());
backgroundImage = new JLabel(new ImageIcon("/background.png"));
String htmlButtonGuide = "<html>← - Move Left<br>→ - Move Right<br>A - Attack<br>S - Fire Gun<br>P - Position<br>esc - Exit</html>";
controlKeyPanel = new JLabel(htmlButtonGuide);
statusLabel = new JLabel("Game Status");
frame.add(backgroundImage, BorderLayout.CENTER); //This should be displaying the background image
frame.add(controlKeyPanel, BorderLayout.EAST);
frame.add(statusLabel, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(game);
frame.setVisible(true);
game.start();
}
public static Player getPlayer() {
return player;
}
}
Here is the catch, and where I think part of the issue is visible. Inside the render() method there are these 2 lines:
player.render(g);
healthBars.render(g);
If I put player.render(g) first, then both the player and health bars appear on the screen. However, if I put healthBars.render(g) before player.render(g), then the player doesn't appear. This really confuses me, because I would expect the one that renders second causes the first one to be hidden behind, but the opposite happens. Could anyone point me in the right direction?
BufferStrategy doesn't play well with Swing, as you've taken control of the painting process
Canvas can't be transparent, so it will hide anything beneath it...
When you use frame.add(game) you are replaceing what ever use to be at BorderLayout.CENTER
Instead of mixing lightweight (Swing) and heavy weight (AWT) components, paint the background image as part of your render process
public void render() {
BufferStrategy bs = this.getBufferStrategy();
if(bs == null)
{
createBufferStrategy(3); //Use 5 at most
return;
}
Graphics g = bs.getDrawGraphics();
//RENDER HERE
// Paint background here...
player.render(g);
healthBars.render(g);
//END RENDER
g.dispose();
bs.show();
}
Im working on a game in Java and having an issue (i believe its with the content pane) when rendering. I have a screen class which draws the background and all sprites to an Image. The frame then displays the image using a doubleBuffer. For some odd reason tho the image is rendering off the edge of the frame. You can see in the link below that the image is rendering 3 pixels to the left and 28 pixels above where it should be. Does anyone have any idea what could be causing this?
![enter image description here][1]
http://imageshack.us/photo/my-images/41/weirdg.png/
public class Game extends JFrame implements Runnable{
private static final long serialVersionUID = 1L;
//graphics
public BufferStrategy buffy;
BufferedImage image;
Screen screen;
public Boolean running = false;
public Boolean playerTurn = false;
public InputManager input;
public Level level;
//JButton b;
public static final int HEIGHT = 452;
public static final int WIDTH = 768;
public Game() {
super("GridWars");
setDefaultCloseOperation(EXIT_ON_CLOSE);
JPanel drawPanel = new JPanel();
drawPanel.setPreferredSize(new Dimension(WIDTH, HEIGHT));
drawPanel.setLayout(null);
drawPanel.setOpaque(false);
//drawPanel.setLocation(50,50);
setContentPane(drawPanel);
setResizable(false);
pack();
setLocationRelativeTo(null);
setVisible(true);
requestFocus();
createBufferStrategy(2);
//b = new JButton("this sucks");
//getContentPane().add(b);
//b.setBounds(300, 300, 100, 50);
buffy = getBufferStrategy();
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
screen = new Screen(WIDTH, HEIGHT);
input = new InputManager(this);
level = new Level(WIDTH, HEIGHT, input, this);
}
public void start() {
running = true;
new Thread(this).start();
}
public void setup(){
}
public void run() {
final double TICKS = 30.0;
final double UPDATE_INTERVAL_NS = 1000000000 / TICKS;
double pastUpdateNS = System.nanoTime();
int updateCount = 0;
int frameCount = 0;
final double FRAPS = 60.0;
final double RENDER_INTERVAL_NS = 1000000000 / FRAPS;
double pastRenderNS = System.nanoTime();
int pastSecondNS = (int) (pastUpdateNS/1000000000);
while(running) {
double nowNS = System.nanoTime();
if(nowNS - pastUpdateNS >= UPDATE_INTERVAL_NS) {
update();
pastUpdateNS += UPDATE_INTERVAL_NS;
updateCount++;
}
float interp = Math.min(1.0f, (float) ((nowNS - pastUpdateNS) / UPDATE_INTERVAL_NS) );
render(interp);
pastRenderNS += RENDER_INTERVAL_NS;
frameCount++;
int thisSecondNS = (int) (pastUpdateNS/1000000000);
if (thisSecondNS > pastSecondNS) {
//System.out.println("TICKS: "+updateCount+" | FRAPS: "+frameCount);
updateCount = 0;
frameCount = 0;
pastSecondNS = thisSecondNS;
}
while( nowNS - pastRenderNS < RENDER_INTERVAL_NS && nowNS - pastUpdateNS < UPDATE_INTERVAL_NS) {
try { Thread.sleep(1); } catch(Exception e) {};
nowNS = System.nanoTime();
}
}
}
public void update() {
input.update();
level.update();
}
public void render(float interp) {
level.render(screen, interp);
image = screen.getImage();
Graphics g = buffy.getDrawGraphics();
g.drawImage(image, 0, 0, null, null);
//b.repaint();
g.dispose();
buffy.show();
}
public static void main(String[] args) {
Game game = new Game();
game.start();
}
}
The 0,0 coordinate of Graphics object you obtain from buffy.getDrawGraphics(); is exactly at top left corner of JFrame and it is ignoring frame decorations.
UPD I forgot one obvious option. JFrame.getInsets() provides information about decorations. You could simply shift your rendering.
You would make frame undecorated (setUndecorated(true)) and render/manage window controls yourself.
Or, and i think it is easier way, you would forget about direct rendering on JFrame, place Canvas on it, and use it instead. Canvas also contains createBufferStrategy method, so you need few simple changes.
JPanel drawPanel = new JPanel();
drawPanel.setLayout(new BorderLayout());
Canvas canvas = new Canvas();
canvas.setPreferredSize(new Dimension(WIDTH, HEIGHT));
drawPanel.add(canvas, BorderLayout.CENTER);
// some code skipped
canvas.setIgnoreRepaint(true); //important
canvas.createBufferStrategy(2);
buffy = canvas.getBufferStrategy();
I've created simple demo with similar render few days ago for another answer. Maybe it will helpful.