I'm trying to create a game loop in java which has a frame cap which can be set while the game is running. The problem im having is I have a render() function and a update() function. Just setting frame cap for both render() and update() means that the speed of the game logic will change when you change the frame cap. Any idea how to have a frame cap which can be set in game while not affecting the speed of the game logic (update())?
As stated in the comments: You can create two threads one of which is responsible for updating and the other is responsible for rendering.
Try to think of an architecture that suits your needs and your game. You can try something like:
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Runner extends JPanel {
private static final long serialVersionUID = -5029528072437981456L;
private JFrame window;
private Renderer renderer;
public Runner() {
setPreferredSize(new Dimension(WindowData.WIDTH, WindowData.HEIGHT));
setFocusable(true);
requestFocus();
window = new JFrame(WindowData.TITLE);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.add(this);
window.setResizable(false);
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
renderer = new Renderer(getGraphics());
renderer.start();
}
public static void main(String[] args) {
new Runner();
}
}
import java.awt.Graphics;
public class Renderer implements Runnable {
private Graphics context;
private Thread thread;
private boolean running;
public Renderer(Graphics context) {
this.context = context;
thread = new Thread(this, "Renderer");
running = false;
}
public void start() {
if (running)
return;
running = true;
thread.start();
}
public void render() {
context.clearRect(0, 0, WindowData.WIDTH, WindowData.HEIGHT);
context.fillRect(50, 50, 100, 100);
}
public void run() {
while (running) {
render();
// ** DO YOUR TIME CONTROL HERE ** \\
}
}
}
This code will actually lead to serious performance issues because you are not in control over the rendering ( repaint() ) time.
But this is just a demo to show you how you can use different threads. Do your own architecture.
Related
I'm trying to make my own version of Snake for learning purposes. Everything seems to work fine except that if I want my frame to be repainted, I have to resize my window manually. Here is my code:
package snake;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class PlayGame extends JPanel implements Runnable{
public boolean animate = false;
public final int FRAME_DELAY = 750;
PickupBall b = new PickupBall();
Snake bob = new Snake();
public synchronized void start() {
animate = true;
}
public synchronized void stop() {
animate = false;
}
private synchronized boolean animationEnabled() {
return animate;
}
#Override
public void run(){
while(true){
if (animationEnabled()){
repaint();
}
try {
Thread.sleep(FRAME_DELAY);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
b.draw(g);
bob.draw(g);
}
public static void main(String[] args) {
JFrame jfr = new JFrame("Snake");
jfr.setSize(640,640);
jfr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jfr.setResizable(true);
PlayGame p = new PlayGame();
jfr.setContentPane(p);
p.setBackground(Color.WHITE);
p.start();
new Thread(p).start();
jfr.setVisible(true);
}
}
Why isn't repaint() triggered without altering the frame size? I get the correlation but it makes no sense to me why it needs such a trigger when it's in a while(true) loop anyway.
What am I missing here?
Edit 1:
Removed thread object
Replaced t.start() with p.start()
Edit 2:
Added new Thread(p).start(); and now it works! Thanks.
Edit 3:
Removed revalidate();
You are executing repaint() in the worker thread and not in the event dispatch tread (EDT) which is the only one actually drawing onto the screen.
You have to en queue the call to repaint() in the EDT using SwingUtilities static methods invokeLater() or invokeAndWait().
Added new Thread(p).start();
Still have no idea how or why this is different to
Thread t = new Thread(p);
t.start();
But it worked.
I am trying to show a label from another class. However when I add it to the frame it will not show. I have tried drawing it from the counter class itself by passing in the Frame which I would assume is not good practice (ignoring the fact it didn't work). As well as what is in the code below. Can anybody help me and explain why my solution will not show the created label? As you can most likely tell i'm very new to using JPanel.
CookieChaser Class
public class CookieChaser extends JPanel {
public static void main(String[] args) throws InterruptedException {
JFrame frame = new JFrame("Cookie Chaser");
CookieChaser game = new CookieChaser();
frame.add(game);
frame.setSize(1000, 1000);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
counter Score = new counter(frame);
cookie Cookie = new cookie();
JLabel item = counter.getLabel();
frame.add(item);
frame.setVisible(true);
while (true) {
game.repaint();
Thread.sleep(10);
}
}
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
}
Counter Class
public class counter {
int count;
static JLabel text;
public counter(JFrame frame){
count = 0;
text = new JLabel(String.valueOf(count));
text.setLocation(0,0);
text.setSize(50,50);
}
public static JLabel getLabel(){
return text;
}
I modified your code to create the following Swing GUI.
Whenever I create a Swing game or application, I use the model / view / controller pattern. This means I create a GUI model. The GUI model contains all of the fields that my GUI needs. Next, I create a GUI view which reads the values from the GUI model. Finally, I create one or more GUI controllers, which update the GUI model and refresh / repaint the GUI view.
I made the following changes to your code:
I created a GUI model. I created the Counter class. All the Counter class does is hold a counter value.
I created a GUI view, which uses the GUI model. I created the JFrame, JPanel, and JLabel all in the view class. You may use more than one class to create the view. Since this view was simple, I created everything in one class.
All Swing applications must start with a call to the SwingUtilities invokeLater method. The invokeLater method puts the creation and updating of the Swing components on the Event Dispatch thread. Oracle and I insist that all Swing applications start this way.
I created a separate Animation runnable so that you can see the JLabel updates. I increment the counter once a second.
The repaint method in the Animation class calls the SwingUtilities invokeLater method to ensure that the JLabel update is done on the Event Dispatch thread. The animation loop runs in a separate thread to keep the GUI responsive.
Here's the revised code.
package com.ggl.testing;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class CookieChaser implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new CookieChaser());
}
private JLabel counterLabel;
#Override
public void run() {
Counter counter = new Counter();
JFrame frame = new JFrame("Cookie Chaser");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel();
counterLabel = new JLabel(" ");
mainPanel.add(counterLabel);
frame.add(mainPanel);
frame.setSize(300, 200);
frame.setVisible(true);
new Thread(new Animation(this, counter)).start();
}
public void setCounterLabel(String text) {
counterLabel.setText(text);
}
public class Counter {
private int counter;
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
public void incrementCounter() {
this.counter++;
}
}
public class Animation implements Runnable {
private Counter counter;
private CookieChaser cookieChaser;
public Animation(CookieChaser cookieChaser, Counter counter) {
this.cookieChaser = cookieChaser;
this.counter = counter;
}
#Override
public void run() {
while (true) {
counter.incrementCounter();
repaint();
sleep(1000L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
cookieChaser.setCounterLabel(Integer.toString(counter
.getCounter()));
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
}
}
}
}
I'm trying to work with the Java paint
utility and it's been a bit of a hassle.
I'm trying to do something which I assume is quite basic.
I'm drawing a square Graphic to a JPanel and then trying
to move it using repaint
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
public class testGui {
static gui gc_gui;
static int gv_x;
static int gv_y;
public static void main(String[] args) {
gc_gui = new gui();
gv_x = 50;
gv_y = 50;
gc_gui.cv_frame.setVisible(true);
}
public static class gui {
JFrame cv_frame;
content cv_content;
public gui() {
cv_frame = new JFrame();
cv_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
cv_frame.setTitle("Test GUI");
cv_frame.setSize(600, 400);
cv_frame.setLayout(new FlowLayout());
cv_content = new content();
cv_content.setBackground(Color.Black);
cv_content.setPreferredSize(new Dimension(500, 300));
cv_frame.add(cv_content);
gv_x = 0;
gv_y = 0;
cv_content.update();
}
}
public static class content extends JPanel {
public void paint(Graphics graphic) {
super.paint(graphic);
draw(graphic);
}
public void update() {
super.repaint();
}
public void draw(Graphics graphic) {
Graphics2D graphic2D = (Graphics2D) graphic;
graphic2D.setPaint(Color.Red);
graphic2D.fillRect(gv_x, gv_y, 100, 100);
}
}
}
I don't know why the call to the update function isn't doing
anything though.
It draws the square at 50x and 50y, the sets it to 0x and 0y
immediately and then when I call repaint I expected it to
be moved to it's new coordinates although it's still at
50x and 50y.
Why is this?
Your solution is to use KeyBindings.
https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
and also.
You need to create a Swing Timer, Thread, or Loop , that manages the frames to be painted. and such
Here is a link for Swing Timers as they are pretty easy to implement:
https://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html
A lot of programs I see also have this ( AKA. working with threads.):
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
I'm starting to learn to create Games in Java, and one of the methods I'm using includes BufferedImage. This is the error I get:
"Exception in thread "main" java.lang.NullPointerException
at tm.Game.init(Game.java:48)
at tm.Game.<init>(Game.java:54)"
From this code:
package tm;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class Game extends JPanel implements Runnable {
private Settings Settings;
private Thread t;
private BufferedImage offscreenImage;
private Graphics offscr;
public void run() {
while(true) {
repaint();
try {
Thread.sleep(1000/30);
} catch (InterruptedException e) { }
}
}
public void paint(Graphics g) {
offscr.setColor(Color.blue);
offscr.fillRect(0, 0, Settings.GAME_WIDTH, Settings.GAME_HEIGHT);
offscr.setColor(Color.white);
offscr.drawString("Lolz", 10, 10);
g.drawImage(offscreenImage, 0, 0, this);
}
public void update(Graphics g) {
paint(g);
}
public void init() {
t = new Thread(this);
t.start();
offscreenImage = (BufferedImage) createImage(Settings.GAME_WIDTH, Settings.GAME_HEIGHT);
offscr = offscreenImage.getGraphics();
}
public Game() {
Settings = new Settings();
init();
}
}
Settings Class:
package tm;
public class Settings {
public final int GAME_WIDTH = 500;
public final int GAME_HEIGHT = 500;
}
Screen Class:
package tm;
import javax.swing.JFrame;
public class Screen extends JFrame {
private static final long serialVersionUID = 1L;
private JFrame mainScreen;
private Game mainGame;
private Settings Settings;
public Screen() {
mainGame = new Game();
Settings = new Settings();
mainScreen = new JFrame();
mainScreen.add(mainGame);
mainScreen.setSize(Settings.GAME_WIDTH, Settings.GAME_HEIGHT);
mainScreen.setTitle("Lolz");
mainScreen.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainScreen.setResizable(false);
mainScreen.setVisible(true);
}
public static void main(String[] args) {
new Screen();
}
}
It is not getGraphics() that returns null but rather the previous function createImage(). From the Component documentation for createImage():
returns an off-screen drawable image, which can be used for double
buffering. The return value may be null if the component is not
displayable. This will always happen if
GraphicsEnvironment.isHeadless() returns true.
You then get a NullPointerException when calling getGraphics() on offscreenImage which is null.
The reason that throw NullPointer exception is that you initialized the offScreenImage and offScr in wrong place.
offscreenImage = (BufferedImage) createImage(Settings.GAME`WIDTH, Settings.GAME_HEIGHT);
offscr = offscreenImage.getGraphics();
This code should be in the function paint. To get the results the Game class should be defined like this. And another tip it is better to declare variables inn Settings class to public static final so that they can be accessed in static way. Make little change to your Game class as defined below. I think this should help you.
package tm;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import tm.Screen.Settings;
public class Game extends JPanel implements Runnable {
// private Setting Settings;
private Thread t;
private BufferedImage offscreenImage;
private Graphics offscr;
public void run() {
while (true) {
repaint();
try {
Thread.sleep(1000 / 30);
} catch (InterruptedException e) {
}
}
}
public void paint(Graphics g) {
if (offscreenImage == null) {
offscreenImage = (BufferedImage) createImage(Settings.GAME_WIDTH,
Settings.GAME_HEIGHT);
}
offscr = offscreenImage.getGraphics();
offscr.setColor(Color.black);
offscr.fillRect(0, 0, Settings.GAME_WIDTH, Settings.GAME_HEIGHT);
offscr.setColor(Color.white);
offscr.drawString("Lolz", 10, 10);
g.drawImage(offscreenImage, 0, 0, this);
}
public void update(Graphics g) {
paint(g);
}
public void init() {
t = new Thread(this);
t.start();
}
public Game() {
init();
}
}
Since createImage only works after the Component is "displayable" e.g. it is attached to a visible JFrame your current code wont work.
There are several ways you can deal with it.
Add JFrame as a parameter to the ctor and add the Game to the JFrame before calling create component - this should work as long as JFrame.add does not call any methods overridden by the partially initialized Game instance.
Game(JFrame jf){
jf.add(this);
...
}
JFrame mainFrame = new JFrame();
mainFrame.setVisible(true);
Game game = new Game(mainFrame);
Make an additional init method which is called after adding Game to the JFrame. This is ugly since the Game object is not really fully initialized until this method is called.
Game game = new Game();
JFrame mainFrame = new JFrame();
mainFrame.add(game);
mainFrame.setVisible(true);
game.init();
One way to find out when the component is displayable is to listen for a HierarchyEvent. You could modify the Listener shown in the answer to call createImage instead of printing "showing". (The class provided by that answer also needs a extends HierarchyListener to work)
I was wondering if there was a function like void draw() which Processing programming language uses that gets called every frame. Or even just a function that loops infinitely when it gets called but only runs through it every time there is a new frame. I heard of something called a runnable in java how do i go about using this? also is there a better way then having a runnable with a delay like a function that is hardcoded to run every frame. Oh and also what is the function call that will allow me to see how much time (in milliseconds preferably) since the application has started running that way i can make my runnables / frame calls much more precise so that the game runs about the same speed on every computer regardless of the frame rate.
Perhaps you need something like this
import java.awt.Graphics;
import java.awt.Point;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Repainter extends JPanel {
private Point topLeft;
private int increamentX = 5;
public Repainter() {
topLeft = new Point(100, 100);
}
public void move() {
topLeft.x += increamentX;
if (topLeft.x >= 200 || topLeft.x <= 100) {
increamentX = -increamentX;
}
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(topLeft.x, topLeft.y, 100, 100);
}
public void startAnimation() {
SwingWorker<Object, Object> sw = new SwingWorker<Object, Object>() {
#Override
protected Object doInBackground() throws Exception {
while (true) {
move();
Thread.sleep(100);
}
}
};
sw.execute();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Repaint Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
Repainter repainter = new Repainter();
frame.add(repainter);
repainter.startAnimation();
frame.setVisible(true);
}
});
}
}